From cc9e33b374f62017bd506ddc605fbcc848381caa Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Fri, 29 Sep 2023 08:42:19 -0700 Subject: [PATCH 01/74] Created data-connect package --- .vscode/settings.json | 3 +- common/api-review/connect.api.md | 133 +++ common/api-review/data-connect-react.api.md | 120 +++ common/api-review/data-connect.api.md | 331 ++++++ common/api-review/firestore-lite.api.md | 1 - common/api-review/util.api.md | 14 + config/karma.saucelabs.js | 2 +- config/mocharc.node.js | 2 +- packages/app/src/constants.ts | 2 + packages/data-connect/.eslintrc.js | 88 ++ packages/data-connect/.gitignore | 3 + packages/data-connect/.npmignore | 5 + packages/data-connect/api-extractor.json | 11 + packages/data-connect/notes.md | 74 ++ packages/data-connect/package.json | 77 ++ packages/data-connect/rollup.config.js | 166 +++ packages/data-connect/rollup.config.react.js | 238 +++++ packages/data-connect/src/api.browser.ts | 95 ++ packages/data-connect/src/api.node.ts | 18 + packages/data-connect/src/api/DataConnect.ts | 212 ++++ packages/data-connect/src/api/Mutation.ts | 97 ++ packages/data-connect/src/api/Reference.ts | 52 + packages/data-connect/src/api/index.ts | 22 + packages/data-connect/src/api/query.ts | 94 ++ .../src/core/FirebaseAuthProvider.ts | 99 ++ .../data-connect/src/core/QueryManager.ts | 218 ++++ packages/data-connect/src/core/error.ts | 62 ++ packages/data-connect/src/core/version.ts | 27 + packages/data-connect/src/index.node.ts | 25 + packages/data-connect/src/index.ts | 66 ++ packages/data-connect/src/network/fetch.ts | 63 ++ packages/data-connect/src/network/index.ts | 18 + .../src/network/transport/index.ts | 57 ++ .../src/network/transport/rest.ts | 156 +++ packages/data-connect/src/register.ts | 57 ++ packages/data-connect/src/util/encoder.ts | 23 + packages/data-connect/src/util/map.ts | 22 + packages/data-connect/src/util/url.ts | 47 + .../.dataconnect/schema/main/input.gql | 49 + .../.dataconnect/schema/main/mutation.gql | 8 + .../.dataconnect/schema/main/query.gql | 4 + .../.dataconnect/schema/prelude.gql | 953 ++++++++++++++++++ .../test/dataconnect/connector/connector.yaml | 6 + .../test/dataconnect/connector/mutations.gql | 0 .../test/dataconnect/connector/queries.gql | 0 .../test/dataconnect/dataconnect.yaml | 10 + .../test/dataconnect/index.esm.js | 47 + .../dataconnect/logAddMovieVariables.json | 1 + .../dataconnect/logListAllMoviesMovies.json | 1 + .../dataconnect/logListMovieIdsMovies.json | 1 + .../test/dataconnect/movies.tools.json | 6 + .../test/dataconnect/schema/schema.gql | 23 + .../test/dataconnect/test.tools.json | 6 + packages/data-connect/test/emulatorSeeder.ts | 93 ++ .../test/integration/.graphqlrc.yaml | 9 + packages/data-connect/test/mutations.gql | 6 + .../test/mutations.mutation.graphql | 4 + packages/data-connect/test/post.gql | 12 + packages/data-connect/test/queries.schema.gql | 1 + packages/data-connect/test/queries.test.ts | 201 ++++ packages/data-connect/test/runTest.mjs | 88 ++ packages/data-connect/test/util.ts | 52 + packages/data-connect/tsconfig.eslint.json | 9 + packages/data-connect/tsconfig.json | 10 + packages/database/src/api/Reference_impl.ts | 1 + .../database/test/exp/integration.test.ts | 29 +- packages/firebase/data-connect/index.ts | 18 + packages/firebase/data-connect/package.json | 7 + packages/firebase/package.json | 16 +- packages/util/index.node.ts | 1 + packages/util/index.ts | 1 + packages/util/src/fetch_provider.ts | 72 ++ repo-scripts/prune-dts/extract-public-api.ts | 4 +- .../dataconnect-test-runner.ts | 47 + .../emulators/dataconnect-emulator.ts | 37 + .../emulator-testing/emulators/emulator.ts | 44 +- yarn.lock | 35 +- 77 files changed, 4687 insertions(+), 25 deletions(-) create mode 100644 common/api-review/connect.api.md create mode 100644 common/api-review/data-connect-react.api.md create mode 100644 common/api-review/data-connect.api.md create mode 100644 packages/data-connect/.eslintrc.js create mode 100644 packages/data-connect/.gitignore create mode 100644 packages/data-connect/.npmignore create mode 100644 packages/data-connect/api-extractor.json create mode 100644 packages/data-connect/notes.md create mode 100644 packages/data-connect/package.json create mode 100644 packages/data-connect/rollup.config.js create mode 100644 packages/data-connect/rollup.config.react.js create mode 100644 packages/data-connect/src/api.browser.ts create mode 100644 packages/data-connect/src/api.node.ts create mode 100644 packages/data-connect/src/api/DataConnect.ts create mode 100644 packages/data-connect/src/api/Mutation.ts create mode 100644 packages/data-connect/src/api/Reference.ts create mode 100644 packages/data-connect/src/api/index.ts create mode 100644 packages/data-connect/src/api/query.ts create mode 100644 packages/data-connect/src/core/FirebaseAuthProvider.ts create mode 100644 packages/data-connect/src/core/QueryManager.ts create mode 100644 packages/data-connect/src/core/error.ts create mode 100644 packages/data-connect/src/core/version.ts create mode 100644 packages/data-connect/src/index.node.ts create mode 100644 packages/data-connect/src/index.ts create mode 100644 packages/data-connect/src/network/fetch.ts create mode 100644 packages/data-connect/src/network/index.ts create mode 100644 packages/data-connect/src/network/transport/index.ts create mode 100644 packages/data-connect/src/network/transport/rest.ts create mode 100644 packages/data-connect/src/register.ts create mode 100644 packages/data-connect/src/util/encoder.ts create mode 100644 packages/data-connect/src/util/map.ts create mode 100644 packages/data-connect/src/util/url.ts create mode 100755 packages/data-connect/test/dataconnect/.dataconnect/schema/main/input.gql create mode 100755 packages/data-connect/test/dataconnect/.dataconnect/schema/main/mutation.gql create mode 100755 packages/data-connect/test/dataconnect/.dataconnect/schema/main/query.gql create mode 100755 packages/data-connect/test/dataconnect/.dataconnect/schema/prelude.gql create mode 100644 packages/data-connect/test/dataconnect/connector/connector.yaml create mode 100644 packages/data-connect/test/dataconnect/connector/mutations.gql create mode 100644 packages/data-connect/test/dataconnect/connector/queries.gql create mode 100644 packages/data-connect/test/dataconnect/dataconnect.yaml create mode 100644 packages/data-connect/test/dataconnect/index.esm.js create mode 100644 packages/data-connect/test/dataconnect/logAddMovieVariables.json create mode 100644 packages/data-connect/test/dataconnect/logListAllMoviesMovies.json create mode 100644 packages/data-connect/test/dataconnect/logListMovieIdsMovies.json create mode 100644 packages/data-connect/test/dataconnect/movies.tools.json create mode 100644 packages/data-connect/test/dataconnect/schema/schema.gql create mode 100644 packages/data-connect/test/dataconnect/test.tools.json create mode 100644 packages/data-connect/test/emulatorSeeder.ts create mode 100644 packages/data-connect/test/integration/.graphqlrc.yaml create mode 100644 packages/data-connect/test/mutations.gql create mode 100644 packages/data-connect/test/mutations.mutation.graphql create mode 100644 packages/data-connect/test/post.gql create mode 100644 packages/data-connect/test/queries.schema.gql create mode 100644 packages/data-connect/test/queries.test.ts create mode 100644 packages/data-connect/test/runTest.mjs create mode 100644 packages/data-connect/test/util.ts create mode 100644 packages/data-connect/tsconfig.eslint.json create mode 100644 packages/data-connect/tsconfig.json create mode 100644 packages/firebase/data-connect/index.ts create mode 100644 packages/firebase/data-connect/package.json create mode 100644 packages/util/src/fetch_provider.ts create mode 100644 scripts/emulator-testing/dataconnect-test-runner.ts create mode 100644 scripts/emulator-testing/emulators/dataconnect-emulator.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 706e611f5d5..279965399ab 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,5 +11,6 @@ }, "typescript.tsdk": "node_modules/typescript/lib", "files.associations": { "*.json": "jsonc" }, - "eslint.workingDirectories": [{ "mode": "auto" }] + "eslint.workingDirectories": [{ "mode": "auto" }], + "editor.wordWrap": "off", } diff --git a/common/api-review/connect.api.md b/common/api-review/connect.api.md new file mode 100644 index 00000000000..f2b5d2fda4d --- /dev/null +++ b/common/api-review/connect.api.md @@ -0,0 +1,133 @@ +## API Report File for "@firebase/connect" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { AppCheckInternalComponentName } from '@firebase/app-check-interop-types'; +import { FirebaseApp } from '@firebase/app'; +import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; +import { Provider } from '@firebase/component'; + +// @public (undocumented) +export function connectDataConnectEmulator(dc: DataConnect, host: string, port: number, isSecure?: boolean): void; + +// @public (undocumented) +export interface Converter { + // (undocumented) + fromDataConnect: (data: DataConnectSnapshot) => Response; + // (undocumented) + fromResponse: (response: Response) => DataConnectSnapshot; +} + +// @public (undocumented) +export class DataConnect { + constructor(firebaseApp: FirebaseApp, authProvider: Provider, transportOptions: TransportOptions, projectOptions: ProjectOptions, appCheckProvider?: Provider, useRest?: boolean); + // (undocumented) + enableEmulator(host: string, port: number, isSecure: boolean): void; + // (undocumented) + setInitialized(): void; + } + +// @public (undocumented) +export interface DataConnectOptions { + // (undocumented) + projectOptions: ProjectOptions; + // (undocumented) + transportOptions?: TransportOptions; +} + +// @public (undocumented) +export interface DataConnectSnapshot { + // (undocumented) + data: Response; +} + +// @public (undocumented) +export interface DataConnectSubscription { + // (undocumented) + converter: Converter; + // (undocumented) + unsubscribe: () => void; + // (undocumented) + userCallback: (data: Response | undefined, error?: Error) => void; +} + +// @public (undocumented) +export function executeMutation(mutationRef: Mutation): Promise; + +// @public (undocumented) +export function executeQuery(queryRef: Query): Promise; + +// @public (undocumented) +export const FIREBASE_DATA_CONNECT_EMULATOR_HOST_VAR = "FIREBASE_DATA_CONNECT_EMULATOR_HOST"; + +// @public (undocumented) +export function getDataConnect(app: FirebaseApp, options: DataConnectOptions): DataConnect; + +// @public (undocumented) +export function getDataConnect(options: DataConnectOptions): DataConnect; + +// @public (undocumented) +export interface Mutation extends Reference { + // (undocumented) + dcInstance: DataConnect; +} + +// @public (undocumented) +export function mutationRef(dcInstance: DataConnect, queryName: string, variables: Variables): Mutation; + +// @public (undocumented) +export interface ProjectOptions { + // (undocumented) + connector: string; + // (undocumented) + location: string; + // (undocumented) + project: string; + // (undocumented) + service: string; +} + +// @public (undocumented) +export interface Query extends Reference { + // (undocumented) + dcInstance: DataConnect; +} + +// @public (undocumented) +export function queryRef(dcInstance: DataConnect, queryName: string, variables?: Variables): Query; + +// @public (undocumented) +export interface Reference<_Response, Variables> { + // (undocumented) + name: string; + // (undocumented) + refType: ReferenceType; + // (undocumented) + variables: Variables; +} + +// @public (undocumented) +export enum ReferenceType { + // (undocumented) + Mutation = "m", + // (undocumented) + Query = "q" +} + +// @public (undocumented) +export function subscribe(queryRef: Query, onSubscribe: DataConnectSubscription['userCallback'], converter: Converter): void; + +// @public (undocumented) +export interface TransportOptions { + // (undocumented) + host: string; + // (undocumented) + isSecure: boolean; + // (undocumented) + port: number; +} + + +``` diff --git a/common/api-review/data-connect-react.api.md b/common/api-review/data-connect-react.api.md new file mode 100644 index 00000000000..7ee8eac17ed --- /dev/null +++ b/common/api-review/data-connect-react.api.md @@ -0,0 +1,120 @@ +## API Report File for "@firebase/data-connect-react" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +/// + +import { Context } from 'react'; +import { FirebaseApp } from '@firebase/app'; +import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; +import { FirebaseAuthTokenData } from '@firebase/auth-interop-types'; +import { FirebaseOptions } from '@firebase/app'; +import { JSX as JSX_2 } from 'react/jsx-runtime'; +import { PropsWithChildren } from 'react'; +import { Provider } from '@firebase/component'; + +// @public (undocumented) +export type AuthTokenListener = (token: string | null) => void; + +// @public (undocumented) +export class DataConnect { + constructor(firebaseApp: FirebaseApp, authProvider: Provider, dataConnectOptions: DataConnectOptions, appCheckProvider?: Provider<"app-check-internal"> | undefined); + // (undocumented) + enableEmulator(transportOptions: TransportOptions): void; + // (undocumented) + setDCSettings(settings: DataConnectSettings): void; + // (undocumented) + setInitialized(): void; + // (undocumented) + setTransport(transportClass: TransportClass): void; +} + +// @public (undocumented) +export const DataConnectContext: Context; + +// @public (undocumented) +export interface DataConnectContextInfo { + // (undocumented) + app: FirebaseApp; + // (undocumented) + dataConnect: DataConnect; +} + +// @public (undocumented) +export interface DataConnectOptions { + // (undocumented) + connector: string; + // (undocumented) + location: string; + // (undocumented) + projectId: string; + // (undocumented) + service: string; +} + +// @public (undocumented) +export function DataConnectProvider({ connectorConfig, initializeAppOptions, children }: PropsWithChildren): JSX_2.Element; + +// @public (undocumented) +export interface DataConnectProviderProps { + // (undocumented) + connectorConfig: DataConnectOptions; + // (undocumented) + initializeAppOptions: FirebaseOptions; +} + +// @public (undocumented) +export interface DataConnectSettings { + // (undocumented) + transportOptions?: TransportOptions; +} + +// @public (undocumented) +export interface DataConnectTransport { + // (undocumented) + invokeMutation(queryName: string, body?: U): QueryResponse; + // (undocumented) + invokeQuery(queryName: string, body?: U): PromiseLike<{ + data: T; + }>; + // (undocumented) + onTokenChanged: (token: string | null) => void; + // (undocumented) + useEmulator(host: string, port?: number): void; +} + +// @public (undocumented) +export class FirebaseAuthProvider { + constructor(appName: string, options: FirebaseOptions, authProvider_: Provider); + // (undocumented) + addTokenChangeListener(listener: AuthTokenListener): void; + // (undocumented) + getToken(forceRefresh: boolean): Promise; +} + +// @public (undocumented) +export interface QueryResponse { + // (undocumented) + cancel: () => void; + then(onfulfilled?: (value: { data: T; }) => TResult1 | PromiseLike, onrejected?: (reason: any) => TResult2 | PromiseLike): PromiseLike; +} + +// @public (undocumented) +export type TransportClass = new (options: DataConnectOptions, authProvider: FirebaseAuthProvider, transportOptions?: TransportOptions) => DataConnectTransport; + +// @public (undocumented) +export interface TransportOptions { + // (undocumented) + host: string; + // (undocumented) + port?: number; + // (undocumented) + sslEnabled?: boolean; +} + +// @public (undocumented) +export function useCheckContext(): DataConnect; + +``` diff --git a/common/api-review/data-connect.api.md b/common/api-review/data-connect.api.md new file mode 100644 index 00000000000..3b197b3c11d --- /dev/null +++ b/common/api-review/data-connect.api.md @@ -0,0 +1,331 @@ +## API Report File for "@firebase/data-connect" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { FirebaseApp } from '@firebase/app'; +import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; +import { FirebaseAuthTokenData } from '@firebase/auth-interop-types'; +import { FirebaseError } from '@firebase/util'; +import { FirebaseOptions } from '@firebase/app'; +import { Provider } from '@firebase/component'; + +// @public (undocumented) +export type AuthTokenListener = (token: string | null) => void; + +// @public (undocumented) +export interface AuthTokenProvider { + // (undocumented) + addTokenChangeListener(listener: AuthTokenListener): void; + // (undocumented) + getToken(forceRefresh: boolean): Promise; +} + +// @public (undocumented) +export interface CancellableOperation extends PromiseLike<{ + data: T; +}> { + // (undocumented) + cancel: () => void; +} + +// @public (undocumented) +export function connectDataConnectEmulator(dc: DataConnect, host: string, port?: number, sslEnabled?: boolean): void; + +// @public (undocumented) +export interface ConnectorConfig { + // (undocumented) + connector: string; + // (undocumented) + location: string; + // (undocumented) + service: string; +} + +// @public (undocumented) +export class DataConnect { + constructor(app: FirebaseApp, dataConnectOptions: DataConnectOptions, authProvider: Provider); + // (undocumented) + readonly app: FirebaseApp; + // (undocumented) + enableEmulator(transportOptions: TransportOptions): void; + // (undocumented) + getSettings(): ConnectorConfig; + // (undocumented) + initialized: boolean; + // (undocumented) + isEmulator: boolean; + // (undocumented) + setInitialized(): void; + } + +// @public (undocumented) +export interface DataConnectOptions extends ConnectorConfig { + // (undocumented) + projectId: string; +} + +// @public (undocumented) +export interface DataConnectResult extends OpResult { + // (undocumented) + ref: OperationRef; +} + +// @public (undocumented) +export interface DataConnectSubscription { + // (undocumented) + errCallback?: (e?: FirebaseError) => void; + // (undocumented) + unsubscribe: () => void; + // (undocumented) + userCallback: OnResultSubscription; +} + +// @public (undocumented) +export interface DataConnectTransport { + // (undocumented) + invokeMutation(queryName: string, body?: U): PromiseLike<{ + data: T; + errors: Error[]; + }>; + // (undocumented) + invokeQuery(queryName: string, body?: U): PromiseLike<{ + data: T; + errors: Error[]; + }>; + // (undocumented) + onTokenChanged: (token: string | null) => void; + // (undocumented) + useEmulator(host: string, port?: number, sslEnabled?: boolean): void; +} + +// @public (undocumented) +export type DataSource = typeof SOURCE_CACHE | typeof SOURCE_SERVER; + +// @public (undocumented) +export class EmulatorTokenProvider implements AuthTokenProvider { + constructor(accessToken: string); + // (undocumented) + addTokenChangeListener(listener: AuthTokenListener): void; + // (undocumented) + getToken(forceRefresh: boolean): Promise; + // (undocumented) + notifyForInvalidToken(): void; + static OWNER: string; + // (undocumented) + removeTokenChangeListener(listener: (token: string | null) => void): void; +} + +// @public (undocumented) +export function executeMutation(mutationRef: MutationRef): MutationPromise; + +// @public (undocumented) +export function executeQuery(queryRef: QueryRef): QueryPromise; + +// @public (undocumented) +export const FIREBASE_DATA_CONNECT_EMULATOR_HOST_VAR = "FIREBASE_DATA_CONNECT_EMULATOR_HOST"; + +// @public (undocumented) +export class FirebaseAuthProvider implements AuthTokenProvider { + constructor(appName: string, options: FirebaseOptions, authProvider_: Provider); + // (undocumented) + addTokenChangeListener(listener: AuthTokenListener): void; + // (undocumented) + getToken(forceRefresh: boolean): Promise; + // (undocumented) + removeTokenChangeListener(listener: (token: string | null) => void): void; +} + +// @public (undocumented) +export function getDataConnect(options: ConnectorConfig): DataConnect; + +// @public (undocumented) +export function getDataConnect(app: FirebaseApp, options: ConnectorConfig): DataConnect; + +// @public (undocumented) +export class MutationManager { + constructor(transport: DataConnectTransport); + // (undocumented) + executeMutation(mutationRef: MutationRef): MutationPromise; + } + +// @public (undocumented) +export interface MutationPromise extends PromiseLike> { +} + +// @public (undocumented) +export interface MutationRef extends OperationRef { + // (undocumented) + refType: typeof MutationStr; +} + +// @public (undocumented) +export function mutationRef(dcInstance: DataConnect, queryName: string): MutationRef; + +// @public (undocumented) +export function mutationRef(dcInstance: DataConnect, queryName: string, variables: Variables): MutationRef; + +// @public (undocumented) +export interface MutationResponse extends CancellableOperation { +} + +// @public (undocumented) +export interface MutationResult extends DataConnectResult { + // (undocumented) + ref: MutationRef; +} + +// @public (undocumented) +export const MutationStr = "mutation"; + +// @public (undocumented) +export type OnCompleteSubscription = () => void; + +// @public (undocumented) +export type OnErrorSubscription = (err?: FirebaseError) => void; + +// @public (undocumented) +export type OnResultSubscription = (res: QueryResult) => void; + +// @public (undocumented) +export interface OperationRef<_Data, Variables> { + // (undocumented) + dataConnect: DataConnect; + // (undocumented) + name: string; + // (undocumented) + refType: ReferenceType; + // (undocumented) + variables: Variables; +} + +// @public (undocumented) +export interface OpResult { + // (undocumented) + data: Data; + // (undocumented) + fetchTime: string; + // (undocumented) + source: DataSource; +} + +// @public (undocumented) +export function parseOptions(fullHost: string): TransportOptions; + +// @public (undocumented) +export interface ProjectOptions { + // (undocumented) + connector: string; + // (undocumented) + location: string; + // (undocumented) + projectId: string; + // (undocumented) + service: string; +} + +// @public (undocumented) +export interface QueryPromise extends PromiseLike> { +} + +// @public (undocumented) +export interface QueryRef extends OperationRef { + // (undocumented) + refType: typeof QueryStr; +} + +// @public (undocumented) +export function queryRef(dcInstance: DataConnect, queryName: string): QueryRef; + +// @public (undocumented) +export function queryRef(dcInstance: DataConnect, queryName: string, variables: Variables): QueryRef; + +// @public (undocumented) +export interface QueryResponse extends CancellableOperation { +} + +// @public (undocumented) +export interface QueryResult extends DataConnectResult { + // (undocumented) + ref: QueryRef; + // (undocumented) + toJSON: () => SerializedRef; +} + +// @public (undocumented) +export const QueryStr = "query"; + +// @public (undocumented) +export type QueryUnsubscribe = () => void; + +// @public (undocumented) +export type ReferenceType = typeof QueryStr | typeof MutationStr; + +// @public (undocumented) +export interface RefInfo { + // (undocumented) + connectorConfig: DataConnectOptions; + // (undocumented) + name: string; + // (undocumented) + variables: Variables; +} + +// @public (undocumented) +export interface Sender { + // (undocumented) + abort: () => void; + // (undocumented) + send: () => Promise; +} + +// @public (undocumented) +export interface SerializedRef extends OpResult { + // (undocumented) + refInfo: RefInfo; +} + +// @public (undocumented) +export const SOURCE_CACHE = "CACHE"; + +// @public (undocumented) +export const SOURCE_SERVER = "SERVER"; + +// @public (undocumented) +export function subscribe(queryRefOrSerializedResult: QueryRef | SerializedRef, observer: SubscriptionOptions): QueryUnsubscribe; + +// @public (undocumented) +export function subscribe(queryRefOrSerializedResult: QueryRef | SerializedRef, onNext: OnResultSubscription, onError?: OnErrorSubscription, onComplete?: OnCompleteSubscription): QueryUnsubscribe; + +// @public (undocumented) +export interface SubscriptionOptions { + // (undocumented) + onComplete?: OnCompleteSubscription; + // (undocumented) + onErr?: OnErrorSubscription; + // (undocumented) + onNext?: OnResultSubscription; +} + +// @public (undocumented) +export function terminate(dataConnect: DataConnect): void; + +// @public (undocumented) +export function toQueryRef(serializedRef: SerializedRef): QueryRef; + +// @public (undocumented) +export type TransportClass = new (options: DataConnectOptions, apiKey?: string, authProvider?: AuthTokenProvider, transportOptions?: TransportOptions) => DataConnectTransport; + +// @public (undocumented) +export interface TransportOptions { + // (undocumented) + host: string; + // (undocumented) + port?: number; + // (undocumented) + sslEnabled?: boolean; +} + + +``` diff --git a/common/api-review/firestore-lite.api.md b/common/api-review/firestore-lite.api.md index 440fa488c1c..17f3bfeb379 100644 --- a/common/api-review/firestore-lite.api.md +++ b/common/api-review/firestore-lite.api.md @@ -484,5 +484,4 @@ export class WriteBatch { // @public export function writeBatch(firestore: Firestore): WriteBatch; - ``` diff --git a/common/api-review/util.api.md b/common/api-review/util.api.md index 75e484edd50..2f972e9fed8 100644 --- a/common/api-review/util.api.md +++ b/common/api-review/util.api.md @@ -189,6 +189,20 @@ export type ExperimentalKey = 'authTokenSyncURL' | 'authIdTokenMaxAge'; // @public export function extractQuerystring(url: string): string; +// Warning: (ae-missing-release-tag) "FetchProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export class FetchProvider { + // (undocumented) + static fetch(): typeof fetch; + // (undocumented) + static headers(): typeof Headers; + // (undocumented) + static initialize(fetchImpl: typeof fetch, headersImpl?: typeof Headers, responseImpl?: typeof Response): void; + // (undocumented) + static response(): typeof Response; + } + // @public export interface FirebaseDefaults { // (undocumented) diff --git a/config/karma.saucelabs.js b/config/karma.saucelabs.js index 4517d5b6ee1..8710c87ac0d 100644 --- a/config/karma.saucelabs.js +++ b/config/karma.saucelabs.js @@ -210,7 +210,7 @@ module.exports = function (config) { port: 9876, - retryLimit: 3, + retryLimit: 0, // concurrency: 10, diff --git a/config/mocharc.node.js b/config/mocharc.node.js index e493fce4d52..219256f9370 100644 --- a/config/mocharc.node.js +++ b/config/mocharc.node.js @@ -25,7 +25,7 @@ const config = { require: 'ts-node/register', timeout: 5000, - retries: 5, + // retries: 5, exit: true }; diff --git a/packages/app/src/constants.ts b/packages/app/src/constants.ts index e251c75647b..50f5197957f 100644 --- a/packages/app/src/constants.ts +++ b/packages/app/src/constants.ts @@ -24,6 +24,7 @@ import { name as appCheckName } from '../../../packages/app-check/package.json'; import { name as authName } from '../../../packages/auth/package.json'; import { name as authCompatName } from '../../../packages/auth-compat/package.json'; import { name as databaseName } from '../../../packages/database/package.json'; +import { name as dataconnectName } from '../../../packages/data-connect/package.json'; import { name as databaseCompatName } from '../../../packages/database-compat/package.json'; import { name as functionsName } from '../../../packages/functions/package.json'; import { name as functionsCompatName } from '../../../packages/functions-compat/package.json'; @@ -58,6 +59,7 @@ export const PLATFORM_LOG_STRING = { [authName]: 'fire-auth', [authCompatName]: 'fire-auth-compat', [databaseName]: 'fire-rtdb', + [dataconnectName]: 'fire-connect', [databaseCompatName]: 'fire-rtdb-compat', [functionsName]: 'fire-fn', [functionsCompatName]: 'fire-fn-compat', diff --git a/packages/data-connect/.eslintrc.js b/packages/data-connect/.eslintrc.js new file mode 100644 index 00000000000..5dd443333d9 --- /dev/null +++ b/packages/data-connect/.eslintrc.js @@ -0,0 +1,88 @@ +/** + * @license + * Copyright 2020 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. + */ + +module.exports = { + extends: '../../config/.eslintrc.js', + parserOptions: { + project: 'tsconfig.eslint.json', + // to make vscode-eslint work with monorepo + // https://github.com/typescript-eslint/typescript-eslint/issues/251#issuecomment-463943250 + tsconfigRootDir: __dirname + }, + plugins: ['import'], + ignorePatterns: ['compat/*'], + rules: { + 'no-console': ['error', { allow: ['warn', 'error'] }], + '@typescript-eslint/no-unused-vars': [ + 'error', + { + varsIgnorePattern: '^_', + args: 'none' + } + ], + 'import/order': [ + 'error', + { + 'groups': [ + 'builtin', + 'external', + 'internal', + 'parent', + 'sibling', + 'index' + ], + 'newlines-between': 'always', + 'alphabetize': { 'order': 'asc', 'caseInsensitive': true } + } + ], + 'no-restricted-globals': [ + 'error', + { + 'name': 'window', + 'message': 'Use `PlatformSupport.getPlatform().window` instead.' + }, + { + 'name': 'document', + 'message': 'Use `PlatformSupport.getPlatform().document` instead.' + } + ] + }, + overrides: [ + { + files: ['**/*.d.ts'], + rules: { + 'camelcase': 'off', + 'import/no-duplicates': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unused-vars': 'off' + } + }, + { + files: ['**/*.test.ts', '**/test/**/*.ts'], + rules: { + '@typescript-eslint/no-explicit-any': 'error' + } + }, + { + files: ['scripts/*.ts'], + rules: { + 'import/no-extraneous-dependencies': 'off', + '@typescript-eslint/no-require-imports': 'off' + } + } + ] +}; diff --git a/packages/data-connect/.gitignore b/packages/data-connect/.gitignore new file mode 100644 index 00000000000..48a928da5dd --- /dev/null +++ b/packages/data-connect/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +*.tgz \ No newline at end of file diff --git a/packages/data-connect/.npmignore b/packages/data-connect/.npmignore new file mode 100644 index 00000000000..c2969b2bf95 --- /dev/null +++ b/packages/data-connect/.npmignore @@ -0,0 +1,5 @@ +node_modules/ +rollup.config.mjs +package-lock.json +tsconfig.json +src/ \ No newline at end of file diff --git a/packages/data-connect/api-extractor.json b/packages/data-connect/api-extractor.json new file mode 100644 index 00000000000..deee6510e4b --- /dev/null +++ b/packages/data-connect/api-extractor.json @@ -0,0 +1,11 @@ +{ + "extends": "../../config/api-extractor.json", + // Point it to your entry point d.ts file. + "mainEntryPointFilePath": "/dist/public.d.ts", + "apiReport": { + /** + * apiReport is handled by repo-scripts/prune-dts/extract-public-api.ts + */ + "enabled": false + } +} \ No newline at end of file diff --git a/packages/data-connect/notes.md b/packages/data-connect/notes.md new file mode 100644 index 00000000000..caa61c49696 --- /dev/null +++ b/packages/data-connect/notes.md @@ -0,0 +1,74 @@ +# Android SDK notes + +## Structure +* Maybe we should have each query look up its own cache in the QueryManager. This should be a very easy lookup and we wouldn't have to replicate data in two places. +* `QueryManager` handles actual execution and subscription callbacks. +* Add a `QueryManager` to each of the `DataConnect` objects. +* What happens if you have multiple query references and then each of them has a different serializer? + +For example: +```typescript +const query1 = queryRef('myQuery', myConverter1); +const query2 = queryRef('myQuery', myConverter2); +subscribe(query1, () => {}); +subscribe(query2, () => {}); +execute(query1); + +``` +* What if there are two query refs with the same function callback and then you unsubscribe one? +```typescript +const query1 = queryRef('myQuery', myConverter1); +const query2 = queryRef('myQuery', myConverter2); +function mySub() { } +const unsubscribe1 = subscribe(query1, mySub); +const unsubscribe2 = subscribe(query2, mySub); +unsubscribe1(); // would this unsubscribe twice? +``` + +* Potentially, what could happen is that for each reference, it stores the converter and then it queries the querymanager for cache changes? + * Any time you call `new` on the `Query`, it adds an instance to the `QueryManager` to update when it gets data back. +* What if the converter was on the subscription itself? + + +``` typescript +interface Query { + name: string; + variables: Variables; +} +interface DataConnectSubscription { + userCallback: (data: Response, error: Error) => void; + converter: Converter; + unsubscribe: () => void; +} +interface TrackedQuery { + queryRef: Query; + subscriptions: DataConnectSubscription[]; + currentCache: Response | null; +} +interface QueryManager { + // adds a query to track + track(queryName: string, args: T) + // subscribe to a query + subscribe(queryName, args: T) +} + +// Examples +function HomeComponent() { + const getAllMoviesRef = queryRef('getAllMovies'); + const [movies, setMovieData] = useState([]); + function onMovieSubscribe(data: Movie[], error: Error) { + setMovieData(data); + } + function movieConverter(snapshot: DataConnectSnapshot): Movie[] { + return snapshot.data as Movie[]; + } + subscribe(getAllMoviesRef, onMovieSubscribe, movieConverter) +} +// Separate Page, but still needs movie information +function ListComponent() { + // Calls `queryManager`, which would return an existing query ref + const getAllMoviesRef = queryRef('getAllMovies'); +} + +// Two queries with different types? Should be fine. Weird, but fine. +``` diff --git a/packages/data-connect/package.json b/packages/data-connect/package.json new file mode 100644 index 00000000000..878849ce470 --- /dev/null +++ b/packages/data-connect/package.json @@ -0,0 +1,77 @@ +{ + "name": "@firebase/data-connect", + "version": "0.0.1", + "description": "", + "private": true, + "author": "Firebase (https://firebase.google.com/)", + "main": "dist/index.node.cjs.js", + "browser": "dist/index.esm2017.js", + "module": "dist/index.esm2017.js", + "esm5": "dist/index.esm5.js", + "exports": { + ".": { + "types": "./dist/public.d.ts", + "node": { + "import": "./dist/node-esm/index.node.esm.js", + "require": "./dist/index.node.cjs.js" + }, + "esm5": "./dist/index.esm5.js", + "browser": { + "require": "./dist/index.cjs.js", + "import": "./dist/index.esm2017.js" + }, + "default": "./dist/index.esm2017.js" + }, + "./package.json": "./package.json" + }, + "files": [ + "dist" + ], + "scripts": { + "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore' --fix", + "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "build": "rollup -c rollup.config.js && yarn api-report", + "prettier": "prettier --write '*.js' '*.ts' '@(src|test)/**/*.ts'", + "build:deps": "lerna run --scope @firebase/'{app,data-connect}' --include-dependencies build", + "dev": "rollup -c -w", + "test": "run-p --npm-path npm lint test:emulator", + "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:emulator", + "test:all": "npm run test:node", + "test:browser": "karma start --single-run", + "test:node": "TS_NODE_FILES=true TS_NODE_CACHE=NO TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha 'test/{,!(browser)/**/}*.test.ts' --file src/index.node.ts --config ../../config/mocharc.node.js", + "test:emulator": "ts-node --compiler-options='{\"module\":\"commonjs\"}' ../../scripts/emulator-testing/dataconnect-test-runner.ts", + "api-report": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' ts-node ../../repo-scripts/prune-dts/extract-public-api.ts --package data-connect --packageRoot . --typescriptDts ./dist/src/index.d.ts --rollupDts ./dist/private.d.ts --untrimmedRollupDts ./dist/internal.d.ts --publicDts ./dist/public.d.ts && yarn api-report:api-json", + "api-report:api-json": "rm -rf temp && api-extractor run --local --verbose", + "doc": "api-documenter markdown --input temp --output docs", + "typings:public": "node ../../scripts/build/use_typings.js ./dist/public.d.ts" +}, + "license": "Apache-2.0", + "dependencies": { + "@firebase/auth-interop-types": "0.2.2", + "@firebase/component": "0.6.6", + "@firebase/logger": "0.4.1", + "@firebase/util": "1.9.5", + "tslib": "^2.1.0" + }, + "devDependencies": { + "@firebase/app": "0.10.2", + "rollup": "2.79.1", + "rollup-plugin-typescript2": "0.31.2", + "typescript": "4.7.4" + }, + "repository": { + "directory": "packages/data-connect", + "type": "git", + "url": "https://github.com/firebase/firebase-js-sdk.git" + }, + "bugs": { + "url": "https://github.com/firebase/firebase-js-sdk/issues" + }, + "typings": "dist/src/index.d.ts", + "nyc": { + "extension": [ + ".ts" + ], + "reportDir": "./coverage/node" + } +} diff --git a/packages/data-connect/rollup.config.js b/packages/data-connect/rollup.config.js new file mode 100644 index 00000000000..2422c89e3cb --- /dev/null +++ b/packages/data-connect/rollup.config.js @@ -0,0 +1,166 @@ +/** + * @license + * Copyright 2018 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 json from '@rollup/plugin-json'; +import typescriptPlugin from 'rollup-plugin-typescript2'; +import replace from 'rollup-plugin-replace'; +import typescript from 'typescript'; +import { generateBuildTargetReplaceConfig } from '../../scripts/build/rollup_replace_build_target'; +import { emitModulePackageFile } from '../../scripts/build/rollup_emit_module_package_file'; +import pkg from './package.json'; + +const deps = [ + ...Object.keys({ ...pkg.peerDependencies, ...pkg.dependencies }), + '@firebase/app' +]; + +function onWarn(warning, defaultWarn) { + if (warning.code === 'CIRCULAR_DEPENDENCY') { + throw new Error(warning); + } + defaultWarn(warning); +} + +const es5BuildPlugins = [ + typescriptPlugin({ + typescript, + abortOnError: false + }), + json() +]; + +const es2017BuildPlugins = [ + typescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + target: 'es2017' + } + }, + abortOnError: false + }), + json({ preferConst: true }) +]; + +const browserBuilds = [ + { + input: 'src/index.ts', + output: [ + { + file: pkg.esm5, + format: 'es', + sourcemap: true + } + ], + plugins: [ + ...es5BuildPlugins, + replace(generateBuildTargetReplaceConfig('esm', 5)) + ], + treeshake: { + moduleSideEffects: false + }, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), + onwarn: onWarn + }, + { + input: 'src/index.ts', + output: [ + { + file: pkg.module, + format: 'es', + sourcemap: true + } + ], + plugins: [ + ...es2017BuildPlugins, + replace(generateBuildTargetReplaceConfig('esm', 2017)) + ], + treeshake: { + moduleSideEffects: false + }, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), + onwarn: onWarn + }, + { + input: 'src/index.ts', + output: [ + { + file: 'dist/index.cjs.js', + format: 'cjs', + sourcemap: true + } + ], + plugins: [ + ...es2017BuildPlugins, + replace(generateBuildTargetReplaceConfig('cjs', 2017)) + ], + treeshake: { + moduleSideEffects: false + }, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), + onwarn: onWarn + } +]; + +const nodeBuilds = [ + { + input: 'src/index.node.ts', + output: { file: pkg.main, format: 'cjs', sourcemap: true }, + plugins: [ + ...es5BuildPlugins, + replace(generateBuildTargetReplaceConfig('cjs', 5)) + ], + treeshake: { + moduleSideEffects: false + }, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), + onwarn: onWarn + }, + { + input: 'src/index.node.ts', + output: { + file: pkg.exports['.'].node.import, + format: 'es', + sourcemap: true + }, + plugins: [ + ...es2017BuildPlugins, + replace(generateBuildTargetReplaceConfig('esm', 2017)), + emitModulePackageFile() + ], + treeshake: { + moduleSideEffects: false + }, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), + onwarn: onWarn + } + /** + * Standalone Build for Admin SDK + */ + // { + // input: 'src/index.standalone.ts', + // output: [{ file: pkg.standalone, format: 'cjs', sourcemap: true }], + // plugins: es5BuildPlugins, + // treeshake: { + // moduleSideEffects: false + // }, + // external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), + // onwarn: onWarn + // } +]; + +export default [...browserBuilds, ...nodeBuilds]; diff --git a/packages/data-connect/rollup.config.react.js b/packages/data-connect/rollup.config.react.js new file mode 100644 index 00000000000..a61ac8a640d --- /dev/null +++ b/packages/data-connect/rollup.config.react.js @@ -0,0 +1,238 @@ +/** + * @license + * Copyright 2020 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 tmp from 'tmp'; +import path from 'path'; +import json from '@rollup/plugin-json'; +import alias from '@rollup/plugin-alias'; +import typescriptPlugin from 'rollup-plugin-typescript2'; +import typescript from 'typescript'; +import sourcemaps from 'rollup-plugin-sourcemaps'; +import replace from 'rollup-plugin-replace'; +import { terser } from 'rollup-plugin-terser'; + +import pkg from './react/package.json'; +import { generateBuildTargetReplaceConfig } from '../../scripts/build/rollup_replace_build_target'; + +const util = require('./rollup.shared'); + +const nodePlugins = function () { + return [ + typescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + target: 'es2017' + } + }, + cacheDir: tmp.dirSync(), + abortOnError: true, + transformers: [util.removeAssertTransformer] + }), + json({ preferConst: true }) + ]; +}; + +const browserPlugins = function () { + return [ + typescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + target: 'es2017' + } + }, + cacheDir: tmp.dirSync(), + abortOnError: true, + transformers: [util.removeAssertAndPrefixInternalTransformer] + }), + json({ preferConst: true }), + terser(util.manglePrivatePropertiesOptions) + ]; +}; + +const allBuilds = [ + // Intermidiate Node ESM build without build target reporting + // this is an intermidiate build used to generate the actual esm and cjs builds + // which add build target reporting + { + input: './react/index.tsx', + output: { + file: path.resolve('./react', pkg['main-esm']), + format: 'es', + sourcemap: true + }, + plugins: [ + alias(util.generateAliasConfig('node_react')), + ...nodePlugins(), + replace({ + '__RUNTIME_ENV__': 'node' + }) + ], + external: util.resolveNodeExterns, + treeshake: { + moduleSideEffects: false + }, + onwarn: util.onwarn + }, + // Node CJS build + { + input: path.resolve('./react', pkg['main-esm']), + output: { + file: path.resolve('./react', pkg.main), + format: 'cjs', + sourcemap: true + }, + plugins: [ + typescriptPlugin({ + typescript, + compilerOptions: { + allowJs: true, + target: 'es5' + }, + include: ['dist/react/*.js'] + }), + json(), + sourcemaps(), + replace(generateBuildTargetReplaceConfig('cjs', 5)) + ], + external: util.resolveNodeExterns, + treeshake: { + moduleSideEffects: false + } + }, + // Node ESM build + { + input: path.resolve('./react', pkg['main-esm']), + output: { + file: path.resolve('./react', pkg['main-esm']), + format: 'es', + sourcemap: true + }, + plugins: [ + sourcemaps(), + replace(generateBuildTargetReplaceConfig('esm', 2017)) + ], + external: util.resolveNodeExterns, + treeshake: { + moduleSideEffects: false + } + }, + // Intermidiate browser build without build target reporting + // this is an intermidiate build used to generate the actual esm and cjs builds + // which add build target reporting + { + input: './react/index.tsx', + output: { + file: path.resolve('./react', pkg.browser), + format: 'es', + sourcemap: true + }, + plugins: [ + alias(util.generateAliasConfig('browser_react')), + ...browserPlugins(), + // setting it to empty string because browser is the default env + replace({ + '__RUNTIME_ENV__': '' + }) + ], + external: util.resolveBrowserExterns, + treeshake: { + moduleSideEffects: false + } + }, + // Convert es2017 build to ES5 + { + input: path.resolve('./react', pkg.browser), + output: [ + { + file: path.resolve('./react', pkg.esm5), + format: 'es', + sourcemap: true + } + ], + plugins: [ + ...util.es2017ToEs5Plugins(/* mangled= */ true), + replace(generateBuildTargetReplaceConfig('esm', 5)) + ], + external: util.resolveBrowserExterns, + treeshake: { + moduleSideEffects: false + } + }, + // Convert es2017 build to CJS + { + input: path.resolve('./react', pkg.browser), + output: [ + { + file: './dist/react/index.cjs.js', + format: 'es', + sourcemap: true + } + ], + plugins: [ + sourcemaps(), + replace(generateBuildTargetReplaceConfig('cjs', 2017)) + ], + external: util.resolveBrowserExterns, + treeshake: { + moduleSideEffects: false + } + }, + // Browser es2017 build + { + input: path.resolve('./react', pkg.browser), + output: [ + { + file: path.resolve('./react', pkg.browser), + format: 'es', + sourcemap: true + } + ], + plugins: [ + sourcemaps(), + replace(generateBuildTargetReplaceConfig('esm', 2017)) + ], + external: util.resolveBrowserExterns, + treeshake: { + moduleSideEffects: false + } + }, + // RN build + { + input: './react/index.ts', + output: { + file: path.resolve('./react', pkg['react-native']), + format: 'es', + sourcemap: true + }, + plugins: [ + alias(util.generateAliasConfig('rn_react')), + ...browserPlugins(), + replace({ + ...generateBuildTargetReplaceConfig('esm', 2017), + '__RUNTIME_ENV__': 'rn' + }) + ], + external: util.resolveBrowserExterns, + treeshake: { + moduleSideEffects: false + } + } +]; + +export default allBuilds; diff --git a/packages/data-connect/src/api.browser.ts b/packages/data-connect/src/api.browser.ts new file mode 100644 index 00000000000..1e0bc61399b --- /dev/null +++ b/packages/data-connect/src/api.browser.ts @@ -0,0 +1,95 @@ +/** + * @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 { + OnCompleteSubscription, + OnErrorSubscription, + OnResultSubscription, + QueryRef, + QueryUnsubscribe, + SubscriptionOptions, + toQueryRef +} from './api/query'; +import { OpResult, SerializedRef } from './api/Reference'; +import { DataConnectError, Code } from './core/error'; + +/** + * + * @public + * @param queryRef + * @param onResult + * @param onErr + * @param initialCache + * @returns + */ +export function subscribe( + queryRefOrSerializedResult: + | QueryRef + | SerializedRef, + observer: SubscriptionOptions +): QueryUnsubscribe; +export function subscribe( + queryRefOrSerializedResult: + | QueryRef + | SerializedRef, + onNext: OnResultSubscription, + onError?: OnErrorSubscription, + onComplete?: OnCompleteSubscription +): QueryUnsubscribe; +export function subscribe( + queryRefOrSerializedResult: + | QueryRef + | SerializedRef, + observerOrOnNext: + | SubscriptionOptions + | OnResultSubscription, + onError?: OnErrorSubscription, + onComplete?: OnCompleteSubscription +): QueryUnsubscribe { + let ref: QueryRef; + let initialCache: OpResult | undefined; + if ('refInfo' in queryRefOrSerializedResult) { + let serializedRef: SerializedRef = + queryRefOrSerializedResult; + const { data, source, fetchTime } = serializedRef; + initialCache = { + data, + source, + fetchTime + }; + ref = toQueryRef(serializedRef); + } else { + ref = queryRefOrSerializedResult; + } + let onResult: OnResultSubscription | undefined = undefined; + if (typeof observerOrOnNext === 'function') { + onResult = observerOrOnNext; + } else { + onResult = observerOrOnNext.onNext; + onError = observerOrOnNext.onErr; + onComplete = observerOrOnNext.onComplete; + } + if (!onResult) { + throw new DataConnectError(Code.INVALID_ARGUMENT, 'Must provide onNext'); + } + return ref.dataConnect._queryManager.addSubscription( + ref, + onResult, + onError, + initialCache + ); +} diff --git a/packages/data-connect/src/api.node.ts b/packages/data-connect/src/api.node.ts new file mode 100644 index 00000000000..f8236ebe2d7 --- /dev/null +++ b/packages/data-connect/src/api.node.ts @@ -0,0 +1,18 @@ +/** + * @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. + */ + +export { subscribe } from './api.browser'; diff --git a/packages/data-connect/src/api/DataConnect.ts b/packages/data-connect/src/api/DataConnect.ts new file mode 100644 index 00000000000..ecc13b22963 --- /dev/null +++ b/packages/data-connect/src/api/DataConnect.ts @@ -0,0 +1,212 @@ +/** + * @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 { + FirebaseApp, + FirebaseError, + _getProvider, + _removeServiceInstance, + getApp +} from '@firebase/app'; +import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; +import { Provider } from '@firebase/component'; + +import { AuthTokenProvider, EmulatorTokenProvider, FirebaseAuthProvider } from '../core/FirebaseAuthProvider'; +import { QueryManager } from '../core/QueryManager'; +import { DataConnectTransport, TransportClass } from '../network'; +import { RESTTransport } from '../network/transport/rest'; + +import { MutationManager } from './Mutation'; +import { Code, DataConnectError } from '../core/error'; + +export interface ProjectOptions { + location: string; + connector: string; + service: string; + projectId: string; +} + +export interface ConnectorConfig { + location: string; + connector: string; + service: string; +} + +export interface TransportOptions { + host: string; + sslEnabled?: boolean; + port?: number; +} + +export const FIREBASE_DATA_CONNECT_EMULATOR_HOST_VAR = + 'FIREBASE_DATA_CONNECT_EMULATOR_HOST'; + +export function parseOptions(fullHost: string): TransportOptions { + const [protocol, hostName] = fullHost.split('://'); + const isSecure = protocol === 'https'; + const [host, portAsString] = hostName.split(':'); + const port = Number(portAsString); + return { host, port, sslEnabled: isSecure }; +} +export interface DataConnectOptions extends ConnectorConfig { + projectId: string; +} + +export class DataConnect { + _queryManager!: QueryManager; + _mutationManager!: MutationManager; + public isEmulator = false; + initialized = false; + private _transport!: DataConnectTransport; + private transportClass: TransportClass | undefined; + private transportOptions?: TransportOptions; + private authTokenProvider?: AuthTokenProvider; + constructor( + public readonly app: FirebaseApp, + private readonly dataConnectOptions: DataConnectOptions, + private readonly authProvider: Provider + ) { + if (typeof process !== 'undefined' && process.env) { + const host = process.env[FIREBASE_DATA_CONNECT_EMULATOR_HOST_VAR]; + if (host) { + this.isEmulator = true; + this.transportOptions = parseOptions(host); + } + } + } + _delete() { + _removeServiceInstance( + this.app, + 'data-connect', + JSON.stringify(this.getSettings()) + ); + return Promise.resolve(); + } + + getSettings(): ConnectorConfig { + const copy = JSON.parse(JSON.stringify(this.dataConnectOptions)); + delete copy.projectId; + return copy; + } + + setInitialized() { + if (this.initialized) { + return; + } + if (this.transportClass === undefined) { + this.transportClass = RESTTransport; + } + + + if (this.authProvider) { + this.authTokenProvider = this.isEmulator + ? new EmulatorTokenProvider(EmulatorTokenProvider.OWNER) + : new FirebaseAuthProvider( + this.app.name, + this.app.options, + this.authProvider + ); + this.authTokenProvider.addTokenChangeListener(token => { + this._transport.onTokenChanged(token); + }); + } + + this.initialized = true; + this._transport = new this.transportClass( + this.dataConnectOptions, + this.app.options.apiKey, + this.authTokenProvider + ); + if (this.transportOptions) { + this._transport.useEmulator( + this.transportOptions.host, + this.transportOptions.port, + this.transportOptions.sslEnabled + ); + } + this._queryManager = new QueryManager(this._transport); + this._mutationManager = new MutationManager(this._transport); + } + + enableEmulator(transportOptions: TransportOptions) { + if (this.initialized) { + throw new DataConnectError( + Code.ALREADY_INITIALIZED, + 'DataConnect instance already initialized!' + ); + } + this.transportOptions = transportOptions; + this.isEmulator = true; + } + +} + +export function connectDataConnectEmulator( + dc: DataConnect, + host: string, + port?: number, + sslEnabled = false +) { + dc.enableEmulator({ host, port, sslEnabled }); +} + +export function getDataConnect(options: ConnectorConfig): DataConnect; +export function getDataConnect( + app: FirebaseApp, + options: ConnectorConfig +): DataConnect; +export function getDataConnect( + appOrOptions: FirebaseApp | ConnectorConfig, + optionalOptions?: ConnectorConfig +): DataConnect { + let app: FirebaseApp; + let dcOptions: ConnectorConfig; + if ('location' in appOrOptions) { + dcOptions = appOrOptions; + app = getApp(); + } else { + dcOptions = optionalOptions!; + app = appOrOptions; + } + + if (!app) { + app = getApp(); + } + const provider = _getProvider(app, 'data-connect'); + const identifier = JSON.stringify(dcOptions); + if (provider.isInitialized(identifier)) { + const dcInstance = provider.getImmediate({ identifier }); + const options = provider.getOptions(identifier); + const optionsValid = Object.keys(options).length > 0; + if (optionsValid) { + return dcInstance; + } + } + if (!dcOptions) { + throw new DataConnectError(Code.INVALID_ARGUMENT, 'DC Option Required'); + } + // Initialize with options. + return provider.initialize({ + instanceIdentifier: identifier, + options: dcOptions + }); +} + +export function terminate(dataConnect: DataConnect) { + dataConnect._delete(); + // TODO(mtewani): Stop pending tasks +} diff --git a/packages/data-connect/src/api/Mutation.ts b/packages/data-connect/src/api/Mutation.ts new file mode 100644 index 00000000000..4a9a6db4a94 --- /dev/null +++ b/packages/data-connect/src/api/Mutation.ts @@ -0,0 +1,97 @@ +/** + * @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 { DataConnectTransport } from '../network/transport'; + +import { DataConnect } from './DataConnect'; +import { + DataConnectResult, + MutationStr, + OperationRef, + SOURCE_SERVER +} from './Reference'; + +export interface MutationRef + extends OperationRef { + refType: typeof MutationStr; +} + +export function mutationRef( + dcInstance: DataConnect, + queryName: string +): MutationRef; +export function mutationRef( + dcInstance: DataConnect, + queryName: string, + variables: Variables +): MutationRef; +export function mutationRef( + dcInstance: DataConnect, + queryName: string, + variables?: Variables +): MutationRef { + dcInstance.setInitialized(); + const ref: MutationRef = { + dataConnect: dcInstance, + name: queryName, + refType: MutationStr, + variables: variables as Variables + }; + return ref; +} + +export class MutationManager { + private _inflight: Array> = []; + constructor(private transport: DataConnectTransport) {} + executeMutation( + mutationRef: MutationRef + ): MutationPromise { + const result = this.transport.invokeMutation( + mutationRef.name, + mutationRef.variables + ); + const withRefPromise = result.then(res => { + const obj: MutationResult = { + ...res, // Double check that the result is result.data, not just result + source: SOURCE_SERVER, + ref: mutationRef, + fetchTime: Date.now().toLocaleString() + }; + return obj; + }); + this._inflight.push(result); + const removePromise = () => + (this._inflight = this._inflight.filter(promise => promise !== result)); + result.then(removePromise, removePromise); + return withRefPromise; + } +} + +export interface MutationResult + extends DataConnectResult { + ref: MutationRef; +} +export interface MutationPromise + extends PromiseLike> { + // reserved for special actions like cancellation +} + +export function executeMutation( + mutationRef: MutationRef +): MutationPromise { + return mutationRef.dataConnect._mutationManager.executeMutation(mutationRef); +} diff --git a/packages/data-connect/src/api/Reference.ts b/packages/data-connect/src/api/Reference.ts new file mode 100644 index 00000000000..e0a0e510c6e --- /dev/null +++ b/packages/data-connect/src/api/Reference.ts @@ -0,0 +1,52 @@ +/** + * @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 { DataConnect, DataConnectOptions } from './DataConnect'; +export const QueryStr = 'query'; +export const MutationStr = 'mutation'; +export type ReferenceType = typeof QueryStr | typeof MutationStr; + +export const SOURCE_SERVER = 'SERVER'; +export const SOURCE_CACHE = 'CACHE'; +export type DataSource = typeof SOURCE_CACHE | typeof SOURCE_SERVER; + +export interface OpResult { + data: Data; + source: DataSource; + fetchTime: string; +} + +export interface OperationRef<_Data, Variables> { + name: string; + variables: Variables; + refType: ReferenceType; + dataConnect: DataConnect; +} + +export interface DataConnectResult extends OpResult { + ref: OperationRef; + // future metadata +} + +export interface RefInfo { + name: string; + variables: Variables; + connectorConfig: DataConnectOptions; +} +export interface SerializedRef extends OpResult { + refInfo: RefInfo; +} diff --git a/packages/data-connect/src/api/index.ts b/packages/data-connect/src/api/index.ts new file mode 100644 index 00000000000..41bcb3701fc --- /dev/null +++ b/packages/data-connect/src/api/index.ts @@ -0,0 +1,22 @@ +/** + * @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. + */ + +export * from '../network'; +export * from './DataConnect'; +export * from './Reference'; +export * from './Mutation'; +export * from './query'; diff --git a/packages/data-connect/src/api/query.ts b/packages/data-connect/src/api/query.ts new file mode 100644 index 00000000000..f68d30426ac --- /dev/null +++ b/packages/data-connect/src/api/query.ts @@ -0,0 +1,94 @@ +/** + * @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 { DataConnectError } from '../core/error'; +import { DataConnect, getDataConnect } from './DataConnect'; +import { + OperationRef, + QueryStr, + DataConnectResult, + SerializedRef +} from './Reference'; + +export type OnResultSubscription = ( + res: QueryResult +) => void; +export type OnErrorSubscription = (err?: DataConnectError) => void; +export type QueryUnsubscribe = () => void; +export interface DataConnectSubscription { + userCallback: OnResultSubscription; + errCallback?: (e?: DataConnectError) => void; + unsubscribe: () => void; +} +export interface QueryRef + extends OperationRef { + refType: typeof QueryStr; +} +export interface QueryResult + extends DataConnectResult { + ref: QueryRef; + toJSON: () => SerializedRef; +} +export interface QueryPromise + extends PromiseLike> { + // reserved for special actions like cancellation +} + +export function executeQuery( + queryRef: QueryRef +): QueryPromise { + return queryRef.dataConnect._queryManager.executeQuery(queryRef); +} + +export function queryRef( + dcInstance: DataConnect, + queryName: string +): QueryRef; +export function queryRef( + dcInstance: DataConnect, + queryName: string, + variables: Variables +): QueryRef; +export function queryRef( + dcInstance: DataConnect, + queryName: string, + variables?: Variables, + initialCache?: QueryResult +): QueryRef { + dcInstance.setInitialized(); + dcInstance._queryManager.track(queryName, variables, initialCache); + return { + dataConnect: dcInstance, + refType: QueryStr, + name: queryName, + variables: variables as Variables + }; +} +export function toQueryRef( + serializedRef: SerializedRef +) { + const { + refInfo: { name, variables, connectorConfig } + } = serializedRef; + return queryRef(getDataConnect(connectorConfig), name, variables); +} +export type OnCompleteSubscription = () => void; +export interface SubscriptionOptions { + onNext?: OnResultSubscription; + onErr?: OnErrorSubscription; + onComplete?: OnCompleteSubscription; +} diff --git a/packages/data-connect/src/core/FirebaseAuthProvider.ts b/packages/data-connect/src/core/FirebaseAuthProvider.ts new file mode 100644 index 00000000000..159d4e046ad --- /dev/null +++ b/packages/data-connect/src/core/FirebaseAuthProvider.ts @@ -0,0 +1,99 @@ +/** + * @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 { FirebaseOptions } from '@firebase/app'; +import { + FirebaseAuthInternal, + FirebaseAuthInternalName, + FirebaseAuthTokenData +} from '@firebase/auth-interop-types'; +import { Provider } from '@firebase/component'; + +export interface AuthTokenProvider { + getToken(forceRefresh: boolean): Promise; + addTokenChangeListener(listener: AuthTokenListener): void; +} +export type AuthTokenListener = (token: string | null) => void; + +// Mostly borrowed from packages/database/src/core/AuthTokenProvider.ts +export class FirebaseAuthProvider implements AuthTokenProvider { + private auth_: FirebaseAuthInternal; + constructor( + private appName: string, + private options: FirebaseOptions, + private authProvider_: Provider + ) { + this.auth_ = authProvider_.getImmediate({ optional: true })!; + if (!this.auth_) { + authProvider_.onInit(auth => (this.auth_ = auth)); + } + } + getToken(forceRefresh: boolean): Promise { + if (!this.auth_) { + return new Promise((resolve, reject) => { + setTimeout(() => { + if (this.auth_) { + this.getToken(forceRefresh).then(resolve, reject); + } else { + resolve(null); + } + }, 0); + }); + } + return this.auth_.getToken(forceRefresh).catch(error => { + if (error && error.code === 'auth/token-not-initialized') { + // TODO(mtewani): Implement logging mechanism + // log( + // 'Got auth/token-not-initialized error. Treating as null token.' + // ); + return null; + } else { + return Promise.reject(error); + } + }); + } + addTokenChangeListener(listener: AuthTokenListener) { + this.auth_?.addAuthTokenListener(listener); + } + removeTokenChangeListener(listener: (token: string | null) => void): void { + this.authProvider_ + .get() + .then(auth => auth.removeAuthTokenListener(listener)); + } +} +export class EmulatorTokenProvider implements AuthTokenProvider { + /** A string that is treated as an admin access token by the RTDB emulator. Used by Admin SDK. */ + static OWNER = 'owner'; + + constructor(private accessToken: string) {} + + getToken(forceRefresh: boolean): Promise { + return Promise.resolve({ + accessToken: this.accessToken + }); + } + + addTokenChangeListener(listener: AuthTokenListener): void { + // Invoke the listener immediately to match the behavior in Firebase Auth + // (see packages/auth/src/auth.js#L1807) + listener(this.accessToken); + } + + removeTokenChangeListener(listener: (token: string | null) => void): void {} + + notifyForInvalidToken(): void {} +} diff --git a/packages/data-connect/src/core/QueryManager.ts b/packages/data-connect/src/core/QueryManager.ts new file mode 100644 index 00000000000..c5647350ed9 --- /dev/null +++ b/packages/data-connect/src/core/QueryManager.ts @@ -0,0 +1,218 @@ +/** + * @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 { + DataConnectSubscription, + OnErrorSubscription, + OnResultSubscription, + QueryPromise, + QueryRef, + QueryResult +} from '../api/query'; +import { + OperationRef, + QueryStr, + OpResult, + SerializedRef, + SOURCE_SERVER, + DataSource, + SOURCE_CACHE +} from '../api/Reference'; +import { DataConnectTransport } from '../network'; +import { encoderImpl } from '../util/encoder'; +import { setIfNotExists } from '../util/map'; +import { DataConnectError } from './error'; + +interface TrackedQuery { + ref: Omit, 'dataConnect'>; + subscriptions: Array>; + currentCache: OpResult | null; + lastError: DataConnectError | null; +} + +function getRefSerializer( + queryRef: QueryRef, + data: Data, + source: DataSource +) { + return function toJSON(): SerializedRef { + return { + data, + refInfo: { + name: queryRef.name, + variables: queryRef.variables, + connectorConfig: { + projectId: queryRef.dataConnect.app.options.projectId!, + ...queryRef.dataConnect.getSettings() + } + }, + fetchTime: Date.now().toLocaleString(), + source + }; + }; +} + +export class QueryManager { + _queries: Map>; + constructor(private transport: DataConnectTransport) { + this._queries = new Map(); + } + track( + queryName: string, + variables: Variables, + initialCache?: OpResult + ) { + const ref: TrackedQuery['ref'] = { + name: queryName, + variables, + refType: QueryStr + }; + const key = encoderImpl(ref); + const newTrackedQuery: TrackedQuery = { + ref, + subscriptions: [], + currentCache: initialCache || null, + lastError: null + }; + // @ts-ignore + setIfNotExists(this._queries, key, newTrackedQuery); + return this._queries.get(key); + } + addSubscription( + queryRef: OperationRef, + onResultCallback: OnResultSubscription, + onErrorCallback?: OnErrorSubscription, + initialCache?: OpResult + ) { + const key = encoderImpl({ + name: queryRef.name, + variables: queryRef.variables, + refType: QueryStr + }); + const trackedQuery = this._queries.get(key) as TrackedQuery< + Response, + Variables + >; + const subscription = { + userCallback: onResultCallback, + errCallback: onErrorCallback + }; + const unsubscribe = () => { + const trackedQuery = this._queries.get(key)!; + trackedQuery.subscriptions = trackedQuery.subscriptions.filter( + sub => sub !== subscription + ); + }; + if (initialCache && trackedQuery.currentCache !== initialCache) { + if ( + !trackedQuery.currentCache || + (trackedQuery.currentCache && + compareDates( + trackedQuery.currentCache.fetchTime, + initialCache.fetchTime + )) + ) { + trackedQuery.currentCache = initialCache; + } + } + if (trackedQuery.currentCache !== null) { + const cachedData = trackedQuery.currentCache.data; + onResultCallback({ + data: cachedData, + source: SOURCE_CACHE, + ref: queryRef as QueryRef, + toJSON: getRefSerializer( + queryRef as QueryRef, + trackedQuery.currentCache.data, + SOURCE_CACHE + ), + fetchTime: trackedQuery.currentCache.fetchTime + }); + if (trackedQuery.lastError !== null && onErrorCallback) { + onErrorCallback(undefined); + } + } + + trackedQuery.subscriptions.push({ + userCallback: onResultCallback, + errCallback: onErrorCallback, + unsubscribe + }); + if (!trackedQuery.currentCache) { + const promise = this.executeQuery( + queryRef as QueryRef + ); + // We want to ignore the error and let subscriptions handle it + promise.then(undefined, err => {}); + } + return unsubscribe; + } + executeQuery( + queryRef: QueryRef + ): QueryPromise { + const key = encoderImpl({ + name: queryRef.name, + variables: queryRef.variables, + refType: QueryStr + }); + const trackedQuery = this._queries.get(key)!; + const result = this.transport.invokeQuery( + queryRef.name, + queryRef.variables + ); + const newR = result.then( + res => { + const fetchTime = new Date().toString(); + const result: QueryResult = { + ...res, + source: SOURCE_SERVER, + ref: queryRef, + toJSON: getRefSerializer(queryRef, res.data, SOURCE_SERVER), + fetchTime + }; + trackedQuery.subscriptions.forEach(subscription => { + subscription.userCallback(result); + }); + trackedQuery.currentCache = { + data: res.data, + source: SOURCE_CACHE, + fetchTime + }; + return result; + }, + err => { + trackedQuery.lastError = err; + trackedQuery.subscriptions.forEach(subscription => { + if (subscription.errCallback) { + subscription.errCallback(err); + } + }); + throw err; + } + ); + + return newR; + } + enableEmulator(host: string, port: number) { + this.transport.useEmulator(host, port); + } +} +function compareDates(str1: string, str2: string) { + const date1 = new Date(str1); + const date2 = new Date(str2); + return date1.getTime() < date2.getTime(); +} diff --git a/packages/data-connect/src/core/error.ts b/packages/data-connect/src/core/error.ts new file mode 100644 index 00000000000..16082e949cf --- /dev/null +++ b/packages/data-connect/src/core/error.ts @@ -0,0 +1,62 @@ +/** + * @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 { FirebaseError } from '@firebase/util'; + +export type DataConnectErrorCode = + | 'other' + | 'already-initialized' + | 'not-initialized' + | 'not-supported' + | 'invalid-argument' + | 'partial-error'; + +export type Code = DataConnectErrorCode; + +export const Code = { + OTHER: 'other' as DataConnectErrorCode, + ALREADY_INITIALIZED: 'already-initialized' as DataConnectErrorCode, + NOT_INITIALIZED: 'not-initialized' as DataConnectErrorCode, + NOT_SUPPORTED: 'not-supported' as DataConnectErrorCode, + INVALID_ARGUMENT: 'invalid-argument' as DataConnectErrorCode, + PARTIAL_ERROR: 'partial-error' as DataConnectErrorCode +}; + +/** An error returned by a Firestore operation. */ +export class DataConnectError extends FirebaseError { + /** The stack of the error. */ + readonly stack?: string; + + /** @hideconstructor */ + constructor( + /** + * The backend error code associated with this error. + */ + readonly code: DataConnectErrorCode, + /** + * A custom error description. + */ + readonly message: string + ) { + super(code, message); + + // HACK: We write a toString property directly because Error is not a real + // class and so inheritance does not work correctly. We could alternatively + // do the same "back-door inheritance" trick that FirebaseError does. + this.toString = () => `${this.name}: [code=${this.code}]: ${this.message}`; + } +} diff --git a/packages/data-connect/src/core/version.ts b/packages/data-connect/src/core/version.ts new file mode 100644 index 00000000000..7c18e8c2949 --- /dev/null +++ b/packages/data-connect/src/core/version.ts @@ -0,0 +1,27 @@ +/** + * @license + * Copyright 2019 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. + */ + +/** The semver (www.semver.org) version of the SDK. */ +export let SDK_VERSION = ''; + +/** + * SDK_VERSION should be set before any database instance is created + * @internal + */ +export function setSDKVersion(version: string): void { + SDK_VERSION = version; +} diff --git a/packages/data-connect/src/index.node.ts b/packages/data-connect/src/index.node.ts new file mode 100644 index 00000000000..0a4970e4856 --- /dev/null +++ b/packages/data-connect/src/index.node.ts @@ -0,0 +1,25 @@ +/** + * @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 { initializeFetch } from './network/fetch'; +import { registerDataConnect } from './register'; + +export * from './api'; +export * from './api.node'; +initializeFetch(fetch); + +registerDataConnect('node'); diff --git a/packages/data-connect/src/index.ts b/packages/data-connect/src/index.ts new file mode 100644 index 00000000000..7402cd77702 --- /dev/null +++ b/packages/data-connect/src/index.ts @@ -0,0 +1,66 @@ +/** + * Firebase Data Connect + * + * @packageDocumentation + */ + +/** + * @license + * Copyright 2023 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 { DataConnect } from './api/DataConnect'; +import { registerDataConnect } from './register'; +// import CryptoJS from 'crypto-js/hmac-sha512'; + +export * from './api'; +export * from './api.browser'; + +registerDataConnect(); +// function sha512(str) { +// return window.crypto.subtle.encrypt() +// } +// async function encoder(object: unknown): Promise { +// const encoder = new TextEncoder(); +// const data = encoder.encode(JSON.stringify(object)); +// const hash = await crypto.subtle.digest('SHA-256', data); +// return hash; +// } +// setEncoder(encoder); + +declare module '@firebase/component' { + interface NameServiceMapping { + 'data-connect': DataConnect; + } +} +// import { getDataConnect, queryRef } from './api'; +// import { getApp } from '@firebase/app'; +// const app = getApp(); +// const dc = getDataConnect({ location: '', connector: '', serviceId: '', projectId: '' }); +// interface Response { +// name: string; +// } +// const converter: Converter = { +// fromDataConnect(input: string) { +// return { name: input }; +// }, +// fromType(input) { +// return input; +// } +// }; +// const myRef = queryRef(dc, '', converter); +// // Ref's shouldn't have access to their own cache, right? +// const a = execute(myRef); +// subscribe(myRef, (res) => { +// }) diff --git a/packages/data-connect/src/network/fetch.ts b/packages/data-connect/src/network/fetch.ts new file mode 100644 index 00000000000..d4838c29895 --- /dev/null +++ b/packages/data-connect/src/network/fetch.ts @@ -0,0 +1,63 @@ +/** + * @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 { Code, DataConnectError } from '../core/error'; + +let connectFetch: typeof fetch | null = globalThis.fetch; +export function initializeFetch(fetchImpl: typeof fetch) { + connectFetch = fetchImpl; +} +export function dcFetch( + url: string, + body: U, + { signal }: AbortController, + accessToken: string | null +) { + if (!connectFetch) { + throw new DataConnectError(Code.OTHER, 'No Fetch Implementation detected!'); + } + const headers: HeadersInit = { + 'Content-Type': 'application/json' + }; + if (accessToken) { + headers['X-Firebase-Auth-Token'] = accessToken; + } + return connectFetch(url, { + body: JSON.stringify(body), + method: 'POST', + headers, + signal + }) + .then(async response => { + let jsonResponse = null; + try { + jsonResponse = await response.json(); + } catch (e) { + throw new DataConnectError(Code.OTHER, JSON.stringify(e)); + } + if (response.status >= 400) { + throw new DataConnectError(Code.OTHER, JSON.stringify(jsonResponse)); + } + return jsonResponse; + }) + .then(res => { + if (res.errors && res.errors.length) { + throw new DataConnectError(Code.OTHER, JSON.stringify(res.errors)); + } + return res as { data: T; errors: Error[] }; + }); +} diff --git a/packages/data-connect/src/network/index.ts b/packages/data-connect/src/network/index.ts new file mode 100644 index 00000000000..33a2202d57f --- /dev/null +++ b/packages/data-connect/src/network/index.ts @@ -0,0 +1,18 @@ +/** + * @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. + */ + +export * from './transport'; diff --git a/packages/data-connect/src/network/transport/index.ts b/packages/data-connect/src/network/transport/index.ts new file mode 100644 index 00000000000..45f31c3f50b --- /dev/null +++ b/packages/data-connect/src/network/transport/index.ts @@ -0,0 +1,57 @@ +/** + * @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 { DataConnectOptions, TransportOptions } from '../../api/DataConnect'; +import { AuthTokenProvider, FirebaseAuthProvider } from '../../core/FirebaseAuthProvider'; + +// Change this to only specify specific args. +export interface DataConnectTransport { + invokeQuery( + queryName: string, + body?: U + ): PromiseLike<{ data: T; errors: Error[] }>; + invokeMutation( + queryName: string, + body?: U + ): PromiseLike<{ data: T; errors: Error[] }>; + useEmulator(host: string, port?: number, sslEnabled?: boolean): void; + onTokenChanged: (token: string | null) => void; +} + +export interface CancellableOperation extends PromiseLike<{ data: T }> { + cancel: () => void; +} + +export interface QueryResponse extends CancellableOperation {} +// export type QueryResponse = { +// // Type '{ data: T; }' is not assignable to type 'T'. +// then: (a: (data: T) => void) => void; +// } +export interface MutationResponse extends CancellableOperation {} + +export interface Sender { + abort: () => void; + send: () => Promise; +} + +export type TransportClass = new ( + options: DataConnectOptions, + apiKey?: string, + authProvider?: AuthTokenProvider, + transportOptions?: TransportOptions +) => DataConnectTransport; +export * from '../../core/FirebaseAuthProvider'; diff --git a/packages/data-connect/src/network/transport/rest.ts b/packages/data-connect/src/network/transport/rest.ts new file mode 100644 index 00000000000..6bbb6246043 --- /dev/null +++ b/packages/data-connect/src/network/transport/rest.ts @@ -0,0 +1,156 @@ +/** + * @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 { DataConnectTransport } from '.'; +import { DataConnectOptions, TransportOptions } from '../../api/DataConnect'; +import { DataConnectError, Code } from '../../core/error'; +import { AuthTokenProvider, FirebaseAuthProvider } from '../../core/FirebaseAuthProvider'; +import { addToken, urlBuilder } from '../../util/url'; +import { dcFetch } from '../fetch'; + +export class RESTTransport implements DataConnectTransport { + private host = ''; + private port: number | undefined; + private location = 'l'; + private connectorName = ''; + private secure = true; + private project = 'p'; + private serviceName: string; + private accessToken: string | null = null; + private authInitialized_ = false; + constructor( + options: DataConnectOptions, + private apiKey?: string | undefined, + private authProvider?: AuthTokenProvider | undefined, + transportOptions?: TransportOptions | undefined + ) { + if (transportOptions) { + if (typeof transportOptions.port === 'number') { + this.port = transportOptions.port; + } + if (typeof transportOptions.sslEnabled !== 'undefined') { + this.secure = transportOptions.sslEnabled; + } + this.host = transportOptions.host; + } + const { location, projectId: project, connector, service } = options; + if (location) { + this.location = location; + } + if (project) { + this.project = project; + } + this.serviceName = service; + if (!connector) { + throw new DataConnectError( + Code.INVALID_ARGUMENT, + 'Connector Name required!' + ); + } + this.connectorName = connector; + } + get endpointUrl(): string { + return urlBuilder( + { + connector: this.connectorName, + location: this.location, + projectId: this.project, + service: this.serviceName + }, + { host: this.host, sslEnabled: this.secure, port: this.port } + ); + } + useEmulator(host: string, port?: number, isSecure?: boolean): void { + this.host = host; + if (typeof port === 'number') { + this.port = port; + } + if (typeof isSecure !== 'undefined') { + this.secure = isSecure; + } + } + onTokenChanged(newToken: string | null) { + this.accessToken = newToken; + } + + getWithAuth() { + let starterPromise: Promise = new Promise(resolve => + resolve(this.accessToken) + ); + if (!this.authInitialized_) { + if (this.authProvider) { + starterPromise = this.authProvider + .getToken(/*forceToken=*/ false) + .then(data => { + if (!data) { + return null; + } + this.accessToken = data.accessToken; + return this.accessToken; + }); + } else { + starterPromise = new Promise(resolve => resolve('')); + } + } + return starterPromise; + } + + // TODO(mtewani): Update U to include shape of body defined in line 13. + invokeQuery = (queryName: string, body: U) => { + const abortController = new AbortController(); + + // TODO(mtewani): Update to proper value + const withAuth = this.getWithAuth().then(() => { + return dcFetch( + addToken(`${this.endpointUrl}:executeQuery`, this.apiKey), + { + name: `projects/${this.project}/locations/${this.location}/services/${this.serviceName}/connectors/${this.connectorName}`, + operationName: queryName, + variables: body + } as unknown as U, // TODO(mtewani): This is a patch, fix this. + abortController, + this.accessToken + ); + }); + + return { + then: withAuth.then.bind(withAuth) + }; + }; + invokeMutation = (mutationName: string, body: U) => { + const abortController = new AbortController(); + const taskResult = this.getWithAuth().then(() => { + return dcFetch( + addToken(`${this.endpointUrl}:executeMutation`, this.apiKey), + { + name: `projects/${this.project}/locations/${this.location}/services/${this.serviceName}/connectors/${this.connectorName}`, + operationName: mutationName, + variables: body + } as unknown as U, + abortController, + this.accessToken + ); + }); + + return { + then: taskResult.then.bind(taskResult), + // catch: taskResult.catch.bind(taskResult), + // finally: taskResult.finally.bind(taskResult), + cancel: () => abortController.abort() + }; + }; +} diff --git a/packages/data-connect/src/register.ts b/packages/data-connect/src/register.ts new file mode 100644 index 00000000000..1344db24582 --- /dev/null +++ b/packages/data-connect/src/register.ts @@ -0,0 +1,57 @@ +/** + * @license + * Copyright 2021 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. + */ +// eslint-disable-next-line import/no-extraneous-dependencies +import { + _registerComponent, + registerVersion, + SDK_VERSION +} from '@firebase/app'; +import { Component, ComponentType } from '@firebase/component'; + +import { name, version } from '../package.json'; +import { setSDKVersion } from '../src/core/version'; + +import { DataConnect, ConnectorConfig } from './api/DataConnect'; + +export function registerDataConnect(variant?: string): void { + setSDKVersion(SDK_VERSION); + _registerComponent( + new Component( + 'data-connect', + ( + container, + { instanceIdentifier: settings, options } + ) => { + const app = container.getProvider('app').getImmediate()!; + const authProvider = container.getProvider('auth-internal'); + let newOpts = options as ConnectorConfig; + if (settings) { + newOpts = JSON.parse(settings); + } + return new DataConnect( + app, + { ...newOpts, projectId: app.options.projectId! }, + authProvider + ); + }, + ComponentType.PUBLIC + ).setMultipleInstances(true) + ); + registerVersion(name, version, variant); + // BUILD_TARGET will be replaced by values like esm5, esm2017, cjs5, etc during the compilation + registerVersion(name, version, '__BUILD_TARGET__'); +} diff --git a/packages/data-connect/src/util/encoder.ts b/packages/data-connect/src/util/encoder.ts new file mode 100644 index 00000000000..22fcaa612af --- /dev/null +++ b/packages/data-connect/src/util/encoder.ts @@ -0,0 +1,23 @@ +/** + * @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. + */ + +export type HmacImpl = (obj: unknown) => string; +export let encoderImpl: HmacImpl; +export function setEncoder(encoder: HmacImpl) { + encoderImpl = encoder; +} +setEncoder(o => JSON.stringify(o)); diff --git a/packages/data-connect/src/util/map.ts b/packages/data-connect/src/util/map.ts new file mode 100644 index 00000000000..5921365d674 --- /dev/null +++ b/packages/data-connect/src/util/map.ts @@ -0,0 +1,22 @@ +/** + * @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. + */ + +export function setIfNotExists(map: Map, key: string, val: T) { + if (!map.has(key)) { + map.set(key, val); + } +} diff --git a/packages/data-connect/src/util/url.ts b/packages/data-connect/src/util/url.ts new file mode 100644 index 00000000000..24396082270 --- /dev/null +++ b/packages/data-connect/src/util/url.ts @@ -0,0 +1,47 @@ +/** + * @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 { ProjectOptions, TransportOptions } from '../api/DataConnect'; +import { Code, DataConnectError } from '../core/error'; + +export function urlBuilder( + projectConfig: ProjectOptions, + transportOptions: TransportOptions +) { + const { connector, location, projectId: project, service } = projectConfig; + const { host, sslEnabled, port } = transportOptions; + const protocol = sslEnabled ? 'https' : 'http'; + const realHost = host || `firebasedataconnect.googleapis.com`; + let baseUrl = `${protocol}://${realHost}`; + if (typeof port === 'number') { + baseUrl += `:${port}`; + } else if (typeof port !== 'undefined') { + throw new DataConnectError( + Code.INVALID_ARGUMENT, + 'Incorrect type for port passed in!' + ); + } + return `${baseUrl}/v1alpha/projects/${project}/locations/${location}/services/${service}/connectors/${connector}`; +} +export function addToken(url: string, apiKey?: string): string { + if (!apiKey) { + return url; + } + const newUrl = new URL(url); + newUrl.searchParams.append('key', apiKey); + return newUrl.toString(); +} diff --git a/packages/data-connect/test/dataconnect/.dataconnect/schema/main/input.gql b/packages/data-connect/test/dataconnect/.dataconnect/schema/main/input.gql new file mode 100755 index 00000000000..8c472f99a6e --- /dev/null +++ b/packages/data-connect/test/dataconnect/.dataconnect/schema/main/input.gql @@ -0,0 +1,49 @@ +scalar Movie_Key +input Movie_Data { + id: String + id_expr: String_Expr + description: String + description_expr: String_Expr + genre: String + genre_expr: String_Expr + name: String + name_expr: String_Expr + test: String + test_expr: String_Expr +} +input Movie_Filter { + _and: [Movie_Filter!] + _not: Movie_Filter + _or: [Movie_Filter!] + id: String_Filter + description: String_Filter + genre: String_Filter + name: String_Filter + test: String_Filter +} +input Movie_ListFilter { + count: Int_Filter + exist: Movie_Filter +} +input Movie_ListUpdate { + append: [Movie_Data!] + delete: Int + i: Int + prepend: [Movie_Data!] + set: [Movie_Data!] + update: [Movie_Update!] +} +input Movie_Order { + id: OrderDirection + description: OrderDirection + genre: OrderDirection + name: OrderDirection + test: OrderDirection +} +input Movie_Update { + id: [String_Update!] + description: [String_Update!] + genre: [String_Update!] + name: [String_Update!] + test: [String_Update!] +} diff --git a/packages/data-connect/test/dataconnect/.dataconnect/schema/main/mutation.gql b/packages/data-connect/test/dataconnect/.dataconnect/schema/main/mutation.gql new file mode 100755 index 00000000000..b6896486e4a --- /dev/null +++ b/packages/data-connect/test/dataconnect/.dataconnect/schema/main/mutation.gql @@ -0,0 +1,8 @@ +extend type Mutation { + movie_insert(data: Movie_Data!): Movie_Key! + movie_upsert(data: Movie_Data!, update: Movie_Update): Movie_Key! + movie_update(id: String, id_expr: String_Expr, key: Movie_Key, data: Movie_Data, update: Movie_Update): Movie_Key + movie_updateMany(where: Movie_Filter, all: Boolean = false, data: Movie_Data, update: Movie_Update): Int! + movie_delete(id: String, id_expr: String_Expr, key: Movie_Key): Movie_Key + movie_deleteMany(where: Movie_Filter, all: Boolean = false): Int! +} diff --git a/packages/data-connect/test/dataconnect/.dataconnect/schema/main/query.gql b/packages/data-connect/test/dataconnect/.dataconnect/schema/main/query.gql new file mode 100755 index 00000000000..53ee30ce8ad --- /dev/null +++ b/packages/data-connect/test/dataconnect/.dataconnect/schema/main/query.gql @@ -0,0 +1,4 @@ +extend type Query { + movie(id: String, id_expr: String_Expr, key: Movie_Key): Movie + movies(where: Movie_Filter, orderBy: [Movie_Order!], limit: Int = 100): [Movie!] +} diff --git a/packages/data-connect/test/dataconnect/.dataconnect/schema/prelude.gql b/packages/data-connect/test/dataconnect/.dataconnect/schema/prelude.gql new file mode 100755 index 00000000000..4007a693025 --- /dev/null +++ b/packages/data-connect/test/dataconnect/.dataconnect/schema/prelude.gql @@ -0,0 +1,953 @@ +"Conditions on a string value" +input String_Filter { + isNull: Boolean + eq: String + eq_expr: String_Expr + ne: String + ne_expr: String_Expr + in: [String!] + nin: [String!] + gt: String + ge: String + lt: String + le: String + contains: String + startsWith: String + endsWith: String + pattern: String_Pattern +} + +""" +The pattern match condition on a string. Specify either like or regex. +https://www.postgresql.org/docs/current/functions-matching.html +""" +input String_Pattern { + "the LIKE expression to use" + like: String + "the POSIX regular expression" + regex: String + "when true, it's case-insensitive. In Postgres: ILIKE, ~*" + ignoreCase: Boolean + "when true, invert the condition. In Postgres: NOT LIKE, !~" + invert: Boolean +} + +"Conditions on a string list" +input String_ListFilter { + includes: String + excludes: String + includesAll: [String!] + excludesAll: [String!] +} + +"Conditions on a UUID value" +input UUID_Filter { + isNull: Boolean + eq: UUID + ne: UUID + in: [UUID!] + nin: [UUID!] +} + +"Conditions on a UUID list" +input UUID_ListFilter { + includes: UUID + excludes: UUID + includesAll: [UUID!] + excludesAll: [UUID!] +} + +"Conditions on an Int value" +input Int_Filter { + isNull: Boolean + eq: Int + ne: Int + in: [Int!] + nin: [Int!] + gt: Int + ge: Int + lt: Int + le: Int +} + +"Conditions on an Int list" +input Int_ListFilter { + includes: Int + excludes: Int + includesAll: [Int!] + excludesAll: [Int!] +} + +"Conditions on an Int64 value" +input Int64_Filter { + isNull: Boolean + eq: Int64 + ne: Int64 + in: [Int64!] + nin: [Int64!] + gt: Int64 + ge: Int64 + lt: Int64 + le: Int64 +} + +"Conditions on an Int64 list" +input Int64_ListFilter { + includes: Int64 + excludes: Int64 + includesAll: [Int64!] + excludesAll: [Int64!] +} + +"Conditions on a Float value" +input Float_Filter { + isNull: Boolean + eq: Float + ne: Float + in: [Float!] + nin: [Float!] + gt: Float + ge: Float + lt: Float + le: Float +} + +"Conditions on a Float list" +input Float_ListFilter { + includes: Float + excludes: Float + includesAll: [Float!] + excludesAll: [Float!] +} + +"Conditions on a Boolean value" +input Boolean_Filter { + isNull: Boolean + eq: Boolean + ne: Boolean + in: [Boolean!] + nin: [Boolean!] +} + +"Conditions on a Boolean list" +input Boolean_ListFilter { + includes: Boolean + excludes: Boolean + includesAll: [Boolean!] + excludesAll: [Boolean!] +} + +"Conditions on a Date value" +input Date_Filter { + isNull: Boolean + eq: Date + ne: Date + in: [Date!] + nin: [Date!] + gt: Date + ge: Date + lt: Date + le: Date + """ + Offset the date filters by a fixed duration. + last 3 months is {ge: {today: true}, offset: {months: -3}} + """ + offset: Date_Offset +} + +"Duration to offset a date value" +input Date_Offset { + days: Int + months: Int + years: Int +} + +"Conditions on a Date list" +input Date_ListFilter { + includes: Date + excludes: Date + includesAll: [Date!] + excludesAll: [Date!] +} + +"Conditions on an Timestamp value" +input Timestamp_Filter { + isNull: Boolean + eq: Timestamp + eq_expr: Timestamp_Expr + ne: Timestamp + ne_expr: Timestamp_Expr + in: [Timestamp!] + nin: [Timestamp!] + gt: Timestamp + gt_expr: Timestamp_Expr + ge: Timestamp + ge_expr: Timestamp_Expr + lt: Timestamp + lt_expr: Timestamp_Expr + le: Timestamp + le_expr: Timestamp_Expr + + """ + Offset timestamp input by a fixed duration. + in 12h is {le: {now: true}, offset: {hours: 12}} + """ + offset: Timestamp_Offset @deprecated +} + +"Duration to offset a timestamp value" +input Timestamp_Offset @fdc_deprecated { + milliseconds: Int + seconds: Int + minutes: Int + hours: Int + days: Int + months: Int + years: Int +} + +"Conditions on a Timestamp list" +input Timestamp_ListFilter { + includes: Timestamp + includes_expr: Timestamp_Expr + excludes: Timestamp + excludes_expr: Timestamp_Expr + includesAll: [Timestamp!] + excludesAll: [Timestamp!] +} + +"Conditions on an Any value" +input Any_Filter { + isNull: Boolean + eq: Any + ne: Any + in: [Any!] + nin: [Any!] +} + +"Conditions on a Any list" +input Any_ListFilter { + includes: Any + excludes: Any + includesAll: [Any!] + excludesAll: [Any!] +} + +"Conditions on an AuthUID value" +input AuthUID_Filter @fdc_deprecated { + eq: AuthUID + ne: AuthUID + in: [AuthUID!] + nin: [AuthUID!] + isNull: Boolean +} + +input AuthUID_ListFilter @fdc_deprecated { + "When true, will match if the list includes the id of the current user." + includes: AuthUID + excludes: AuthUID + includesAll: [AuthUID!] + excludesAll: [AuthUID!] +} + +"Conditions on an Vector value" +input Vector_Filter { + eq: Vector + ne: Vector + in: [Vector!] + nin: [Vector!] + isNull: Boolean +} + +input Vector_ListFilter { + "When true, will match if the list includes the supplied vector." + includes: Vector + excludes: Vector + includesAll: [Vector!] + excludesAll: [Vector!] +} +type Query { + _service: _Service! +} + +type Mutation { + # This is just a dummy field so that Mutation is always non-empty. + _firebase: Void @fdc_deprecated(reason: "dummy field -- does nothing useful") +} + +type _Service { + sdl: String! +} + +"(Internal) Added to things that may be removed from FDC and will soon be no longer usable in schema or operations." +directive @fdc_deprecated(reason: String = "No longer supported") on + | SCHEMA + | SCALAR + | OBJECT + | FIELD_DEFINITION + | ARGUMENT_DEFINITION + | INTERFACE + | UNION + | ENUM + | ENUM_VALUE + | INPUT_OBJECT + | INPUT_FIELD_DEFINITION + +"(Internal) Added to scalars representing quoted CEL expressions." +directive @fdc_celExpression( + "The expected CEL type that the expression should evaluate to." + returnType: String +) on SCALAR + +"(Internal) Added to scalars representing quoted SQL expressions." +directive @fdc_sqlExpression( + "The expected SQL type that the expression should evaluate to." + dataType: String +) on SCALAR + +"(Internal) Added to types that may not be used as variables." +directive @fdc_forbiddenAsVariableType on SCALAR | OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT + +"(Internal) Added to types that may not be used as fields in schema." +directive @fdc_forbiddenAsFieldType on SCALAR | OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT + +"Provides a frequently used example for this type / field / argument." +directive @fdc_example( + "A GraphQL literal value (verbatim) whose type matches the target." + value: Any + "A human-readable text description of what `value` means in this context." + description: String +) repeatable on SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | INPUT_OBJECT | INPUT_FIELD_DEFINITION + +"(Internal) Marks this field / argument as conflicting with others in the same group." +directive @fdc_oneOf( + "The group name where fields / arguments conflict with each other." + group: String! = "" + "If true, exactly one field / argument in the group must be specified." + required: Boolean! = false +) repeatable on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION +"AccessLevel specifies coarse access policies for common situations." +enum AccessLevel { + """ + This operation can be executed by anyone with or without authentication. + Equivalent to @auth(expr: "true") + """ + PUBLIC + + """ + This operation can only be executed with a valid Firebase Auth ID token. + Note: it allows anonymous auth and unverified accounts, so may be subjected to abuses. + It’s equivalent to @auth(expr: "auth.uid != nil") + """ + USER_ANON + + """ + This operation can only be executed by a non-anonymous Firebase Auth account. + It’s equivalent to @auth(expr: "auth.uid != nil && auth.token.firebase.sign_in_provider != 'anonymous'")" + """ + USER + + """ + This operation can only be executed by a verified Firebase Auth account. + It’s equivalent to @auth(expr: "auth.uid != nil && auth.token.email_verified")" + """ + USER_EMAIL_VERIFIED + + """ + This operation can not be executed with no IAM credentials. + It’s equivalent to @auth(expr: "false") + """ + NO_ACCESS +} + +""" +Defines the auth policy for a query or mutation. This directive must be added to +any operation you wish to be accessible from a client application. If left +unspecified, defaults to `@auth(level: NO_ACCESS)`. +""" +directive @auth( + "The minimal level of access required to perform this operation." + level: AccessLevel @fdc_oneOf(required: true) + """ + A CEL expression that allows access to this operation if the expression + evaluates to `true`. + """ + expr: Boolean_Expr @fdc_oneOf(required: true) +) on QUERY | MUTATION +""" +Mark this field as a customized resolver. +It may takes customized input arguments and return customized types. + +TODO(b/315857408): Funnel this through API review. + +See: +- go/firemat:custom-resolvers +- go/custom-resolvers-hackweek +""" +directive @resolver on FIELD_DEFINITION +scalar UUID @specifiedBy(url: "https://tools.ietf.org/html/rfc4122") +scalar Int64 +scalar Date +scalar Timestamp @specifiedBy(url: "https://scalars.graphql.org/andimarek/date-time") +scalar Any +scalar Void +""" +AuthUID is a string representing a Firebase Auth uid. When passing a literal +value for an AuthUID in a query, you may instead pass `{current: true}` and the +currently signed in user's uid will be injected instead. For example: + +```gql +type Order { + customerId: AuthUID! + # ... +} + +query myOrders { + orders: (where: { + customerId: {eq: {current: true}} + }) { customerId } +} +``` +""" +scalar AuthUID @fdc_deprecated +scalar Vector +"Define the intervals used in timestamps and dates (subset)" +enum TimestampInterval @fdc_deprecated { + second + minute + hour + day + week + month + year +} + +input Timestamp_Sentinel @fdc_deprecated { + "Return the current time." + now: Boolean, + "Defines a timestamp relative to the current time. Offset values can be positive or negative." + fromNow: Timestamp_Offset + "Truncate the current/offset time to the specified interval." + truncateTo: TimestampInterval +} + +""" +A Common Expression Language (CEL) expression that returns a boolean at runtime. + +The expression can reference the `auth` variable, which is null if Firebase Auth +is not used. Otherwise, it contains the following fields: + + - `auth.uid`: The current user ID. + - `auth.token`: A map of all token fields (i.e. "claims"). +""" +scalar Boolean_Expr + @specifiedBy(url: "https://github.com/google/cel-spec") + @fdc_celExpression(returnType: "bool") + @fdc_forbiddenAsVariableType + @fdc_forbiddenAsFieldType + @fdc_example(value: "auth != null", description: "Allow only if a Firebase Auth user is present.") + +""" +A Common Expression Language (CEL) expression that returns a string at runtime. + +Limitation: Right now, only a few expressions are supported. Those are listed +using the @fdc_example directive on this scalar. +""" +scalar String_Expr + @specifiedBy(url: "https://github.com/google/cel-spec") + @fdc_celExpression(returnType: "string") + @fdc_forbiddenAsVariableType + @fdc_forbiddenAsFieldType + @fdc_example(value: "auth.uid", description: "The ID of the currently logged in user in Firebase Auth. (Errors if not logged in.)") + @fdc_example(value: "uuidV4()", description: "Generates a new random UUID (version 4) string, formatted as 32 lower-case hex digits without delimiters.") + +""" +A Common Expression Language (CEL) expression that returns a Timestamp at runtime. + +Limitation: Right now, only a few expressions are supported. Those are listed +using the @fdc_example directive on this scalar. +""" +scalar Timestamp_Expr + @specifiedBy(url: "https://github.com/google/cel-spec") + @fdc_celExpression(returnType: "google.protobuf.Timestamp") + @fdc_forbiddenAsVariableType + @fdc_forbiddenAsFieldType + @fdc_example(value: "request.time", description: "The timestamp when the request is received (with microseconds precision).") + +""" +A Common Expression Language (CEL) expression that returns a UUID string at runtime. + +Limitation: Right now, only a few expressions are supported. Those are listed +using the @fdc_example directive on this scalar. +""" +scalar UUID_Expr + @specifiedBy(url: "https://github.com/google/cel-spec") + @fdc_celExpression(returnType: "string") + @fdc_forbiddenAsVariableType + @fdc_forbiddenAsFieldType + @fdc_example(value: "uuidV4()", description: "Generates a new random UUID (version 4) every time.") + +""" +A Common Expression Language (CEL) expression whose return type is unspecified. + +Limitation: Only a limited set of expressions are supported for now for each +type. For type XXX, see the @fdc_example directives on XXX_Expr for a full list. +""" +scalar Any_Expr + @specifiedBy(url: "https://github.com/google/cel-spec") + @fdc_celExpression + @fdc_forbiddenAsVariableType + @fdc_forbiddenAsFieldType + @fdc_example(value: "auth.uid", description: "The ID of the currently logged in user in Firebase Auth. (Errors if not logged in.)") + @fdc_example(value: "uuidV4()", description: "Generates a new random UUID version 4 (formatted as 32 lower-case hex digits without delimiters if result type is String).") + @fdc_example(value: "request.time", description: "The timestamp when the request is received (with microseconds precision).") + +""" +A PostgreSQL value expression whose return type is unspecified. +""" +scalar Any_SQL + @specifiedBy(url: "https://www.postgresql.org/docs/current/sql-expressions.html") + @fdc_sqlExpression + @fdc_forbiddenAsVariableType + @fdc_forbiddenAsFieldType +""" +Defines a relational database table. + +Given `type TableName @table`, + + - `TableName` is the GQL type name. + - `tableName` is the singular name. Override with `@table(singular)`. + - `tableNames` is the plural name. Override with `@table(plural)`. + - `table_name` is the SQL table id. Override with `@table(name)`. + +Only a table type can be configured further with: + + - Customized data types. See `@col`. + - Index. See `@index` + - Unique constraint. See `@unqiue` + - Relation. See `@ref` + - Embedded Json. See `@embed` + +A scalar field map to a SQL database column. +An object field (like `type TableName @table { field: AnotherType }`) are either + + - a relation reference field if `AnotherType` is annotated with `@table`. + - an embedded json field if `field` is annotated with `@embed`. + +""" +directive @table( + "Override the SQL database table name. Defaults to ." + name: String + "Override the singular name. Default is the camel case of the type name." + singular: String + "Override the plural name. Default is generated based on English patterns." + plural: String + "The primary key of the table. Defaults to a single field `id: UUID!`. Generate if missing." + key: [String!] +) on OBJECT + +""" +Defines a relational database view. + +Given `type ViewName @view`, + - `ViewName` is the GQL type name. + - `viewName` is the singular name. Override with `@view(singular)`. + - `viewNames` is the plural name. Override with `@view(plural)`. + - `view_name` is the SQL view id. Override with `@view(name)`. + When `@view(sql)` is defined, it uses the given raw SQL as the view instead. + +A view type can be used just as a table type with queries. +A view type may have a nullable `@ref` field to another table, but cannot be +referenced in a `@ref`. + +WARNING: Firebase Data Connect does not validate the SQL of the view or +evaluate whether it matches the defined fields. + +If the SQL view is invalid or undefined, related requests may fail. +If the SQL view return incompatible types. Firebase Data Connect will surface +an error in the response. +""" +directive @view( + """ + The SQL view name. If no `name` or `sql` are provided, defaults to + snake_case of the singular type name. + """ + name: String @fdc_oneOf + """ + SQL SELECT statement to use as the basis for this type. Note that all SQL + identifiers should be snake_case and all GraphQL identifiers should be + camelCase. + """ + sql: String @fdc_oneOf + "Override the singular name. Default is the camel case of the type name." + singular: String + "Override the plural name. Default is generated based on English patterns." + plural: String +) on OBJECT + +""" +Specify additional column options. + +Given `type TableName @table { fieldName: Int } ` + + - `field_name` is the SQL column name. Override with `@col(name)`. + +""" +directive @col( + "The SQL database column name. Defaults to ." + name: String + """ + Override SQL columns data type. + Each GraphQL type could map to many SQL data types. + Refer to Postgres supported data types and mappings to GQL. + """ + dataType: String + """ + Defines a fixed column size for certain scalar types. + + - For Vector, size is required. It establishes the length of the vector. + - For String, size converts `text` type to `varchar(size)`. + """ + size: Int +) on FIELD_DEFINITION + + +""" +Define an embedded JSON field represented as Postgres `jsonb` (or `json`). + +Given `type TableName @table { fieldName: EmbeddedType @embed }` +`EmbeddedType` must NOT have `@table`. + + - Store JSON object if `EmbeddedType`. Required column if `EmbeddedType!`. + - Store JSON array if `[EmbeddedType]`. Required column if `[EmbeddedType]!`. + +""" +directive @embed on FIELD_DEFINITION + +""" +Define a reference field to another table. + +Given `type TableName @table { refField: AnotherTableName }`, it defines a foreign-key constraint + + - with id `table_name_ref_field_fkey` (override with `@ref(constraintName)`) + - from `table_name.ref_field` (override with `@ref(fields)`) + - to `another_table_name.id` (override with `@ref(references)`) + +Does not support `[AnotherTableName]` because array fields cannot have foreign-key constraints. +Nullability determines whether the reference is required. + + - `refField: AnotherTableName`: optional reference, SET_NULL on delete. + - `refField: AnotherTableName!`: required reference, CASCADE on delete. + +Consider all types of SQL relations: + + - many-to-one relations involve a reference field on the many-side. + - many-to-maybe-one if `refField: AnotherTableName`. + - many-to-exactly-one if `refField: AnotherTableName!`. + - one-to-one relations involve a unique reference field on one side. + - maybe-one-to-maybe-one if `refField: AnotherTableName @unique`. + - maybe-one-to-exact-one if `refField: AnotherTableName! @unique`. + - exact-one-to-exact-one shall be represented as a single table instead. + - many-to-many relations involve a join table. + - Its primary keys must be two non-null reference fields to tables bridged together to guarantee at most one relation per pair. + +type TableNameToAnotherTableName @table(key: ["refField", "anotherRefField"]) { + refField: TableName! + anotherRefField: AnotherTableName! +} + +""" +directive @ref( + "The SQL database foreign key constraint name. Default to __fkey." + constraintName: String + """ + Foreign key fields. Default to . + """ + fields: [String!] + "The fields that the foreign key references in the other table. Default to the primary key." + references: [String!] +) on FIELD_DEFINITION + +enum IndexFieldOrder { ASC DESC } + +""" +Defines a database index to optimize query performance. + +Given `type TableName @table @index(fields: [“fieldName”, “secondFieldName”])`, +`table_name_field_name_second_field_name_aa_idx` is the SQL index id. +`table_name_field_name_second_field_name_ad_idx` if `order: [ASC DESC]`. +`table_name_field_name_second_field_name_dd_idx` if `order: [DESC DESC]`. + +Given `type TableName @table { fieldName: Int @index } ` +`table_name_field_name_idx` is the SQL index id. +`order` matters less for single field indexes because they can be scanned in both ways. + +Override with `@index(name)` in case of index name conflicts. +""" +directive @index( + "The SQL database index id. Defaults to __idx." + name: String + """ + Only allowed and required when used on OBJECT. + The fields to create an index on. + """ + fields: [String!] + """ + Only allowed when used on OBJECT. + Index order of each column. Default to all ASC. + """ + order: [IndexFieldOrder!] +) repeatable on FIELD_DEFINITION | OBJECT + +""" +Defines a unique constraint. + +Given `type TableName @table @unique(fields: [“fieldName”, “secondFieldName”])`, +`table_name_field_name_second_field_name_uidx` is the SQL unique index id. +Given `type TableName @table { fieldName: Int @unique } ` +`table_name_field_name_idx` is the SQL unique index id. + +Override with `@unique(indexName)` in case of index name conflicts. +""" +directive @unique( + "The SQL database unique index name. Defaults to __uidx." + indexName: String + """ + Only allowed and required when used on OBJECT. + The fields to create a unique constraint on. + """ + fields: [String!] +) repeatable on FIELD_DEFINITION | OBJECT + +"Define the direction of an orderby query" +enum OrderDirection { + ASC + DESC +} + +""" +Defines what siliarlity function to use for fetching vectors. +Details here: https://github.com/pgvector/pgvector?tab=readme-ov-file#vector-functions +""" +enum VectorSimilarityMethod { + L2 + COSINE + INNER_PRODUCT +} + +enum ColDefault @fdc_deprecated { + """ + Generates a random UUID (v4) as the default column value. + Compatible with String or UUID typed fields. + """ + UUID + """ + Generates an auto-incrementing sequence as the default column value. + Compatible with Int and Int64 typed fields. + """ + SEQUENCE + """ + Populates the default column value with the current date or timestamp. + Compatible with Date and Timestamp typed fields. + """ + NOW +} + +""" +Specify the default column value. + +The supported arguments vary based on the field type. +""" +directive @default( + "A constant value. Validated against the field GraphQL type at compile-time." + value: Any @fdc_oneOf(required: true) + "(Deprecated) Built-in common ways to generate initial value." + generate: ColDefault @fdc_oneOf(required: true) @deprecated + "A CEL expression, whose return value must match the field data type." + expr: Any_Expr @fdc_oneOf(required: true) + """ + A raw SQL expression, whose SQL data type must match the underlying column. + + The value is any variable-free expression (in particular, cross-references to + other columns in the current table are not allowed). Subqueries are not allowed either. + https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-PARMS-DEFAULT + """ + sql: Any_SQL @fdc_oneOf(required: true) +) on FIELD_DEFINITION +"Update input of a String value" +input String_Update { + set: String @fdc_oneOf(group: "set") + set_expr: String_Expr @fdc_oneOf(group: "set") +} + +"Update input of a String list value" +input String_ListUpdate { + set: [String!] + append: [String!] + prepend: [String!] + delete: Int + i: Int + update: String +} + +"Update input of a UUID value" +input UUID_Update { + set: UUID @fdc_oneOf(group: "set") + set_expr: UUID_Expr @fdc_oneOf(group: "set") +} + +"Update input of an ID list value" +input UUID_ListUpdate { + set: [UUID!] + append: [UUID!] + prepend: [UUID!] + delete: Int + i: Int + update: UUID +} + +"Update input of an Int value" +input Int_Update { + set: Int + inc: Int + dec: Int +} + +"Update input of an Int list value" +input Int_ListUpdate { + set: [Int!] + append: [Int!] + prepend: [Int!] + delete: Int + i: Int + update: Int +} + +"Update input of an Int64 value" +input Int64_Update { + set: Int64 + inc: Int64 + dec: Int64 +} + +"Update input of an Int64 list value" +input Int64_ListUpdate { + set: [Int64!] + append: [Int64!] + prepend: [Int64!] + delete: Int + i: Int + update: Int64 +} + +"Update input of a Float value" +input Float_Update { + set: Float + inc: Float + dec: Float +} + +"Update input of a Float list value" +input Float_ListUpdate { + set: [Float!] + append: [Float!] + prepend: [Float!] + delete: Int + i: Int + update: Float +} + +"Update input of a Boolean value" +input Boolean_Update { + set: Boolean +} + +"Update input of a Boolean list value" +input Boolean_ListUpdate { + set: [Boolean!] + append: [Boolean!] + prepend: [Boolean!] + delete: Int + i: Int + update: Boolean +} + +"Update input of a Date value" +input Date_Update { + set: Date + inc: Date_Offset + dec: Date_Offset +} + +"Update input of a Date list value" +input Date_ListUpdate { + set: [Date!] + append: [Date!] + prepend: [Date!] + delete: Int + i: Int + update: Date +} + +"Update input of a Timestamp value" +input Timestamp_Update { + set: Timestamp @fdc_oneOf(group: "set") + set_expr: Timestamp_Expr @fdc_oneOf(group: "set") + inc: Timestamp_Offset + dec: Timestamp_Offset +} + +"Update input of a Timestamp list value" +input Timestamp_ListUpdate { + set: [Timestamp!] + append: [Timestamp!] + prepend: [Timestamp!] + delete: Int + i: Int + update: Timestamp +} + +"Update input of an Any value" +input Any_Update { + set: Any +} + +"Update input of an Any list value" +input Any_ListUpdate { + set: [Any!] + append: [Any!] + prepend: [Any!] + delete: Int + i: Int + update: Any +} + +"Update input of an AuthUID value" +input AuthUID_Update @fdc_deprecated { + set: AuthUID +} + +"Update input of an AuthUID list value" +input AuthUID_ListUpdate @fdc_deprecated { + set: [AuthUID] + append: [AuthUID] + prepend: [AuthUID] + delete: Int + i: Int + update: AuthUID +} + +"Update input of an Vector value" +input Vector_Update { + set: Vector +} + +"Update input of a Vector list value" +input Vector_ListUpdate { + set: [Vector] + append: [Vector] + prepend: [Vector] + delete: Int + i: Int + update: Vector +} diff --git a/packages/data-connect/test/dataconnect/connector/connector.yaml b/packages/data-connect/test/dataconnect/connector/connector.yaml new file mode 100644 index 00000000000..3779c6a3700 --- /dev/null +++ b/packages/data-connect/test/dataconnect/connector/connector.yaml @@ -0,0 +1,6 @@ +connectorId: "movies" +authMode: "PUBLIC" +generate: + javascriptSdk: + outputDir: "./gen/web" + jsPackageName: "@movie-app-ssr/movies" diff --git a/packages/data-connect/test/dataconnect/connector/mutations.gql b/packages/data-connect/test/dataconnect/connector/mutations.gql new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/data-connect/test/dataconnect/connector/queries.gql b/packages/data-connect/test/dataconnect/connector/queries.gql new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/data-connect/test/dataconnect/dataconnect.yaml b/packages/data-connect/test/dataconnect/dataconnect.yaml new file mode 100644 index 00000000000..1931e7eb963 --- /dev/null +++ b/packages/data-connect/test/dataconnect/dataconnect.yaml @@ -0,0 +1,10 @@ +specVersion: "v1alpha" +serviceId: "dataconnect" +schema: + source: "./schema" + datasource: + postgresql: + database: "dataconnect-test" + cloudSql: + instanceId: "local" +connectorDirs: ["./connector"] diff --git a/packages/data-connect/test/dataconnect/index.esm.js b/packages/data-connect/test/dataconnect/index.esm.js new file mode 100644 index 00000000000..6c7c8f8a49a --- /dev/null +++ b/packages/data-connect/test/dataconnect/index.esm.js @@ -0,0 +1,47 @@ +/** + * @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 { + getDataConnect, + queryRef, + mutationRef, + executeQuery, + executeMutation +} from 'firebase/data-connect'; + +export const connectorConfig = { + connector: 'test', + service: 'dataconnect', + location: 'us-central1' +}; + +function validateArgs(dcOrVars, vars, validateVars) { + let dcInstance; + let realVars; + // TODO(mtewani); Check what happens if this is undefined. + if (dcOrVars && 'dataConnectOptions' in dcOrVars) { + dcInstance = dcOrVars; + realVars = vars; + } else { + dcInstance = getDataConnect(connectorConfig); + realVars = dcOrVars; + } + if (!dcInstance || (!realVars && validateVars)) { + throw new Error('You didn\t pass in the vars!'); + } + return { dc: dcInstance, vars: realVars }; +} diff --git a/packages/data-connect/test/dataconnect/logAddMovieVariables.json b/packages/data-connect/test/dataconnect/logAddMovieVariables.json new file mode 100644 index 00000000000..92e237b649f --- /dev/null +++ b/packages/data-connect/test/dataconnect/logAddMovieVariables.json @@ -0,0 +1 @@ +{"Name":"addMovie","Kind":"mutation","Variables":[{"Name":"id","TypeName":"String","TypeInfo":{"Name":"String","Kind":"NativeScalar","Fields":null,"KeyTypeInfo":null,"HasPrimaryKeyFields":false,"Description":""},"FullTypeInfo":null,"Attribute":"NonNull","DefaultValue":null,"Description":""},{"Name":"name","TypeName":"String","TypeInfo":{"Name":"String","Kind":"NativeScalar","Fields":null,"KeyTypeInfo":null,"HasPrimaryKeyFields":false,"Description":""},"FullTypeInfo":null,"Attribute":"NonNull","DefaultValue":null,"Description":""},{"Name":"genre","TypeName":"String","TypeInfo":{"Name":"String","Kind":"NativeScalar","Fields":null,"KeyTypeInfo":null,"HasPrimaryKeyFields":false,"Description":""},"FullTypeInfo":null,"Attribute":"NonNull","DefaultValue":null,"Description":""},{"Name":"description","TypeName":"String","TypeInfo":{"Name":"String","Kind":"NativeScalar","Fields":null,"KeyTypeInfo":null,"HasPrimaryKeyFields":false,"Description":""},"FullTypeInfo":null,"Attribute":"NonNull","DefaultValue":null,"Description":""}],"Response":[{"Name":"movie_insert","TypeName":"Movie_Key","TypeInfo":{"Name":"Movie_Key","Kind":"TypeKey","Fields":[{"Name":"id","TypeName":"String","TypeInfo":{"Name":"String","Kind":"NativeScalar","Fields":null,"KeyTypeInfo":null,"HasPrimaryKeyFields":false,"Description":""},"FullTypeInfo":null,"Attribute":"NonNull","DefaultValue":null,"Description":""}],"KeyTypeInfo":null,"HasPrimaryKeyFields":false,"Description":""},"FullTypeInfo":{"Name":"Movie_Key","Kind":"TypeKey","Fields":[{"Name":"id","TypeName":"String","TypeInfo":{"Name":"String","Kind":"NativeScalar","Fields":null,"KeyTypeInfo":null,"HasPrimaryKeyFields":false,"Description":""},"FullTypeInfo":null,"Attribute":"NonNull","DefaultValue":null,"Description":""}],"KeyTypeInfo":null,"HasPrimaryKeyFields":false,"Description":""},"Attribute":"NonNull","DefaultValue":null,"Description":""}],"Description":"# Example mutations\n# TODO: Replace with a really good illustrative example from devrel!\nmutation createOrder($name: String!) {\n order_insert(data : {name: $name})\n}"} \ No newline at end of file diff --git a/packages/data-connect/test/dataconnect/logListAllMoviesMovies.json b/packages/data-connect/test/dataconnect/logListAllMoviesMovies.json new file mode 100644 index 00000000000..ec747fa47dd --- /dev/null +++ b/packages/data-connect/test/dataconnect/logListAllMoviesMovies.json @@ -0,0 +1 @@ +null \ No newline at end of file diff --git a/packages/data-connect/test/dataconnect/logListMovieIdsMovies.json b/packages/data-connect/test/dataconnect/logListMovieIdsMovies.json new file mode 100644 index 00000000000..ec747fa47dd --- /dev/null +++ b/packages/data-connect/test/dataconnect/logListMovieIdsMovies.json @@ -0,0 +1 @@ +null \ No newline at end of file diff --git a/packages/data-connect/test/dataconnect/movies.tools.json b/packages/data-connect/test/dataconnect/movies.tools.json new file mode 100644 index 00000000000..f6938f8c163 --- /dev/null +++ b/packages/data-connect/test/dataconnect/movies.tools.json @@ -0,0 +1,6 @@ +{ + "connector": "movies", + "location": "us-central1", + "service": "dataconnect", + "tools": [] +} \ No newline at end of file diff --git a/packages/data-connect/test/dataconnect/schema/schema.gql b/packages/data-connect/test/dataconnect/schema/schema.gql new file mode 100644 index 00000000000..1b9ca01d832 --- /dev/null +++ b/packages/data-connect/test/dataconnect/schema/schema.gql @@ -0,0 +1,23 @@ +# # Example schema +# # TODO: Replace with a really good illustrative example from devrel! +# type Product @table { +# name: String! +# price: Int! +# } + +# type Order @table { +# name: String! +# } + +# type OrderItem @table(key: ["order", "product"]) { +# order: Order! +# product: Product! +# quantity: Int! +# } +type Movie @table { + id: String! + name: String! + genre: String! + description: String! + test: String +} diff --git a/packages/data-connect/test/dataconnect/test.tools.json b/packages/data-connect/test/dataconnect/test.tools.json new file mode 100644 index 00000000000..a048e8da310 --- /dev/null +++ b/packages/data-connect/test/dataconnect/test.tools.json @@ -0,0 +1,6 @@ +{ + "connector": "test", + "location": "us-central1", + "service": "dataconnect", + "tools": [] +} \ No newline at end of file diff --git a/packages/data-connect/test/emulatorSeeder.ts b/packages/data-connect/test/emulatorSeeder.ts new file mode 100644 index 00000000000..6649ea166fe --- /dev/null +++ b/packages/data-connect/test/emulatorSeeder.ts @@ -0,0 +1,93 @@ +/** + * @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 * as path from 'path'; +// curl localhost:3628/setupSchema -X POST -d '{ +// "service_id": "s", +// "schema": { +// "files": [ +// { +// "path": "schema/post.gql", +// "content": "type Post @table {content: String!}" +// } +// ] +// }, +// "connectors": { +// "crud": { +// "files": [ +// { +// "path": "operations/post.gql", +// "content": "query getPost($id: UUID!) @auth(level: PUBLIC) {post(id: $id) {content}} query listPosts @auth(level: PUBLIC) {posts {content}} mutation createPost($id: UUID!, content: String!) @auth(level: PUBLIC) {post_insert(data: {id: $id, content: $content})} mutation deletePost($id: UUID!) @auth(level: PUBLIC) { post_delete(id: $id)}" +// } +// ] +// } +// } + +import fs from 'fs'; + +import fetch from 'node-fetch'; + +import { ReferenceType } from '../src'; + +// } +import { CONNECTOR_NAME, EMULATOR_PORT } from './util'; + +export interface SeedInfo { + type: ReferenceType; + name: string; +} +export async function setupQueries( + schema: string, + seedInfoArray: SeedInfo[], + skipSchema = false +) { + const schemaPath = path.resolve(__dirname, schema); + const schemaFileContents = fs.readFileSync(schemaPath).toString(); + const toWrite = { + 'service_id': 'l', + 'schema': { + 'files': [ + { + 'path': `schema/${schema}`, + 'content': schemaFileContents + } + ] + }, + 'connectors': { + 'c': { + 'files': seedInfoArray.map(seedInfo => { + const fileName = seedInfo.name + '.gql'; + const operationFilePath = path.resolve(__dirname, fileName); + const operationFileContents = fs + .readFileSync(operationFilePath) + .toString(); + return { + path: `operations/${seedInfo.name}.gql`, + content: operationFileContents + }; + }) + } + }, + connection_string: + 'postgresql://postgres:secretpassword@localhost:5432/postgres?sslmode=disable' + }; + fs.writeFileSync('./emulator.json', JSON.stringify(toWrite)); + return fetch(`http://localhost:${EMULATOR_PORT}/setupSchema`, { + method: 'POST', + body: JSON.stringify(toWrite) + }); +} diff --git a/packages/data-connect/test/integration/.graphqlrc.yaml b/packages/data-connect/test/integration/.graphqlrc.yaml new file mode 100644 index 00000000000..4953f9bd343 --- /dev/null +++ b/packages/data-connect/test/integration/.graphqlrc.yaml @@ -0,0 +1,9 @@ +schema: + - ./dataconnect/schema/**/*.gql + - ./dataconnect/.dataconnect/**/*.gql +documents: + - ./dataconnect/connector/**/*.gql +extensions: + endpoints: + default: + url: http://127.0.0.1:8080/__/graphql diff --git a/packages/data-connect/test/mutations.gql b/packages/data-connect/test/mutations.gql new file mode 100644 index 00000000000..a826a39529a --- /dev/null +++ b/packages/data-connect/test/mutations.gql @@ -0,0 +1,6 @@ +mutation seedDatabase($id: UUID!, $content: String!) @auth(level: PUBLIC) { + post: post_insert(data: {id: $id, content: $content}) +} +mutation removePost($id: UUID!) @auth(level: PUBLIC) { + post: post_delete(id: $id) +} \ No newline at end of file diff --git a/packages/data-connect/test/mutations.mutation.graphql b/packages/data-connect/test/mutations.mutation.graphql new file mode 100644 index 00000000000..9e86f1c1eae --- /dev/null +++ b/packages/data-connect/test/mutations.mutation.graphql @@ -0,0 +1,4 @@ +mutation seeddatabase @auth(level: PUBLIC) { + post1: post_insert(data: {content: "do dishes"}) + post2: post_insert(data: {content: "schedule appointment"}) +} \ No newline at end of file diff --git a/packages/data-connect/test/post.gql b/packages/data-connect/test/post.gql new file mode 100644 index 00000000000..79bd6688efe --- /dev/null +++ b/packages/data-connect/test/post.gql @@ -0,0 +1,12 @@ +query getPost($id: UUID!) @auth(level: PUBLIC) { + post(id: $id) { + content + } +} +query listPosts @auth(level: PUBLIC) { + posts { + id, + content + } +} + diff --git a/packages/data-connect/test/queries.schema.gql b/packages/data-connect/test/queries.schema.gql new file mode 100644 index 00000000000..eb2c8ba86e0 --- /dev/null +++ b/packages/data-connect/test/queries.schema.gql @@ -0,0 +1 @@ +type Post @table {content: String!} \ No newline at end of file diff --git a/packages/data-connect/test/queries.test.ts b/packages/data-connect/test/queries.test.ts new file mode 100644 index 00000000000..e2183a426ef --- /dev/null +++ b/packages/data-connect/test/queries.test.ts @@ -0,0 +1,201 @@ +/** + * @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 { FirebaseApp } from '@firebase/app'; +import { uuidv4 } from '@firebase/util'; +import { expect, use } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; + +import { + connectDataConnectEmulator, + DataConnect, + executeMutation, + executeQuery, + getDataConnect, + mutationRef, + OnResultSubscription, + QueryRef, + queryRef, + QueryResult, + SerializedRef, + subscribe, + terminate +} from '../src'; + +import { setupQueries } from './emulatorSeeder'; +import { app, getConnectionConfig, initDatabase, PROJECT_ID } from './util'; +import { SOURCE_CACHE, SOURCE_SERVER } from '../src'; + +use(chaiAsPromised); + +interface Task { + id: string; + content: string; +} +interface TaskListResponse { + posts: Task[]; +} + +const SEEDED_DATA = [ + { + id: uuidv4(), + content: 'task 1' + }, + { + id: uuidv4(), + content: 'task 2' + } +]; +function seedDatabase(instance: DataConnect) { + // call mutation query that adds SEEDED_DATA to database + return new Promise((resolve, reject) => { + async function run() { + let idx = 0; + while (idx < SEEDED_DATA.length) { + const data = SEEDED_DATA[idx]; + const ref = mutationRef(instance, 'seedDatabase', data); + await executeMutation(ref); + idx++; + } + } + run().then(resolve, reject); + }); +} +async function deleteDatabase(instance: DataConnect) { + for (let i = 0; i < SEEDED_DATA.length; i++) { + const data = SEEDED_DATA[i]; + const ref = mutationRef(instance, 'removePost', { id: data.id }); + await executeMutation(ref); + } +} + +describe('DataConnect Tests', async () => { + let dc: DataConnect; + beforeEach(async () => { + dc = initDatabase(); + await setupQueries('queries.schema.gql', [ + { type: 'query', name: 'post' }, + { type: 'mutation', name: 'mutations' } + ]); + await seedDatabase(dc); + }); + afterEach(async () => { + await deleteDatabase(dc); + terminate(dc); + }); + it('Can get all posts', async () => { + const taskListQuery = queryRef(dc, 'listPosts'); + const taskListRes = await executeQuery(taskListQuery); + expect(taskListRes.data).to.deep.eq({ + posts: SEEDED_DATA + }); + }); + it(`instantly executes a query if one hasn't been subscribed to`, async () => { + const taskListQuery = queryRef(dc, 'listPosts'); + const promise = new Promise>( + (resolve, reject) => { + const unsubscribe = subscribe(taskListQuery, { + onResult: res => { + unsubscribe(); + resolve(res); + }, + onErr: () => { + unsubscribe(); + reject(res); + } + }); + } + ); + const res = await promise; + expect(res.data).to.deep.eq({ + posts: SEEDED_DATA + }); + expect(res.source).to.eq(SOURCE_SERVER); + }); + it(`returns the result source as cache when data already exists`, async () => { + const taskListQuery = queryRef(dc, 'listPosts'); + const queryResult = await executeQuery(taskListQuery); + const result = await waitForFirstEvent(taskListQuery); + expect(result.data).to.eq(queryResult.data); + expect(result.source).to.eq(SOURCE_CACHE); + }); + it(`returns the proper JSON when calling .toJSON()`, async () => { + const taskListQuery = queryRef(dc, 'listPosts'); + await executeQuery(taskListQuery); + const result = await waitForFirstEvent(taskListQuery); + const serializedRef: SerializedRef = { + data: { + posts: SEEDED_DATA + }, + fetchTime: Date.now().toLocaleString(), + refInfo: { + connectorConfig: { + ...getConnectionConfig(), + projectId: PROJECT_ID + }, + name: taskListQuery.name, + variables: undefined + }, + source: SOURCE_SERVER + }; + expect(result.toJSON()).to.deep.eq(serializedRef); + expect(result.source).to.deep.eq(SOURCE_CACHE); + }); + it(`throws an error when the user can't connect to the server`, async () => { + // You can't point an existing data connect instance to a new emulator port, so we have to create a new one + const fakeInstance = getDataConnect({ + connector: 'wrong', + location: 'wrong', + service: 'wrong' + }); + connectDataConnectEmulator(fakeInstance, 'localhost', Number(0)); + const taskListQuery = queryRef(dc, 'listPosts'); + expect(executeQuery(taskListQuery)).to.eventually.be.rejected; + }); +}); +async function waitForFirstEvent( + query: QueryRef +) { + return await new Promise<{ + result: QueryResult; + unsubscribe: () => void; + }>((resolve, reject) => { + const onResult = (result: QueryResult) => { + setTimeout(() => { + resolve({ + result, + unsubscribe + }); + }); + }; + let unsubscribe = subscribe(query, { + onResult, + onErr: e => { + reject({ e, unsubscribe }); + } + }); + }).then( + ({ result, unsubscribe }) => { + unsubscribe(); + return result; + }, + ({ e, unsubscribe }) => { + unsubscribe(); + throw e; + } + ); +} diff --git a/packages/data-connect/test/runTest.mjs b/packages/data-connect/test/runTest.mjs new file mode 100644 index 00000000000..1201bb48602 --- /dev/null +++ b/packages/data-connect/test/runTest.mjs @@ -0,0 +1,88 @@ +import { uuidv4 } from '@firebase/util'; +import fetch from 'node-fetch'; +// initializeApp({ projectId: 'p' }); +// const dc = getDataConnect({ connector: 'c', location: 'l', service: 'l' }); + +// connectDataConnectEmulator(dc, 'localhost', 3628); +// const ref = queryRef(dc, 'listPosts'); +// const res = executeQuery(ref); +// res.then(res => console.log(res.data)); +function listPosts() { + // perform Mutation of full data + const executeBody = { + name: 'projects/p/locations/l/services/l/connectors/c', + operationName: 'listPosts', + variables: undefined + }; + return fetch( + 'http://localhost:3628/v1alpha/projects/p/locations/l/services/l/connectors/c:executeQuery', + { + method: 'POST', + body: JSON.stringify(executeBody) + } + ); +} +const SEEDED_DATA = [ + { + id: uuidv4(), + content: 'task 1' + }, + { + id: uuidv4(), + content: 'task 2' + } +]; +async function seedDatabase() { + // perform Mutation of full data + for(let i = 0; i < SEEDED_DATA.length; i++) { + const data = SEEDED_DATA[i]; + const executeBody = { + name: 'projects/p/locations/l/services/l/connectors/c', + operationName: 'createPost', + variables: data + }; + await fetch( + 'http://localhost:3628/v1alpha/projects/p/locations/l/services/l/connectors/c:executeMutation', + { + method: 'POST', + body: JSON.stringify(executeBody) + } + ); + } +} +function removeData() { + // perform mutation of removing data +} +function setupSchema() { + const obj = { + "service_id": "l", + "schema": { + "files": [ + { + "path": "schema/post.gql", + "content": "type Post @table {content: String!}" + } + ] + }, + "connectors": { + "c": { + "files": [ + { + "path": "operations/post.gql", + "content": "query getPost($id: UUID!) @auth(level: PUBLIC) {post(id: $id) {content}} query listPosts @auth(level: PUBLIC) {posts {content}} mutation createPost($id: UUID!, $content: String!) @auth(level: PUBLIC) {post_insert(data: {id: $id, content: $content})} mutation deletePost($id: UUID!) @auth(level: PUBLIC) { post_delete(id: $id)}" + } + ] + } + } +}; +return fetch(`http://localhost:3628/setupSchema`, { + method: 'POST', + body: JSON.stringify(obj) + }); +} + +await setupSchema().then(res => res.json()); +const databaseSeeded = await seedDatabase() +const posts = await listPosts().then(res => res.json()); +console.log(posts); +// console.log(databaseSeeded); diff --git a/packages/data-connect/test/util.ts b/packages/data-connect/test/util.ts new file mode 100644 index 00000000000..a5c0293dcf6 --- /dev/null +++ b/packages/data-connect/test/util.ts @@ -0,0 +1,52 @@ +/** + * @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 { initializeApp } from '@firebase/app'; + +import { + connectDataConnectEmulator, + ConnectorConfig, + getDataConnect +} from '../src'; + +export const USE_EMULATOR = true; +export const EMULATOR_PORT = process.env.DC_EMULATOR_PORT; +// export const EMULATOR_PROJECT = process.env.PROJECT; +export const CONNECTOR_NAME = 'c'; +export const LOCATION_NAME = 'l'; +export const SERVICE_NAME = 'l'; +export const PROJECT_ID = 'p'; +export function getConnectionConfig(): ConnectorConfig { + return { + connector: CONNECTOR_NAME, + location: LOCATION_NAME, + service: SERVICE_NAME + }; +} + +export const app = initializeApp({ + projectId: PROJECT_ID +}); + +// Seed the database to have the proper fields to query, such as a list of tasks. +export function initDatabase() { + const instance = getDataConnect(getConnectionConfig()); + if (!instance.isEmulator) { + connectDataConnectEmulator(instance, 'localhost', Number(EMULATOR_PORT)); + } + return instance; +} diff --git a/packages/data-connect/tsconfig.eslint.json b/packages/data-connect/tsconfig.eslint.json new file mode 100644 index 00000000000..09f747b4d46 --- /dev/null +++ b/packages/data-connect/tsconfig.eslint.json @@ -0,0 +1,9 @@ +{ + "extends": "../../config/tsconfig.base.json", + "compilerOptions": { + "outDir": "dist" + }, + "exclude": [ + "dist/**/*" + ] +} diff --git a/packages/data-connect/tsconfig.json b/packages/data-connect/tsconfig.json new file mode 100644 index 00000000000..198ba4b1bc5 --- /dev/null +++ b/packages/data-connect/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../config/tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "strict": true + }, + "exclude": [ + "dist/**/*" + ] +} diff --git a/packages/database/src/api/Reference_impl.ts b/packages/database/src/api/Reference_impl.ts index 7870bf93e53..53d4577368a 100644 --- a/packages/database/src/api/Reference_impl.ts +++ b/packages/database/src/api/Reference_impl.ts @@ -1985,6 +1985,7 @@ class QueryLimitToLastConstraint extends QueryConstraint { constructor(private readonly _limit: number) { super(); + console.log(this.type); } _apply(query: QueryImpl): QueryImpl { diff --git a/packages/database/test/exp/integration.test.ts b/packages/database/test/exp/integration.test.ts index adf5094f222..ae82bd913f6 100644 --- a/packages/database/test/exp/integration.test.ts +++ b/packages/database/test/exp/integration.test.ts @@ -27,12 +27,13 @@ import { onChildAdded, onValue, orderByChild, - query, refFromURL, set, startAt, update, - orderByKey + orderByKey, + query, + limitToLast } from '../../src/api/Reference_impl'; import { getDatabase, @@ -60,7 +61,7 @@ export function createTestApp() { } // Note: these run in parallel with the node environment. If you use the same paths in parallel, you may experience race conditions. -describe('Database@exp Tests', () => { +describe.only('Database@exp Tests', () => { let defaultApp; beforeEach(() => { @@ -77,6 +78,27 @@ describe('Database@exp Tests', () => { const db = getDatabase(defaultApp); expect(db).to.be.ok; }); + it.only('can use `toString` as parameter to refFromUrl', () => { + const app2 = initializeApp( + { + apiKey: 'AIzaSyAVya8asBa3HkmWFDG2SdNuljlcgTGqMq4', + authDomain: 'movie-picker-729bb.firebaseapp.com', + databaseURL: + 'https://movie-picker-729bb.europe-west1.firebasedatabase.app/', + projectId: 'movie-picker-729bb', + storageBucket: 'movie-picker-729bb.appspot.com', + messagingSenderId: '592198782208', + appId: '1:592198782208:web:cc1d88c2a2d53b20ea77e5' + }, + 'app2' + ); + const db = getDatabase(app2); + const q = ref(db, 'posts'); + const url = q.toString(); + const r = refFromURL(db, url); + const limitConstraint = limitToLast(100); + console.log(limitConstraint); + }); it("doesn't try to connect to emulator after database has already started", async () => { const db = getDatabase(defaultApp); const r = ref(db, '.info/connected'); @@ -146,6 +168,7 @@ describe('Database@exp Tests', () => { await set(root, {}); const q = query(root, orderByChild('testIndex'), limitToFirst(2)); + console.log(q.toString()); const i1 = child(root, 'i1'); await set(root, { diff --git a/packages/firebase/data-connect/index.ts b/packages/firebase/data-connect/index.ts new file mode 100644 index 00000000000..b4a08750867 --- /dev/null +++ b/packages/firebase/data-connect/index.ts @@ -0,0 +1,18 @@ +/** + * @license + * Copyright 2020 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. + */ + +export * from '@firebase/data-connect'; diff --git a/packages/firebase/data-connect/package.json b/packages/firebase/data-connect/package.json new file mode 100644 index 00000000000..950ea5670a5 --- /dev/null +++ b/packages/firebase/data-connect/package.json @@ -0,0 +1,7 @@ +{ + "name": "firebase/data-connect", + "main": "dist/index.cjs.js", + "browser": "dist/esm/index.esm.js", + "module": "dist/esm/index.esm.js", + "typings": "dist/data-connect/index.d.ts" +} \ No newline at end of file diff --git a/packages/firebase/package.json b/packages/firebase/package.json index c7cb156aec9..a776b1a5ca2 100644 --- a/packages/firebase/package.json +++ b/packages/firebase/package.json @@ -107,6 +107,18 @@ }, "default": "./database/dist/esm/index.esm.js" }, + "./data-connect": { + "types": "./data-connect/dist/data-connect/index.d.ts", + "node": { + "require": "./data-connect/dist/index.cjs.js", + "import": "./data-connect/dist/index.mjs" + }, + "browser": { + "require": "./data-connect/dist/index.cjs.js", + "import": "./data-connect/dist/esm/index.esm.js" + }, + "default": "./data-connect/dist/esm/index.esm.js" + }, "./firestore": { "types": "./firestore/dist/firestore/index.d.ts", "node": { @@ -379,6 +391,7 @@ "@firebase/app-types": "0.9.1", "@firebase/auth": "1.7.2", "@firebase/auth-compat": "0.5.7", + "@firebase/data-connect": "1.0.1", "@firebase/database": "1.0.4", "@firebase/database-compat": "1.0.4", "@firebase/firestore": "4.6.1", @@ -431,7 +444,8 @@ "remote-config", "messaging", "messaging/sw", - "database" + "database", + "data-connect" ], "typings": "empty.d.ts" } diff --git a/packages/util/index.node.ts b/packages/util/index.node.ts index 9c3b54b1c86..c7ba551a368 100644 --- a/packages/util/index.node.ts +++ b/packages/util/index.node.ts @@ -43,3 +43,4 @@ export * from './src/exponential_backoff'; export * from './src/formatters'; export * from './src/compat'; export * from './src/global'; +export * from './src/fetch_provider'; diff --git a/packages/util/index.ts b/packages/util/index.ts index 38b944cd9b5..7dc79a09ebb 100644 --- a/packages/util/index.ts +++ b/packages/util/index.ts @@ -38,3 +38,4 @@ export * from './src/exponential_backoff'; export * from './src/formatters'; export * from './src/compat'; export * from './src/global'; +export * from './src/fetch_provider'; diff --git a/packages/util/src/fetch_provider.ts b/packages/util/src/fetch_provider.ts new file mode 100644 index 00000000000..c6eff767cd6 --- /dev/null +++ b/packages/util/src/fetch_provider.ts @@ -0,0 +1,72 @@ +/** + * @license + * Copyright 2023 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. + */ + +export class FetchProvider { + private static fetchImpl: typeof fetch | null; + private static headersImpl: typeof Headers | null; + private static responseImpl: typeof Response | null; + + static initialize( + fetchImpl: typeof fetch, + headersImpl?: typeof Headers, + responseImpl?: typeof Response + ): void { + this.fetchImpl = fetchImpl; + if (headersImpl) { + this.headersImpl = headersImpl; + } + if (responseImpl) { + this.responseImpl = responseImpl; + } + } + + static fetch(): typeof fetch { + if (this.fetchImpl) { + return this.fetchImpl; + } + if (typeof self !== 'undefined' && 'fetch' in self) { + return self.fetch; + } + throw new Error( + 'Could not find fetch implementation, make sure you call FetchProvider.initialize() with an appropriate polyfill' + ); + } + + static headers(): typeof Headers { + if (this.headersImpl) { + return this.headersImpl; + } + if (typeof self !== 'undefined' && 'Headers' in self) { + return self.Headers; + } + throw new Error( + 'Could not find Headers implementation, make sure you call FetchProvider.initialize() with an appropriate polyfill' + ); + } + + static response(): typeof Response { + if (this.responseImpl) { + return this.responseImpl; + } + if (typeof self !== 'undefined' && 'Response' in self) { + return self.Response; + } + throw new Error( + 'Could not find Response implementation, make sure you call FetchProvider.initialize() with an appropriate polyfill' + ); + } +} diff --git a/repo-scripts/prune-dts/extract-public-api.ts b/repo-scripts/prune-dts/extract-public-api.ts index c6ca172a70f..d082d87e2eb 100644 --- a/repo-scripts/prune-dts/extract-public-api.ts +++ b/repo-scripts/prune-dts/extract-public-api.ts @@ -156,7 +156,9 @@ export async function generateApi( /* apiReportEnabled= */ false ); Extractor.invoke(extractorConfig, { - localBuild: true + localBuild: true, + showDiagnostics: true, + showVerboseMessages: true }); console.log('Generated rollup DTS'); diff --git a/scripts/emulator-testing/dataconnect-test-runner.ts b/scripts/emulator-testing/dataconnect-test-runner.ts new file mode 100644 index 00000000000..e362ef59cbe --- /dev/null +++ b/scripts/emulator-testing/dataconnect-test-runner.ts @@ -0,0 +1,47 @@ +/** + * @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 { DataConnectEmulator } from './emulators/dataconnect-emulator'; +import { spawn } from 'child-process-promise'; +import * as path from 'path'; +function runTest(port: number) { + console.log( + 'path: ' + path.resolve(__dirname, '../../packages/data-connect') + ); + const options = { + cwd: path.resolve(__dirname, '../../packages/data-connect'), + env: Object.assign({}, process.env, { + DC_EMULATOR_PORT: port + }), + stdio: 'inherit' as const + }; + return spawn('yarn', ['test:all'], options); +} +async function run(): Promise { + const emulator = new DataConnectEmulator(); + try { + await emulator.download(); + await emulator.setUp(); + await runTest(emulator.port); + } finally { + await emulator.tearDown(); + } +} +run().catch(err => { + console.error(err); + process.exitCode = 1; +}); diff --git a/scripts/emulator-testing/emulators/dataconnect-emulator.ts b/scripts/emulator-testing/emulators/dataconnect-emulator.ts new file mode 100644 index 00000000000..d240f7227bf --- /dev/null +++ b/scripts/emulator-testing/emulators/dataconnect-emulator.ts @@ -0,0 +1,37 @@ +/** + * @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 { Emulator } from './emulator'; + +const DATABASE_EMULATOR_VERSION = '1.1.4'; + +export class DataConnectEmulator extends Emulator { + // namespace: string; + + constructor(port = 3628, namespace = 'test-emulator') { + super( + `cli-v${DATABASE_EMULATOR_VERSION}`, + // Use locked version of emulator for test to be deterministic. + // The latest version can be found from database emulator doc: + // https://firebase.google.com/docs/database/security/test-rules-emulator + `https://firebasestorage.googleapis.com/v0/b/firemat-preview-drop/o/emulator%2Fdataconnect-emulator-macos-v1.1.4?alt=media&token=45a9ae02-568f-4f1e-bd2d-e841411ef221`, + port + ); + this.isJar = false; + // this.namespace = namespace; + } +} diff --git a/scripts/emulator-testing/emulators/emulator.ts b/scripts/emulator-testing/emulators/emulator.ts index 1295d413e4b..48de98500f5 100644 --- a/scripts/emulator-testing/emulators/emulator.ts +++ b/scripts/emulator-testing/emulators/emulator.ts @@ -16,7 +16,11 @@ */ // @ts-ignore -import { spawn } from 'child-process-promise'; +import { + ChildProcessPromise, + spawn, + SpawnPromiseResult +} from 'child-process-promise'; import { ChildProcess } from 'child_process'; import * as fs from 'fs'; import * as os from 'os'; @@ -32,6 +36,8 @@ export abstract class Emulator { cacheDirectory: string; cacheBinaryPath: string; + isJar = true; + constructor( private binaryName: string, private binaryUrl: string, @@ -89,19 +95,29 @@ export abstract class Emulator { if (!this.binaryPath) { throw new Error('You must call download() before setUp()'); } - const promise = spawn( - 'java', - [ - '-jar', - path.basename(this.binaryPath), - '--port', - this.port.toString() - ], - { - cwd: path.dirname(this.binaryPath), - stdio: 'inherit' - } - ); + let promise: ChildProcessPromise; + if (!this.isJar) { + promise = spawn(this.binaryPath, [ + 'dev', + '--local_connection_string', + "'postgresql://postgres:secretpassword@localhost:5432/postgres?sslmode=disable'" + ]); + } else { + promise = spawn( + 'java', + [ + '-jar', + path.basename(this.binaryPath), + '--port', + this.port.toString() + ], + { + cwd: path.dirname(this.binaryPath), + stdio: 'inherit' + } + ); + } + promise.catch(reject); this.emulator = promise.childProcess; diff --git a/yarn.lock b/yarn.lock index f8df951e815..1d41740318c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3566,6 +3566,11 @@ resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.3.2.tgz" integrity sha512-eI5Yrz3Qv4KPUa/nSIAi0h+qX0XyewOliug5F2QAtuRg6Kjg6jfmxe1GIwoIRhZspD1A0RP8ANrPwvEXXtRFog== +"@types/prop-types@*": + version "15.7.11" + resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz#2596fb352ee96a1379c657734d4b913a613ad563" + integrity sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng== + "@types/q@^0.0.32": version "0.0.32" resolved "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz" @@ -3581,6 +3586,15 @@ resolved "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz" integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== +"@types/react@18.2.59": + version "18.2.59" + resolved "https://registry.npmjs.org/@types/react/-/react-18.2.59.tgz#14c7bcab22e2ce71d9eaa02f78d3d55067724d7f" + integrity sha512-DE+F6BYEC8VtajY85Qr7mmhTd/79rJKIHCg99MU9SWPB4xvLb6D1za2vYflgZfmPqQVEr6UqJTnLXEwzpVPuOg== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + "@types/request@2.48.12": version "2.48.12" resolved "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz#0f590f615a10f87da18e9790ac94c29ec4c5ef30" @@ -3610,6 +3624,11 @@ dependencies: "@types/node" "*" +"@types/scheduler@*": + version "0.16.8" + resolved "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz#ce5ace04cfeabe7ef87c0091e50752e36707deff" + integrity sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A== + "@types/selenium-webdriver@^3.0.0": version "3.0.19" resolved "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.19.tgz" @@ -6512,6 +6531,11 @@ css@^3.0.0: source-map "^0.6.1" source-map-resolve "^0.6.0" +csstype@^3.0.2: + version "3.1.3" + resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== + csv-generate@^3.4.3: version "3.4.3" resolved "https://registry.npmjs.org/csv-generate/-/csv-generate-3.4.3.tgz" @@ -9566,7 +9590,14 @@ hasha@^5.0.0: is-stream "^2.0.0" type-fest "^0.8.0" -hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: +hasown@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz#26f48f039de2c0f8d3356c223fb8d50253519faa" + integrity sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA== + dependencies: + function-bind "^1.1.2" + +hasown@^2.0.1, hasown@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== @@ -15123,7 +15154,7 @@ resolve@^1.22.0, resolve@^1.22.4: resolve@~1.17.0: version "1.17.0" - resolved "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== dependencies: path-parse "^1.0.6" From 2fbfba26ce00dc3d3d756e92c40b4ad614f8d765 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Wed, 8 May 2024 09:36:02 -0700 Subject: [PATCH 02/74] Added debug logs --- packages/data-connect/src/api/DataConnect.ts | 16 +++++++++---- packages/data-connect/src/api/Mutation.ts | 1 + packages/data-connect/src/api/index.ts | 1 + .../src/core/FirebaseAuthProvider.ts | 12 ++++++---- .../data-connect/src/core/QueryManager.ts | 3 +++ packages/data-connect/src/core/error.ts | 2 +- packages/data-connect/src/network/fetch.ts | 10 ++++++-- .../src/network/transport/index.ts | 4 +++- .../src/network/transport/rest.ts | 4 +++- packages/data-connect/src/register.ts | 7 ++---- packages/data-connect/src/util/url.ts | 2 ++ packages/firebase/package.json | 2 +- yarn.lock | 24 ------------------- 13 files changed, 44 insertions(+), 44 deletions(-) diff --git a/packages/data-connect/src/api/DataConnect.ts b/packages/data-connect/src/api/DataConnect.ts index ecc13b22963..4d9ae004852 100644 --- a/packages/data-connect/src/api/DataConnect.ts +++ b/packages/data-connect/src/api/DataConnect.ts @@ -17,7 +17,6 @@ import { FirebaseApp, - FirebaseError, _getProvider, _removeServiceInstance, getApp @@ -25,13 +24,18 @@ import { import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; import { Provider } from '@firebase/component'; -import { AuthTokenProvider, EmulatorTokenProvider, FirebaseAuthProvider } from '../core/FirebaseAuthProvider'; +import { + AuthTokenProvider, + EmulatorTokenProvider, + FirebaseAuthProvider +} from '../core/FirebaseAuthProvider'; import { QueryManager } from '../core/QueryManager'; import { DataConnectTransport, TransportClass } from '../network'; import { RESTTransport } from '../network/transport/rest'; import { MutationManager } from './Mutation'; import { Code, DataConnectError } from '../core/error'; +import { logger } from '../logger'; export interface ProjectOptions { location: string; @@ -83,6 +87,7 @@ export class DataConnect { if (typeof process !== 'undefined' && process.env) { const host = process.env[FIREBASE_DATA_CONNECT_EMULATOR_HOST_VAR]; if (host) { + logger.info("Found custom host. Using emulator"); this.isEmulator = true; this.transportOptions = parseOptions(host); } @@ -108,10 +113,10 @@ export class DataConnect { return; } if (this.transportClass === undefined) { + logger.info("transportClass not provided. Defaulting to RESTTransport."); this.transportClass = RESTTransport; } - if (this.authProvider) { this.authTokenProvider = this.isEmulator ? new EmulatorTokenProvider(EmulatorTokenProvider.OWNER) @@ -121,6 +126,7 @@ export class DataConnect { this.authProvider ); this.authTokenProvider.addTokenChangeListener(token => { + logger.info(`New Token Available: ${token}`); this._transport.onTokenChanged(token); }); } @@ -144,6 +150,7 @@ export class DataConnect { enableEmulator(transportOptions: TransportOptions) { if (this.initialized) { + logger.error("enableEmulator called without initializing"); throw new DataConnectError( Code.ALREADY_INITIALIZED, 'DataConnect instance already initialized!' @@ -152,7 +159,6 @@ export class DataConnect { this.transportOptions = transportOptions; this.isEmulator = true; } - } export function connectDataConnectEmulator( @@ -193,12 +199,14 @@ export function getDataConnect( const options = provider.getOptions(identifier); const optionsValid = Object.keys(options).length > 0; if (optionsValid) { + logger.debug("Re-using cached instance"); return dcInstance; } } if (!dcOptions) { throw new DataConnectError(Code.INVALID_ARGUMENT, 'DC Option Required'); } + logger.debug("Creating new DataConnect instance"); // Initialize with options. return provider.initialize({ instanceIdentifier: identifier, diff --git a/packages/data-connect/src/api/Mutation.ts b/packages/data-connect/src/api/Mutation.ts index 4a9a6db4a94..76666f20e2d 100644 --- a/packages/data-connect/src/api/Mutation.ts +++ b/packages/data-connect/src/api/Mutation.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import { logger } from '../logger'; import { DataConnectTransport } from '../network/transport'; import { DataConnect } from './DataConnect'; diff --git a/packages/data-connect/src/api/index.ts b/packages/data-connect/src/api/index.ts index 41bcb3701fc..43b9df2c014 100644 --- a/packages/data-connect/src/api/index.ts +++ b/packages/data-connect/src/api/index.ts @@ -20,3 +20,4 @@ export * from './DataConnect'; export * from './Reference'; export * from './Mutation'; export * from './query'; +export * from '../logger'; diff --git a/packages/data-connect/src/core/FirebaseAuthProvider.ts b/packages/data-connect/src/core/FirebaseAuthProvider.ts index 159d4e046ad..d07a371a748 100644 --- a/packages/data-connect/src/core/FirebaseAuthProvider.ts +++ b/packages/data-connect/src/core/FirebaseAuthProvider.ts @@ -22,6 +22,7 @@ import { FirebaseAuthTokenData } from '@firebase/auth-interop-types'; import { Provider } from '@firebase/component'; +import { logger } from '../logger'; export interface AuthTokenProvider { getToken(forceRefresh: boolean): Promise; @@ -29,7 +30,6 @@ export interface AuthTokenProvider { } export type AuthTokenListener = (token: string | null) => void; -// Mostly borrowed from packages/database/src/core/AuthTokenProvider.ts export class FirebaseAuthProvider implements AuthTokenProvider { private auth_: FirebaseAuthInternal; constructor( @@ -56,12 +56,14 @@ export class FirebaseAuthProvider implements AuthTokenProvider { } return this.auth_.getToken(forceRefresh).catch(error => { if (error && error.code === 'auth/token-not-initialized') { - // TODO(mtewani): Implement logging mechanism - // log( - // 'Got auth/token-not-initialized error. Treating as null token.' - // ); + logger.debug( + 'Got auth/token-not-initialized error. Treating as null token.' + ); return null; } else { + logger.error( + 'Error received when attempting to retrieve token: ' + JSON.stringify(error) + ); return Promise.reject(error); } }); diff --git a/packages/data-connect/src/core/QueryManager.ts b/packages/data-connect/src/core/QueryManager.ts index c5647350ed9..6fe2154da87 100644 --- a/packages/data-connect/src/core/QueryManager.ts +++ b/packages/data-connect/src/core/QueryManager.ts @@ -32,6 +32,7 @@ import { DataSource, SOURCE_CACHE } from '../api/Reference'; +import { logger } from '../logger'; import { DataConnectTransport } from '../network'; import { encoderImpl } from '../util/encoder'; import { setIfNotExists } from '../util/map'; @@ -118,6 +119,7 @@ export class QueryManager { ); }; if (initialCache && trackedQuery.currentCache !== initialCache) { + logger.debug('Initial cache found. Comparing dates.'); if ( !trackedQuery.currentCache || (trackedQuery.currentCache && @@ -153,6 +155,7 @@ export class QueryManager { unsubscribe }); if (!trackedQuery.currentCache) { + logger.info(`No cache available for query ${queryRef.name} with variables ${JSON.stringify(queryRef.variables)}. Calling executeQuery.`); const promise = this.executeQuery( queryRef as QueryRef ); diff --git a/packages/data-connect/src/core/error.ts b/packages/data-connect/src/core/error.ts index 16082e949cf..c89f028be0c 100644 --- a/packages/data-connect/src/core/error.ts +++ b/packages/data-connect/src/core/error.ts @@ -36,7 +36,7 @@ export const Code = { PARTIAL_ERROR: 'partial-error' as DataConnectErrorCode }; -/** An error returned by a Firestore operation. */ +/** An error returned by a DataConnect operation. */ export class DataConnectError extends FirebaseError { /** The stack of the error. */ readonly stack?: string; diff --git a/packages/data-connect/src/network/fetch.ts b/packages/data-connect/src/network/fetch.ts index d4838c29895..1d101060311 100644 --- a/packages/data-connect/src/network/fetch.ts +++ b/packages/data-connect/src/network/fetch.ts @@ -16,6 +16,7 @@ */ import { Code, DataConnectError } from '../core/error'; +import { logger } from '../logger'; let connectFetch: typeof fetch | null = globalThis.fetch; export function initializeFetch(fetchImpl: typeof fetch) { @@ -36,8 +37,10 @@ export function dcFetch( if (accessToken) { headers['X-Firebase-Auth-Token'] = accessToken; } + const bodyStr = JSON.stringify(body); + logger.info(`Making request out to ${url} with body: ${bodyStr}`); return connectFetch(url, { - body: JSON.stringify(body), + body: bodyStr, method: 'POST', headers, signal @@ -50,13 +53,16 @@ export function dcFetch( throw new DataConnectError(Code.OTHER, JSON.stringify(e)); } if (response.status >= 400) { + logger.error("Error while performing request: " + JSON.stringify(jsonResponse)); throw new DataConnectError(Code.OTHER, JSON.stringify(jsonResponse)); } return jsonResponse; }) .then(res => { if (res.errors && res.errors.length) { - throw new DataConnectError(Code.OTHER, JSON.stringify(res.errors)); + const stringified = JSON.stringify(res.errors); + logger.error("DataConnect error while performing request: " + stringified); + throw new DataConnectError(Code.OTHER, stringified); } return res as { data: T; errors: Error[] }; }); diff --git a/packages/data-connect/src/network/transport/index.ts b/packages/data-connect/src/network/transport/index.ts index 45f31c3f50b..e55f1cd11b5 100644 --- a/packages/data-connect/src/network/transport/index.ts +++ b/packages/data-connect/src/network/transport/index.ts @@ -16,7 +16,9 @@ */ import { DataConnectOptions, TransportOptions } from '../../api/DataConnect'; -import { AuthTokenProvider, FirebaseAuthProvider } from '../../core/FirebaseAuthProvider'; +import { + AuthTokenProvider, +} from '../../core/FirebaseAuthProvider'; // Change this to only specify specific args. export interface DataConnectTransport { diff --git a/packages/data-connect/src/network/transport/rest.ts b/packages/data-connect/src/network/transport/rest.ts index 6bbb6246043..b6152dc8686 100644 --- a/packages/data-connect/src/network/transport/rest.ts +++ b/packages/data-connect/src/network/transport/rest.ts @@ -18,7 +18,9 @@ import { DataConnectTransport } from '.'; import { DataConnectOptions, TransportOptions } from '../../api/DataConnect'; import { DataConnectError, Code } from '../../core/error'; -import { AuthTokenProvider, FirebaseAuthProvider } from '../../core/FirebaseAuthProvider'; +import { + AuthTokenProvider, +} from '../../core/FirebaseAuthProvider'; import { addToken, urlBuilder } from '../../util/url'; import { dcFetch } from '../fetch'; diff --git a/packages/data-connect/src/register.ts b/packages/data-connect/src/register.ts index 1344db24582..eaa0e7bbacd 100644 --- a/packages/data-connect/src/register.ts +++ b/packages/data-connect/src/register.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2021 Google LLC + * 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. @@ -32,10 +32,7 @@ export function registerDataConnect(variant?: string): void { _registerComponent( new Component( 'data-connect', - ( - container, - { instanceIdentifier: settings, options } - ) => { + (container, { instanceIdentifier: settings, options }) => { const app = container.getProvider('app').getImmediate()!; const authProvider = container.getProvider('auth-internal'); let newOpts = options as ConnectorConfig; diff --git a/packages/data-connect/src/util/url.ts b/packages/data-connect/src/util/url.ts index 24396082270..44308b3ed67 100644 --- a/packages/data-connect/src/util/url.ts +++ b/packages/data-connect/src/util/url.ts @@ -17,6 +17,7 @@ import { ProjectOptions, TransportOptions } from '../api/DataConnect'; import { Code, DataConnectError } from '../core/error'; +import { logger } from '../logger'; export function urlBuilder( projectConfig: ProjectOptions, @@ -30,6 +31,7 @@ export function urlBuilder( if (typeof port === 'number') { baseUrl += `:${port}`; } else if (typeof port !== 'undefined') { + logger.error("Port type is of an invalid type"); throw new DataConnectError( Code.INVALID_ARGUMENT, 'Incorrect type for port passed in!' diff --git a/packages/firebase/package.json b/packages/firebase/package.json index a776b1a5ca2..7b2a2acb616 100644 --- a/packages/firebase/package.json +++ b/packages/firebase/package.json @@ -391,7 +391,7 @@ "@firebase/app-types": "0.9.1", "@firebase/auth": "1.7.2", "@firebase/auth-compat": "0.5.7", - "@firebase/data-connect": "1.0.1", + "@firebase/data-connect": "0.0.1", "@firebase/database": "1.0.4", "@firebase/database-compat": "1.0.4", "@firebase/firestore": "4.6.1", diff --git a/yarn.lock b/yarn.lock index 1d41740318c..05c0d09dbce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3566,11 +3566,6 @@ resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.3.2.tgz" integrity sha512-eI5Yrz3Qv4KPUa/nSIAi0h+qX0XyewOliug5F2QAtuRg6Kjg6jfmxe1GIwoIRhZspD1A0RP8ANrPwvEXXtRFog== -"@types/prop-types@*": - version "15.7.11" - resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz#2596fb352ee96a1379c657734d4b913a613ad563" - integrity sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng== - "@types/q@^0.0.32": version "0.0.32" resolved "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz" @@ -3586,15 +3581,6 @@ resolved "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz" integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== -"@types/react@18.2.59": - version "18.2.59" - resolved "https://registry.npmjs.org/@types/react/-/react-18.2.59.tgz#14c7bcab22e2ce71d9eaa02f78d3d55067724d7f" - integrity sha512-DE+F6BYEC8VtajY85Qr7mmhTd/79rJKIHCg99MU9SWPB4xvLb6D1za2vYflgZfmPqQVEr6UqJTnLXEwzpVPuOg== - dependencies: - "@types/prop-types" "*" - "@types/scheduler" "*" - csstype "^3.0.2" - "@types/request@2.48.12": version "2.48.12" resolved "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz#0f590f615a10f87da18e9790ac94c29ec4c5ef30" @@ -3624,11 +3610,6 @@ dependencies: "@types/node" "*" -"@types/scheduler@*": - version "0.16.8" - resolved "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz#ce5ace04cfeabe7ef87c0091e50752e36707deff" - integrity sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A== - "@types/selenium-webdriver@^3.0.0": version "3.0.19" resolved "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.19.tgz" @@ -6531,11 +6512,6 @@ css@^3.0.0: source-map "^0.6.1" source-map-resolve "^0.6.0" -csstype@^3.0.2: - version "3.1.3" - resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" - integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== - csv-generate@^3.4.3: version "3.4.3" resolved "https://registry.npmjs.org/csv-generate/-/csv-generate-3.4.3.tgz" From 144e9e49e74b8498662987186597014ce8383a81 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Wed, 8 May 2024 09:42:11 -0700 Subject: [PATCH 03/74] Added licensing to logger file --- packages/data-connect/src/api/DataConnect.ts | 10 +++++----- .../data-connect/src/core/FirebaseAuthProvider.ts | 13 +++++++------ packages/data-connect/src/core/QueryManager.ts | 8 +++++++- packages/data-connect/src/network/fetch.ts | 8 ++++++-- .../data-connect/src/network/transport/index.ts | 4 +--- packages/data-connect/src/network/transport/rest.ts | 4 +--- packages/data-connect/src/util/url.ts | 2 +- 7 files changed, 28 insertions(+), 21 deletions(-) diff --git a/packages/data-connect/src/api/DataConnect.ts b/packages/data-connect/src/api/DataConnect.ts index 4d9ae004852..39828739487 100644 --- a/packages/data-connect/src/api/DataConnect.ts +++ b/packages/data-connect/src/api/DataConnect.ts @@ -87,7 +87,7 @@ export class DataConnect { if (typeof process !== 'undefined' && process.env) { const host = process.env[FIREBASE_DATA_CONNECT_EMULATOR_HOST_VAR]; if (host) { - logger.info("Found custom host. Using emulator"); + logger.info('Found custom host. Using emulator'); this.isEmulator = true; this.transportOptions = parseOptions(host); } @@ -113,7 +113,7 @@ export class DataConnect { return; } if (this.transportClass === undefined) { - logger.info("transportClass not provided. Defaulting to RESTTransport."); + logger.info('transportClass not provided. Defaulting to RESTTransport.'); this.transportClass = RESTTransport; } @@ -150,7 +150,7 @@ export class DataConnect { enableEmulator(transportOptions: TransportOptions) { if (this.initialized) { - logger.error("enableEmulator called without initializing"); + logger.error('enableEmulator called without initializing'); throw new DataConnectError( Code.ALREADY_INITIALIZED, 'DataConnect instance already initialized!' @@ -199,14 +199,14 @@ export function getDataConnect( const options = provider.getOptions(identifier); const optionsValid = Object.keys(options).length > 0; if (optionsValid) { - logger.debug("Re-using cached instance"); + logger.debug('Re-using cached instance'); return dcInstance; } } if (!dcOptions) { throw new DataConnectError(Code.INVALID_ARGUMENT, 'DC Option Required'); } - logger.debug("Creating new DataConnect instance"); + logger.debug('Creating new DataConnect instance'); // Initialize with options. return provider.initialize({ instanceIdentifier: identifier, diff --git a/packages/data-connect/src/core/FirebaseAuthProvider.ts b/packages/data-connect/src/core/FirebaseAuthProvider.ts index d07a371a748..87b93fa8a55 100644 --- a/packages/data-connect/src/core/FirebaseAuthProvider.ts +++ b/packages/data-connect/src/core/FirebaseAuthProvider.ts @@ -56,14 +56,15 @@ export class FirebaseAuthProvider implements AuthTokenProvider { } return this.auth_.getToken(forceRefresh).catch(error => { if (error && error.code === 'auth/token-not-initialized') { - logger.debug( - 'Got auth/token-not-initialized error. Treating as null token.' - ); + logger.debug( + 'Got auth/token-not-initialized error. Treating as null token.' + ); return null; } else { - logger.error( - 'Error received when attempting to retrieve token: ' + JSON.stringify(error) - ); + logger.error( + 'Error received when attempting to retrieve token: ' + + JSON.stringify(error) + ); return Promise.reject(error); } }); diff --git a/packages/data-connect/src/core/QueryManager.ts b/packages/data-connect/src/core/QueryManager.ts index 6fe2154da87..a573757217d 100644 --- a/packages/data-connect/src/core/QueryManager.ts +++ b/packages/data-connect/src/core/QueryManager.ts @@ -155,7 +155,13 @@ export class QueryManager { unsubscribe }); if (!trackedQuery.currentCache) { - logger.info(`No cache available for query ${queryRef.name} with variables ${JSON.stringify(queryRef.variables)}. Calling executeQuery.`); + logger.info( + `No cache available for query ${ + queryRef.name + } with variables ${JSON.stringify( + queryRef.variables + )}. Calling executeQuery.` + ); const promise = this.executeQuery( queryRef as QueryRef ); diff --git a/packages/data-connect/src/network/fetch.ts b/packages/data-connect/src/network/fetch.ts index 1d101060311..4aaf3a427c1 100644 --- a/packages/data-connect/src/network/fetch.ts +++ b/packages/data-connect/src/network/fetch.ts @@ -53,7 +53,9 @@ export function dcFetch( throw new DataConnectError(Code.OTHER, JSON.stringify(e)); } if (response.status >= 400) { - logger.error("Error while performing request: " + JSON.stringify(jsonResponse)); + logger.error( + 'Error while performing request: ' + JSON.stringify(jsonResponse) + ); throw new DataConnectError(Code.OTHER, JSON.stringify(jsonResponse)); } return jsonResponse; @@ -61,7 +63,9 @@ export function dcFetch( .then(res => { if (res.errors && res.errors.length) { const stringified = JSON.stringify(res.errors); - logger.error("DataConnect error while performing request: " + stringified); + logger.error( + 'DataConnect error while performing request: ' + stringified + ); throw new DataConnectError(Code.OTHER, stringified); } return res as { data: T; errors: Error[] }; diff --git a/packages/data-connect/src/network/transport/index.ts b/packages/data-connect/src/network/transport/index.ts index e55f1cd11b5..a557f843513 100644 --- a/packages/data-connect/src/network/transport/index.ts +++ b/packages/data-connect/src/network/transport/index.ts @@ -16,9 +16,7 @@ */ import { DataConnectOptions, TransportOptions } from '../../api/DataConnect'; -import { - AuthTokenProvider, -} from '../../core/FirebaseAuthProvider'; +import { AuthTokenProvider } from '../../core/FirebaseAuthProvider'; // Change this to only specify specific args. export interface DataConnectTransport { diff --git a/packages/data-connect/src/network/transport/rest.ts b/packages/data-connect/src/network/transport/rest.ts index b6152dc8686..1d707646ef0 100644 --- a/packages/data-connect/src/network/transport/rest.ts +++ b/packages/data-connect/src/network/transport/rest.ts @@ -18,9 +18,7 @@ import { DataConnectTransport } from '.'; import { DataConnectOptions, TransportOptions } from '../../api/DataConnect'; import { DataConnectError, Code } from '../../core/error'; -import { - AuthTokenProvider, -} from '../../core/FirebaseAuthProvider'; +import { AuthTokenProvider } from '../../core/FirebaseAuthProvider'; import { addToken, urlBuilder } from '../../util/url'; import { dcFetch } from '../fetch'; diff --git a/packages/data-connect/src/util/url.ts b/packages/data-connect/src/util/url.ts index 44308b3ed67..b07bc407861 100644 --- a/packages/data-connect/src/util/url.ts +++ b/packages/data-connect/src/util/url.ts @@ -31,7 +31,7 @@ export function urlBuilder( if (typeof port === 'number') { baseUrl += `:${port}`; } else if (typeof port !== 'undefined') { - logger.error("Port type is of an invalid type"); + logger.error('Port type is of an invalid type'); throw new DataConnectError( Code.INVALID_ARGUMENT, 'Incorrect type for port passed in!' From 8581bf0540cc7c1b5bf3addc10f82b88094dbe23 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Wed, 8 May 2024 10:07:49 -0700 Subject: [PATCH 04/74] Updated licensing years --- packages/data-connect/src/core/version.ts | 2 +- packages/data-connect/src/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/data-connect/src/core/version.ts b/packages/data-connect/src/core/version.ts index 7c18e8c2949..dd9e7850454 100644 --- a/packages/data-connect/src/core/version.ts +++ b/packages/data-connect/src/core/version.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Google LLC + * 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. diff --git a/packages/data-connect/src/index.ts b/packages/data-connect/src/index.ts index 7402cd77702..1b6a552d3fc 100644 --- a/packages/data-connect/src/index.ts +++ b/packages/data-connect/src/index.ts @@ -6,7 +6,7 @@ /** * @license - * Copyright 2023 Google LLC + * 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. From b8e015c81ed089111a8358bf12b9d4c6d9195fb7 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Wed, 8 May 2024 10:51:57 -0700 Subject: [PATCH 05/74] Removed function from type --- common/api-review/data-connect-react.api.md | 120 ------------------ common/api-review/data-connect.api.md | 7 +- packages/data-connect/src/api/DataConnect.ts | 20 ++- packages/data-connect/src/api/Mutation.ts | 1 - packages/data-connect/src/api/index.ts | 2 +- .../src/core/FirebaseAuthProvider.ts | 6 +- .../data-connect/src/core/QueryManager.ts | 6 +- packages/data-connect/src/logger.ts | 34 +++++ packages/data-connect/src/network/fetch.ts | 8 +- packages/data-connect/src/util/url.ts | 4 +- 10 files changed, 64 insertions(+), 144 deletions(-) delete mode 100644 common/api-review/data-connect-react.api.md create mode 100644 packages/data-connect/src/logger.ts diff --git a/common/api-review/data-connect-react.api.md b/common/api-review/data-connect-react.api.md deleted file mode 100644 index 7ee8eac17ed..00000000000 --- a/common/api-review/data-connect-react.api.md +++ /dev/null @@ -1,120 +0,0 @@ -## API Report File for "@firebase/data-connect-react" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -/// - -import { Context } from 'react'; -import { FirebaseApp } from '@firebase/app'; -import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; -import { FirebaseAuthTokenData } from '@firebase/auth-interop-types'; -import { FirebaseOptions } from '@firebase/app'; -import { JSX as JSX_2 } from 'react/jsx-runtime'; -import { PropsWithChildren } from 'react'; -import { Provider } from '@firebase/component'; - -// @public (undocumented) -export type AuthTokenListener = (token: string | null) => void; - -// @public (undocumented) -export class DataConnect { - constructor(firebaseApp: FirebaseApp, authProvider: Provider, dataConnectOptions: DataConnectOptions, appCheckProvider?: Provider<"app-check-internal"> | undefined); - // (undocumented) - enableEmulator(transportOptions: TransportOptions): void; - // (undocumented) - setDCSettings(settings: DataConnectSettings): void; - // (undocumented) - setInitialized(): void; - // (undocumented) - setTransport(transportClass: TransportClass): void; -} - -// @public (undocumented) -export const DataConnectContext: Context; - -// @public (undocumented) -export interface DataConnectContextInfo { - // (undocumented) - app: FirebaseApp; - // (undocumented) - dataConnect: DataConnect; -} - -// @public (undocumented) -export interface DataConnectOptions { - // (undocumented) - connector: string; - // (undocumented) - location: string; - // (undocumented) - projectId: string; - // (undocumented) - service: string; -} - -// @public (undocumented) -export function DataConnectProvider({ connectorConfig, initializeAppOptions, children }: PropsWithChildren): JSX_2.Element; - -// @public (undocumented) -export interface DataConnectProviderProps { - // (undocumented) - connectorConfig: DataConnectOptions; - // (undocumented) - initializeAppOptions: FirebaseOptions; -} - -// @public (undocumented) -export interface DataConnectSettings { - // (undocumented) - transportOptions?: TransportOptions; -} - -// @public (undocumented) -export interface DataConnectTransport { - // (undocumented) - invokeMutation(queryName: string, body?: U): QueryResponse; - // (undocumented) - invokeQuery(queryName: string, body?: U): PromiseLike<{ - data: T; - }>; - // (undocumented) - onTokenChanged: (token: string | null) => void; - // (undocumented) - useEmulator(host: string, port?: number): void; -} - -// @public (undocumented) -export class FirebaseAuthProvider { - constructor(appName: string, options: FirebaseOptions, authProvider_: Provider); - // (undocumented) - addTokenChangeListener(listener: AuthTokenListener): void; - // (undocumented) - getToken(forceRefresh: boolean): Promise; -} - -// @public (undocumented) -export interface QueryResponse { - // (undocumented) - cancel: () => void; - then(onfulfilled?: (value: { data: T; }) => TResult1 | PromiseLike, onrejected?: (reason: any) => TResult2 | PromiseLike): PromiseLike; -} - -// @public (undocumented) -export type TransportClass = new (options: DataConnectOptions, authProvider: FirebaseAuthProvider, transportOptions?: TransportOptions) => DataConnectTransport; - -// @public (undocumented) -export interface TransportOptions { - // (undocumented) - host: string; - // (undocumented) - port?: number; - // (undocumented) - sslEnabled?: boolean; -} - -// @public (undocumented) -export function useCheckContext(): DataConnect; - -``` diff --git a/common/api-review/data-connect.api.md b/common/api-review/data-connect.api.md index 3b197b3c11d..c5a1a78e589 100644 --- a/common/api-review/data-connect.api.md +++ b/common/api-review/data-connect.api.md @@ -9,6 +9,7 @@ import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; import { FirebaseAuthTokenData } from '@firebase/auth-interop-types'; import { FirebaseError } from '@firebase/util'; import { FirebaseOptions } from '@firebase/app'; +import { LogLevelString } from '@firebase/logger'; import { Provider } from '@firebase/component'; // @public (undocumented) @@ -210,9 +211,6 @@ export interface OpResult { source: DataSource; } -// @public (undocumented) -export function parseOptions(fullHost: string): TransportOptions; - // @public (undocumented) export interface ProjectOptions { // (undocumented) @@ -286,6 +284,9 @@ export interface SerializedRef extends OpResult { refInfo: RefInfo; } +// @public (undocumented) +export function setLogLevel(logLevel: LogLevelString): void; + // @public (undocumented) export const SOURCE_CACHE = "CACHE"; diff --git a/packages/data-connect/src/api/DataConnect.ts b/packages/data-connect/src/api/DataConnect.ts index 39828739487..abbc457ac20 100644 --- a/packages/data-connect/src/api/DataConnect.ts +++ b/packages/data-connect/src/api/DataConnect.ts @@ -35,7 +35,7 @@ import { RESTTransport } from '../network/transport/rest'; import { MutationManager } from './Mutation'; import { Code, DataConnectError } from '../core/error'; -import { logger } from '../logger'; +import { logDebug, logError } from '../logger'; export interface ProjectOptions { location: string; @@ -59,6 +59,12 @@ export interface TransportOptions { export const FIREBASE_DATA_CONNECT_EMULATOR_HOST_VAR = 'FIREBASE_DATA_CONNECT_EMULATOR_HOST'; +/** + * + * @param fullHost + * @returns TransportOptions + * @internal + */ export function parseOptions(fullHost: string): TransportOptions { const [protocol, hostName] = fullHost.split('://'); const isSecure = protocol === 'https'; @@ -87,7 +93,7 @@ export class DataConnect { if (typeof process !== 'undefined' && process.env) { const host = process.env[FIREBASE_DATA_CONNECT_EMULATOR_HOST_VAR]; if (host) { - logger.info('Found custom host. Using emulator'); + logDebug('Found custom host. Using emulator'); this.isEmulator = true; this.transportOptions = parseOptions(host); } @@ -113,7 +119,7 @@ export class DataConnect { return; } if (this.transportClass === undefined) { - logger.info('transportClass not provided. Defaulting to RESTTransport.'); + logDebug('transportClass not provided. Defaulting to RESTTransport.'); this.transportClass = RESTTransport; } @@ -126,7 +132,7 @@ export class DataConnect { this.authProvider ); this.authTokenProvider.addTokenChangeListener(token => { - logger.info(`New Token Available: ${token}`); + logDebug(`New Token Available: ${token}`); this._transport.onTokenChanged(token); }); } @@ -150,7 +156,7 @@ export class DataConnect { enableEmulator(transportOptions: TransportOptions) { if (this.initialized) { - logger.error('enableEmulator called without initializing'); + logError('enableEmulator called without initializing'); throw new DataConnectError( Code.ALREADY_INITIALIZED, 'DataConnect instance already initialized!' @@ -199,14 +205,14 @@ export function getDataConnect( const options = provider.getOptions(identifier); const optionsValid = Object.keys(options).length > 0; if (optionsValid) { - logger.debug('Re-using cached instance'); + logDebug('Re-using cached instance'); return dcInstance; } } if (!dcOptions) { throw new DataConnectError(Code.INVALID_ARGUMENT, 'DC Option Required'); } - logger.debug('Creating new DataConnect instance'); + logDebug('Creating new DataConnect instance'); // Initialize with options. return provider.initialize({ instanceIdentifier: identifier, diff --git a/packages/data-connect/src/api/Mutation.ts b/packages/data-connect/src/api/Mutation.ts index 76666f20e2d..4a9a6db4a94 100644 --- a/packages/data-connect/src/api/Mutation.ts +++ b/packages/data-connect/src/api/Mutation.ts @@ -15,7 +15,6 @@ * limitations under the License. */ -import { logger } from '../logger'; import { DataConnectTransport } from '../network/transport'; import { DataConnect } from './DataConnect'; diff --git a/packages/data-connect/src/api/index.ts b/packages/data-connect/src/api/index.ts index 43b9df2c014..cd8e4c195a6 100644 --- a/packages/data-connect/src/api/index.ts +++ b/packages/data-connect/src/api/index.ts @@ -20,4 +20,4 @@ export * from './DataConnect'; export * from './Reference'; export * from './Mutation'; export * from './query'; -export * from '../logger'; +export { setLogLevel } from '../logger'; diff --git a/packages/data-connect/src/core/FirebaseAuthProvider.ts b/packages/data-connect/src/core/FirebaseAuthProvider.ts index 87b93fa8a55..177b64e8e3b 100644 --- a/packages/data-connect/src/core/FirebaseAuthProvider.ts +++ b/packages/data-connect/src/core/FirebaseAuthProvider.ts @@ -22,7 +22,7 @@ import { FirebaseAuthTokenData } from '@firebase/auth-interop-types'; import { Provider } from '@firebase/component'; -import { logger } from '../logger'; +import { logDebug, logError } from '../logger'; export interface AuthTokenProvider { getToken(forceRefresh: boolean): Promise; @@ -56,12 +56,12 @@ export class FirebaseAuthProvider implements AuthTokenProvider { } return this.auth_.getToken(forceRefresh).catch(error => { if (error && error.code === 'auth/token-not-initialized') { - logger.debug( + logDebug( 'Got auth/token-not-initialized error. Treating as null token.' ); return null; } else { - logger.error( + logError( 'Error received when attempting to retrieve token: ' + JSON.stringify(error) ); diff --git a/packages/data-connect/src/core/QueryManager.ts b/packages/data-connect/src/core/QueryManager.ts index a573757217d..6cb5645924f 100644 --- a/packages/data-connect/src/core/QueryManager.ts +++ b/packages/data-connect/src/core/QueryManager.ts @@ -32,7 +32,7 @@ import { DataSource, SOURCE_CACHE } from '../api/Reference'; -import { logger } from '../logger'; +import { logDebug } from '../logger'; import { DataConnectTransport } from '../network'; import { encoderImpl } from '../util/encoder'; import { setIfNotExists } from '../util/map'; @@ -119,7 +119,7 @@ export class QueryManager { ); }; if (initialCache && trackedQuery.currentCache !== initialCache) { - logger.debug('Initial cache found. Comparing dates.'); + logDebug('Initial cache found. Comparing dates.'); if ( !trackedQuery.currentCache || (trackedQuery.currentCache && @@ -155,7 +155,7 @@ export class QueryManager { unsubscribe }); if (!trackedQuery.currentCache) { - logger.info( + logDebug( `No cache available for query ${ queryRef.name } with variables ${JSON.stringify( diff --git a/packages/data-connect/src/logger.ts b/packages/data-connect/src/logger.ts new file mode 100644 index 00000000000..abc7749aeb4 --- /dev/null +++ b/packages/data-connect/src/logger.ts @@ -0,0 +1,34 @@ +/** + * @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 { Logger, LogLevel, LogLevelString } from "@firebase/logger"; +import { SDK_VERSION } from "./core/version"; + +const logger = new Logger('@firebase/data-connect'); +export function setLogLevel(logLevel: LogLevelString) { + logger.setLogLevel(logLevel); +} +export function logDebug(msg: string): void { + // if (logger.logLevel <= LogLevel.DEBUG) { + logger.debug(`DataConnect (${SDK_VERSION}): ${msg}`); + // } +} + +export function logError(msg: string): void { + // if (logger.logLevel <= LogLevel.ERROR) { + logger.error(`DataConnect (${SDK_VERSION}): ${msg}`); + // } +} diff --git a/packages/data-connect/src/network/fetch.ts b/packages/data-connect/src/network/fetch.ts index 4aaf3a427c1..77ab811a820 100644 --- a/packages/data-connect/src/network/fetch.ts +++ b/packages/data-connect/src/network/fetch.ts @@ -16,7 +16,7 @@ */ import { Code, DataConnectError } from '../core/error'; -import { logger } from '../logger'; +import { logDebug, logError } from '../logger'; let connectFetch: typeof fetch | null = globalThis.fetch; export function initializeFetch(fetchImpl: typeof fetch) { @@ -38,7 +38,7 @@ export function dcFetch( headers['X-Firebase-Auth-Token'] = accessToken; } const bodyStr = JSON.stringify(body); - logger.info(`Making request out to ${url} with body: ${bodyStr}`); + logDebug(`Making request out to ${url} with body: ${bodyStr}`); return connectFetch(url, { body: bodyStr, method: 'POST', @@ -53,7 +53,7 @@ export function dcFetch( throw new DataConnectError(Code.OTHER, JSON.stringify(e)); } if (response.status >= 400) { - logger.error( + logError( 'Error while performing request: ' + JSON.stringify(jsonResponse) ); throw new DataConnectError(Code.OTHER, JSON.stringify(jsonResponse)); @@ -63,7 +63,7 @@ export function dcFetch( .then(res => { if (res.errors && res.errors.length) { const stringified = JSON.stringify(res.errors); - logger.error( + logError( 'DataConnect error while performing request: ' + stringified ); throw new DataConnectError(Code.OTHER, stringified); diff --git a/packages/data-connect/src/util/url.ts b/packages/data-connect/src/util/url.ts index b07bc407861..67840475c0a 100644 --- a/packages/data-connect/src/util/url.ts +++ b/packages/data-connect/src/util/url.ts @@ -17,7 +17,7 @@ import { ProjectOptions, TransportOptions } from '../api/DataConnect'; import { Code, DataConnectError } from '../core/error'; -import { logger } from '../logger'; +import { logError } from '../logger'; export function urlBuilder( projectConfig: ProjectOptions, @@ -31,7 +31,7 @@ export function urlBuilder( if (typeof port === 'number') { baseUrl += `:${port}`; } else if (typeof port !== 'undefined') { - logger.error('Port type is of an invalid type'); + logError('Port type is of an invalid type'); throw new DataConnectError( Code.INVALID_ARGUMENT, 'Incorrect type for port passed in!' From f2a1a4bfb9a4f5d3dc7beca6433521a81508ca5a Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Wed, 8 May 2024 11:26:22 -0700 Subject: [PATCH 06/74] Added token to release files --- .github/workflows/prerelease-manual-deploy.yml | 1 + .github/workflows/release-prod.yml | 1 + .github/workflows/release-staging.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/.github/workflows/prerelease-manual-deploy.yml b/.github/workflows/prerelease-manual-deploy.yml index 54f75383f95..74149b78b8c 100644 --- a/.github/workflows/prerelease-manual-deploy.yml +++ b/.github/workflows/prerelease-manual-deploy.yml @@ -55,6 +55,7 @@ jobs: NPM_TOKEN_AUTH_INTEROP_TYPES: ${{secrets.NPM_TOKEN_AUTH_INTEROP_TYPES}} NPM_TOKEN_AUTH_TYPES: ${{secrets.NPM_TOKEN_AUTH_TYPES}} NPM_TOKEN_COMPONENT: ${{secrets.NPM_TOKEN_COMPONENT}} + NPM_TOKEN_DATA_CONNECT: ${{secrets.NPM_TOKEN_DATA_CONNECT}} NPM_TOKEN_DATABASE: ${{secrets.NPM_TOKEN_DATABASE}} NPM_TOKEN_DATABASE_TYPES: ${{secrets.NPM_TOKEN_DATABASE_TYPES}} NPM_TOKEN_FIRESTORE: ${{secrets.NPM_TOKEN_FIRESTORE}} diff --git a/.github/workflows/release-prod.yml b/.github/workflows/release-prod.yml index 09d1797ec4c..47da73af542 100644 --- a/.github/workflows/release-prod.yml +++ b/.github/workflows/release-prod.yml @@ -65,6 +65,7 @@ jobs: NPM_TOKEN_AUTH_INTEROP_TYPES: ${{secrets.NPM_TOKEN_AUTH_INTEROP_TYPES}} NPM_TOKEN_AUTH_TYPES: ${{secrets.NPM_TOKEN_AUTH_TYPES}} NPM_TOKEN_COMPONENT: ${{secrets.NPM_TOKEN_COMPONENT}} + NPM_TOKEN_DATA_CONNECT: ${{secrets.NPM_TOKEN_DATA_CONNECT}} NPM_TOKEN_DATABASE: ${{secrets.NPM_TOKEN_DATABASE}} NPM_TOKEN_DATABASE_TYPES: ${{secrets.NPM_TOKEN_DATABASE_TYPES}} NPM_TOKEN_FIRESTORE: ${{secrets.NPM_TOKEN_FIRESTORE}} diff --git a/.github/workflows/release-staging.yml b/.github/workflows/release-staging.yml index 6a687ed5ee2..203c342c3fc 100644 --- a/.github/workflows/release-staging.yml +++ b/.github/workflows/release-staging.yml @@ -91,6 +91,7 @@ jobs: NPM_TOKEN_AUTH_INTEROP_TYPES: ${{secrets.NPM_TOKEN_AUTH_INTEROP_TYPES}} NPM_TOKEN_AUTH_TYPES: ${{secrets.NPM_TOKEN_AUTH_TYPES}} NPM_TOKEN_COMPONENT: ${{secrets.NPM_TOKEN_COMPONENT}} + NPM_TOKEN_DATA_CONNECT: ${{secrets.NPM_TOKEN_DATA_CONNECT}} NPM_TOKEN_DATABASE: ${{secrets.NPM_TOKEN_DATABASE}} NPM_TOKEN_DATABASE_TYPES: ${{secrets.NPM_TOKEN_DATABASE_TYPES}} NPM_TOKEN_FIRESTORE: ${{secrets.NPM_TOKEN_FIRESTORE}} From 4f89ef789c17e07d2d587f02c5c3ef7e670cdd9e Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Wed, 8 May 2024 12:25:04 -0700 Subject: [PATCH 07/74] Made data-connect public --- packages/data-connect/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/data-connect/package.json b/packages/data-connect/package.json index 878849ce470..b15f343e6f8 100644 --- a/packages/data-connect/package.json +++ b/packages/data-connect/package.json @@ -2,7 +2,6 @@ "name": "@firebase/data-connect", "version": "0.0.1", "description": "", - "private": true, "author": "Firebase (https://firebase.google.com/)", "main": "dist/index.node.cjs.js", "browser": "dist/index.esm2017.js", From f2ddc3d7b2697e49c38afb91f2895816bb7d9c62 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Wed, 8 May 2024 13:03:22 -0700 Subject: [PATCH 08/74] Pulled in publish file from vertexai branch --- scripts/release/utils/publish.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/scripts/release/utils/publish.ts b/scripts/release/utils/publish.ts index 55d641cc510..1ed3e6261d7 100644 --- a/scripts/release/utils/publish.ts +++ b/scripts/release/utils/publish.ts @@ -75,9 +75,14 @@ export async function publishInCI( continue; } } catch (e) { - // 404 from NPM indicates the package doesn't exist there. - console.log(`Skipping pkg: ${pkg} - it has never been published to NPM.`); - continue; + const versionParts = version.split('-'); + if (versionParts[0] !== '0.0.1') { + // 404 from NPM indicates the package doesn't exist there. + console.log( + `Skipping pkg: ${pkg} - it has never been published to NPM.` + ); + continue; + } } const tag = `${pkg}@${version}`; From 42eb5e521c6fbaa3639c143f860e5a78c5ab4e37 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Wed, 8 May 2024 14:32:45 -0700 Subject: [PATCH 09/74] Trigger release From 323fdc1b85aa0bef7755b3b6e98fccf11164b8c2 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Thu, 9 May 2024 10:11:59 -0700 Subject: [PATCH 10/74] Squashed commit of the following: commit d9ed6147cb5a1b34408342193896d9153a90898c Author: Maneesh Tewani Date: Thu May 9 10:09:49 2024 -0700 Fixed issue where transport is undefined --- packages/data-connect/src/api/DataConnect.ts | 8 ++------ packages/data-connect/src/logger.ts | 12 ++++-------- packages/data-connect/src/network/fetch.ts | 4 +--- packages/data-connect/src/network/transport/rest.ts | 5 +++++ 4 files changed, 12 insertions(+), 17 deletions(-) diff --git a/packages/data-connect/src/api/DataConnect.ts b/packages/data-connect/src/api/DataConnect.ts index abbc457ac20..4b43e5719b1 100644 --- a/packages/data-connect/src/api/DataConnect.ts +++ b/packages/data-connect/src/api/DataConnect.ts @@ -60,8 +60,8 @@ export const FIREBASE_DATA_CONNECT_EMULATOR_HOST_VAR = 'FIREBASE_DATA_CONNECT_EMULATOR_HOST'; /** - * - * @param fullHost + * + * @param fullHost * @returns TransportOptions * @internal */ @@ -131,10 +131,6 @@ export class DataConnect { this.app.options, this.authProvider ); - this.authTokenProvider.addTokenChangeListener(token => { - logDebug(`New Token Available: ${token}`); - this._transport.onTokenChanged(token); - }); } this.initialized = true; diff --git a/packages/data-connect/src/logger.ts b/packages/data-connect/src/logger.ts index abc7749aeb4..5f02de2578d 100644 --- a/packages/data-connect/src/logger.ts +++ b/packages/data-connect/src/logger.ts @@ -14,21 +14,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Logger, LogLevel, LogLevelString } from "@firebase/logger"; -import { SDK_VERSION } from "./core/version"; +import { Logger, LogLevel, LogLevelString } from '@firebase/logger'; +import { SDK_VERSION } from './core/version'; const logger = new Logger('@firebase/data-connect'); export function setLogLevel(logLevel: LogLevelString) { logger.setLogLevel(logLevel); } export function logDebug(msg: string): void { - // if (logger.logLevel <= LogLevel.DEBUG) { - logger.debug(`DataConnect (${SDK_VERSION}): ${msg}`); - // } + logger.debug(`DataConnect (${SDK_VERSION}): ${msg}`); } export function logError(msg: string): void { - // if (logger.logLevel <= LogLevel.ERROR) { - logger.error(`DataConnect (${SDK_VERSION}): ${msg}`); - // } + logger.error(`DataConnect (${SDK_VERSION}): ${msg}`); } diff --git a/packages/data-connect/src/network/fetch.ts b/packages/data-connect/src/network/fetch.ts index 77ab811a820..7a29e11bdc2 100644 --- a/packages/data-connect/src/network/fetch.ts +++ b/packages/data-connect/src/network/fetch.ts @@ -63,9 +63,7 @@ export function dcFetch( .then(res => { if (res.errors && res.errors.length) { const stringified = JSON.stringify(res.errors); - logError( - 'DataConnect error while performing request: ' + stringified - ); + logError('DataConnect error while performing request: ' + stringified); throw new DataConnectError(Code.OTHER, stringified); } return res as { data: T; errors: Error[] }; diff --git a/packages/data-connect/src/network/transport/rest.ts b/packages/data-connect/src/network/transport/rest.ts index 1d707646ef0..79ba5e86006 100644 --- a/packages/data-connect/src/network/transport/rest.ts +++ b/packages/data-connect/src/network/transport/rest.ts @@ -19,6 +19,7 @@ import { DataConnectTransport } from '.'; import { DataConnectOptions, TransportOptions } from '../../api/DataConnect'; import { DataConnectError, Code } from '../../core/error'; import { AuthTokenProvider } from '../../core/FirebaseAuthProvider'; +import { logDebug } from '../../logger'; import { addToken, urlBuilder } from '../../util/url'; import { dcFetch } from '../fetch'; @@ -62,6 +63,10 @@ export class RESTTransport implements DataConnectTransport { ); } this.connectorName = connector; + this.authProvider?.addTokenChangeListener(token => { + logDebug(`New Token Available: ${token}`); + this.accessToken = token; + }); } get endpointUrl(): string { return urlBuilder( From ed9123dc47e8c4638304dc62efb66f6c8ea286a7 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Thu, 9 May 2024 11:51:56 -0700 Subject: [PATCH 11/74] Removed emulator provider --- packages/data-connect/src/api/DataConnect.ts | 5 +--- .../database/src/core/AuthTokenProvider.ts | 24 ------------------- 2 files changed, 1 insertion(+), 28 deletions(-) diff --git a/packages/data-connect/src/api/DataConnect.ts b/packages/data-connect/src/api/DataConnect.ts index 4b43e5719b1..ff3a6c3d2ee 100644 --- a/packages/data-connect/src/api/DataConnect.ts +++ b/packages/data-connect/src/api/DataConnect.ts @@ -26,7 +26,6 @@ import { Provider } from '@firebase/component'; import { AuthTokenProvider, - EmulatorTokenProvider, FirebaseAuthProvider } from '../core/FirebaseAuthProvider'; import { QueryManager } from '../core/QueryManager'; @@ -124,9 +123,7 @@ export class DataConnect { } if (this.authProvider) { - this.authTokenProvider = this.isEmulator - ? new EmulatorTokenProvider(EmulatorTokenProvider.OWNER) - : new FirebaseAuthProvider( + this.authTokenProvider = new FirebaseAuthProvider( this.app.name, this.app.options, this.authProvider diff --git a/packages/database/src/core/AuthTokenProvider.ts b/packages/database/src/core/AuthTokenProvider.ts index 46ccf9b0a68..df378ea09c7 100644 --- a/packages/database/src/core/AuthTokenProvider.ts +++ b/packages/database/src/core/AuthTokenProvider.ts @@ -120,27 +120,3 @@ export class FirebaseAuthTokenProvider implements AuthTokenProvider { warn(errorMessage); } } - -/* AuthTokenProvider that supplies a constant token. Used by Admin SDK or mockUserToken with emulators. */ -export class EmulatorTokenProvider implements AuthTokenProvider { - /** A string that is treated as an admin access token by the RTDB emulator. Used by Admin SDK. */ - static OWNER = 'owner'; - - constructor(private accessToken: string) {} - - getToken(forceRefresh: boolean): Promise { - return Promise.resolve({ - accessToken: this.accessToken - }); - } - - addTokenChangeListener(listener: (token: string | null) => void): void { - // Invoke the listener immediately to match the behavior in Firebase Auth - // (see packages/auth/src/auth.js#L1807) - listener(this.accessToken); - } - - removeTokenChangeListener(listener: (token: string | null) => void): void {} - - notifyForInvalidToken(): void {} -} From 81ee5169ca51b81af0b552128a70bb36621a6129 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Thu, 9 May 2024 13:08:09 -0700 Subject: [PATCH 12/74] Fixed removal of database emulatortokenprovider --- common/api-review/data-connect.api.md | 14 ----------- .../src/core/FirebaseAuthProvider.ts | 21 ---------------- .../database/src/core/AuthTokenProvider.ts | 24 +++++++++++++++++++ 3 files changed, 24 insertions(+), 35 deletions(-) diff --git a/common/api-review/data-connect.api.md b/common/api-review/data-connect.api.md index c5a1a78e589..8f133312868 100644 --- a/common/api-review/data-connect.api.md +++ b/common/api-review/data-connect.api.md @@ -104,20 +104,6 @@ export interface DataConnectTransport { // @public (undocumented) export type DataSource = typeof SOURCE_CACHE | typeof SOURCE_SERVER; -// @public (undocumented) -export class EmulatorTokenProvider implements AuthTokenProvider { - constructor(accessToken: string); - // (undocumented) - addTokenChangeListener(listener: AuthTokenListener): void; - // (undocumented) - getToken(forceRefresh: boolean): Promise; - // (undocumented) - notifyForInvalidToken(): void; - static OWNER: string; - // (undocumented) - removeTokenChangeListener(listener: (token: string | null) => void): void; -} - // @public (undocumented) export function executeMutation(mutationRef: MutationRef): MutationPromise; diff --git a/packages/data-connect/src/core/FirebaseAuthProvider.ts b/packages/data-connect/src/core/FirebaseAuthProvider.ts index 177b64e8e3b..92d14009329 100644 --- a/packages/data-connect/src/core/FirebaseAuthProvider.ts +++ b/packages/data-connect/src/core/FirebaseAuthProvider.ts @@ -78,25 +78,4 @@ export class FirebaseAuthProvider implements AuthTokenProvider { .then(auth => auth.removeAuthTokenListener(listener)); } } -export class EmulatorTokenProvider implements AuthTokenProvider { - /** A string that is treated as an admin access token by the RTDB emulator. Used by Admin SDK. */ - static OWNER = 'owner'; - constructor(private accessToken: string) {} - - getToken(forceRefresh: boolean): Promise { - return Promise.resolve({ - accessToken: this.accessToken - }); - } - - addTokenChangeListener(listener: AuthTokenListener): void { - // Invoke the listener immediately to match the behavior in Firebase Auth - // (see packages/auth/src/auth.js#L1807) - listener(this.accessToken); - } - - removeTokenChangeListener(listener: (token: string | null) => void): void {} - - notifyForInvalidToken(): void {} -} diff --git a/packages/database/src/core/AuthTokenProvider.ts b/packages/database/src/core/AuthTokenProvider.ts index df378ea09c7..46ccf9b0a68 100644 --- a/packages/database/src/core/AuthTokenProvider.ts +++ b/packages/database/src/core/AuthTokenProvider.ts @@ -120,3 +120,27 @@ export class FirebaseAuthTokenProvider implements AuthTokenProvider { warn(errorMessage); } } + +/* AuthTokenProvider that supplies a constant token. Used by Admin SDK or mockUserToken with emulators. */ +export class EmulatorTokenProvider implements AuthTokenProvider { + /** A string that is treated as an admin access token by the RTDB emulator. Used by Admin SDK. */ + static OWNER = 'owner'; + + constructor(private accessToken: string) {} + + getToken(forceRefresh: boolean): Promise { + return Promise.resolve({ + accessToken: this.accessToken + }); + } + + addTokenChangeListener(listener: (token: string | null) => void): void { + // Invoke the listener immediately to match the behavior in Firebase Auth + // (see packages/auth/src/auth.js#L1807) + listener(this.accessToken); + } + + removeTokenChangeListener(listener: (token: string | null) => void): void {} + + notifyForInvalidToken(): void {} +} From aa08c9234a9633a2d90191526a9e6489a2afee3b Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Mon, 13 May 2024 10:43:57 -0700 Subject: [PATCH 13/74] Addressed comments (#8244) * Addressed comments * Included api report * Removed console.log --- common/api-review/data-connect.api.md | 46 +++++------- packages/data-connect/package.json | 2 +- packages/data-connect/src/api.browser.ts | 2 +- packages/data-connect/src/api/DataConnect.ts | 61 ++++++++-------- packages/data-connect/src/api/Mutation.ts | 38 +++++----- packages/data-connect/src/api/Reference.ts | 6 +- packages/data-connect/src/api/query.ts | 7 +- .../src/core/FirebaseAuthProvider.ts | 27 +++---- .../data-connect/src/core/QueryManager.ts | 53 +++++++------- packages/data-connect/src/index.ts | 33 +-------- packages/data-connect/src/logger.ts | 3 +- packages/data-connect/src/network/fetch.ts | 2 + .../src/network/transport/rest.ts | 71 ++++++++++--------- packages/data-connect/src/util/map.ts | 2 +- packages/data-connect/src/util/url.ts | 4 +- packages/data-connect/test/emulatorSeeder.ts | 5 +- packages/data-connect/test/queries.test.ts | 21 +++--- packages/data-connect/test/util.ts | 3 +- packages/data-connect/tsconfig.json | 5 +- .../emulators/dataconnect-emulator.ts | 4 +- 20 files changed, 177 insertions(+), 218 deletions(-) diff --git a/common/api-review/data-connect.api.md b/common/api-review/data-connect.api.md index 8f133312868..ae9db6ae19c 100644 --- a/common/api-review/data-connect.api.md +++ b/common/api-review/data-connect.api.md @@ -46,7 +46,7 @@ export interface ConnectorConfig { // @public (undocumented) export class DataConnect { - constructor(app: FirebaseApp, dataConnectOptions: DataConnectOptions, authProvider: Provider); + constructor(app: FirebaseApp, dataConnectOptions: DataConnectOptions, _authProvider: Provider); // (undocumented) readonly app: FirebaseApp; // (undocumented) @@ -59,7 +59,7 @@ export class DataConnect { isEmulator: boolean; // (undocumented) setInitialized(): void; - } +} // @public (undocumented) export interface DataConnectOptions extends ConnectorConfig { @@ -115,7 +115,7 @@ export const FIREBASE_DATA_CONNECT_EMULATOR_HOST_VAR = "FIREBASE_DATA_CONNECT_EM // @public (undocumented) export class FirebaseAuthProvider implements AuthTokenProvider { - constructor(appName: string, options: FirebaseOptions, authProvider_: Provider); + constructor(_appName: string, _options: FirebaseOptions, _authProvider: Provider); // (undocumented) addTokenChangeListener(listener: AuthTokenListener): void; // (undocumented) @@ -130,28 +130,31 @@ export function getDataConnect(options: ConnectorConfig): DataConnect; // @public (undocumented) export function getDataConnect(app: FirebaseApp, options: ConnectorConfig): DataConnect; +// @public (undocumented) +export const MUTATION_STR = "mutation"; + // @public (undocumented) export class MutationManager { - constructor(transport: DataConnectTransport); + constructor(_transport: DataConnectTransport); // (undocumented) - executeMutation(mutationRef: MutationRef): MutationPromise; - } + executeMutation(mutationRef: MutationRef): MutationPromise; +} // @public (undocumented) export interface MutationPromise extends PromiseLike> { } // @public (undocumented) -export interface MutationRef extends OperationRef { +export interface MutationRef extends OperationRef { // (undocumented) - refType: typeof MutationStr; + refType: typeof MUTATION_STR; } // @public (undocumented) -export function mutationRef(dcInstance: DataConnect, queryName: string): MutationRef; +export function mutationRef(dcInstance: DataConnect, queryName: string): MutationRef; // @public (undocumented) -export function mutationRef(dcInstance: DataConnect, queryName: string, variables: Variables): MutationRef; +export function mutationRef(dcInstance: DataConnect, mutationName: string, variables: Variables): MutationRef; // @public (undocumented) export interface MutationResponse extends CancellableOperation { @@ -163,9 +166,6 @@ export interface MutationResult extends DataConnectResult; } -// @public (undocumented) -export const MutationStr = "mutation"; - // @public (undocumented) export type OnCompleteSubscription = () => void; @@ -198,16 +198,7 @@ export interface OpResult { } // @public (undocumented) -export interface ProjectOptions { - // (undocumented) - connector: string; - // (undocumented) - location: string; - // (undocumented) - projectId: string; - // (undocumented) - service: string; -} +export const QUERY_STR = "query"; // @public (undocumented) export interface QueryPromise extends PromiseLike> { @@ -216,7 +207,7 @@ export interface QueryPromise extends PromiseLike extends OperationRef { // (undocumented) - refType: typeof QueryStr; + refType: typeof QUERY_STR; } // @public (undocumented) @@ -237,14 +228,11 @@ export interface QueryResult extends DataConnectResult SerializedRef; } -// @public (undocumented) -export const QueryStr = "query"; - // @public (undocumented) export type QueryUnsubscribe = () => void; // @public (undocumented) -export type ReferenceType = typeof QueryStr | typeof MutationStr; +export type ReferenceType = typeof QUERY_STR | typeof MUTATION_STR; // @public (undocumented) export interface RefInfo { @@ -296,7 +284,7 @@ export interface SubscriptionOptions { } // @public (undocumented) -export function terminate(dataConnect: DataConnect): void; +export function terminate(dataConnect: DataConnect): Promise; // @public (undocumented) export function toQueryRef(serializedRef: SerializedRef): QueryRef; diff --git a/packages/data-connect/package.json b/packages/data-connect/package.json index b15f343e6f8..aa06a74c23f 100644 --- a/packages/data-connect/package.json +++ b/packages/data-connect/package.json @@ -33,7 +33,7 @@ "prettier": "prettier --write '*.js' '*.ts' '@(src|test)/**/*.ts'", "build:deps": "lerna run --scope @firebase/'{app,data-connect}' --include-dependencies build", "dev": "rollup -c -w", - "test": "run-p --npm-path npm lint test:emulator", + "test": "run-p --npm-path npm test:emulator", "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:emulator", "test:all": "npm run test:node", "test:browser": "karma start --single-run", diff --git a/packages/data-connect/src/api.browser.ts b/packages/data-connect/src/api.browser.ts index 1e0bc61399b..a1547eabd7d 100644 --- a/packages/data-connect/src/api.browser.ts +++ b/packages/data-connect/src/api.browser.ts @@ -63,7 +63,7 @@ export function subscribe( let ref: QueryRef; let initialCache: OpResult | undefined; if ('refInfo' in queryRefOrSerializedResult) { - let serializedRef: SerializedRef = + const serializedRef: SerializedRef = queryRefOrSerializedResult; const { data, source, fetchTime } = serializedRef; initialCache = { diff --git a/packages/data-connect/src/api/DataConnect.ts b/packages/data-connect/src/api/DataConnect.ts index ff3a6c3d2ee..60347758880 100644 --- a/packages/data-connect/src/api/DataConnect.ts +++ b/packages/data-connect/src/api/DataConnect.ts @@ -24,24 +24,18 @@ import { import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; import { Provider } from '@firebase/component'; +import { Code, DataConnectError } from '../core/error'; import { AuthTokenProvider, FirebaseAuthProvider } from '../core/FirebaseAuthProvider'; import { QueryManager } from '../core/QueryManager'; +import { logDebug, logError } from '../logger'; import { DataConnectTransport, TransportClass } from '../network'; import { RESTTransport } from '../network/transport/rest'; import { MutationManager } from './Mutation'; -import { Code, DataConnectError } from '../core/error'; -import { logDebug, logError } from '../logger'; -export interface ProjectOptions { - location: string; - connector: string; - service: string; - projectId: string; -} export interface ConnectorConfig { location: string; @@ -78,27 +72,28 @@ export interface DataConnectOptions extends ConnectorConfig { export class DataConnect { _queryManager!: QueryManager; _mutationManager!: MutationManager; - public isEmulator = false; + isEmulator = false; initialized = false; private _transport!: DataConnectTransport; - private transportClass: TransportClass | undefined; - private transportOptions?: TransportOptions; - private authTokenProvider?: AuthTokenProvider; + private _transportClass: TransportClass | undefined; + private _transportOptions?: TransportOptions; + private _authTokenProvider?: AuthTokenProvider; constructor( public readonly app: FirebaseApp, + // TODO(mtewani): Replace with _dataConnectOptions in the future private readonly dataConnectOptions: DataConnectOptions, - private readonly authProvider: Provider + private readonly _authProvider: Provider ) { if (typeof process !== 'undefined' && process.env) { const host = process.env[FIREBASE_DATA_CONNECT_EMULATOR_HOST_VAR]; if (host) { logDebug('Found custom host. Using emulator'); this.isEmulator = true; - this.transportOptions = parseOptions(host); + this._transportOptions = parseOptions(host); } } } - _delete() { + _delete(): Promise { _removeServiceInstance( this.app, 'data-connect', @@ -113,49 +108,49 @@ export class DataConnect { return copy; } - setInitialized() { + setInitialized(): void { if (this.initialized) { return; } - if (this.transportClass === undefined) { + if (this._transportClass === undefined) { logDebug('transportClass not provided. Defaulting to RESTTransport.'); - this.transportClass = RESTTransport; + this._transportClass = RESTTransport; } - if (this.authProvider) { - this.authTokenProvider = new FirebaseAuthProvider( + if (this._authProvider) { + this._authTokenProvider = new FirebaseAuthProvider( this.app.name, this.app.options, - this.authProvider + this._authProvider ); } this.initialized = true; - this._transport = new this.transportClass( + this._transport = new this._transportClass( this.dataConnectOptions, this.app.options.apiKey, - this.authTokenProvider + this._authTokenProvider ); - if (this.transportOptions) { + if (this._transportOptions) { this._transport.useEmulator( - this.transportOptions.host, - this.transportOptions.port, - this.transportOptions.sslEnabled + this._transportOptions.host, + this._transportOptions.port, + this._transportOptions.sslEnabled ); } this._queryManager = new QueryManager(this._transport); this._mutationManager = new MutationManager(this._transport); } - enableEmulator(transportOptions: TransportOptions) { + enableEmulator(transportOptions: TransportOptions): void { if (this.initialized) { - logError('enableEmulator called without initializing'); + logError('enableEmulator called after initialization'); throw new DataConnectError( Code.ALREADY_INITIALIZED, 'DataConnect instance already initialized!' ); } - this.transportOptions = transportOptions; + this._transportOptions = transportOptions; this.isEmulator = true; } } @@ -165,7 +160,7 @@ export function connectDataConnectEmulator( host: string, port?: number, sslEnabled = false -) { +): void { dc.enableEmulator({ host, port, sslEnabled }); } @@ -213,7 +208,7 @@ export function getDataConnect( }); } -export function terminate(dataConnect: DataConnect) { - dataConnect._delete(); +export function terminate(dataConnect: DataConnect): Promise { + return dataConnect._delete(); // TODO(mtewani): Stop pending tasks } diff --git a/packages/data-connect/src/api/Mutation.ts b/packages/data-connect/src/api/Mutation.ts index 4a9a6db4a94..21f4e49d49c 100644 --- a/packages/data-connect/src/api/Mutation.ts +++ b/packages/data-connect/src/api/Mutation.ts @@ -20,35 +20,35 @@ import { DataConnectTransport } from '../network/transport'; import { DataConnect } from './DataConnect'; import { DataConnectResult, - MutationStr, + MUTATION_STR, OperationRef, SOURCE_SERVER } from './Reference'; -export interface MutationRef - extends OperationRef { - refType: typeof MutationStr; +export interface MutationRef + extends OperationRef { + refType: typeof MUTATION_STR; } -export function mutationRef( +export function mutationRef( dcInstance: DataConnect, queryName: string -): MutationRef; -export function mutationRef( +): MutationRef; +export function mutationRef( dcInstance: DataConnect, - queryName: string, + mutationName: string, variables: Variables -): MutationRef; -export function mutationRef( +): MutationRef; +export function mutationRef( dcInstance: DataConnect, queryName: string, variables?: Variables -): MutationRef { +): MutationRef { dcInstance.setInitialized(); - const ref: MutationRef = { + const ref: MutationRef = { dataConnect: dcInstance, name: queryName, - refType: MutationStr, + refType: MUTATION_STR, variables: variables as Variables }; return ref; @@ -56,16 +56,16 @@ export function mutationRef( export class MutationManager { private _inflight: Array> = []; - constructor(private transport: DataConnectTransport) {} - executeMutation( - mutationRef: MutationRef - ): MutationPromise { - const result = this.transport.invokeMutation( + constructor(private _transport: DataConnectTransport) {} + executeMutation( + mutationRef: MutationRef + ): MutationPromise { + const result = this._transport.invokeMutation( mutationRef.name, mutationRef.variables ); const withRefPromise = result.then(res => { - const obj: MutationResult = { + const obj: MutationResult = { ...res, // Double check that the result is result.data, not just result source: SOURCE_SERVER, ref: mutationRef, diff --git a/packages/data-connect/src/api/Reference.ts b/packages/data-connect/src/api/Reference.ts index e0a0e510c6e..10550e82682 100644 --- a/packages/data-connect/src/api/Reference.ts +++ b/packages/data-connect/src/api/Reference.ts @@ -16,9 +16,9 @@ */ import { DataConnect, DataConnectOptions } from './DataConnect'; -export const QueryStr = 'query'; -export const MutationStr = 'mutation'; -export type ReferenceType = typeof QueryStr | typeof MutationStr; +export const QUERY_STR = 'query'; +export const MUTATION_STR = 'mutation'; +export type ReferenceType = typeof QUERY_STR | typeof MUTATION_STR; export const SOURCE_SERVER = 'SERVER'; export const SOURCE_CACHE = 'CACHE'; diff --git a/packages/data-connect/src/api/query.ts b/packages/data-connect/src/api/query.ts index f68d30426ac..c84aa283f13 100644 --- a/packages/data-connect/src/api/query.ts +++ b/packages/data-connect/src/api/query.ts @@ -16,10 +16,11 @@ */ import { DataConnectError } from '../core/error'; + import { DataConnect, getDataConnect } from './DataConnect'; import { OperationRef, - QueryStr, + QUERY_STR, DataConnectResult, SerializedRef } from './Reference'; @@ -36,7 +37,7 @@ export interface DataConnectSubscription { } export interface QueryRef extends OperationRef { - refType: typeof QueryStr; + refType: typeof QUERY_STR; } export interface QueryResult extends DataConnectResult { @@ -73,7 +74,7 @@ export function queryRef( dcInstance._queryManager.track(queryName, variables, initialCache); return { dataConnect: dcInstance, - refType: QueryStr, + refType: QUERY_STR, name: queryName, variables: variables as Variables }; diff --git a/packages/data-connect/src/core/FirebaseAuthProvider.ts b/packages/data-connect/src/core/FirebaseAuthProvider.ts index 92d14009329..e74e6581388 100644 --- a/packages/data-connect/src/core/FirebaseAuthProvider.ts +++ b/packages/data-connect/src/core/FirebaseAuthProvider.ts @@ -22,6 +22,7 @@ import { FirebaseAuthTokenData } from '@firebase/auth-interop-types'; import { Provider } from '@firebase/component'; + import { logDebug, logError } from '../logger'; export interface AuthTokenProvider { @@ -31,22 +32,22 @@ export interface AuthTokenProvider { export type AuthTokenListener = (token: string | null) => void; export class FirebaseAuthProvider implements AuthTokenProvider { - private auth_: FirebaseAuthInternal; + private _auth: FirebaseAuthInternal; constructor( - private appName: string, - private options: FirebaseOptions, - private authProvider_: Provider + private _appName: string, + private _options: FirebaseOptions, + private _authProvider: Provider ) { - this.auth_ = authProvider_.getImmediate({ optional: true })!; - if (!this.auth_) { - authProvider_.onInit(auth => (this.auth_ = auth)); + this._auth = _authProvider.getImmediate({ optional: true })!; + if (!this._auth) { + _authProvider.onInit(auth => (this._auth = auth)); } } getToken(forceRefresh: boolean): Promise { - if (!this.auth_) { + if (!this._auth) { return new Promise((resolve, reject) => { setTimeout(() => { - if (this.auth_) { + if (this._auth) { this.getToken(forceRefresh).then(resolve, reject); } else { resolve(null); @@ -54,7 +55,7 @@ export class FirebaseAuthProvider implements AuthTokenProvider { }, 0); }); } - return this.auth_.getToken(forceRefresh).catch(error => { + return this._auth.getToken(forceRefresh).catch(error => { if (error && error.code === 'auth/token-not-initialized') { logDebug( 'Got auth/token-not-initialized error. Treating as null token.' @@ -69,11 +70,11 @@ export class FirebaseAuthProvider implements AuthTokenProvider { } }); } - addTokenChangeListener(listener: AuthTokenListener) { - this.auth_?.addAuthTokenListener(listener); + addTokenChangeListener(listener: AuthTokenListener): void { + this._auth?.addAuthTokenListener(listener); } removeTokenChangeListener(listener: (token: string | null) => void): void { - this.authProvider_ + this._authProvider .get() .then(auth => auth.removeAuthTokenListener(listener)); } diff --git a/packages/data-connect/src/core/QueryManager.ts b/packages/data-connect/src/core/QueryManager.ts index 6cb5645924f..6df1a4f9a42 100644 --- a/packages/data-connect/src/core/QueryManager.ts +++ b/packages/data-connect/src/core/QueryManager.ts @@ -25,7 +25,7 @@ import { } from '../api/query'; import { OperationRef, - QueryStr, + QUERY_STR, OpResult, SerializedRef, SOURCE_SERVER, @@ -36,12 +36,13 @@ import { logDebug } from '../logger'; import { DataConnectTransport } from '../network'; import { encoderImpl } from '../util/encoder'; import { setIfNotExists } from '../util/map'; + import { DataConnectError } from './error'; -interface TrackedQuery { - ref: Omit, 'dataConnect'>; - subscriptions: Array>; - currentCache: OpResult | null; +interface TrackedQuery { + ref: Omit, 'dataConnect'>; + subscriptions: Array>; + currentCache: OpResult | null; lastError: DataConnectError | null; } @@ -72,18 +73,18 @@ export class QueryManager { constructor(private transport: DataConnectTransport) { this._queries = new Map(); } - track( + track( queryName: string, variables: Variables, - initialCache?: OpResult + initialCache?: OpResult ) { - const ref: TrackedQuery['ref'] = { + const ref: TrackedQuery['ref'] = { name: queryName, variables, - refType: QueryStr + refType: QUERY_STR }; const key = encoderImpl(ref); - const newTrackedQuery: TrackedQuery = { + const newTrackedQuery: TrackedQuery = { ref, subscriptions: [], currentCache: initialCache || null, @@ -93,19 +94,19 @@ export class QueryManager { setIfNotExists(this._queries, key, newTrackedQuery); return this._queries.get(key); } - addSubscription( - queryRef: OperationRef, - onResultCallback: OnResultSubscription, + addSubscription( + queryRef: OperationRef, + onResultCallback: OnResultSubscription, onErrorCallback?: OnErrorSubscription, - initialCache?: OpResult - ) { + initialCache?: OpResult + ): () => void { const key = encoderImpl({ name: queryRef.name, variables: queryRef.variables, - refType: QueryStr + refType: QUERY_STR }); const trackedQuery = this._queries.get(key) as TrackedQuery< - Response, + Data, Variables >; const subscription = { @@ -136,9 +137,9 @@ export class QueryManager { onResultCallback({ data: cachedData, source: SOURCE_CACHE, - ref: queryRef as QueryRef, + ref: queryRef as QueryRef, toJSON: getRefSerializer( - queryRef as QueryRef, + queryRef as QueryRef, trackedQuery.currentCache.data, SOURCE_CACHE ), @@ -163,30 +164,30 @@ export class QueryManager { )}. Calling executeQuery.` ); const promise = this.executeQuery( - queryRef as QueryRef + queryRef as QueryRef ); // We want to ignore the error and let subscriptions handle it promise.then(undefined, err => {}); } return unsubscribe; } - executeQuery( - queryRef: QueryRef - ): QueryPromise { + executeQuery( + queryRef: QueryRef + ): QueryPromise { const key = encoderImpl({ name: queryRef.name, variables: queryRef.variables, - refType: QueryStr + refType: QUERY_STR }); const trackedQuery = this._queries.get(key)!; - const result = this.transport.invokeQuery( + const result = this.transport.invokeQuery( queryRef.name, queryRef.variables ); const newR = result.then( res => { const fetchTime = new Date().toString(); - const result: QueryResult = { + const result: QueryResult = { ...res, source: SOURCE_SERVER, ref: queryRef, diff --git a/packages/data-connect/src/index.ts b/packages/data-connect/src/index.ts index 1b6a552d3fc..b59c6e72b0c 100644 --- a/packages/data-connect/src/index.ts +++ b/packages/data-connect/src/index.ts @@ -28,39 +28,10 @@ export * from './api'; export * from './api.browser'; registerDataConnect(); -// function sha512(str) { -// return window.crypto.subtle.encrypt() -// } -// async function encoder(object: unknown): Promise { -// const encoder = new TextEncoder(); -// const data = encoder.encode(JSON.stringify(object)); -// const hash = await crypto.subtle.digest('SHA-256', data); -// return hash; -// } -// setEncoder(encoder); + declare module '@firebase/component' { interface NameServiceMapping { 'data-connect': DataConnect; } -} -// import { getDataConnect, queryRef } from './api'; -// import { getApp } from '@firebase/app'; -// const app = getApp(); -// const dc = getDataConnect({ location: '', connector: '', serviceId: '', projectId: '' }); -// interface Response { -// name: string; -// } -// const converter: Converter = { -// fromDataConnect(input: string) { -// return { name: input }; -// }, -// fromType(input) { -// return input; -// } -// }; -// const myRef = queryRef(dc, '', converter); -// // Ref's shouldn't have access to their own cache, right? -// const a = execute(myRef); -// subscribe(myRef, (res) => { -// }) +} \ No newline at end of file diff --git a/packages/data-connect/src/logger.ts b/packages/data-connect/src/logger.ts index 5f02de2578d..523d96bf9ad 100644 --- a/packages/data-connect/src/logger.ts +++ b/packages/data-connect/src/logger.ts @@ -14,7 +14,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Logger, LogLevel, LogLevelString } from '@firebase/logger'; +import { Logger, LogLevelString } from '@firebase/logger'; + import { SDK_VERSION } from './core/version'; const logger = new Logger('@firebase/data-connect'); diff --git a/packages/data-connect/src/network/fetch.ts b/packages/data-connect/src/network/fetch.ts index 7a29e11bdc2..67b8169d3a0 100644 --- a/packages/data-connect/src/network/fetch.ts +++ b/packages/data-connect/src/network/fetch.ts @@ -44,6 +44,8 @@ export function dcFetch( method: 'POST', headers, signal + }).catch(err => { + throw new DataConnectError(Code.OTHER, "Failed to fetch: " + JSON.stringify(err)); }) .then(async response => { let jsonResponse = null; diff --git a/packages/data-connect/src/network/transport/rest.ts b/packages/data-connect/src/network/transport/rest.ts index 79ba5e86006..72e6eba31a7 100644 --- a/packages/data-connect/src/network/transport/rest.ts +++ b/packages/data-connect/src/network/transport/rest.ts @@ -15,7 +15,6 @@ * limitations under the License. */ -import { DataConnectTransport } from '.'; import { DataConnectOptions, TransportOptions } from '../../api/DataConnect'; import { DataConnectError, Code } from '../../core/error'; import { AuthTokenProvider } from '../../core/FirebaseAuthProvider'; @@ -23,16 +22,18 @@ import { logDebug } from '../../logger'; import { addToken, urlBuilder } from '../../util/url'; import { dcFetch } from '../fetch'; +import { DataConnectTransport } from '.'; + export class RESTTransport implements DataConnectTransport { - private host = ''; - private port: number | undefined; - private location = 'l'; - private connectorName = ''; - private secure = true; - private project = 'p'; - private serviceName: string; - private accessToken: string | null = null; - private authInitialized_ = false; + private _host = ''; + private _port: number | undefined; + private _location = 'l'; + private _connectorName = ''; + private _secure = true; + private _project = 'p'; + private _serviceName: string; + private _accessToken: string | null = null; + private _authInitialized = false; constructor( options: DataConnectOptions, private apiKey?: string | undefined, @@ -41,62 +42,62 @@ export class RESTTransport implements DataConnectTransport { ) { if (transportOptions) { if (typeof transportOptions.port === 'number') { - this.port = transportOptions.port; + this._port = transportOptions.port; } if (typeof transportOptions.sslEnabled !== 'undefined') { - this.secure = transportOptions.sslEnabled; + this._secure = transportOptions.sslEnabled; } - this.host = transportOptions.host; + this._host = transportOptions.host; } const { location, projectId: project, connector, service } = options; if (location) { - this.location = location; + this._location = location; } if (project) { - this.project = project; + this._project = project; } - this.serviceName = service; + this._serviceName = service; if (!connector) { throw new DataConnectError( Code.INVALID_ARGUMENT, 'Connector Name required!' ); } - this.connectorName = connector; + this._connectorName = connector; this.authProvider?.addTokenChangeListener(token => { logDebug(`New Token Available: ${token}`); - this.accessToken = token; + this._accessToken = token; }); } get endpointUrl(): string { return urlBuilder( { - connector: this.connectorName, - location: this.location, - projectId: this.project, - service: this.serviceName + connector: this._connectorName, + location: this._location, + projectId: this._project, + service: this._serviceName }, - { host: this.host, sslEnabled: this.secure, port: this.port } + { host: this._host, sslEnabled: this._secure, port: this._port } ); } useEmulator(host: string, port?: number, isSecure?: boolean): void { - this.host = host; + this._host = host; if (typeof port === 'number') { - this.port = port; + this._port = port; } if (typeof isSecure !== 'undefined') { - this.secure = isSecure; + this._secure = isSecure; } } onTokenChanged(newToken: string | null) { - this.accessToken = newToken; + this._accessToken = newToken; } getWithAuth() { let starterPromise: Promise = new Promise(resolve => - resolve(this.accessToken) + resolve(this._accessToken) ); - if (!this.authInitialized_) { + if (!this._authInitialized) { if (this.authProvider) { starterPromise = this.authProvider .getToken(/*forceToken=*/ false) @@ -104,8 +105,8 @@ export class RESTTransport implements DataConnectTransport { if (!data) { return null; } - this.accessToken = data.accessToken; - return this.accessToken; + this._accessToken = data.accessToken; + return this._accessToken; }); } else { starterPromise = new Promise(resolve => resolve('')); @@ -123,12 +124,12 @@ export class RESTTransport implements DataConnectTransport { return dcFetch( addToken(`${this.endpointUrl}:executeQuery`, this.apiKey), { - name: `projects/${this.project}/locations/${this.location}/services/${this.serviceName}/connectors/${this.connectorName}`, + name: `projects/${this._project}/locations/${this._location}/services/${this._serviceName}/connectors/${this._connectorName}`, operationName: queryName, variables: body } as unknown as U, // TODO(mtewani): This is a patch, fix this. abortController, - this.accessToken + this._accessToken ); }); @@ -142,12 +143,12 @@ export class RESTTransport implements DataConnectTransport { return dcFetch( addToken(`${this.endpointUrl}:executeMutation`, this.apiKey), { - name: `projects/${this.project}/locations/${this.location}/services/${this.serviceName}/connectors/${this.connectorName}`, + name: `projects/${this._project}/locations/${this._location}/services/${this._serviceName}/connectors/${this._connectorName}`, operationName: mutationName, variables: body } as unknown as U, abortController, - this.accessToken + this._accessToken ); }); diff --git a/packages/data-connect/src/util/map.ts b/packages/data-connect/src/util/map.ts index 5921365d674..a86d3aef0ef 100644 --- a/packages/data-connect/src/util/map.ts +++ b/packages/data-connect/src/util/map.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -export function setIfNotExists(map: Map, key: string, val: T) { +export function setIfNotExists(map: Map, key: string, val: T): void { if (!map.has(key)) { map.set(key, val); } diff --git a/packages/data-connect/src/util/url.ts b/packages/data-connect/src/util/url.ts index 67840475c0a..b3a8f7c2cff 100644 --- a/packages/data-connect/src/util/url.ts +++ b/packages/data-connect/src/util/url.ts @@ -15,12 +15,12 @@ * limitations under the License. */ -import { ProjectOptions, TransportOptions } from '../api/DataConnect'; +import { DataConnectOptions, TransportOptions } from '../api/DataConnect'; import { Code, DataConnectError } from '../core/error'; import { logError } from '../logger'; export function urlBuilder( - projectConfig: ProjectOptions, + projectConfig: DataConnectOptions, transportOptions: TransportOptions ) { const { connector, location, projectId: project, service } = projectConfig; diff --git a/packages/data-connect/test/emulatorSeeder.ts b/packages/data-connect/test/emulatorSeeder.ts index 6649ea166fe..edcb512b9ef 100644 --- a/packages/data-connect/test/emulatorSeeder.ts +++ b/packages/data-connect/test/emulatorSeeder.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import fs from 'fs'; import * as path from 'path'; // curl localhost:3628/setupSchema -X POST -d '{ // "service_id": "s", @@ -37,14 +38,12 @@ import * as path from 'path'; // } // } -import fs from 'fs'; -import fetch from 'node-fetch'; import { ReferenceType } from '../src'; // } -import { CONNECTOR_NAME, EMULATOR_PORT } from './util'; +import { EMULATOR_PORT } from './util'; export interface SeedInfo { type: ReferenceType; diff --git a/packages/data-connect/test/queries.test.ts b/packages/data-connect/test/queries.test.ts index e2183a426ef..fb912005356 100644 --- a/packages/data-connect/test/queries.test.ts +++ b/packages/data-connect/test/queries.test.ts @@ -15,7 +15,6 @@ * limitations under the License. */ -import { FirebaseApp } from '@firebase/app'; import { uuidv4 } from '@firebase/util'; import { expect, use } from 'chai'; import chaiAsPromised from 'chai-as-promised'; @@ -27,18 +26,16 @@ import { executeQuery, getDataConnect, mutationRef, - OnResultSubscription, QueryRef, queryRef, QueryResult, SerializedRef, subscribe, terminate -} from '../src'; +, SOURCE_CACHE, SOURCE_SERVER } from '../src'; import { setupQueries } from './emulatorSeeder'; -import { app, getConnectionConfig, initDatabase, PROJECT_ID } from './util'; -import { SOURCE_CACHE, SOURCE_SERVER } from '../src'; +import { getConnectionConfig, initDatabase, PROJECT_ID } from './util'; use(chaiAsPromised); @@ -60,7 +57,7 @@ const SEEDED_DATA = [ content: 'task 2' } ]; -function seedDatabase(instance: DataConnect) { +function seedDatabase(instance: DataConnect): Promise { // call mutation query that adds SEEDED_DATA to database return new Promise((resolve, reject) => { async function run() { @@ -95,7 +92,7 @@ describe('DataConnect Tests', async () => { }); afterEach(async () => { await deleteDatabase(dc); - terminate(dc); + await terminate(dc); }); it('Can get all posts', async () => { const taskListQuery = queryRef(dc, 'listPosts'); @@ -109,7 +106,7 @@ describe('DataConnect Tests', async () => { const promise = new Promise>( (resolve, reject) => { const unsubscribe = subscribe(taskListQuery, { - onResult: res => { + onNext: res => { unsubscribe(); resolve(res); }, @@ -164,12 +161,12 @@ describe('DataConnect Tests', async () => { }); connectDataConnectEmulator(fakeInstance, 'localhost', Number(0)); const taskListQuery = queryRef(dc, 'listPosts'); - expect(executeQuery(taskListQuery)).to.eventually.be.rejected; + expect(await executeQuery(taskListQuery)).to.eventually.be.rejectedWith('ECONNREFUSED'); }); }); async function waitForFirstEvent( query: QueryRef -) { +): Promise> { return await new Promise<{ result: QueryResult; unsubscribe: () => void; @@ -182,8 +179,8 @@ async function waitForFirstEvent( }); }); }; - let unsubscribe = subscribe(query, { - onResult, + const unsubscribe = subscribe(query, { + onNext: onResult, onErr: e => { reject({ e, unsubscribe }); } diff --git a/packages/data-connect/test/util.ts b/packages/data-connect/test/util.ts index a5c0293dcf6..cd9149ed41e 100644 --- a/packages/data-connect/test/util.ts +++ b/packages/data-connect/test/util.ts @@ -20,6 +20,7 @@ import { initializeApp } from '@firebase/app'; import { connectDataConnectEmulator, ConnectorConfig, + DataConnect, getDataConnect } from '../src'; @@ -43,7 +44,7 @@ export const app = initializeApp({ }); // Seed the database to have the proper fields to query, such as a list of tasks. -export function initDatabase() { +export function initDatabase(): DataConnect { const instance = getDataConnect(getConnectionConfig()); if (!instance.isEmulator) { connectDataConnectEmulator(instance, 'localhost', Number(EMULATOR_PORT)); diff --git a/packages/data-connect/tsconfig.json b/packages/data-connect/tsconfig.json index 198ba4b1bc5..2355409eed4 100644 --- a/packages/data-connect/tsconfig.json +++ b/packages/data-connect/tsconfig.json @@ -2,9 +2,10 @@ "extends": "../../config/tsconfig.base.json", "compilerOptions": { "outDir": "dist", - "strict": true + "strict": false }, "exclude": [ - "dist/**/*" + "dist/**/*", + "./test/**/*" ] } diff --git a/scripts/emulator-testing/emulators/dataconnect-emulator.ts b/scripts/emulator-testing/emulators/dataconnect-emulator.ts index d240f7227bf..487319e5ffd 100644 --- a/scripts/emulator-testing/emulators/dataconnect-emulator.ts +++ b/scripts/emulator-testing/emulators/dataconnect-emulator.ts @@ -17,7 +17,7 @@ import { Emulator } from './emulator'; -const DATABASE_EMULATOR_VERSION = '1.1.4'; +const DATABASE_EMULATOR_VERSION = '1.1.17'; export class DataConnectEmulator extends Emulator { // namespace: string; @@ -28,7 +28,7 @@ export class DataConnectEmulator extends Emulator { // Use locked version of emulator for test to be deterministic. // The latest version can be found from database emulator doc: // https://firebase.google.com/docs/database/security/test-rules-emulator - `https://firebasestorage.googleapis.com/v0/b/firemat-preview-drop/o/emulator%2Fdataconnect-emulator-macos-v1.1.4?alt=media&token=45a9ae02-568f-4f1e-bd2d-e841411ef221`, + `https://firebasestorage.googleapis.com/v0/b/firemat-preview-drop/o/emulator%2Fdataconnect-emulator-macos-v1.1.17?alt=media&token=c5e758bc-aaad-4be6-bd41-bcc08f3944a7`, port ); this.isJar = false; From 3bf4ab27d566aa79c25b15594ef1d26dccb968a0 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Mon, 13 May 2024 10:45:46 -0700 Subject: [PATCH 14/74] 0.0.2 release --- packages/data-connect/package.json | 2 +- packages/firebase/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/data-connect/package.json b/packages/data-connect/package.json index aa06a74c23f..bc8b4c71bad 100644 --- a/packages/data-connect/package.json +++ b/packages/data-connect/package.json @@ -1,6 +1,6 @@ { "name": "@firebase/data-connect", - "version": "0.0.1", + "version": "0.0.2", "description": "", "author": "Firebase (https://firebase.google.com/)", "main": "dist/index.node.cjs.js", diff --git a/packages/firebase/package.json b/packages/firebase/package.json index 7b2a2acb616..a964346a8dd 100644 --- a/packages/firebase/package.json +++ b/packages/firebase/package.json @@ -391,7 +391,7 @@ "@firebase/app-types": "0.9.1", "@firebase/auth": "1.7.2", "@firebase/auth-compat": "0.5.7", - "@firebase/data-connect": "0.0.1", + "@firebase/data-connect": "0.0.2", "@firebase/database": "1.0.4", "@firebase/database-compat": "1.0.4", "@firebase/firestore": "4.6.1", From 877f8b7d0ac21ef64969605c9936f37ff2c5d47b Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Mon, 13 May 2024 10:48:42 -0700 Subject: [PATCH 15/74] Trigger 0.0.2 real release From 0427a2810235671948fd760e8a03f55130158daf Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Mon, 13 May 2024 11:51:51 -0700 Subject: [PATCH 16/74] Updated comments --- packages/data-connect/src/api.browser.ts | 27 ++++++--- packages/data-connect/src/api/DataConnect.ts | 33 +++++++++++ packages/data-connect/src/api/Mutation.ts | 38 ++++++++++++- packages/data-connect/src/api/Reference.ts | 6 ++ packages/data-connect/src/api/query.ts | 59 ++++++++++++++++++++ 5 files changed, 153 insertions(+), 10 deletions(-) diff --git a/packages/data-connect/src/api.browser.ts b/packages/data-connect/src/api.browser.ts index a1547eabd7d..1ffcb8d1647 100644 --- a/packages/data-connect/src/api.browser.ts +++ b/packages/data-connect/src/api.browser.ts @@ -28,13 +28,10 @@ import { OpResult, SerializedRef } from './api/Reference'; import { DataConnectError, Code } from './core/error'; /** - * - * @public - * @param queryRef - * @param onResult - * @param onErr - * @param initialCache - * @returns + * Subscribe to a `QueryRef` + * @param queryRefOrSerializedResult query ref or serialized result. + * @param observer observer object to use for subscribing. + * @returns `SubscriptionOptions` */ export function subscribe( queryRefOrSerializedResult: @@ -42,6 +39,14 @@ export function subscribe( | SerializedRef, observer: SubscriptionOptions ): QueryUnsubscribe; +/** + * Subscribe to a `QueryRef` + * @param queryRefOrSerializedResult query ref or serialized result. + * @param onNext Callback to call when result comes back. + * @param onError Callback to call when error gets thrown. + * @param onComplete Called when subscription completes. + * @returns `SubscriptionOptions` + */ export function subscribe( queryRefOrSerializedResult: | QueryRef @@ -50,6 +55,14 @@ export function subscribe( onError?: OnErrorSubscription, onComplete?: OnCompleteSubscription ): QueryUnsubscribe; +/** + * Subscribe to a `QueryRef` + * @param queryRefOrSerializedResult query ref or serialized result. + * @param observerOrOnNext observer object or next function. + * @param onError Callback to call when error gets thrown. + * @param onComplete Called when subscription completes. + * @returns `SubscriptionOptions` + */ export function subscribe( queryRefOrSerializedResult: | QueryRef diff --git a/packages/data-connect/src/api/DataConnect.ts b/packages/data-connect/src/api/DataConnect.ts index 60347758880..00e9615f9a2 100644 --- a/packages/data-connect/src/api/DataConnect.ts +++ b/packages/data-connect/src/api/DataConnect.ts @@ -37,12 +37,18 @@ import { RESTTransport } from '../network/transport/rest'; import { MutationManager } from './Mutation'; +/** + * Connector Config for calling Data Connect backend. + */ export interface ConnectorConfig { location: string; connector: string; service: string; } +/** + * Options to connect to emulator + */ export interface TransportOptions { host: string; sslEnabled?: boolean; @@ -65,10 +71,16 @@ export function parseOptions(fullHost: string): TransportOptions { const port = Number(portAsString); return { host, port, sslEnabled: isSecure }; } +/** + * DataConnectOptions including project id + */ export interface DataConnectOptions extends ConnectorConfig { projectId: string; } +/** + * Class representing Firebase Data Connect + */ export class DataConnect { _queryManager!: QueryManager; _mutationManager!: MutationManager; @@ -155,6 +167,13 @@ export class DataConnect { } } +/** + * Connect to the DataConnect Emulator + * @param dc Data Connect instance + * @param host host of emulator server + * @param port port of emulator server + * @param sslEnabled use https + */ export function connectDataConnectEmulator( dc: DataConnect, host: string, @@ -164,7 +183,16 @@ export function connectDataConnectEmulator( dc.enableEmulator({ host, port, sslEnabled }); } +/** + * Initialize DataConnect instance + * @param options ConnectorConfig + */ export function getDataConnect(options: ConnectorConfig): DataConnect; +/** + * Initialize DataConnect instance + * @param app FirebaseApp to initialize to. + * @param options ConnectorConfig + */ export function getDataConnect( app: FirebaseApp, options: ConnectorConfig @@ -208,6 +236,11 @@ export function getDataConnect( }); } +/** + * Delete DataConnect instance + * @param dataConnect DataConnect instance + * @returns + */ export function terminate(dataConnect: DataConnect): Promise { return dataConnect._delete(); // TODO(mtewani): Stop pending tasks diff --git a/packages/data-connect/src/api/Mutation.ts b/packages/data-connect/src/api/Mutation.ts index 21f4e49d49c..3a475d4c625 100644 --- a/packages/data-connect/src/api/Mutation.ts +++ b/packages/data-connect/src/api/Mutation.ts @@ -30,30 +30,51 @@ export interface MutationRef refType: typeof MUTATION_STR; } +/** + * Creates a `MutationRef` + * @param dcInstance Data Connect instance + * @param mutationName name of mutation + */ export function mutationRef( dcInstance: DataConnect, - queryName: string + mutationName: string ): MutationRef; +/** + * + * @param dcInstance Data Connect instance + * @param mutationName name of mutation + * @param variables variables to send with mutation + */ export function mutationRef( dcInstance: DataConnect, mutationName: string, variables: Variables ): MutationRef; +/** + * + * @param dcInstance Data Connect instance + * @param mutationName name of mutation + * @param variables variables to send with mutation + * @returns `MutationRef` + */ export function mutationRef( dcInstance: DataConnect, - queryName: string, + mutationName: string, variables?: Variables ): MutationRef { dcInstance.setInitialized(); const ref: MutationRef = { dataConnect: dcInstance, - name: queryName, + name: mutationName, refType: MUTATION_STR, variables: variables as Variables }; return ref; } +/** + * @internal + */ export class MutationManager { private _inflight: Array> = []; constructor(private _transport: DataConnectTransport) {} @@ -81,15 +102,26 @@ export class MutationManager { } } +/** + * Mutation Result from `executeMutation` + */ export interface MutationResult extends DataConnectResult { ref: MutationRef; } +/** + * Mutation return value from `executeMutation` + */ export interface MutationPromise extends PromiseLike> { // reserved for special actions like cancellation } +/** + * Execute Mutation + * @param mutationRef mutation to execute + * @returns `MutationRef` + */ export function executeMutation( mutationRef: MutationRef ): MutationPromise { diff --git a/packages/data-connect/src/api/Reference.ts b/packages/data-connect/src/api/Reference.ts index 10550e82682..f9d7687dd18 100644 --- a/packages/data-connect/src/api/Reference.ts +++ b/packages/data-connect/src/api/Reference.ts @@ -42,11 +42,17 @@ export interface DataConnectResult extends OpResult { // future metadata } +/** + * Serialized RefInfo as a result of `QueryResult.toJSON().refInfo` + */ export interface RefInfo { name: string; variables: Variables; connectorConfig: DataConnectOptions; } +/** + * Serialized Ref as a result of `QueryResult.toJSON()` + */ export interface SerializedRef extends OpResult { refInfo: RefInfo; } diff --git a/packages/data-connect/src/api/query.ts b/packages/data-connect/src/api/query.ts index c84aa283f13..a5d83c8badd 100644 --- a/packages/data-connect/src/api/query.ts +++ b/packages/data-connect/src/api/query.ts @@ -25,45 +25,93 @@ import { SerializedRef } from './Reference'; +/** + * Signature for `OnResultSubscription` for `subscribe` + */ export type OnResultSubscription = ( res: QueryResult ) => void; +/** + * Signature for `OnErrorSubscription` for `subscribe` + */ export type OnErrorSubscription = (err?: DataConnectError) => void; +/** + * Signature for unsubscribe from `subscribe` + */ export type QueryUnsubscribe = () => void; +/** + * Representation of user provided subscription options. + */ export interface DataConnectSubscription { userCallback: OnResultSubscription; errCallback?: (e?: DataConnectError) => void; unsubscribe: () => void; } + +/** + * QueryRef object + */ export interface QueryRef extends OperationRef { refType: typeof QUERY_STR; } +/** + * Result of `executeQuery` + */ export interface QueryResult extends DataConnectResult { ref: QueryRef; toJSON: () => SerializedRef; } +/** + * Promise returned from `executeQuery` + */ export interface QueryPromise extends PromiseLike> { // reserved for special actions like cancellation } +/** + * Execute Query + * @param queryRef query to execute. + * @returns `QueryPromise` + */ export function executeQuery( queryRef: QueryRef ): QueryPromise { return queryRef.dataConnect._queryManager.executeQuery(queryRef); } +/** + * Execute Query + * @param dcInstance Data Connect instance to use. + * @param queryName Query to execute + * @returns `QueryRef` + */ export function queryRef( dcInstance: DataConnect, queryName: string ): QueryRef; +/** + * Execute Query + * @param dcInstance Data Connect instance to use. + * @param queryName Query to execute + * @param variables Variables to execute with + * @returns `QueryRef` + */ export function queryRef( dcInstance: DataConnect, queryName: string, variables: Variables ): QueryRef; +/** + * Execute Query + * @param dcInstance Data Connect instance to use. + * @param queryName Query to execute + * @param variables Variables to execute with + * @param initialCache initial cache to use for client hydration + * @returns `QueryRef` + */ export function queryRef( dcInstance: DataConnect, queryName: string, @@ -79,6 +127,11 @@ export function queryRef( variables: variables as Variables }; } +/** + * Converts serialized ref to query ref + * @param serializedRef ref to convert to `QueryRef` + * @returns `QueryRef` + */ export function toQueryRef( serializedRef: SerializedRef ) { @@ -87,7 +140,13 @@ export function toQueryRef( } = serializedRef; return queryRef(getDataConnect(connectorConfig), name, variables); } +/** + * `OnCompleteSubscription` + */ export type OnCompleteSubscription = () => void; +/** + * Representation of full observer options in `subscribe` + */ export interface SubscriptionOptions { onNext?: OnResultSubscription; onErr?: OnErrorSubscription; From 388b61c7ec989783cbbec51c99d29916455358b3 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Mon, 13 May 2024 11:52:30 -0700 Subject: [PATCH 17/74] Updated comments in api report --- common/api-review/data-connect.api.md | 67 ++++++++++++--------------- 1 file changed, 30 insertions(+), 37 deletions(-) diff --git a/common/api-review/data-connect.api.md b/common/api-review/data-connect.api.md index ae9db6ae19c..ea3a4c41c53 100644 --- a/common/api-review/data-connect.api.md +++ b/common/api-review/data-connect.api.md @@ -31,10 +31,10 @@ export interface CancellableOperation extends PromiseLike<{ cancel: () => void; } -// @public (undocumented) +// @public export function connectDataConnectEmulator(dc: DataConnect, host: string, port?: number, sslEnabled?: boolean): void; -// @public (undocumented) +// @public export interface ConnectorConfig { // (undocumented) connector: string; @@ -44,7 +44,7 @@ export interface ConnectorConfig { service: string; } -// @public (undocumented) +// @public export class DataConnect { constructor(app: FirebaseApp, dataConnectOptions: DataConnectOptions, _authProvider: Provider); // (undocumented) @@ -61,7 +61,7 @@ export class DataConnect { setInitialized(): void; } -// @public (undocumented) +// @public export interface DataConnectOptions extends ConnectorConfig { // (undocumented) projectId: string; @@ -73,7 +73,7 @@ export interface DataConnectResult extends OpResult { ref: OperationRef; } -// @public (undocumented) +// @public export interface DataConnectSubscription { // (undocumented) errCallback?: (e?: FirebaseError) => void; @@ -104,10 +104,10 @@ export interface DataConnectTransport { // @public (undocumented) export type DataSource = typeof SOURCE_CACHE | typeof SOURCE_SERVER; -// @public (undocumented) +// @public export function executeMutation(mutationRef: MutationRef): MutationPromise; -// @public (undocumented) +// @public export function executeQuery(queryRef: QueryRef): QueryPromise; // @public (undocumented) @@ -124,23 +124,16 @@ export class FirebaseAuthProvider implements AuthTokenProvider { removeTokenChangeListener(listener: (token: string | null) => void): void; } -// @public (undocumented) +// @public export function getDataConnect(options: ConnectorConfig): DataConnect; -// @public (undocumented) +// @public export function getDataConnect(app: FirebaseApp, options: ConnectorConfig): DataConnect; // @public (undocumented) export const MUTATION_STR = "mutation"; -// @public (undocumented) -export class MutationManager { - constructor(_transport: DataConnectTransport); - // (undocumented) - executeMutation(mutationRef: MutationRef): MutationPromise; -} - -// @public (undocumented) +// @public export interface MutationPromise extends PromiseLike> { } @@ -150,8 +143,8 @@ export interface MutationRef extends OperationRef(dcInstance: DataConnect, queryName: string): MutationRef; +// @public +export function mutationRef(dcInstance: DataConnect, mutationName: string): MutationRef; // @public (undocumented) export function mutationRef(dcInstance: DataConnect, mutationName: string, variables: Variables): MutationRef; @@ -160,19 +153,19 @@ export function mutationRef(dcInstance: DataConnect, mutationNa export interface MutationResponse extends CancellableOperation { } -// @public (undocumented) +// @public export interface MutationResult extends DataConnectResult { // (undocumented) ref: MutationRef; } -// @public (undocumented) +// @public export type OnCompleteSubscription = () => void; -// @public (undocumented) +// @public export type OnErrorSubscription = (err?: FirebaseError) => void; -// @public (undocumented) +// @public export type OnResultSubscription = (res: QueryResult) => void; // @public (undocumented) @@ -200,27 +193,27 @@ export interface OpResult { // @public (undocumented) export const QUERY_STR = "query"; -// @public (undocumented) +// @public export interface QueryPromise extends PromiseLike> { } -// @public (undocumented) +// @public export interface QueryRef extends OperationRef { // (undocumented) refType: typeof QUERY_STR; } -// @public (undocumented) +// @public export function queryRef(dcInstance: DataConnect, queryName: string): QueryRef; -// @public (undocumented) +// @public export function queryRef(dcInstance: DataConnect, queryName: string, variables: Variables): QueryRef; // @public (undocumented) export interface QueryResponse extends CancellableOperation { } -// @public (undocumented) +// @public export interface QueryResult extends DataConnectResult { // (undocumented) ref: QueryRef; @@ -228,13 +221,13 @@ export interface QueryResult extends DataConnectResult SerializedRef; } -// @public (undocumented) +// @public export type QueryUnsubscribe = () => void; // @public (undocumented) export type ReferenceType = typeof QUERY_STR | typeof MUTATION_STR; -// @public (undocumented) +// @public export interface RefInfo { // (undocumented) connectorConfig: DataConnectOptions; @@ -252,7 +245,7 @@ export interface Sender { send: () => Promise; } -// @public (undocumented) +// @public export interface SerializedRef extends OpResult { // (undocumented) refInfo: RefInfo; @@ -267,13 +260,13 @@ export const SOURCE_CACHE = "CACHE"; // @public (undocumented) export const SOURCE_SERVER = "SERVER"; -// @public (undocumented) +// @public export function subscribe(queryRefOrSerializedResult: QueryRef | SerializedRef, observer: SubscriptionOptions): QueryUnsubscribe; -// @public (undocumented) +// @public export function subscribe(queryRefOrSerializedResult: QueryRef | SerializedRef, onNext: OnResultSubscription, onError?: OnErrorSubscription, onComplete?: OnCompleteSubscription): QueryUnsubscribe; -// @public (undocumented) +// @public export interface SubscriptionOptions { // (undocumented) onComplete?: OnCompleteSubscription; @@ -283,16 +276,16 @@ export interface SubscriptionOptions { onNext?: OnResultSubscription; } -// @public (undocumented) +// @public export function terminate(dataConnect: DataConnect): Promise; -// @public (undocumented) +// @public export function toQueryRef(serializedRef: SerializedRef): QueryRef; // @public (undocumented) export type TransportClass = new (options: DataConnectOptions, apiKey?: string, authProvider?: AuthTokenProvider, transportOptions?: TransportOptions) => DataConnectTransport; -// @public (undocumented) +// @public export interface TransportOptions { // (undocumented) host: string; From 01ca9df71179f071bbd3807bbc1fdc45e522c30e Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Fri, 31 May 2024 11:01:31 -0700 Subject: [PATCH 18/74] Updated deps --- packages/data-connect/package.json | 10 +++++----- packages/data-connect/src/api/DataConnect.ts | 11 +++++------ packages/data-connect/src/api/Mutation.ts | 4 ++-- .../data-connect/src/core/FirebaseAuthProvider.ts | 1 - packages/data-connect/src/core/QueryManager.ts | 4 +--- packages/data-connect/src/index.ts | 3 +-- packages/data-connect/src/network/fetch.ts | 8 ++++++-- packages/data-connect/src/util/map.ts | 6 +++++- packages/data-connect/test/emulatorSeeder.ts | 2 -- packages/data-connect/test/queries.test.ts | 10 +++++++--- 10 files changed, 32 insertions(+), 27 deletions(-) diff --git a/packages/data-connect/package.json b/packages/data-connect/package.json index bc8b4c71bad..ceb9a93b0d5 100644 --- a/packages/data-connect/package.json +++ b/packages/data-connect/package.json @@ -46,14 +46,14 @@ }, "license": "Apache-2.0", "dependencies": { - "@firebase/auth-interop-types": "0.2.2", - "@firebase/component": "0.6.6", - "@firebase/logger": "0.4.1", - "@firebase/util": "1.9.5", + "@firebase/auth-interop-types": "0.2.3", + "@firebase/component": "0.6.7", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.9.6", "tslib": "^2.1.0" }, "devDependencies": { - "@firebase/app": "0.10.2", + "@firebase/app": "0.10.5", "rollup": "2.79.1", "rollup-plugin-typescript2": "0.31.2", "typescript": "4.7.4" diff --git a/packages/data-connect/src/api/DataConnect.ts b/packages/data-connect/src/api/DataConnect.ts index 00e9615f9a2..2e3cd0157ea 100644 --- a/packages/data-connect/src/api/DataConnect.ts +++ b/packages/data-connect/src/api/DataConnect.ts @@ -36,7 +36,6 @@ import { RESTTransport } from '../network/transport/rest'; import { MutationManager } from './Mutation'; - /** * Connector Config for calling Data Connect backend. */ @@ -131,10 +130,10 @@ export class DataConnect { if (this._authProvider) { this._authTokenProvider = new FirebaseAuthProvider( - this.app.name, - this.app.options, - this._authProvider - ); + this.app.name, + this.app.options, + this._authProvider + ); } this.initialized = true; @@ -239,7 +238,7 @@ export function getDataConnect( /** * Delete DataConnect instance * @param dataConnect DataConnect instance - * @returns + * @returns */ export function terminate(dataConnect: DataConnect): Promise { return dataConnect._delete(); diff --git a/packages/data-connect/src/api/Mutation.ts b/packages/data-connect/src/api/Mutation.ts index 3a475d4c625..ce307f953d1 100644 --- a/packages/data-connect/src/api/Mutation.ts +++ b/packages/data-connect/src/api/Mutation.ts @@ -40,7 +40,7 @@ export function mutationRef( mutationName: string ): MutationRef; /** - * + * * @param dcInstance Data Connect instance * @param mutationName name of mutation * @param variables variables to send with mutation @@ -51,7 +51,7 @@ export function mutationRef( variables: Variables ): MutationRef; /** - * + * * @param dcInstance Data Connect instance * @param mutationName name of mutation * @param variables variables to send with mutation diff --git a/packages/data-connect/src/core/FirebaseAuthProvider.ts b/packages/data-connect/src/core/FirebaseAuthProvider.ts index e74e6581388..e98f5ae09c8 100644 --- a/packages/data-connect/src/core/FirebaseAuthProvider.ts +++ b/packages/data-connect/src/core/FirebaseAuthProvider.ts @@ -79,4 +79,3 @@ export class FirebaseAuthProvider implements AuthTokenProvider { .then(auth => auth.removeAuthTokenListener(listener)); } } - diff --git a/packages/data-connect/src/core/QueryManager.ts b/packages/data-connect/src/core/QueryManager.ts index 6df1a4f9a42..0a9a170281d 100644 --- a/packages/data-connect/src/core/QueryManager.ts +++ b/packages/data-connect/src/core/QueryManager.ts @@ -163,9 +163,7 @@ export class QueryManager { queryRef.variables )}. Calling executeQuery.` ); - const promise = this.executeQuery( - queryRef as QueryRef - ); + const promise = this.executeQuery(queryRef as QueryRef); // We want to ignore the error and let subscriptions handle it promise.then(undefined, err => {}); } diff --git a/packages/data-connect/src/index.ts b/packages/data-connect/src/index.ts index b59c6e72b0c..946c72c38d5 100644 --- a/packages/data-connect/src/index.ts +++ b/packages/data-connect/src/index.ts @@ -29,9 +29,8 @@ export * from './api.browser'; registerDataConnect(); - declare module '@firebase/component' { interface NameServiceMapping { 'data-connect': DataConnect; } -} \ No newline at end of file +} diff --git a/packages/data-connect/src/network/fetch.ts b/packages/data-connect/src/network/fetch.ts index 67b8169d3a0..7a104521f5c 100644 --- a/packages/data-connect/src/network/fetch.ts +++ b/packages/data-connect/src/network/fetch.ts @@ -44,9 +44,13 @@ export function dcFetch( method: 'POST', headers, signal - }).catch(err => { - throw new DataConnectError(Code.OTHER, "Failed to fetch: " + JSON.stringify(err)); }) + .catch(err => { + throw new DataConnectError( + Code.OTHER, + 'Failed to fetch: ' + JSON.stringify(err) + ); + }) .then(async response => { let jsonResponse = null; try { diff --git a/packages/data-connect/src/util/map.ts b/packages/data-connect/src/util/map.ts index a86d3aef0ef..5b96eb2f3dc 100644 --- a/packages/data-connect/src/util/map.ts +++ b/packages/data-connect/src/util/map.ts @@ -15,7 +15,11 @@ * limitations under the License. */ -export function setIfNotExists(map: Map, key: string, val: T): void { +export function setIfNotExists( + map: Map, + key: string, + val: T +): void { if (!map.has(key)) { map.set(key, val); } diff --git a/packages/data-connect/test/emulatorSeeder.ts b/packages/data-connect/test/emulatorSeeder.ts index edcb512b9ef..3f9b86b117c 100644 --- a/packages/data-connect/test/emulatorSeeder.ts +++ b/packages/data-connect/test/emulatorSeeder.ts @@ -38,8 +38,6 @@ import * as path from 'path'; // } // } - - import { ReferenceType } from '../src'; // } diff --git a/packages/data-connect/test/queries.test.ts b/packages/data-connect/test/queries.test.ts index fb912005356..d84279d014f 100644 --- a/packages/data-connect/test/queries.test.ts +++ b/packages/data-connect/test/queries.test.ts @@ -31,8 +31,10 @@ import { QueryResult, SerializedRef, subscribe, - terminate -, SOURCE_CACHE, SOURCE_SERVER } from '../src'; + terminate, + SOURCE_CACHE, + SOURCE_SERVER +} from '../src'; import { setupQueries } from './emulatorSeeder'; import { getConnectionConfig, initDatabase, PROJECT_ID } from './util'; @@ -161,7 +163,9 @@ describe('DataConnect Tests', async () => { }); connectDataConnectEmulator(fakeInstance, 'localhost', Number(0)); const taskListQuery = queryRef(dc, 'listPosts'); - expect(await executeQuery(taskListQuery)).to.eventually.be.rejectedWith('ECONNREFUSED'); + expect(await executeQuery(taskListQuery)).to.eventually.be.rejectedWith( + 'ECONNREFUSED' + ); }); }); async function waitForFirstEvent( From ceed0567b60e65676b49dda1deef4a0754c63d8e Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Tue, 4 Jun 2024 12:00:49 -0700 Subject: [PATCH 19/74] Began reporting client sdk information (#8271) * Began reporting client sdk information --- docs-devsite/index.md | 1 + packages/data-connect/src/network/fetch.ts | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs-devsite/index.md b/docs-devsite/index.md index 2c22b58d80c..7cf4e7f4e3b 100644 --- a/docs-devsite/index.md +++ b/docs-devsite/index.md @@ -19,6 +19,7 @@ https://github.com/firebase/firebase-js-sdk | [@firebase/app](./app.md#app_package) | Firebase App | | [@firebase/app-check](./app-check.md#app-check_package) | The Firebase App Check Web SDK. | | [@firebase/auth](./auth.md#auth_package) | Firebase Authentication | +| [@firebase/data-connect](./data-connect.md#data-connect_package) | Firebase Data Connect | | [@firebase/database](./database.md#database_package) | Firebase Realtime Database | | [@firebase/firestore](./firestore.md#firestore_package) | Cloud Firestore | | [@firebase/functions](./functions.md#functions_package) | Cloud Functions for Firebase | diff --git a/packages/data-connect/src/network/fetch.ts b/packages/data-connect/src/network/fetch.ts index 7a104521f5c..0bfd1629b2a 100644 --- a/packages/data-connect/src/network/fetch.ts +++ b/packages/data-connect/src/network/fetch.ts @@ -16,12 +16,16 @@ */ import { Code, DataConnectError } from '../core/error'; +import { SDK_VERSION } from '../core/version'; import { logDebug, logError } from '../logger'; let connectFetch: typeof fetch | null = globalThis.fetch; export function initializeFetch(fetchImpl: typeof fetch) { connectFetch = fetchImpl; } +function getGoogApiClientValue(): string { + return 'gl-js/ fire/' + SDK_VERSION; +} export function dcFetch( url: string, body: U, @@ -32,7 +36,8 @@ export function dcFetch( throw new DataConnectError(Code.OTHER, 'No Fetch Implementation detected!'); } const headers: HeadersInit = { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + 'X-Goog-Api-Client': getGoogApiClientValue() }; if (accessToken) { headers['X-Firebase-Auth-Token'] = accessToken; From 842463d2c7270e7f5b76b843120f98518ae19613 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Wed, 3 Jul 2024 11:07:56 -0700 Subject: [PATCH 20/74] Modified reporting name (#8283) --- packages/app/src/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/constants.ts b/packages/app/src/constants.ts index 6648b2bbcb2..8ef4eada39c 100644 --- a/packages/app/src/constants.ts +++ b/packages/app/src/constants.ts @@ -60,7 +60,7 @@ export const PLATFORM_LOG_STRING = { [authName]: 'fire-auth', [authCompatName]: 'fire-auth-compat', [databaseName]: 'fire-rtdb', - [dataconnectName]: 'fire-connect', + [dataconnectName]: 'fire-data-connect', [databaseCompatName]: 'fire-rtdb-compat', [functionsName]: 'fire-fn', [functionsCompatName]: 'fire-fn-compat', From 4254b10735637f0382db702c409d084026cc3f4a Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Wed, 3 Jul 2024 11:19:35 -0700 Subject: [PATCH 21/74] Removed unnecessary rtdb changes (#8350) --- common/api-review/connect.api.md | 133 ----- .../data-connect.authtokenprovider.md | 61 ++ .../data-connect.cancellableoperation.md | 43 ++ docs-devsite/data-connect.connectorconfig.md | 51 ++ docs-devsite/data-connect.dataconnect.md | 124 ++++ .../data-connect.dataconnectoptions.md | 34 ++ .../data-connect.dataconnectresult.md | 32 ++ .../data-connect.dataconnectsubscription.md | 51 ++ .../data-connect.dataconnecttransport.md | 104 ++++ .../data-connect.firebaseauthprovider.md | 105 ++++ docs-devsite/data-connect.md | 536 ++++++++++++++++++ docs-devsite/data-connect.mutationpromise.md | 21 + docs-devsite/data-connect.mutationref.md | 32 ++ docs-devsite/data-connect.mutationresponse.md | 19 + docs-devsite/data-connect.mutationresult.md | 34 ++ docs-devsite/data-connect.operationref.md | 58 ++ docs-devsite/data-connect.opresult.md | 49 ++ docs-devsite/data-connect.querypromise.md | 21 + docs-devsite/data-connect.queryref.md | 34 ++ docs-devsite/data-connect.queryresponse.md | 19 + docs-devsite/data-connect.queryresult.md | 43 ++ docs-devsite/data-connect.refinfo.md | 51 ++ docs-devsite/data-connect.sender.md | 40 ++ docs-devsite/data-connect.serializedref.md | 34 ++ .../data-connect.subscriptionoptions.md | 51 ++ docs-devsite/data-connect.transportoptions.md | 51 ++ packages/data-connect/karma.conf.js | 34 ++ packages/database/src/api/Reference_impl.ts | 1 - .../database/test/exp/integration.test.ts | 29 +- 29 files changed, 1735 insertions(+), 160 deletions(-) delete mode 100644 common/api-review/connect.api.md create mode 100644 docs-devsite/data-connect.authtokenprovider.md create mode 100644 docs-devsite/data-connect.cancellableoperation.md create mode 100644 docs-devsite/data-connect.connectorconfig.md create mode 100644 docs-devsite/data-connect.dataconnect.md create mode 100644 docs-devsite/data-connect.dataconnectoptions.md create mode 100644 docs-devsite/data-connect.dataconnectresult.md create mode 100644 docs-devsite/data-connect.dataconnectsubscription.md create mode 100644 docs-devsite/data-connect.dataconnecttransport.md create mode 100644 docs-devsite/data-connect.firebaseauthprovider.md create mode 100644 docs-devsite/data-connect.md create mode 100644 docs-devsite/data-connect.mutationpromise.md create mode 100644 docs-devsite/data-connect.mutationref.md create mode 100644 docs-devsite/data-connect.mutationresponse.md create mode 100644 docs-devsite/data-connect.mutationresult.md create mode 100644 docs-devsite/data-connect.operationref.md create mode 100644 docs-devsite/data-connect.opresult.md create mode 100644 docs-devsite/data-connect.querypromise.md create mode 100644 docs-devsite/data-connect.queryref.md create mode 100644 docs-devsite/data-connect.queryresponse.md create mode 100644 docs-devsite/data-connect.queryresult.md create mode 100644 docs-devsite/data-connect.refinfo.md create mode 100644 docs-devsite/data-connect.sender.md create mode 100644 docs-devsite/data-connect.serializedref.md create mode 100644 docs-devsite/data-connect.subscriptionoptions.md create mode 100644 docs-devsite/data-connect.transportoptions.md create mode 100644 packages/data-connect/karma.conf.js diff --git a/common/api-review/connect.api.md b/common/api-review/connect.api.md deleted file mode 100644 index f2b5d2fda4d..00000000000 --- a/common/api-review/connect.api.md +++ /dev/null @@ -1,133 +0,0 @@ -## API Report File for "@firebase/connect" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { AppCheckInternalComponentName } from '@firebase/app-check-interop-types'; -import { FirebaseApp } from '@firebase/app'; -import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; -import { Provider } from '@firebase/component'; - -// @public (undocumented) -export function connectDataConnectEmulator(dc: DataConnect, host: string, port: number, isSecure?: boolean): void; - -// @public (undocumented) -export interface Converter { - // (undocumented) - fromDataConnect: (data: DataConnectSnapshot) => Response; - // (undocumented) - fromResponse: (response: Response) => DataConnectSnapshot; -} - -// @public (undocumented) -export class DataConnect { - constructor(firebaseApp: FirebaseApp, authProvider: Provider, transportOptions: TransportOptions, projectOptions: ProjectOptions, appCheckProvider?: Provider, useRest?: boolean); - // (undocumented) - enableEmulator(host: string, port: number, isSecure: boolean): void; - // (undocumented) - setInitialized(): void; - } - -// @public (undocumented) -export interface DataConnectOptions { - // (undocumented) - projectOptions: ProjectOptions; - // (undocumented) - transportOptions?: TransportOptions; -} - -// @public (undocumented) -export interface DataConnectSnapshot { - // (undocumented) - data: Response; -} - -// @public (undocumented) -export interface DataConnectSubscription { - // (undocumented) - converter: Converter; - // (undocumented) - unsubscribe: () => void; - // (undocumented) - userCallback: (data: Response | undefined, error?: Error) => void; -} - -// @public (undocumented) -export function executeMutation(mutationRef: Mutation): Promise; - -// @public (undocumented) -export function executeQuery(queryRef: Query): Promise; - -// @public (undocumented) -export const FIREBASE_DATA_CONNECT_EMULATOR_HOST_VAR = "FIREBASE_DATA_CONNECT_EMULATOR_HOST"; - -// @public (undocumented) -export function getDataConnect(app: FirebaseApp, options: DataConnectOptions): DataConnect; - -// @public (undocumented) -export function getDataConnect(options: DataConnectOptions): DataConnect; - -// @public (undocumented) -export interface Mutation extends Reference { - // (undocumented) - dcInstance: DataConnect; -} - -// @public (undocumented) -export function mutationRef(dcInstance: DataConnect, queryName: string, variables: Variables): Mutation; - -// @public (undocumented) -export interface ProjectOptions { - // (undocumented) - connector: string; - // (undocumented) - location: string; - // (undocumented) - project: string; - // (undocumented) - service: string; -} - -// @public (undocumented) -export interface Query extends Reference { - // (undocumented) - dcInstance: DataConnect; -} - -// @public (undocumented) -export function queryRef(dcInstance: DataConnect, queryName: string, variables?: Variables): Query; - -// @public (undocumented) -export interface Reference<_Response, Variables> { - // (undocumented) - name: string; - // (undocumented) - refType: ReferenceType; - // (undocumented) - variables: Variables; -} - -// @public (undocumented) -export enum ReferenceType { - // (undocumented) - Mutation = "m", - // (undocumented) - Query = "q" -} - -// @public (undocumented) -export function subscribe(queryRef: Query, onSubscribe: DataConnectSubscription['userCallback'], converter: Converter): void; - -// @public (undocumented) -export interface TransportOptions { - // (undocumented) - host: string; - // (undocumented) - isSecure: boolean; - // (undocumented) - port: number; -} - - -``` diff --git a/docs-devsite/data-connect.authtokenprovider.md b/docs-devsite/data-connect.authtokenprovider.md new file mode 100644 index 00000000000..0dd0a7ae6f4 --- /dev/null +++ b/docs-devsite/data-connect.authtokenprovider.md @@ -0,0 +1,61 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# AuthTokenProvider interface +Signature: + +```typescript +export declare interface AuthTokenProvider +``` + +## Methods + +| Method | Description | +| --- | --- | +| [addTokenChangeListener(listener)](./data-connect.authtokenprovider.md#authtokenprovideraddtokenchangelistener) | | +| [getToken(forceRefresh)](./data-connect.authtokenprovider.md#authtokenprovidergettoken) | | + +## AuthTokenProvider.addTokenChangeListener() + +Signature: + +```typescript +addTokenChangeListener(listener: AuthTokenListener): void; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| listener | [AuthTokenListener](./data-connect.md#authtokenlistener) | | + +Returns: + +void + +## AuthTokenProvider.getToken() + +Signature: + +```typescript +getToken(forceRefresh: boolean): Promise; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| forceRefresh | boolean | | + +Returns: + +Promise<FirebaseAuthTokenData \| null> + diff --git a/docs-devsite/data-connect.cancellableoperation.md b/docs-devsite/data-connect.cancellableoperation.md new file mode 100644 index 00000000000..3c5e89a42c0 --- /dev/null +++ b/docs-devsite/data-connect.cancellableoperation.md @@ -0,0 +1,43 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# CancellableOperation interface +Signature: + +```typescript +export declare interface CancellableOperation extends PromiseLike<{ + data: T; +}> +``` +Extends: PromiseLike<{ data: T; }> + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [cancel](./data-connect.cancellableoperation.md#cancellableoperationcancel) | () => void | | +| [data](./data-connect.cancellableoperation.md#cancellableoperationdata) | T | | + +## CancellableOperation.cancel + +Signature: + +```typescript +cancel: () => void; +``` + +## CancellableOperation.data + +Signature: + +```typescript +data: T; +``` diff --git a/docs-devsite/data-connect.connectorconfig.md b/docs-devsite/data-connect.connectorconfig.md new file mode 100644 index 00000000000..b4998e93a14 --- /dev/null +++ b/docs-devsite/data-connect.connectorconfig.md @@ -0,0 +1,51 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# ConnectorConfig interface +Connector Config for calling Data Connect backend. + +Signature: + +```typescript +export declare interface ConnectorConfig +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [connector](./data-connect.connectorconfig.md#connectorconfigconnector) | string | | +| [location](./data-connect.connectorconfig.md#connectorconfiglocation) | string | | +| [service](./data-connect.connectorconfig.md#connectorconfigservice) | string | | + +## ConnectorConfig.connector + +Signature: + +```typescript +connector: string; +``` + +## ConnectorConfig.location + +Signature: + +```typescript +location: string; +``` + +## ConnectorConfig.service + +Signature: + +```typescript +service: string; +``` diff --git a/docs-devsite/data-connect.dataconnect.md b/docs-devsite/data-connect.dataconnect.md new file mode 100644 index 00000000000..3b72473eae8 --- /dev/null +++ b/docs-devsite/data-connect.dataconnect.md @@ -0,0 +1,124 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# DataConnect class +Class representing Firebase Data Connect + +Signature: + +```typescript +export declare class DataConnect +``` + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)(app, dataConnectOptions, \_authProvider)](./data-connect.dataconnect.md#dataconnectconstructor) | | Constructs a new instance of the DataConnect class | + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [app](./data-connect.dataconnect.md#dataconnectapp) | | [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) | | +| [initialized](./data-connect.dataconnect.md#dataconnectinitialized) | | boolean | | +| [isEmulator](./data-connect.dataconnect.md#dataconnectisemulator) | | boolean | | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [enableEmulator(transportOptions)](./data-connect.dataconnect.md#dataconnectenableemulator) | | | +| [getSettings()](./data-connect.dataconnect.md#dataconnectgetsettings) | | | +| [setInitialized()](./data-connect.dataconnect.md#dataconnectsetinitialized) | | | + +## DataConnect.(constructor) + +Constructs a new instance of the `DataConnect` class + +Signature: + +```typescript +constructor(app: FirebaseApp, dataConnectOptions: DataConnectOptions, _authProvider: Provider); +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| app | [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) | | +| dataConnectOptions | [DataConnectOptions](./data-connect.dataconnectoptions.md#dataconnectoptions_interface) | | +| \_authProvider | Provider<FirebaseAuthInternalName> | | + +## DataConnect.app + +Signature: + +```typescript +readonly app: FirebaseApp; +``` + +## DataConnect.initialized + +Signature: + +```typescript +initialized: boolean; +``` + +## DataConnect.isEmulator + +Signature: + +```typescript +isEmulator: boolean; +``` + +## DataConnect.enableEmulator() + +Signature: + +```typescript +enableEmulator(transportOptions: TransportOptions): void; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| transportOptions | [TransportOptions](./data-connect.transportoptions.md#transportoptions_interface) | | + +Returns: + +void + +## DataConnect.getSettings() + +Signature: + +```typescript +getSettings(): ConnectorConfig; +``` +Returns: + +[ConnectorConfig](./data-connect.connectorconfig.md#connectorconfig_interface) + +## DataConnect.setInitialized() + +Signature: + +```typescript +setInitialized(): void; +``` +Returns: + +void + diff --git a/docs-devsite/data-connect.dataconnectoptions.md b/docs-devsite/data-connect.dataconnectoptions.md new file mode 100644 index 00000000000..8428cb65ca4 --- /dev/null +++ b/docs-devsite/data-connect.dataconnectoptions.md @@ -0,0 +1,34 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# DataConnectOptions interface +DataConnectOptions including project id + +Signature: + +```typescript +export declare interface DataConnectOptions extends ConnectorConfig +``` +Extends: [ConnectorConfig](./data-connect.connectorconfig.md#connectorconfig_interface) + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [projectId](./data-connect.dataconnectoptions.md#dataconnectoptionsprojectid) | string | | + +## DataConnectOptions.projectId + +Signature: + +```typescript +projectId: string; +``` diff --git a/docs-devsite/data-connect.dataconnectresult.md b/docs-devsite/data-connect.dataconnectresult.md new file mode 100644 index 00000000000..788eef76869 --- /dev/null +++ b/docs-devsite/data-connect.dataconnectresult.md @@ -0,0 +1,32 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# DataConnectResult interface +Signature: + +```typescript +export declare interface DataConnectResult extends OpResult +``` +Extends: [OpResult](./data-connect.opresult.md#opresult_interface)<Data> + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [ref](./data-connect.dataconnectresult.md#dataconnectresultref) | [OperationRef](./data-connect.operationref.md#operationref_interface)<Data, Variables> | | + +## DataConnectResult.ref + +Signature: + +```typescript +ref: OperationRef; +``` diff --git a/docs-devsite/data-connect.dataconnectsubscription.md b/docs-devsite/data-connect.dataconnectsubscription.md new file mode 100644 index 00000000000..8eb7d303faa --- /dev/null +++ b/docs-devsite/data-connect.dataconnectsubscription.md @@ -0,0 +1,51 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# DataConnectSubscription interface +Representation of user provided subscription options. + +Signature: + +```typescript +export declare interface DataConnectSubscription +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [errCallback](./data-connect.dataconnectsubscription.md#dataconnectsubscriptionerrcallback) | (e?: [FirebaseError](./util.firebaseerror.md#firebaseerror_class)) => void | | +| [unsubscribe](./data-connect.dataconnectsubscription.md#dataconnectsubscriptionunsubscribe) | () => void | | +| [userCallback](./data-connect.dataconnectsubscription.md#dataconnectsubscriptionusercallback) | [OnResultSubscription](./data-connect.md#onresultsubscription)<Data, Variables> | | + +## DataConnectSubscription.errCallback + +Signature: + +```typescript +errCallback?: (e?: FirebaseError) => void; +``` + +## DataConnectSubscription.unsubscribe + +Signature: + +```typescript +unsubscribe: () => void; +``` + +## DataConnectSubscription.userCallback + +Signature: + +```typescript +userCallback: OnResultSubscription; +``` diff --git a/docs-devsite/data-connect.dataconnecttransport.md b/docs-devsite/data-connect.dataconnecttransport.md new file mode 100644 index 00000000000..f84693e0c2f --- /dev/null +++ b/docs-devsite/data-connect.dataconnecttransport.md @@ -0,0 +1,104 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# DataConnectTransport interface +Signature: + +```typescript +export declare interface DataConnectTransport +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [onTokenChanged](./data-connect.dataconnecttransport.md#dataconnecttransportontokenchanged) | (token: string \| null) => void | | + +## Methods + +| Method | Description | +| --- | --- | +| [invokeMutation(queryName, body)](./data-connect.dataconnecttransport.md#dataconnecttransportinvokemutation) | | +| [invokeQuery(queryName, body)](./data-connect.dataconnecttransport.md#dataconnecttransportinvokequery) | | +| [useEmulator(host, port, sslEnabled)](./data-connect.dataconnecttransport.md#dataconnecttransportuseemulator) | | + +## DataConnectTransport.onTokenChanged + +Signature: + +```typescript +onTokenChanged: (token: string | null) => void; +``` + +## DataConnectTransport.invokeMutation() + +Signature: + +```typescript +invokeMutation(queryName: string, body?: U): PromiseLike<{ + data: T; + errors: Error[]; + }>; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| queryName | string | | +| body | U | | + +Returns: + +PromiseLike<{ data: T; errors: Error\[\]; }> + +## DataConnectTransport.invokeQuery() + +Signature: + +```typescript +invokeQuery(queryName: string, body?: U): PromiseLike<{ + data: T; + errors: Error[]; + }>; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| queryName | string | | +| body | U | | + +Returns: + +PromiseLike<{ data: T; errors: Error\[\]; }> + +## DataConnectTransport.useEmulator() + +Signature: + +```typescript +useEmulator(host: string, port?: number, sslEnabled?: boolean): void; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| host | string | | +| port | number | | +| sslEnabled | boolean | | + +Returns: + +void + diff --git a/docs-devsite/data-connect.firebaseauthprovider.md b/docs-devsite/data-connect.firebaseauthprovider.md new file mode 100644 index 00000000000..27d2a7be277 --- /dev/null +++ b/docs-devsite/data-connect.firebaseauthprovider.md @@ -0,0 +1,105 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# FirebaseAuthProvider class +Signature: + +```typescript +export declare class FirebaseAuthProvider implements AuthTokenProvider +``` +Implements: [AuthTokenProvider](./data-connect.authtokenprovider.md#authtokenprovider_interface) + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)(\_appName, \_options, \_authProvider)](./data-connect.firebaseauthprovider.md#firebaseauthproviderconstructor) | | Constructs a new instance of the FirebaseAuthProvider class | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [addTokenChangeListener(listener)](./data-connect.firebaseauthprovider.md#firebaseauthprovideraddtokenchangelistener) | | | +| [getToken(forceRefresh)](./data-connect.firebaseauthprovider.md#firebaseauthprovidergettoken) | | | +| [removeTokenChangeListener(listener)](./data-connect.firebaseauthprovider.md#firebaseauthproviderremovetokenchangelistener) | | | + +## FirebaseAuthProvider.(constructor) + +Constructs a new instance of the `FirebaseAuthProvider` class + +Signature: + +```typescript +constructor(_appName: string, _options: FirebaseOptions, _authProvider: Provider); +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| \_appName | string | | +| \_options | [FirebaseOptions](./app.firebaseoptions.md#firebaseoptions_interface) | | +| \_authProvider | Provider<FirebaseAuthInternalName> | | + +## FirebaseAuthProvider.addTokenChangeListener() + +Signature: + +```typescript +addTokenChangeListener(listener: AuthTokenListener): void; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| listener | [AuthTokenListener](./data-connect.md#authtokenlistener) | | + +Returns: + +void + +## FirebaseAuthProvider.getToken() + +Signature: + +```typescript +getToken(forceRefresh: boolean): Promise; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| forceRefresh | boolean | | + +Returns: + +Promise<FirebaseAuthTokenData \| null> + +## FirebaseAuthProvider.removeTokenChangeListener() + +Signature: + +```typescript +removeTokenChangeListener(listener: (token: string | null) => void): void; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| listener | (token: string \| null) => void | | + +Returns: + +void + diff --git a/docs-devsite/data-connect.md b/docs-devsite/data-connect.md new file mode 100644 index 00000000000..36603f6dcd7 --- /dev/null +++ b/docs-devsite/data-connect.md @@ -0,0 +1,536 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# data-connect package +Firebase Data Connect + +## Functions + +| Function | Description | +| --- | --- | +| function(app, ...) | +| [getDataConnect(app, options)](./data-connect.md#getdataconnect_4a1329a) | Initialize DataConnect instance | +| function(dataConnect, ...) | +| [terminate(dataConnect)](./data-connect.md#terminate_43d8d88) | Delete DataConnect instance | +| function(dc, ...) | +| [connectDataConnectEmulator(dc, host, port, sslEnabled)](./data-connect.md#connectdataconnectemulator_842a91d) | Connect to the DataConnect Emulator | +| function(dcInstance, ...) | +| [mutationRef(dcInstance, mutationName)](./data-connect.md#mutationref_48ae5fa) | Creates a MutationRef | +| [mutationRef(dcInstance, mutationName, variables)](./data-connect.md#mutationref_1511672) | | +| [queryRef(dcInstance, queryName)](./data-connect.md#queryref_d968e5d) | Execute Query | +| [queryRef(dcInstance, queryName, variables)](./data-connect.md#queryref_6179f74) | Execute Query | +| function(logLevel, ...) | +| [setLogLevel(logLevel)](./data-connect.md#setloglevel_697d53a) | | +| function(mutationRef, ...) | +| [executeMutation(mutationRef)](./data-connect.md#executemutation_e8103b9) | Execute Mutation | +| function(options, ...) | +| [getDataConnect(options)](./data-connect.md#getdataconnect_f991922) | Initialize DataConnect instance | +| function(queryRef, ...) | +| [executeQuery(queryRef)](./data-connect.md#executequery_692a270) | Execute Query | +| function(queryRefOrSerializedResult, ...) | +| [subscribe(queryRefOrSerializedResult, observer)](./data-connect.md#subscribe_b2b4b07) | Subscribe to a QueryRef | +| [subscribe(queryRefOrSerializedResult, onNext, onError, onComplete)](./data-connect.md#subscribe_c02958e) | Subscribe to a QueryRef | +| function(serializedRef, ...) | +| [toQueryRef(serializedRef)](./data-connect.md#toqueryref_2c70e31) | Converts serialized ref to query ref | + +## Classes + +| Class | Description | +| --- | --- | +| [DataConnect](./data-connect.dataconnect.md#dataconnect_class) | Class representing Firebase Data Connect | +| [FirebaseAuthProvider](./data-connect.firebaseauthprovider.md#firebaseauthprovider_class) | | + +## Interfaces + +| Interface | Description | +| --- | --- | +| [AuthTokenProvider](./data-connect.authtokenprovider.md#authtokenprovider_interface) | | +| [CancellableOperation](./data-connect.cancellableoperation.md#cancellableoperation_interface) | | +| [ConnectorConfig](./data-connect.connectorconfig.md#connectorconfig_interface) | Connector Config for calling Data Connect backend. | +| [DataConnectOptions](./data-connect.dataconnectoptions.md#dataconnectoptions_interface) | DataConnectOptions including project id | +| [DataConnectResult](./data-connect.dataconnectresult.md#dataconnectresult_interface) | | +| [DataConnectSubscription](./data-connect.dataconnectsubscription.md#dataconnectsubscription_interface) | Representation of user provided subscription options. | +| [DataConnectTransport](./data-connect.dataconnecttransport.md#dataconnecttransport_interface) | | +| [MutationPromise](./data-connect.mutationpromise.md#mutationpromise_interface) | Mutation return value from executeMutation | +| [MutationRef](./data-connect.mutationref.md#mutationref_interface) | | +| [MutationResponse](./data-connect.mutationresponse.md#mutationresponse_interface) | | +| [MutationResult](./data-connect.mutationresult.md#mutationresult_interface) | Mutation Result from executeMutation | +| [OperationRef](./data-connect.operationref.md#operationref_interface) | | +| [OpResult](./data-connect.opresult.md#opresult_interface) | | +| [QueryPromise](./data-connect.querypromise.md#querypromise_interface) | Promise returned from executeQuery | +| [QueryRef](./data-connect.queryref.md#queryref_interface) | QueryRef object | +| [QueryResponse](./data-connect.queryresponse.md#queryresponse_interface) | | +| [QueryResult](./data-connect.queryresult.md#queryresult_interface) | Result of executeQuery | +| [RefInfo](./data-connect.refinfo.md#refinfo_interface) | Serialized RefInfo as a result of QueryResult.toJSON().refInfo | +| [Sender](./data-connect.sender.md#sender_interface) | | +| [SerializedRef](./data-connect.serializedref.md#serializedref_interface) | Serialized Ref as a result of QueryResult.toJSON() | +| [SubscriptionOptions](./data-connect.subscriptionoptions.md#subscriptionoptions_interface) | Representation of full observer options in subscribe | +| [TransportOptions](./data-connect.transportoptions.md#transportoptions_interface) | Options to connect to emulator | + +## Variables + +| Variable | Description | +| --- | --- | +| [FIREBASE\_DATA\_CONNECT\_EMULATOR\_HOST\_VAR](./data-connect.md#firebase_data_connect_emulator_host_var) | | +| [MUTATION\_STR](./data-connect.md#mutation_str) | | +| [QUERY\_STR](./data-connect.md#query_str) | | +| [SOURCE\_CACHE](./data-connect.md#source_cache) | | +| [SOURCE\_SERVER](./data-connect.md#source_server) | | + +## Type Aliases + +| Type Alias | Description | +| --- | --- | +| [AuthTokenListener](./data-connect.md#authtokenlistener) | | +| [DataSource](./data-connect.md#datasource) | | +| [OnCompleteSubscription](./data-connect.md#oncompletesubscription) | OnCompleteSubscription | +| [OnErrorSubscription](./data-connect.md#onerrorsubscription) | Signature for OnErrorSubscription for subscribe | +| [OnResultSubscription](./data-connect.md#onresultsubscription) | Signature for OnResultSubscription for subscribe | +| [QueryUnsubscribe](./data-connect.md#queryunsubscribe) | Signature for unsubscribe from subscribe | +| [ReferenceType](./data-connect.md#referencetype) | | +| [TransportClass](./data-connect.md#transportclass) | | + +## function(app, ...) + +### getDataConnect(app, options) {:#getdataconnect_4a1329a} + +Initialize DataConnect instance + +Signature: + +```typescript +export declare function getDataConnect(app: FirebaseApp, options: ConnectorConfig): DataConnect; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| app | [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) | FirebaseApp to initialize to. | +| options | [ConnectorConfig](./data-connect.connectorconfig.md#connectorconfig_interface) | ConnectorConfig | + +Returns: + +[DataConnect](./data-connect.dataconnect.md#dataconnect_class) + +## function(dataConnect, ...) + +### terminate(dataConnect) {:#terminate_43d8d88} + +Delete DataConnect instance + +Signature: + +```typescript +export declare function terminate(dataConnect: DataConnect): Promise; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| dataConnect | [DataConnect](./data-connect.dataconnect.md#dataconnect_class) | DataConnect instance | + +Returns: + +Promise<void> + + +## function(dc, ...) + +### connectDataConnectEmulator(dc, host, port, sslEnabled) {:#connectdataconnectemulator_842a91d} + +Connect to the DataConnect Emulator + +Signature: + +```typescript +export declare function connectDataConnectEmulator(dc: DataConnect, host: string, port?: number, sslEnabled?: boolean): void; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| dc | [DataConnect](./data-connect.dataconnect.md#dataconnect_class) | Data Connect instance | +| host | string | host of emulator server | +| port | number | port of emulator server | +| sslEnabled | boolean | use https | + +Returns: + +void + +## function(dcInstance, ...) + +### mutationRef(dcInstance, mutationName) {:#mutationref_48ae5fa} + +Creates a `MutationRef` + +Signature: + +```typescript +export declare function mutationRef(dcInstance: DataConnect, mutationName: string): MutationRef; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| dcInstance | [DataConnect](./data-connect.dataconnect.md#dataconnect_class) | Data Connect instance | +| mutationName | string | name of mutation | + +Returns: + +[MutationRef](./data-connect.mutationref.md#mutationref_interface)<Data, undefined> + +### mutationRef(dcInstance, mutationName, variables) {:#mutationref_1511672} + +Signature: + +```typescript +export declare function mutationRef(dcInstance: DataConnect, mutationName: string, variables: Variables): MutationRef; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| dcInstance | [DataConnect](./data-connect.dataconnect.md#dataconnect_class) | Data Connect instance | +| mutationName | string | name of mutation | +| variables | Variables | variables to send with mutation | + +Returns: + +[MutationRef](./data-connect.mutationref.md#mutationref_interface)<Data, Variables> + +### queryRef(dcInstance, queryName) {:#queryref_d968e5d} + +Execute Query + +Signature: + +```typescript +export declare function queryRef(dcInstance: DataConnect, queryName: string): QueryRef; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| dcInstance | [DataConnect](./data-connect.dataconnect.md#dataconnect_class) | Data Connect instance to use. | +| queryName | string | Query to execute | + +Returns: + +[QueryRef](./data-connect.queryref.md#queryref_interface)<Data, undefined> + +`QueryRef` + +### queryRef(dcInstance, queryName, variables) {:#queryref_6179f74} + +Execute Query + +Signature: + +```typescript +export declare function queryRef(dcInstance: DataConnect, queryName: string, variables: Variables): QueryRef; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| dcInstance | [DataConnect](./data-connect.dataconnect.md#dataconnect_class) | Data Connect instance to use. | +| queryName | string | Query to execute | +| variables | Variables | Variables to execute with | + +Returns: + +[QueryRef](./data-connect.queryref.md#queryref_interface)<Data, Variables> + +`QueryRef` + +## function(logLevel, ...) + +### setLogLevel(logLevel) {:#setloglevel_697d53a} + +Signature: + +```typescript +export declare function setLogLevel(logLevel: LogLevelString): void; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| logLevel | LogLevelString | | + +Returns: + +void + +## function(mutationRef, ...) + +### executeMutation(mutationRef) {:#executemutation_e8103b9} + +Execute Mutation + +Signature: + +```typescript +export declare function executeMutation(mutationRef: MutationRef): MutationPromise; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| mutationRef | [MutationRef](./data-connect.mutationref.md#mutationref_interface)<Data, Variables> | mutation to execute | + +Returns: + +[MutationPromise](./data-connect.mutationpromise.md#mutationpromise_interface)<Data, Variables> + +`MutationRef` + +## function(options, ...) + +### getDataConnect(options) {:#getdataconnect_f991922} + +Initialize DataConnect instance + +Signature: + +```typescript +export declare function getDataConnect(options: ConnectorConfig): DataConnect; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| options | [ConnectorConfig](./data-connect.connectorconfig.md#connectorconfig_interface) | ConnectorConfig | + +Returns: + +[DataConnect](./data-connect.dataconnect.md#dataconnect_class) + +## function(queryRef, ...) + +### executeQuery(queryRef) {:#executequery_692a270} + +Execute Query + +Signature: + +```typescript +export declare function executeQuery(queryRef: QueryRef): QueryPromise; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| queryRef | [QueryRef](./data-connect.queryref.md#queryref_interface)<Data, Variables> | query to execute. | + +Returns: + +[QueryPromise](./data-connect.querypromise.md#querypromise_interface)<Data, Variables> + +`QueryPromise` + +## function(queryRefOrSerializedResult, ...) + +### subscribe(queryRefOrSerializedResult, observer) {:#subscribe_b2b4b07} + +Subscribe to a `QueryRef` + +Signature: + +```typescript +export declare function subscribe(queryRefOrSerializedResult: QueryRef | SerializedRef, observer: SubscriptionOptions): QueryUnsubscribe; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| queryRefOrSerializedResult | [QueryRef](./data-connect.queryref.md#queryref_interface)<Data, Variables> \| [SerializedRef](./data-connect.serializedref.md#serializedref_interface)<Data, Variables> | query ref or serialized result. | +| observer | [SubscriptionOptions](./data-connect.subscriptionoptions.md#subscriptionoptions_interface)<Data, Variables> | observer object to use for subscribing. | + +Returns: + +[QueryUnsubscribe](./data-connect.md#queryunsubscribe) + +`SubscriptionOptions` + +### subscribe(queryRefOrSerializedResult, onNext, onError, onComplete) {:#subscribe_c02958e} + +Subscribe to a `QueryRef` + +Signature: + +```typescript +export declare function subscribe(queryRefOrSerializedResult: QueryRef | SerializedRef, onNext: OnResultSubscription, onError?: OnErrorSubscription, onComplete?: OnCompleteSubscription): QueryUnsubscribe; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| queryRefOrSerializedResult | [QueryRef](./data-connect.queryref.md#queryref_interface)<Data, Variables> \| [SerializedRef](./data-connect.serializedref.md#serializedref_interface)<Data, Variables> | query ref or serialized result. | +| onNext | [OnResultSubscription](./data-connect.md#onresultsubscription)<Data, Variables> | Callback to call when result comes back. | +| onError | [OnErrorSubscription](./data-connect.md#onerrorsubscription) | Callback to call when error gets thrown. | +| onComplete | [OnCompleteSubscription](./data-connect.md#oncompletesubscription) | Called when subscription completes. | + +Returns: + +[QueryUnsubscribe](./data-connect.md#queryunsubscribe) + +`SubscriptionOptions` + +## function(serializedRef, ...) + +### toQueryRef(serializedRef) {:#toqueryref_2c70e31} + +Converts serialized ref to query ref + +Signature: + +```typescript +export declare function toQueryRef(serializedRef: SerializedRef): QueryRef; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| serializedRef | [SerializedRef](./data-connect.serializedref.md#serializedref_interface)<Data, Variables> | ref to convert to QueryRef | + +Returns: + +[QueryRef](./data-connect.queryref.md#queryref_interface)<unknown, Variables> + +`QueryRef` + +## FIREBASE\_DATA\_CONNECT\_EMULATOR\_HOST\_VAR + +Signature: + +```typescript +FIREBASE_DATA_CONNECT_EMULATOR_HOST_VAR = "FIREBASE_DATA_CONNECT_EMULATOR_HOST" +``` + +## MUTATION\_STR + +Signature: + +```typescript +MUTATION_STR = "mutation" +``` + +## QUERY\_STR + +Signature: + +```typescript +QUERY_STR = "query" +``` + +## SOURCE\_CACHE + +Signature: + +```typescript +SOURCE_CACHE = "CACHE" +``` + +## SOURCE\_SERVER + +Signature: + +```typescript +SOURCE_SERVER = "SERVER" +``` + +## AuthTokenListener + +Signature: + +```typescript +export declare type AuthTokenListener = (token: string | null) => void; +``` + +## DataSource + +Signature: + +```typescript +export declare type DataSource = typeof SOURCE_CACHE | typeof SOURCE_SERVER; +``` + +## OnCompleteSubscription + +`OnCompleteSubscription` + +Signature: + +```typescript +export declare type OnCompleteSubscription = () => void; +``` + +## OnErrorSubscription + +Signature for `OnErrorSubscription` for `subscribe` + +Signature: + +```typescript +export declare type OnErrorSubscription = (err?: FirebaseError) => void; +``` + +## OnResultSubscription + +Signature for `OnResultSubscription` for `subscribe` + +Signature: + +```typescript +export declare type OnResultSubscription = (res: QueryResult) => void; +``` + +## QueryUnsubscribe + +Signature for unsubscribe from `subscribe` + +Signature: + +```typescript +export declare type QueryUnsubscribe = () => void; +``` + +## ReferenceType + +Signature: + +```typescript +export declare type ReferenceType = typeof QUERY_STR | typeof MUTATION_STR; +``` + +## TransportClass + +Signature: + +```typescript +export declare type TransportClass = new (options: DataConnectOptions, apiKey?: string, authProvider?: AuthTokenProvider, transportOptions?: TransportOptions) => DataConnectTransport; +``` diff --git a/docs-devsite/data-connect.mutationpromise.md b/docs-devsite/data-connect.mutationpromise.md new file mode 100644 index 00000000000..60624870c1d --- /dev/null +++ b/docs-devsite/data-connect.mutationpromise.md @@ -0,0 +1,21 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# MutationPromise interface +Mutation return value from `executeMutation` + +Signature: + +```typescript +export declare interface MutationPromise extends PromiseLike> +``` +Extends: PromiseLike<[MutationResult](./data-connect.mutationresult.md#mutationresult_interface)<Data, Variables>> + diff --git a/docs-devsite/data-connect.mutationref.md b/docs-devsite/data-connect.mutationref.md new file mode 100644 index 00000000000..0cf5a7185c4 --- /dev/null +++ b/docs-devsite/data-connect.mutationref.md @@ -0,0 +1,32 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# MutationRef interface +Signature: + +```typescript +export declare interface MutationRef extends OperationRef +``` +Extends: [OperationRef](./data-connect.operationref.md#operationref_interface)<Data, Variables> + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [refType](./data-connect.mutationref.md#mutationrefreftype) | typeof [MUTATION\_STR](./data-connect.md#mutation_str) | | + +## MutationRef.refType + +Signature: + +```typescript +refType: typeof MUTATION_STR; +``` diff --git a/docs-devsite/data-connect.mutationresponse.md b/docs-devsite/data-connect.mutationresponse.md new file mode 100644 index 00000000000..0b018d140ac --- /dev/null +++ b/docs-devsite/data-connect.mutationresponse.md @@ -0,0 +1,19 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# MutationResponse interface +Signature: + +```typescript +export declare interface MutationResponse extends CancellableOperation +``` +Extends: [CancellableOperation](./data-connect.cancellableoperation.md#cancellableoperation_interface)<T> + diff --git a/docs-devsite/data-connect.mutationresult.md b/docs-devsite/data-connect.mutationresult.md new file mode 100644 index 00000000000..94067123cff --- /dev/null +++ b/docs-devsite/data-connect.mutationresult.md @@ -0,0 +1,34 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# MutationResult interface +Mutation Result from `executeMutation` + +Signature: + +```typescript +export declare interface MutationResult extends DataConnectResult +``` +Extends: [DataConnectResult](./data-connect.dataconnectresult.md#dataconnectresult_interface)<Data, Variables> + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [ref](./data-connect.mutationresult.md#mutationresultref) | [MutationRef](./data-connect.mutationref.md#mutationref_interface)<Data, Variables> | | + +## MutationResult.ref + +Signature: + +```typescript +ref: MutationRef; +``` diff --git a/docs-devsite/data-connect.operationref.md b/docs-devsite/data-connect.operationref.md new file mode 100644 index 00000000000..e73d249fbda --- /dev/null +++ b/docs-devsite/data-connect.operationref.md @@ -0,0 +1,58 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# OperationRef interface +Signature: + +```typescript +export declare interface OperationRef<_Data, Variables> +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [dataConnect](./data-connect.operationref.md#operationrefdataconnect) | [DataConnect](./data-connect.dataconnect.md#dataconnect_class) | | +| [name](./data-connect.operationref.md#operationrefname) | string | | +| [refType](./data-connect.operationref.md#operationrefreftype) | [ReferenceType](./data-connect.md#referencetype) | | +| [variables](./data-connect.operationref.md#operationrefvariables) | Variables | | + +## OperationRef.dataConnect + +Signature: + +```typescript +dataConnect: DataConnect; +``` + +## OperationRef.name + +Signature: + +```typescript +name: string; +``` + +## OperationRef.refType + +Signature: + +```typescript +refType: ReferenceType; +``` + +## OperationRef.variables + +Signature: + +```typescript +variables: Variables; +``` diff --git a/docs-devsite/data-connect.opresult.md b/docs-devsite/data-connect.opresult.md new file mode 100644 index 00000000000..1f0a4c2c0f2 --- /dev/null +++ b/docs-devsite/data-connect.opresult.md @@ -0,0 +1,49 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# OpResult interface +Signature: + +```typescript +export declare interface OpResult +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [data](./data-connect.opresult.md#opresultdata) | Data | | +| [fetchTime](./data-connect.opresult.md#opresultfetchtime) | string | | +| [source](./data-connect.opresult.md#opresultsource) | [DataSource](./data-connect.md#datasource) | | + +## OpResult.data + +Signature: + +```typescript +data: Data; +``` + +## OpResult.fetchTime + +Signature: + +```typescript +fetchTime: string; +``` + +## OpResult.source + +Signature: + +```typescript +source: DataSource; +``` diff --git a/docs-devsite/data-connect.querypromise.md b/docs-devsite/data-connect.querypromise.md new file mode 100644 index 00000000000..27dc6204260 --- /dev/null +++ b/docs-devsite/data-connect.querypromise.md @@ -0,0 +1,21 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# QueryPromise interface +Promise returned from `executeQuery` + +Signature: + +```typescript +export declare interface QueryPromise extends PromiseLike> +``` +Extends: PromiseLike<[QueryResult](./data-connect.queryresult.md#queryresult_interface)<Data, Variables>> + diff --git a/docs-devsite/data-connect.queryref.md b/docs-devsite/data-connect.queryref.md new file mode 100644 index 00000000000..2e67c884016 --- /dev/null +++ b/docs-devsite/data-connect.queryref.md @@ -0,0 +1,34 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# QueryRef interface +QueryRef object + +Signature: + +```typescript +export declare interface QueryRef extends OperationRef +``` +Extends: [OperationRef](./data-connect.operationref.md#operationref_interface)<Data, Variables> + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [refType](./data-connect.queryref.md#queryrefreftype) | typeof [QUERY\_STR](./data-connect.md#query_str) | | + +## QueryRef.refType + +Signature: + +```typescript +refType: typeof QUERY_STR; +``` diff --git a/docs-devsite/data-connect.queryresponse.md b/docs-devsite/data-connect.queryresponse.md new file mode 100644 index 00000000000..0c1047339f6 --- /dev/null +++ b/docs-devsite/data-connect.queryresponse.md @@ -0,0 +1,19 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# QueryResponse interface +Signature: + +```typescript +export declare interface QueryResponse extends CancellableOperation +``` +Extends: [CancellableOperation](./data-connect.cancellableoperation.md#cancellableoperation_interface)<T> + diff --git a/docs-devsite/data-connect.queryresult.md b/docs-devsite/data-connect.queryresult.md new file mode 100644 index 00000000000..ac0380e2002 --- /dev/null +++ b/docs-devsite/data-connect.queryresult.md @@ -0,0 +1,43 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# QueryResult interface +Result of `executeQuery` + +Signature: + +```typescript +export declare interface QueryResult extends DataConnectResult +``` +Extends: [DataConnectResult](./data-connect.dataconnectresult.md#dataconnectresult_interface)<Data, Variables> + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [ref](./data-connect.queryresult.md#queryresultref) | [QueryRef](./data-connect.queryref.md#queryref_interface)<Data, Variables> | | +| [toJSON](./data-connect.queryresult.md#queryresulttojson) | () => [SerializedRef](./data-connect.serializedref.md#serializedref_interface)<Data, Variables> | | + +## QueryResult.ref + +Signature: + +```typescript +ref: QueryRef; +``` + +## QueryResult.toJSON + +Signature: + +```typescript +toJSON: () => SerializedRef; +``` diff --git a/docs-devsite/data-connect.refinfo.md b/docs-devsite/data-connect.refinfo.md new file mode 100644 index 00000000000..6114fc0a1fc --- /dev/null +++ b/docs-devsite/data-connect.refinfo.md @@ -0,0 +1,51 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# RefInfo interface +Serialized RefInfo as a result of `QueryResult.toJSON().refInfo` + +Signature: + +```typescript +export declare interface RefInfo +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [connectorConfig](./data-connect.refinfo.md#refinfoconnectorconfig) | [DataConnectOptions](./data-connect.dataconnectoptions.md#dataconnectoptions_interface) | | +| [name](./data-connect.refinfo.md#refinfoname) | string | | +| [variables](./data-connect.refinfo.md#refinfovariables) | Variables | | + +## RefInfo.connectorConfig + +Signature: + +```typescript +connectorConfig: DataConnectOptions; +``` + +## RefInfo.name + +Signature: + +```typescript +name: string; +``` + +## RefInfo.variables + +Signature: + +```typescript +variables: Variables; +``` diff --git a/docs-devsite/data-connect.sender.md b/docs-devsite/data-connect.sender.md new file mode 100644 index 00000000000..64bab9156fe --- /dev/null +++ b/docs-devsite/data-connect.sender.md @@ -0,0 +1,40 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# Sender interface +Signature: + +```typescript +export declare interface Sender +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [abort](./data-connect.sender.md#senderabort) | () => void | | +| [send](./data-connect.sender.md#sendersend) | () => Promise<T> | | + +## Sender.abort + +Signature: + +```typescript +abort: () => void; +``` + +## Sender.send + +Signature: + +```typescript +send: () => Promise; +``` diff --git a/docs-devsite/data-connect.serializedref.md b/docs-devsite/data-connect.serializedref.md new file mode 100644 index 00000000000..f93c94d722d --- /dev/null +++ b/docs-devsite/data-connect.serializedref.md @@ -0,0 +1,34 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# SerializedRef interface +Serialized Ref as a result of `QueryResult.toJSON()` + +Signature: + +```typescript +export declare interface SerializedRef extends OpResult +``` +Extends: [OpResult](./data-connect.opresult.md#opresult_interface)<Data> + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [refInfo](./data-connect.serializedref.md#serializedrefrefinfo) | [RefInfo](./data-connect.refinfo.md#refinfo_interface)<Variables> | | + +## SerializedRef.refInfo + +Signature: + +```typescript +refInfo: RefInfo; +``` diff --git a/docs-devsite/data-connect.subscriptionoptions.md b/docs-devsite/data-connect.subscriptionoptions.md new file mode 100644 index 00000000000..d666f5bb380 --- /dev/null +++ b/docs-devsite/data-connect.subscriptionoptions.md @@ -0,0 +1,51 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# SubscriptionOptions interface +Representation of full observer options in `subscribe` + +Signature: + +```typescript +export declare interface SubscriptionOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [onComplete](./data-connect.subscriptionoptions.md#subscriptionoptionsoncomplete) | [OnCompleteSubscription](./data-connect.md#oncompletesubscription) | | +| [onErr](./data-connect.subscriptionoptions.md#subscriptionoptionsonerr) | [OnErrorSubscription](./data-connect.md#onerrorsubscription) | | +| [onNext](./data-connect.subscriptionoptions.md#subscriptionoptionsonnext) | [OnResultSubscription](./data-connect.md#onresultsubscription)<Data, Variables> | | + +## SubscriptionOptions.onComplete + +Signature: + +```typescript +onComplete?: OnCompleteSubscription; +``` + +## SubscriptionOptions.onErr + +Signature: + +```typescript +onErr?: OnErrorSubscription; +``` + +## SubscriptionOptions.onNext + +Signature: + +```typescript +onNext?: OnResultSubscription; +``` diff --git a/docs-devsite/data-connect.transportoptions.md b/docs-devsite/data-connect.transportoptions.md new file mode 100644 index 00000000000..dbff25dc5d1 --- /dev/null +++ b/docs-devsite/data-connect.transportoptions.md @@ -0,0 +1,51 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# TransportOptions interface +Options to connect to emulator + +Signature: + +```typescript +export declare interface TransportOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [host](./data-connect.transportoptions.md#transportoptionshost) | string | | +| [port](./data-connect.transportoptions.md#transportoptionsport) | number | | +| [sslEnabled](./data-connect.transportoptions.md#transportoptionssslenabled) | boolean | | + +## TransportOptions.host + +Signature: + +```typescript +host: string; +``` + +## TransportOptions.port + +Signature: + +```typescript +port?: number; +``` + +## TransportOptions.sslEnabled + +Signature: + +```typescript +sslEnabled?: boolean; +``` diff --git a/packages/data-connect/karma.conf.js b/packages/data-connect/karma.conf.js new file mode 100644 index 00000000000..d51e08d046e --- /dev/null +++ b/packages/data-connect/karma.conf.js @@ -0,0 +1,34 @@ +/** + * @license + * Copyright 2017 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. + */ + +const karmaBase = require('../../config/karma.base'); + +const files = [`test/**/*.test.ts`]; + +module.exports = function (config) { + const karmaConfig = Object.assign({}, karmaBase, { + // files to load into karma + files: files, + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['mocha'] + }); + + config.set(karmaConfig); +}; + +module.exports.files = files; diff --git a/packages/database/src/api/Reference_impl.ts b/packages/database/src/api/Reference_impl.ts index 53d4577368a..7870bf93e53 100644 --- a/packages/database/src/api/Reference_impl.ts +++ b/packages/database/src/api/Reference_impl.ts @@ -1985,7 +1985,6 @@ class QueryLimitToLastConstraint extends QueryConstraint { constructor(private readonly _limit: number) { super(); - console.log(this.type); } _apply(query: QueryImpl): QueryImpl { diff --git a/packages/database/test/exp/integration.test.ts b/packages/database/test/exp/integration.test.ts index ae82bd913f6..adf5094f222 100644 --- a/packages/database/test/exp/integration.test.ts +++ b/packages/database/test/exp/integration.test.ts @@ -27,13 +27,12 @@ import { onChildAdded, onValue, orderByChild, + query, refFromURL, set, startAt, update, - orderByKey, - query, - limitToLast + orderByKey } from '../../src/api/Reference_impl'; import { getDatabase, @@ -61,7 +60,7 @@ export function createTestApp() { } // Note: these run in parallel with the node environment. If you use the same paths in parallel, you may experience race conditions. -describe.only('Database@exp Tests', () => { +describe('Database@exp Tests', () => { let defaultApp; beforeEach(() => { @@ -78,27 +77,6 @@ describe.only('Database@exp Tests', () => { const db = getDatabase(defaultApp); expect(db).to.be.ok; }); - it.only('can use `toString` as parameter to refFromUrl', () => { - const app2 = initializeApp( - { - apiKey: 'AIzaSyAVya8asBa3HkmWFDG2SdNuljlcgTGqMq4', - authDomain: 'movie-picker-729bb.firebaseapp.com', - databaseURL: - 'https://movie-picker-729bb.europe-west1.firebasedatabase.app/', - projectId: 'movie-picker-729bb', - storageBucket: 'movie-picker-729bb.appspot.com', - messagingSenderId: '592198782208', - appId: '1:592198782208:web:cc1d88c2a2d53b20ea77e5' - }, - 'app2' - ); - const db = getDatabase(app2); - const q = ref(db, 'posts'); - const url = q.toString(); - const r = refFromURL(db, url); - const limitConstraint = limitToLast(100); - console.log(limitConstraint); - }); it("doesn't try to connect to emulator after database has already started", async () => { const db = getDatabase(defaultApp); const r = ref(db, '.info/connected'); @@ -168,7 +146,6 @@ describe.only('Database@exp Tests', () => { await set(root, {}); const q = query(root, orderByChild('testIndex'), limitToFirst(2)); - console.log(q.toString()); const i1 = child(root, 'i1'); await set(root, { From 34260394ba287a9f4406e572b8d6ab507fddf2db Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Fri, 12 Jul 2024 16:39:46 -0700 Subject: [PATCH 22/74] Validated whether connector information is passed in correctly --- packages/data-connect/package.json | 1 + packages/data-connect/src/api/DataConnect.ts | 20 ++++++++++++--- packages/data-connect/src/register.ts | 4 +++ .../test/unit/dataconnect.test.ts | 25 +++++++++++++++++++ 4 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 packages/data-connect/test/unit/dataconnect.test.ts diff --git a/packages/data-connect/package.json b/packages/data-connect/package.json index ceb9a93b0d5..321ef678a40 100644 --- a/packages/data-connect/package.json +++ b/packages/data-connect/package.json @@ -38,6 +38,7 @@ "test:all": "npm run test:node", "test:browser": "karma start --single-run", "test:node": "TS_NODE_FILES=true TS_NODE_CACHE=NO TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha 'test/{,!(browser)/**/}*.test.ts' --file src/index.node.ts --config ../../config/mocharc.node.js", + "test:unit": "TS_NODE_FILES=true TS_NODE_CACHE=NO TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha 'test/unit/**/*.test.ts' --file src/index.node.ts --config ../../config/mocharc.node.js", "test:emulator": "ts-node --compiler-options='{\"module\":\"commonjs\"}' ../../scripts/emulator-testing/dataconnect-test-runner.ts", "api-report": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' ts-node ../../repo-scripts/prune-dts/extract-public-api.ts --package data-connect --packageRoot . --typescriptDts ./dist/src/index.d.ts --rollupDts ./dist/private.d.ts --untrimmedRollupDts ./dist/internal.d.ts --publicDts ./dist/public.d.ts && yarn api-report:api-json", "api-report:api-json": "rm -rf temp && api-extractor run --local --verbose", diff --git a/packages/data-connect/src/api/DataConnect.ts b/packages/data-connect/src/api/DataConnect.ts index 2e3cd0157ea..b53f8984c58 100644 --- a/packages/data-connect/src/api/DataConnect.ts +++ b/packages/data-connect/src/api/DataConnect.ts @@ -210,7 +210,7 @@ export function getDataConnect( app = appOrOptions; } - if (!app) { + if (!app || Object.keys(app).length === 0) { app = getApp(); } const provider = _getProvider(app, 'data-connect'); @@ -224,9 +224,8 @@ export function getDataConnect( return dcInstance; } } - if (!dcOptions) { - throw new DataConnectError(Code.INVALID_ARGUMENT, 'DC Option Required'); - } + validateDCOptions(dcOptions); + logDebug('Creating new DataConnect instance'); // Initialize with options. return provider.initialize({ @@ -235,6 +234,19 @@ export function getDataConnect( }); } +export function validateDCOptions(dcOptions: ConnectorConfig) { + const fields = ['connector', 'location', 'service']; + if (!dcOptions) { + throw new DataConnectError(Code.INVALID_ARGUMENT, 'DC Option Required'); + } + fields.forEach(field => { + if (dcOptions[field] === null || dcOptions[field] === undefined) { + throw new DataConnectError(Code.INVALID_ARGUMENT, `${field} Required`); + } + }); + return true; +} + /** * Delete DataConnect instance * @param dataConnect DataConnect instance diff --git a/packages/data-connect/src/register.ts b/packages/data-connect/src/register.ts index eaa0e7bbacd..bc8342c3a4c 100644 --- a/packages/data-connect/src/register.ts +++ b/packages/data-connect/src/register.ts @@ -26,6 +26,7 @@ import { name, version } from '../package.json'; import { setSDKVersion } from '../src/core/version'; import { DataConnect, ConnectorConfig } from './api/DataConnect'; +import { Code, DataConnectError } from './core/error'; export function registerDataConnect(variant?: string): void { setSDKVersion(SDK_VERSION); @@ -39,6 +40,9 @@ export function registerDataConnect(variant?: string): void { if (settings) { newOpts = JSON.parse(settings); } + if(!app.options.projectId) { + throw new DataConnectError(Code.INVALID_ARGUMENT, "Project ID must be provided. Did you pass in a proper projectId to initializeApp?"); + } return new DataConnect( app, { ...newOpts, projectId: app.options.projectId! }, diff --git a/packages/data-connect/test/unit/dataconnect.test.ts b/packages/data-connect/test/unit/dataconnect.test.ts new file mode 100644 index 00000000000..ba202a0b1ca --- /dev/null +++ b/packages/data-connect/test/unit/dataconnect.test.ts @@ -0,0 +1,25 @@ +import { deleteApp, initializeApp } from "@firebase/app"; +import { ConnectorConfig, getDataConnect } from "../../src"; +import { expect } from "chai"; + +describe('Data Connect Test', () => { + it('should throw an error if `projectId` is not provided', () => { + const app = initializeApp({}); + expect(() => getDataConnect({ connector: 'c', location: 'l', service: 's'})).to.throw('Project ID must be provided. Did you pass in a proper projectId to initializeApp?'); + deleteApp(app); + }); + it('should not throw an error if `projectId` is provided', () => { + const projectId = 'p'; + initializeApp({ projectId}); + expect(() => getDataConnect({ connector: 'c', location: 'l', service: 's'})).to.not.throw('Project ID must be provided. Did you pass in a proper projectId to initializeApp?'); + const dc = getDataConnect({ connector: 'c', location: 'l', service: 's'}); + expect(dc.app.options.projectId).to.eq(projectId); + }); + it('should throw an error if `connectorConfig` is not provided', () => { + const projectId = 'p'; + initializeApp({ projectId}); + expect(() => getDataConnect({ } as ConnectorConfig)).to.throw('DC Option Required'); + const dc = getDataConnect({ connector: 'c', location: 'l', service: 's'}); + expect(dc.app.options.projectId).to.eq(projectId); + }); +}); \ No newline at end of file From ebbb1911bc34c2db6b3c9cda9130414ab560711a Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Tue, 16 Jul 2024 16:55:09 -0700 Subject: [PATCH 23/74] Updated package.json --- packages/data-connect/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/data-connect/package.json b/packages/data-connect/package.json index 321ef678a40..be7407fee90 100644 --- a/packages/data-connect/package.json +++ b/packages/data-connect/package.json @@ -48,13 +48,13 @@ "license": "Apache-2.0", "dependencies": { "@firebase/auth-interop-types": "0.2.3", - "@firebase/component": "0.6.7", + "@firebase/component": "0.6.8", "@firebase/logger": "0.4.2", - "@firebase/util": "1.9.6", + "@firebase/util": "1.9.7", "tslib": "^2.1.0" }, "devDependencies": { - "@firebase/app": "0.10.5", + "@firebase/app": "0.10.6", "rollup": "2.79.1", "rollup-plugin-typescript2": "0.31.2", "typescript": "4.7.4" From c6d5f3a110dd581d5db0aa4974e60f996a155df7 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Wed, 17 Jul 2024 16:16:22 -0700 Subject: [PATCH 24/74] Completed app check integration --- packages/data-connect/src/api/DataConnect.ts | 12 ++++++++++-- packages/data-connect/src/network/fetch.ts | 6 +++++- .../data-connect/src/network/transport/index.ts | 2 ++ .../data-connect/src/network/transport/rest.ts | 14 ++++++++++++-- packages/data-connect/src/register.ts | 4 +++- 5 files changed, 32 insertions(+), 6 deletions(-) diff --git a/packages/data-connect/src/api/DataConnect.ts b/packages/data-connect/src/api/DataConnect.ts index b53f8984c58..d46179d3f4d 100644 --- a/packages/data-connect/src/api/DataConnect.ts +++ b/packages/data-connect/src/api/DataConnect.ts @@ -35,6 +35,8 @@ import { DataConnectTransport, TransportClass } from '../network'; import { RESTTransport } from '../network/transport/rest'; import { MutationManager } from './Mutation'; +import { AppCheckTokenProvider } from '../core/AppCheckTokenProvider'; +import { AppCheckInternalComponentName, FirebaseAppCheckInternal } from '@firebase/app-check-interop-types'; /** * Connector Config for calling Data Connect backend. @@ -89,11 +91,13 @@ export class DataConnect { private _transportClass: TransportClass | undefined; private _transportOptions?: TransportOptions; private _authTokenProvider?: AuthTokenProvider; + private _appCheckTokenProvider?: AppCheckTokenProvider constructor( public readonly app: FirebaseApp, // TODO(mtewani): Replace with _dataConnectOptions in the future private readonly dataConnectOptions: DataConnectOptions, - private readonly _authProvider: Provider + private readonly _authProvider: Provider, + private readonly _appCheckProvider: Provider ) { if (typeof process !== 'undefined' && process.env) { const host = process.env[FIREBASE_DATA_CONNECT_EMULATOR_HOST_VAR]; @@ -135,12 +139,16 @@ export class DataConnect { this._authProvider ); } + if (this._appCheckProvider) { + this._appCheckTokenProvider = new AppCheckTokenProvider(this.app.name, this._appCheckProvider); + } this.initialized = true; this._transport = new this._transportClass( this.dataConnectOptions, this.app.options.apiKey, - this._authTokenProvider + this._authTokenProvider, + this._appCheckTokenProvider ); if (this._transportOptions) { this._transport.useEmulator( diff --git a/packages/data-connect/src/network/fetch.ts b/packages/data-connect/src/network/fetch.ts index 0bfd1629b2a..3a354eb2429 100644 --- a/packages/data-connect/src/network/fetch.ts +++ b/packages/data-connect/src/network/fetch.ts @@ -30,7 +30,8 @@ export function dcFetch( url: string, body: U, { signal }: AbortController, - accessToken: string | null + accessToken: string | null, + appCheckToken: string | null ) { if (!connectFetch) { throw new DataConnectError(Code.OTHER, 'No Fetch Implementation detected!'); @@ -42,6 +43,9 @@ export function dcFetch( if (accessToken) { headers['X-Firebase-Auth-Token'] = accessToken; } + if (appCheckToken) { + headers['X-Firebase-AppCheck'] = appCheckToken; + } const bodyStr = JSON.stringify(body); logDebug(`Making request out to ${url} with body: ${bodyStr}`); return connectFetch(url, { diff --git a/packages/data-connect/src/network/transport/index.ts b/packages/data-connect/src/network/transport/index.ts index a557f843513..8c6ddbce17c 100644 --- a/packages/data-connect/src/network/transport/index.ts +++ b/packages/data-connect/src/network/transport/index.ts @@ -16,6 +16,7 @@ */ import { DataConnectOptions, TransportOptions } from '../../api/DataConnect'; +import { AppCheckTokenProvider } from '../../core/AppCheckTokenProvider'; import { AuthTokenProvider } from '../../core/FirebaseAuthProvider'; // Change this to only specify specific args. @@ -52,6 +53,7 @@ export type TransportClass = new ( options: DataConnectOptions, apiKey?: string, authProvider?: AuthTokenProvider, + appCheckProvider?: AppCheckTokenProvider, transportOptions?: TransportOptions ) => DataConnectTransport; export * from '../../core/FirebaseAuthProvider'; diff --git a/packages/data-connect/src/network/transport/rest.ts b/packages/data-connect/src/network/transport/rest.ts index 72e6eba31a7..d7e779bb062 100644 --- a/packages/data-connect/src/network/transport/rest.ts +++ b/packages/data-connect/src/network/transport/rest.ts @@ -23,6 +23,7 @@ import { addToken, urlBuilder } from '../../util/url'; import { dcFetch } from '../fetch'; import { DataConnectTransport } from '.'; +import { AppCheckTokenProvider } from '../../core/AppCheckTokenProvider'; export class RESTTransport implements DataConnectTransport { private _host = ''; @@ -33,11 +34,13 @@ export class RESTTransport implements DataConnectTransport { private _project = 'p'; private _serviceName: string; private _accessToken: string | null = null; + private _appCheckToken: string | null = null; private _authInitialized = false; constructor( options: DataConnectOptions, private apiKey?: string | undefined, private authProvider?: AuthTokenProvider | undefined, + private appCheckProvider?: AppCheckTokenProvider | undefined, transportOptions?: TransportOptions | undefined ) { if (transportOptions) { @@ -68,6 +71,11 @@ export class RESTTransport implements DataConnectTransport { logDebug(`New Token Available: ${token}`); this._accessToken = token; }); + this.appCheckProvider?.addTokenChangeListener(result => { + const { token } = result; + logDebug(`New App Check Token Available: ${token}`); + this._appCheckToken = token; + }); } get endpointUrl(): string { return urlBuilder( @@ -129,7 +137,8 @@ export class RESTTransport implements DataConnectTransport { variables: body } as unknown as U, // TODO(mtewani): This is a patch, fix this. abortController, - this._accessToken + this._accessToken, + this._appCheckToken ); }); @@ -148,7 +157,8 @@ export class RESTTransport implements DataConnectTransport { variables: body } as unknown as U, abortController, - this._accessToken + this._accessToken, + this._appCheckToken ); }); diff --git a/packages/data-connect/src/register.ts b/packages/data-connect/src/register.ts index bc8342c3a4c..0c821d69fc4 100644 --- a/packages/data-connect/src/register.ts +++ b/packages/data-connect/src/register.ts @@ -36,6 +36,7 @@ export function registerDataConnect(variant?: string): void { (container, { instanceIdentifier: settings, options }) => { const app = container.getProvider('app').getImmediate()!; const authProvider = container.getProvider('auth-internal'); + const appCheckProvider = container.getProvider('app-check-internal'); let newOpts = options as ConnectorConfig; if (settings) { newOpts = JSON.parse(settings); @@ -46,7 +47,8 @@ export function registerDataConnect(variant?: string): void { return new DataConnect( app, { ...newOpts, projectId: app.options.projectId! }, - authProvider + authProvider, + appCheckProvider ); }, ComponentType.PUBLIC From 7681448370bed7f0dad15dfd4247fbbe59715011 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Wed, 17 Jul 2024 16:21:18 -0700 Subject: [PATCH 25/74] Added missing files --- packages/data-connect/CHANGELOG.md | 2 + .../src/core/AppCheckTokenProvider.ts | 74 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 packages/data-connect/CHANGELOG.md create mode 100644 packages/data-connect/src/core/AppCheckTokenProvider.ts diff --git a/packages/data-connect/CHANGELOG.md b/packages/data-connect/CHANGELOG.md new file mode 100644 index 00000000000..50eee694e35 --- /dev/null +++ b/packages/data-connect/CHANGELOG.md @@ -0,0 +1,2 @@ +# Unreleased +* Added app check support \ No newline at end of file diff --git a/packages/data-connect/src/core/AppCheckTokenProvider.ts b/packages/data-connect/src/core/AppCheckTokenProvider.ts new file mode 100644 index 00000000000..670ab40788f --- /dev/null +++ b/packages/data-connect/src/core/AppCheckTokenProvider.ts @@ -0,0 +1,74 @@ +/** + * @license + * Copyright 2021 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 { + AppCheckInternalComponentName, + AppCheckTokenListener, + AppCheckTokenResult, + FirebaseAppCheckInternal +} from '@firebase/app-check-interop-types'; +import { Provider } from '@firebase/component'; + + +/** + * Abstraction around AppCheck's token fetching capabilities. + */ +export class AppCheckTokenProvider { + private appCheck?: FirebaseAppCheckInternal; + constructor( + private appName_: string, + private appCheckProvider?: Provider + ) { + this.appCheck = appCheckProvider?.getImmediate({ optional: true }); + if (!this.appCheck) { + appCheckProvider?.get().then(appCheck => (this.appCheck = appCheck)); + } + } + + getToken(forceRefresh?: boolean): Promise { + if (!this.appCheck) { + return new Promise((resolve, reject) => { + // Support delayed initialization of FirebaseAppCheck. This allows our + // customers to initialize the RTDB SDK before initializing Firebase + // AppCheck and ensures that all requests are authenticated if a token + // becomes available before the timoeout below expires. + setTimeout(() => { + if (this.appCheck) { + this.getToken(forceRefresh).then(resolve, reject); + } else { + resolve(null); + } + }, 0); + }); + } + return this.appCheck.getToken(forceRefresh); + } + + addTokenChangeListener(listener: AppCheckTokenListener) { + this.appCheckProvider + ?.get() + .then(appCheck => appCheck.addTokenListener(listener)); + } + + // Not currently used at the moment. Will update if needed. + // notifyForInvalidToken(): void { + // warn( + // `Provided AppCheck credentials for the app named "${this.appName_}" ` + + // 'are invalid. This usually indicates your app was not initialized correctly.' + // ); + // } +} From 45e969b6d692833d1261185ac204f8f751a08a65 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Wed, 17 Jul 2024 16:28:37 -0700 Subject: [PATCH 26/74] Updated formatting --- packages/data-connect/src/api/DataConnect.ts | 14 +++- .../src/core/AppCheckTokenProvider.ts | 1 - packages/data-connect/src/register.ts | 7 +- .../test/unit/dataconnect.test.ts | 73 +++++++++++++------ 4 files changed, 65 insertions(+), 30 deletions(-) diff --git a/packages/data-connect/src/api/DataConnect.ts b/packages/data-connect/src/api/DataConnect.ts index d46179d3f4d..d94eacfcbaa 100644 --- a/packages/data-connect/src/api/DataConnect.ts +++ b/packages/data-connect/src/api/DataConnect.ts @@ -36,7 +36,10 @@ import { RESTTransport } from '../network/transport/rest'; import { MutationManager } from './Mutation'; import { AppCheckTokenProvider } from '../core/AppCheckTokenProvider'; -import { AppCheckInternalComponentName, FirebaseAppCheckInternal } from '@firebase/app-check-interop-types'; +import { + AppCheckInternalComponentName, + FirebaseAppCheckInternal +} from '@firebase/app-check-interop-types'; /** * Connector Config for calling Data Connect backend. @@ -91,7 +94,7 @@ export class DataConnect { private _transportClass: TransportClass | undefined; private _transportOptions?: TransportOptions; private _authTokenProvider?: AuthTokenProvider; - private _appCheckTokenProvider?: AppCheckTokenProvider + private _appCheckTokenProvider?: AppCheckTokenProvider; constructor( public readonly app: FirebaseApp, // TODO(mtewani): Replace with _dataConnectOptions in the future @@ -140,7 +143,10 @@ export class DataConnect { ); } if (this._appCheckProvider) { - this._appCheckTokenProvider = new AppCheckTokenProvider(this.app.name, this._appCheckProvider); + this._appCheckTokenProvider = new AppCheckTokenProvider( + this.app.name, + this._appCheckProvider + ); } this.initialized = true; @@ -233,7 +239,7 @@ export function getDataConnect( } } validateDCOptions(dcOptions); - + logDebug('Creating new DataConnect instance'); // Initialize with options. return provider.initialize({ diff --git a/packages/data-connect/src/core/AppCheckTokenProvider.ts b/packages/data-connect/src/core/AppCheckTokenProvider.ts index 670ab40788f..1497ae53daa 100644 --- a/packages/data-connect/src/core/AppCheckTokenProvider.ts +++ b/packages/data-connect/src/core/AppCheckTokenProvider.ts @@ -23,7 +23,6 @@ import { } from '@firebase/app-check-interop-types'; import { Provider } from '@firebase/component'; - /** * Abstraction around AppCheck's token fetching capabilities. */ diff --git a/packages/data-connect/src/register.ts b/packages/data-connect/src/register.ts index 0c821d69fc4..53b44f4e43d 100644 --- a/packages/data-connect/src/register.ts +++ b/packages/data-connect/src/register.ts @@ -41,8 +41,11 @@ export function registerDataConnect(variant?: string): void { if (settings) { newOpts = JSON.parse(settings); } - if(!app.options.projectId) { - throw new DataConnectError(Code.INVALID_ARGUMENT, "Project ID must be provided. Did you pass in a proper projectId to initializeApp?"); + if (!app.options.projectId) { + throw new DataConnectError( + Code.INVALID_ARGUMENT, + 'Project ID must be provided. Did you pass in a proper projectId to initializeApp?' + ); } return new DataConnect( app, diff --git a/packages/data-connect/test/unit/dataconnect.test.ts b/packages/data-connect/test/unit/dataconnect.test.ts index ba202a0b1ca..9a05abe37a7 100644 --- a/packages/data-connect/test/unit/dataconnect.test.ts +++ b/packages/data-connect/test/unit/dataconnect.test.ts @@ -1,25 +1,52 @@ -import { deleteApp, initializeApp } from "@firebase/app"; -import { ConnectorConfig, getDataConnect } from "../../src"; -import { expect } from "chai"; +/** + * @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 { deleteApp, initializeApp } from '@firebase/app'; +import { ConnectorConfig, getDataConnect } from '../../src'; +import { expect } from 'chai'; describe('Data Connect Test', () => { - it('should throw an error if `projectId` is not provided', () => { - const app = initializeApp({}); - expect(() => getDataConnect({ connector: 'c', location: 'l', service: 's'})).to.throw('Project ID must be provided. Did you pass in a proper projectId to initializeApp?'); - deleteApp(app); - }); - it('should not throw an error if `projectId` is provided', () => { - const projectId = 'p'; - initializeApp({ projectId}); - expect(() => getDataConnect({ connector: 'c', location: 'l', service: 's'})).to.not.throw('Project ID must be provided. Did you pass in a proper projectId to initializeApp?'); - const dc = getDataConnect({ connector: 'c', location: 'l', service: 's'}); - expect(dc.app.options.projectId).to.eq(projectId); - }); - it('should throw an error if `connectorConfig` is not provided', () => { - const projectId = 'p'; - initializeApp({ projectId}); - expect(() => getDataConnect({ } as ConnectorConfig)).to.throw('DC Option Required'); - const dc = getDataConnect({ connector: 'c', location: 'l', service: 's'}); - expect(dc.app.options.projectId).to.eq(projectId); - }); -}); \ No newline at end of file + it('should throw an error if `projectId` is not provided', () => { + const app = initializeApp({}); + expect(() => + getDataConnect({ connector: 'c', location: 'l', service: 's' }) + ).to.throw( + 'Project ID must be provided. Did you pass in a proper projectId to initializeApp?' + ); + deleteApp(app); + }); + it('should not throw an error if `projectId` is provided', () => { + const projectId = 'p'; + initializeApp({ projectId }); + expect(() => + getDataConnect({ connector: 'c', location: 'l', service: 's' }) + ).to.not.throw( + 'Project ID must be provided. Did you pass in a proper projectId to initializeApp?' + ); + const dc = getDataConnect({ connector: 'c', location: 'l', service: 's' }); + expect(dc.app.options.projectId).to.eq(projectId); + }); + it('should throw an error if `connectorConfig` is not provided', () => { + const projectId = 'p'; + initializeApp({ projectId }); + expect(() => getDataConnect({} as ConnectorConfig)).to.throw( + 'DC Option Required' + ); + const dc = getDataConnect({ connector: 'c', location: 'l', service: 's' }); + expect(dc.app.options.projectId).to.eq(projectId); + }); +}); From 7cc2b9cc9295e53caabf651707c6e2c13eb16bfc Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Fri, 19 Jul 2024 10:33:42 -0700 Subject: [PATCH 27/74] Added ability to retry unauthorized requests (#8351) --- common/api-review/data-connect.api.md | 3 + packages/data-connect/CHANGELOG.md | 5 ++ packages/data-connect/src/core/error.ts | 6 +- packages/data-connect/src/network/fetch.ts | 4 + .../src/network/transport/rest.ts | 43 +++++++--- .../data-connect/test/unit/queries.test.ts | 79 +++++++++++++++++++ 6 files changed, 129 insertions(+), 11 deletions(-) create mode 100644 packages/data-connect/CHANGELOG.md create mode 100644 packages/data-connect/test/unit/queries.test.ts diff --git a/common/api-review/data-connect.api.md b/common/api-review/data-connect.api.md index ea3a4c41c53..a5e33415cac 100644 --- a/common/api-review/data-connect.api.md +++ b/common/api-review/data-connect.api.md @@ -295,5 +295,8 @@ export interface TransportOptions { sslEnabled?: boolean; } +// @public (undocumented) +export function validateDCOptions(dcOptions: ConnectorConfig): boolean; + ``` diff --git a/packages/data-connect/CHANGELOG.md b/packages/data-connect/CHANGELOG.md new file mode 100644 index 00000000000..d1d09eb3914 --- /dev/null +++ b/packages/data-connect/CHANGELOG.md @@ -0,0 +1,5 @@ +# @firebase/data-connect +## UNRELEASED +* Updated reporting to use @firebase/data-connect instead of @firebase/connect +* Added functionality to retry queries and mutations if the server responds with UNAUTHENTICATED. + diff --git a/packages/data-connect/src/core/error.ts b/packages/data-connect/src/core/error.ts index c89f028be0c..f0beb128afa 100644 --- a/packages/data-connect/src/core/error.ts +++ b/packages/data-connect/src/core/error.ts @@ -23,7 +23,8 @@ export type DataConnectErrorCode = | 'not-initialized' | 'not-supported' | 'invalid-argument' - | 'partial-error'; + | 'partial-error' + | 'unauthorized'; export type Code = DataConnectErrorCode; @@ -33,7 +34,8 @@ export const Code = { NOT_INITIALIZED: 'not-initialized' as DataConnectErrorCode, NOT_SUPPORTED: 'not-supported' as DataConnectErrorCode, INVALID_ARGUMENT: 'invalid-argument' as DataConnectErrorCode, - PARTIAL_ERROR: 'partial-error' as DataConnectErrorCode + PARTIAL_ERROR: 'partial-error' as DataConnectErrorCode, + UNAUTHORIZED: 'unauthorized' as DataConnectErrorCode }; /** An error returned by a DataConnect operation. */ diff --git a/packages/data-connect/src/network/fetch.ts b/packages/data-connect/src/network/fetch.ts index 0bfd1629b2a..831bc6cc603 100644 --- a/packages/data-connect/src/network/fetch.ts +++ b/packages/data-connect/src/network/fetch.ts @@ -44,6 +44,7 @@ export function dcFetch( } const bodyStr = JSON.stringify(body); logDebug(`Making request out to ${url} with body: ${bodyStr}`); + return connectFetch(url, { body: bodyStr, method: 'POST', @@ -67,6 +68,9 @@ export function dcFetch( logError( 'Error while performing request: ' + JSON.stringify(jsonResponse) ); + if(response.status === 401) { + throw new DataConnectError(Code.UNAUTHORIZED, JSON.stringify(jsonResponse)); + } throw new DataConnectError(Code.OTHER, JSON.stringify(jsonResponse)); } return jsonResponse; diff --git a/packages/data-connect/src/network/transport/rest.ts b/packages/data-connect/src/network/transport/rest.ts index 72e6eba31a7..627aeb8dae8 100644 --- a/packages/data-connect/src/network/transport/rest.ts +++ b/packages/data-connect/src/network/transport/rest.ts @@ -20,7 +20,7 @@ import { DataConnectError, Code } from '../../core/error'; import { AuthTokenProvider } from '../../core/FirebaseAuthProvider'; import { logDebug } from '../../logger'; import { addToken, urlBuilder } from '../../util/url'; -import { dcFetch } from '../fetch'; +import { dcFetch, initializeFetch } from '../fetch'; import { DataConnectTransport } from '.'; @@ -34,6 +34,7 @@ export class RESTTransport implements DataConnectTransport { private _serviceName: string; private _accessToken: string | null = null; private _authInitialized = false; + private _lastToken: string | null = null; constructor( options: DataConnectOptions, private apiKey?: string | undefined, @@ -93,14 +94,14 @@ export class RESTTransport implements DataConnectTransport { this._accessToken = newToken; } - getWithAuth() { + getWithAuth(forceToken = false) { let starterPromise: Promise = new Promise(resolve => resolve(this._accessToken) ); if (!this._authInitialized) { if (this.authProvider) { starterPromise = this.authProvider - .getToken(/*forceToken=*/ false) + .getToken(/*forceToken=*/ forceToken) .then(data => { if (!data) { return null; @@ -115,13 +116,38 @@ export class RESTTransport implements DataConnectTransport { return starterPromise; } + _setLastToken(lastToken: string | null) { + this._lastToken = lastToken; + } + + withRetry(promiseFactory: () => Promise<{ data: T, errors: Error[]}>, retry = false) { + let isNewToken = false; + return this.getWithAuth(retry) + .then(res => { + isNewToken = this._lastToken !== res; + this._lastToken = res; + return res; + }) + .then(promiseFactory) + .catch(err => { + // Only retry if the result is unauthorized and the last token isn't the same as the new one. + if ( + 'code' in err && + err.code === Code.UNAUTHORIZED && + !retry && isNewToken + ) { + logDebug('Retrying due to unauthorized'); + return this.withRetry(promiseFactory, true); + } + throw err; + }); + } + // TODO(mtewani): Update U to include shape of body defined in line 13. invokeQuery = (queryName: string, body: U) => { const abortController = new AbortController(); - // TODO(mtewani): Update to proper value - const withAuth = this.getWithAuth().then(() => { - return dcFetch( + const withAuth = this.withRetry(() => dcFetch( addToken(`${this.endpointUrl}:executeQuery`, this.apiKey), { name: `projects/${this._project}/locations/${this._location}/services/${this._serviceName}/connectors/${this._connectorName}`, @@ -130,8 +156,7 @@ export class RESTTransport implements DataConnectTransport { } as unknown as U, // TODO(mtewani): This is a patch, fix this. abortController, this._accessToken - ); - }); + )); return { then: withAuth.then.bind(withAuth) @@ -139,7 +164,7 @@ export class RESTTransport implements DataConnectTransport { }; invokeMutation = (mutationName: string, body: U) => { const abortController = new AbortController(); - const taskResult = this.getWithAuth().then(() => { + const taskResult = this.withRetry(() => { return dcFetch( addToken(`${this.endpointUrl}:executeMutation`, this.apiKey), { diff --git a/packages/data-connect/test/unit/queries.test.ts b/packages/data-connect/test/unit/queries.test.ts new file mode 100644 index 00000000000..c96ca6d5a0b --- /dev/null +++ b/packages/data-connect/test/unit/queries.test.ts @@ -0,0 +1,79 @@ +import { FirebaseAuthTokenData } from '@firebase/auth-interop-types'; +import { + AuthTokenListener, + AuthTokenProvider, + DataConnectOptions, + FirebaseAuthProvider +} from '../../src'; +import { RESTTransport } from '../../src/network/transport/rest'; +import { initializeFetch } from '../../src/network/fetch'; +import { expect } from 'chai'; +import * as chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import * as sinon from 'sinon'; +chai.use(chaiAsPromised); +const options: DataConnectOptions = { + connector: 'c', + location: 'l', + projectId: 'p', + service: 's' +}; +const INITIAL_TOKEN = 'initial token'; +class FakeAuthProvider implements AuthTokenProvider { + private token: string | null = INITIAL_TOKEN; + addTokenChangeListener(listener: AuthTokenListener): void {} + getToken(forceRefresh: boolean): Promise { + if (!forceRefresh) { + return Promise.resolve({ accessToken: this.token! }); + } + return Promise.resolve({ accessToken: 'testToken' }); + } + setToken(_token: string | null) { + this.token = _token; + } +} +const json = { + message: 'unauthorized' +}; + +const fakeFetchImpl = sinon.stub().returns( + Promise.resolve({ + json: () => { + return Promise.resolve(json); + }, + status: 401 + } as Response) +); +describe('Queries', () => { + afterEach(() => { + fakeFetchImpl.resetHistory(); + }); + it('[QUERY] should retry auth whenever the fetcher returns with unauthorized', async () => { + initializeFetch(fakeFetchImpl); + const authProvider = new FakeAuthProvider(); + const rt = new RESTTransport(options, undefined, authProvider); + await expect( + rt.invokeQuery('test', null) + ).to.eventually.be.rejectedWith(JSON.stringify(json)); + expect(fakeFetchImpl.callCount).to.eq(2); + }); + it('[MUTATION] should retry auth whenever the fetcher returns with unauthorized', async () => { + initializeFetch(fakeFetchImpl); + const authProvider = new FakeAuthProvider(); + const rt = new RESTTransport(options, undefined, authProvider); + await expect( + rt.invokeMutation('test', null) + ).to.eventually.be.rejectedWith(JSON.stringify(json)); + expect(fakeFetchImpl.callCount).to.eq(2); + }); + it("should not retry auth whenever the fetcher returns with unauthorized and the token doesn't change", async () => { + initializeFetch(fakeFetchImpl); + const authProvider = new FakeAuthProvider(); + const rt = new RESTTransport(options, undefined, authProvider); + rt._setLastToken('initial token'); + await expect( + rt.invokeQuery('test', null) as Promise + ).to.eventually.be.rejectedWith(JSON.stringify(json)); + expect(fakeFetchImpl.callCount).to.eq(1); + }); +}); From 92caa93fa3f213ddc158e178016f2a01aaccc037 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Fri, 19 Jul 2024 15:44:27 -0700 Subject: [PATCH 28/74] Fix linting for Data Connect (#8380) --- common/api-review/data-connect.api.md | 4 +- packages/data-connect/package.json | 3 + packages/data-connect/src/api/DataConnect.ts | 4 +- packages/data-connect/src/api/Mutation.ts | 2 +- packages/data-connect/src/api/query.ts | 2 +- .../src/core/FirebaseAuthProvider.ts | 5 +- .../data-connect/src/core/QueryManager.ts | 10 +-- packages/data-connect/src/logger.ts | 2 +- packages/data-connect/src/network/fetch.ts | 13 ++-- .../src/network/transport/rest.ts | 38 +++++++--- packages/data-connect/src/register.ts | 7 +- packages/data-connect/src/util/encoder.ts | 2 +- packages/data-connect/src/util/url.ts | 2 +- packages/data-connect/test/emulatorSeeder.ts | 6 +- packages/data-connect/test/queries.test.ts | 12 +-- .../test/unit/dataconnect.test.ts | 74 +++++++++++++------ .../data-connect/test/unit/queries.test.ts | 47 ++++++++---- 17 files changed, 154 insertions(+), 79 deletions(-) diff --git a/common/api-review/data-connect.api.md b/common/api-review/data-connect.api.md index a5e33415cac..f06dbb0e337 100644 --- a/common/api-review/data-connect.api.md +++ b/common/api-review/data-connect.api.md @@ -8,7 +8,7 @@ import { FirebaseApp } from '@firebase/app'; import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; import { FirebaseAuthTokenData } from '@firebase/auth-interop-types'; import { FirebaseError } from '@firebase/util'; -import { FirebaseOptions } from '@firebase/app'; +import { FirebaseOptions } from '@firebase/app-types'; import { LogLevelString } from '@firebase/logger'; import { Provider } from '@firebase/component'; @@ -280,7 +280,7 @@ export interface SubscriptionOptions { export function terminate(dataConnect: DataConnect): Promise; // @public -export function toQueryRef(serializedRef: SerializedRef): QueryRef; +export function toQueryRef(serializedRef: SerializedRef): QueryRef; // @public (undocumented) export type TransportClass = new (options: DataConnectOptions, apiKey?: string, authProvider?: AuthTokenProvider, transportOptions?: TransportOptions) => DataConnectTransport; diff --git a/packages/data-connect/package.json b/packages/data-connect/package.json index be7407fee90..1bdb149aad8 100644 --- a/packages/data-connect/package.json +++ b/packages/data-connect/package.json @@ -46,6 +46,9 @@ "typings:public": "node ../../scripts/build/use_typings.js ./dist/public.d.ts" }, "license": "Apache-2.0", + "peerDependencies": { + "@firebase/app": "0.x" + }, "dependencies": { "@firebase/auth-interop-types": "0.2.3", "@firebase/component": "0.6.8", diff --git a/packages/data-connect/src/api/DataConnect.ts b/packages/data-connect/src/api/DataConnect.ts index b53f8984c58..50941f32f3e 100644 --- a/packages/data-connect/src/api/DataConnect.ts +++ b/packages/data-connect/src/api/DataConnect.ts @@ -225,7 +225,7 @@ export function getDataConnect( } } validateDCOptions(dcOptions); - + logDebug('Creating new DataConnect instance'); // Initialize with options. return provider.initialize({ @@ -234,7 +234,7 @@ export function getDataConnect( }); } -export function validateDCOptions(dcOptions: ConnectorConfig) { +export function validateDCOptions(dcOptions: ConnectorConfig): boolean { const fields = ['connector', 'location', 'service']; if (!dcOptions) { throw new DataConnectError(Code.INVALID_ARGUMENT, 'DC Option Required'); diff --git a/packages/data-connect/src/api/Mutation.ts b/packages/data-connect/src/api/Mutation.ts index ce307f953d1..ca2efdb7a30 100644 --- a/packages/data-connect/src/api/Mutation.ts +++ b/packages/data-connect/src/api/Mutation.ts @@ -95,7 +95,7 @@ export class MutationManager { return obj; }); this._inflight.push(result); - const removePromise = () => + const removePromise = (): Array> => (this._inflight = this._inflight.filter(promise => promise !== result)); result.then(removePromise, removePromise); return withRefPromise; diff --git a/packages/data-connect/src/api/query.ts b/packages/data-connect/src/api/query.ts index a5d83c8badd..a4ab17b7ceb 100644 --- a/packages/data-connect/src/api/query.ts +++ b/packages/data-connect/src/api/query.ts @@ -134,7 +134,7 @@ export function queryRef( */ export function toQueryRef( serializedRef: SerializedRef -) { +): QueryRef { const { refInfo: { name, variables, connectorConfig } } = serializedRef; diff --git a/packages/data-connect/src/core/FirebaseAuthProvider.ts b/packages/data-connect/src/core/FirebaseAuthProvider.ts index e98f5ae09c8..bf7c9b300a2 100644 --- a/packages/data-connect/src/core/FirebaseAuthProvider.ts +++ b/packages/data-connect/src/core/FirebaseAuthProvider.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { FirebaseOptions } from '@firebase/app'; +import { FirebaseOptions } from '@firebase/app-types'; import { FirebaseAuthInternal, FirebaseAuthInternalName, @@ -76,6 +76,7 @@ export class FirebaseAuthProvider implements AuthTokenProvider { removeTokenChangeListener(listener: (token: string | null) => void): void { this._authProvider .get() - .then(auth => auth.removeAuthTokenListener(listener)); + .then(auth => auth.removeAuthTokenListener(listener)) + .catch(err => logError(err)); } } diff --git a/packages/data-connect/src/core/QueryManager.ts b/packages/data-connect/src/core/QueryManager.ts index 0a9a170281d..c82e0fee903 100644 --- a/packages/data-connect/src/core/QueryManager.ts +++ b/packages/data-connect/src/core/QueryManager.ts @@ -77,7 +77,7 @@ export class QueryManager { queryName: string, variables: Variables, initialCache?: OpResult - ) { + ): TrackedQuery { const ref: TrackedQuery['ref'] = { name: queryName, variables, @@ -92,7 +92,7 @@ export class QueryManager { }; // @ts-ignore setIfNotExists(this._queries, key, newTrackedQuery); - return this._queries.get(key); + return this._queries.get(key) as TrackedQuery; } addSubscription( queryRef: OperationRef, @@ -113,7 +113,7 @@ export class QueryManager { userCallback: onResultCallback, errCallback: onErrorCallback }; - const unsubscribe = () => { + const unsubscribe = (): void => { const trackedQuery = this._queries.get(key)!; trackedQuery.subscriptions = trackedQuery.subscriptions.filter( sub => sub !== subscription @@ -215,11 +215,11 @@ export class QueryManager { return newR; } - enableEmulator(host: string, port: number) { + enableEmulator(host: string, port: number): void { this.transport.useEmulator(host, port); } } -function compareDates(str1: string, str2: string) { +function compareDates(str1: string, str2: string): boolean { const date1 = new Date(str1); const date2 = new Date(str2); return date1.getTime() < date2.getTime(); diff --git a/packages/data-connect/src/logger.ts b/packages/data-connect/src/logger.ts index 523d96bf9ad..ee66e8796c3 100644 --- a/packages/data-connect/src/logger.ts +++ b/packages/data-connect/src/logger.ts @@ -19,7 +19,7 @@ import { Logger, LogLevelString } from '@firebase/logger'; import { SDK_VERSION } from './core/version'; const logger = new Logger('@firebase/data-connect'); -export function setLogLevel(logLevel: LogLevelString) { +export function setLogLevel(logLevel: LogLevelString): void { logger.setLogLevel(logLevel); } export function logDebug(msg: string): void { diff --git a/packages/data-connect/src/network/fetch.ts b/packages/data-connect/src/network/fetch.ts index 831bc6cc603..8bc8432679c 100644 --- a/packages/data-connect/src/network/fetch.ts +++ b/packages/data-connect/src/network/fetch.ts @@ -20,7 +20,7 @@ import { SDK_VERSION } from '../core/version'; import { logDebug, logError } from '../logger'; let connectFetch: typeof fetch | null = globalThis.fetch; -export function initializeFetch(fetchImpl: typeof fetch) { +export function initializeFetch(fetchImpl: typeof fetch): void { connectFetch = fetchImpl; } function getGoogApiClientValue(): string { @@ -31,7 +31,7 @@ export function dcFetch( body: U, { signal }: AbortController, accessToken: string | null -) { +): Promise<{ data: T; errors: Error[] }> { if (!connectFetch) { throw new DataConnectError(Code.OTHER, 'No Fetch Implementation detected!'); } @@ -44,7 +44,7 @@ export function dcFetch( } const bodyStr = JSON.stringify(body); logDebug(`Making request out to ${url} with body: ${bodyStr}`); - + return connectFetch(url, { body: bodyStr, method: 'POST', @@ -68,8 +68,11 @@ export function dcFetch( logError( 'Error while performing request: ' + JSON.stringify(jsonResponse) ); - if(response.status === 401) { - throw new DataConnectError(Code.UNAUTHORIZED, JSON.stringify(jsonResponse)); + if (response.status === 401) { + throw new DataConnectError( + Code.UNAUTHORIZED, + JSON.stringify(jsonResponse) + ); } throw new DataConnectError(Code.OTHER, JSON.stringify(jsonResponse)); } diff --git a/packages/data-connect/src/network/transport/rest.ts b/packages/data-connect/src/network/transport/rest.ts index 627aeb8dae8..f35c6b11695 100644 --- a/packages/data-connect/src/network/transport/rest.ts +++ b/packages/data-connect/src/network/transport/rest.ts @@ -20,7 +20,7 @@ import { DataConnectError, Code } from '../../core/error'; import { AuthTokenProvider } from '../../core/FirebaseAuthProvider'; import { logDebug } from '../../logger'; import { addToken, urlBuilder } from '../../util/url'; -import { dcFetch, initializeFetch } from '../fetch'; +import { dcFetch } from '../fetch'; import { DataConnectTransport } from '.'; @@ -90,11 +90,11 @@ export class RESTTransport implements DataConnectTransport { this._secure = isSecure; } } - onTokenChanged(newToken: string | null) { + onTokenChanged(newToken: string | null): void { this._accessToken = newToken; } - getWithAuth(forceToken = false) { + getWithAuth(forceToken = false): Promise { let starterPromise: Promise = new Promise(resolve => resolve(this._accessToken) ); @@ -116,11 +116,14 @@ export class RESTTransport implements DataConnectTransport { return starterPromise; } - _setLastToken(lastToken: string | null) { + _setLastToken(lastToken: string | null): void { this._lastToken = lastToken; } - withRetry(promiseFactory: () => Promise<{ data: T, errors: Error[]}>, retry = false) { + withRetry( + promiseFactory: () => Promise<{ data: T; errors: Error[] }>, + retry = false + ): Promise<{ data: T; errors: Error[] }> { let isNewToken = false; return this.getWithAuth(retry) .then(res => { @@ -134,7 +137,8 @@ export class RESTTransport implements DataConnectTransport { if ( 'code' in err && err.code === Code.UNAUTHORIZED && - !retry && isNewToken + !retry && + isNewToken ) { logDebug('Retrying due to unauthorized'); return this.withRetry(promiseFactory, true); @@ -144,10 +148,17 @@ export class RESTTransport implements DataConnectTransport { } // TODO(mtewani): Update U to include shape of body defined in line 13. - invokeQuery = (queryName: string, body: U) => { + invokeQuery: ( + queryName: string, + body?: U + ) => PromiseLike<{ data: T; errors: Error[] }> = ( + queryName: string, + body: U + ) => { const abortController = new AbortController(); // TODO(mtewani): Update to proper value - const withAuth = this.withRetry(() => dcFetch( + const withAuth = this.withRetry(() => + dcFetch( addToken(`${this.endpointUrl}:executeQuery`, this.apiKey), { name: `projects/${this._project}/locations/${this._location}/services/${this._serviceName}/connectors/${this._connectorName}`, @@ -156,13 +167,20 @@ export class RESTTransport implements DataConnectTransport { } as unknown as U, // TODO(mtewani): This is a patch, fix this. abortController, this._accessToken - )); + ) + ); return { then: withAuth.then.bind(withAuth) }; }; - invokeMutation = (mutationName: string, body: U) => { + invokeMutation: ( + queryName: string, + body?: U + ) => PromiseLike<{ data: T; errors: Error[] }> = ( + mutationName: string, + body: U + ) => { const abortController = new AbortController(); const taskResult = this.withRetry(() => { return dcFetch( diff --git a/packages/data-connect/src/register.ts b/packages/data-connect/src/register.ts index bc8342c3a4c..52641040bd9 100644 --- a/packages/data-connect/src/register.ts +++ b/packages/data-connect/src/register.ts @@ -40,8 +40,11 @@ export function registerDataConnect(variant?: string): void { if (settings) { newOpts = JSON.parse(settings); } - if(!app.options.projectId) { - throw new DataConnectError(Code.INVALID_ARGUMENT, "Project ID must be provided. Did you pass in a proper projectId to initializeApp?"); + if (!app.options.projectId) { + throw new DataConnectError( + Code.INVALID_ARGUMENT, + 'Project ID must be provided. Did you pass in a proper projectId to initializeApp?' + ); } return new DataConnect( app, diff --git a/packages/data-connect/src/util/encoder.ts b/packages/data-connect/src/util/encoder.ts index 22fcaa612af..55aff801d22 100644 --- a/packages/data-connect/src/util/encoder.ts +++ b/packages/data-connect/src/util/encoder.ts @@ -17,7 +17,7 @@ export type HmacImpl = (obj: unknown) => string; export let encoderImpl: HmacImpl; -export function setEncoder(encoder: HmacImpl) { +export function setEncoder(encoder: HmacImpl): void { encoderImpl = encoder; } setEncoder(o => JSON.stringify(o)); diff --git a/packages/data-connect/src/util/url.ts b/packages/data-connect/src/util/url.ts index b3a8f7c2cff..3cef50ae648 100644 --- a/packages/data-connect/src/util/url.ts +++ b/packages/data-connect/src/util/url.ts @@ -22,7 +22,7 @@ import { logError } from '../logger'; export function urlBuilder( projectConfig: DataConnectOptions, transportOptions: TransportOptions -) { +): string { const { connector, location, projectId: project, service } = projectConfig; const { host, sslEnabled, port } = transportOptions; const protocol = sslEnabled ? 'https' : 'http'; diff --git a/packages/data-connect/test/emulatorSeeder.ts b/packages/data-connect/test/emulatorSeeder.ts index 3f9b86b117c..713e870fd57 100644 --- a/packages/data-connect/test/emulatorSeeder.ts +++ b/packages/data-connect/test/emulatorSeeder.ts @@ -49,9 +49,8 @@ export interface SeedInfo { } export async function setupQueries( schema: string, - seedInfoArray: SeedInfo[], - skipSchema = false -) { + seedInfoArray: SeedInfo[] +): Promise { const schemaPath = path.resolve(__dirname, schema); const schemaFileContents = fs.readFileSync(schemaPath).toString(); const toWrite = { @@ -79,6 +78,7 @@ export async function setupQueries( }) } }, + // eslint-disable-line camelcase connection_string: 'postgresql://postgres:secretpassword@localhost:5432/postgres?sslmode=disable' }; diff --git a/packages/data-connect/test/queries.test.ts b/packages/data-connect/test/queries.test.ts index d84279d014f..459b82f60df 100644 --- a/packages/data-connect/test/queries.test.ts +++ b/packages/data-connect/test/queries.test.ts @@ -62,7 +62,7 @@ const SEEDED_DATA = [ function seedDatabase(instance: DataConnect): Promise { // call mutation query that adds SEEDED_DATA to database return new Promise((resolve, reject) => { - async function run() { + async function run(): Promise { let idx = 0; while (idx < SEEDED_DATA.length) { const data = SEEDED_DATA[idx]; @@ -74,7 +74,7 @@ function seedDatabase(instance: DataConnect): Promise { run().then(resolve, reject); }); } -async function deleteDatabase(instance: DataConnect) { +async function deleteDatabase(instance: DataConnect): Promise { for (let i = 0; i < SEEDED_DATA.length; i++) { const data = SEEDED_DATA[i]; const ref = mutationRef(instance, 'removePost', { id: data.id }); @@ -163,7 +163,7 @@ describe('DataConnect Tests', async () => { }); connectDataConnectEmulator(fakeInstance, 'localhost', Number(0)); const taskListQuery = queryRef(dc, 'listPosts'); - expect(await executeQuery(taskListQuery)).to.eventually.be.rejectedWith( + await expect(executeQuery(taskListQuery)).to.eventually.be.rejectedWith( 'ECONNREFUSED' ); }); @@ -171,11 +171,13 @@ describe('DataConnect Tests', async () => { async function waitForFirstEvent( query: QueryRef ): Promise> { - return await new Promise<{ + return new Promise<{ result: QueryResult; unsubscribe: () => void; }>((resolve, reject) => { - const onResult = (result: QueryResult) => { + const onResult: (result: QueryResult) => void = ( + result: QueryResult + ) => { setTimeout(() => { resolve({ result, diff --git a/packages/data-connect/test/unit/dataconnect.test.ts b/packages/data-connect/test/unit/dataconnect.test.ts index ba202a0b1ca..c390ae6c7e7 100644 --- a/packages/data-connect/test/unit/dataconnect.test.ts +++ b/packages/data-connect/test/unit/dataconnect.test.ts @@ -1,25 +1,53 @@ -import { deleteApp, initializeApp } from "@firebase/app"; -import { ConnectorConfig, getDataConnect } from "../../src"; -import { expect } from "chai"; +/** + * @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 { deleteApp, initializeApp } from '@firebase/app'; +import { expect } from 'chai'; + +import { ConnectorConfig, getDataConnect } from '../../src'; describe('Data Connect Test', () => { - it('should throw an error if `projectId` is not provided', () => { - const app = initializeApp({}); - expect(() => getDataConnect({ connector: 'c', location: 'l', service: 's'})).to.throw('Project ID must be provided. Did you pass in a proper projectId to initializeApp?'); - deleteApp(app); - }); - it('should not throw an error if `projectId` is provided', () => { - const projectId = 'p'; - initializeApp({ projectId}); - expect(() => getDataConnect({ connector: 'c', location: 'l', service: 's'})).to.not.throw('Project ID must be provided. Did you pass in a proper projectId to initializeApp?'); - const dc = getDataConnect({ connector: 'c', location: 'l', service: 's'}); - expect(dc.app.options.projectId).to.eq(projectId); - }); - it('should throw an error if `connectorConfig` is not provided', () => { - const projectId = 'p'; - initializeApp({ projectId}); - expect(() => getDataConnect({ } as ConnectorConfig)).to.throw('DC Option Required'); - const dc = getDataConnect({ connector: 'c', location: 'l', service: 's'}); - expect(dc.app.options.projectId).to.eq(projectId); - }); -}); \ No newline at end of file + it('should throw an error if `projectId` is not provided', async () => { + const app = initializeApp({}); + expect(() => + getDataConnect({ connector: 'c', location: 'l', service: 's' }) + ).to.throw( + 'Project ID must be provided. Did you pass in a proper projectId to initializeApp?' + ); + await deleteApp(app); + }); + it('should not throw an error if `projectId` is provided', () => { + const projectId = 'p'; + initializeApp({ projectId }); + expect(() => + getDataConnect({ connector: 'c', location: 'l', service: 's' }) + ).to.not.throw( + 'Project ID must be provided. Did you pass in a proper projectId to initializeApp?' + ); + const dc = getDataConnect({ connector: 'c', location: 'l', service: 's' }); + expect(dc.app.options.projectId).to.eq(projectId); + }); + it('should throw an error if `connectorConfig` is not provided', () => { + const projectId = 'p'; + initializeApp({ projectId }); + expect(() => getDataConnect({} as ConnectorConfig)).to.throw( + 'DC Option Required' + ); + const dc = getDataConnect({ connector: 'c', location: 'l', service: 's' }); + expect(dc.app.options.projectId).to.eq(projectId); + }); +}); diff --git a/packages/data-connect/test/unit/queries.test.ts b/packages/data-connect/test/unit/queries.test.ts index c96ca6d5a0b..3d9ef848f29 100644 --- a/packages/data-connect/test/unit/queries.test.ts +++ b/packages/data-connect/test/unit/queries.test.ts @@ -1,16 +1,33 @@ +/** + * @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 { FirebaseAuthTokenData } from '@firebase/auth-interop-types'; +import { expect } from 'chai'; +import * as chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import * as sinon from 'sinon'; + import { AuthTokenListener, AuthTokenProvider, - DataConnectOptions, - FirebaseAuthProvider + DataConnectOptions } from '../../src'; -import { RESTTransport } from '../../src/network/transport/rest'; import { initializeFetch } from '../../src/network/fetch'; -import { expect } from 'chai'; -import * as chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import * as sinon from 'sinon'; +import { RESTTransport } from '../../src/network/transport/rest'; chai.use(chaiAsPromised); const options: DataConnectOptions = { connector: 'c', @@ -28,7 +45,7 @@ class FakeAuthProvider implements AuthTokenProvider { } return Promise.resolve({ accessToken: 'testToken' }); } - setToken(_token: string | null) { + setToken(_token: string | null): void { this.token = _token; } } @@ -52,18 +69,18 @@ describe('Queries', () => { initializeFetch(fakeFetchImpl); const authProvider = new FakeAuthProvider(); const rt = new RESTTransport(options, undefined, authProvider); - await expect( - rt.invokeQuery('test', null) - ).to.eventually.be.rejectedWith(JSON.stringify(json)); + await expect(rt.invokeQuery('test', null)).to.eventually.be.rejectedWith( + JSON.stringify(json) + ); expect(fakeFetchImpl.callCount).to.eq(2); }); it('[MUTATION] should retry auth whenever the fetcher returns with unauthorized', async () => { initializeFetch(fakeFetchImpl); const authProvider = new FakeAuthProvider(); const rt = new RESTTransport(options, undefined, authProvider); - await expect( - rt.invokeMutation('test', null) - ).to.eventually.be.rejectedWith(JSON.stringify(json)); + await expect(rt.invokeMutation('test', null)).to.eventually.be.rejectedWith( + JSON.stringify(json) + ); expect(fakeFetchImpl.callCount).to.eq(2); }); it("should not retry auth whenever the fetcher returns with unauthorized and the token doesn't change", async () => { @@ -72,7 +89,7 @@ describe('Queries', () => { const rt = new RESTTransport(options, undefined, authProvider); rt._setLastToken('initial token'); await expect( - rt.invokeQuery('test', null) as Promise + rt.invokeQuery('test', null) as Promise ).to.eventually.be.rejectedWith(JSON.stringify(json)); expect(fakeFetchImpl.callCount).to.eq(1); }); From 608cebf2f2cac4841f378308623f4c9df1c9caf3 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Wed, 24 Jul 2024 15:43:17 -0700 Subject: [PATCH 29/74] Better JSON messages (#8379) --- packages/data-connect/CHANGELOG.md | 2 +- packages/data-connect/src/network/fetch.ts | 17 ++++-- packages/data-connect/test/emulatorSeeder.ts | 2 +- packages/data-connect/test/post.gql | 6 ++ packages/data-connect/test/queries.test.ts | 20 +++++-- .../test/unit/dataconnect.test.ts | 4 +- packages/data-connect/test/unit/fetch.test.ts | 57 +++++++++++++++++++ .../data-connect/test/unit/queries.test.ts | 6 +- 8 files changed, 98 insertions(+), 16 deletions(-) create mode 100644 packages/data-connect/test/unit/fetch.test.ts diff --git a/packages/data-connect/CHANGELOG.md b/packages/data-connect/CHANGELOG.md index d1d09eb3914..47a284d0412 100644 --- a/packages/data-connect/CHANGELOG.md +++ b/packages/data-connect/CHANGELOG.md @@ -2,4 +2,4 @@ ## UNRELEASED * Updated reporting to use @firebase/data-connect instead of @firebase/connect * Added functionality to retry queries and mutations if the server responds with UNAUTHENTICATED. - +* Updated errors to only show relevant details to the user. diff --git a/packages/data-connect/src/network/fetch.ts b/packages/data-connect/src/network/fetch.ts index 8bc8432679c..6ecaf8fdcfe 100644 --- a/packages/data-connect/src/network/fetch.ts +++ b/packages/data-connect/src/network/fetch.ts @@ -64,17 +64,15 @@ export function dcFetch( } catch (e) { throw new DataConnectError(Code.OTHER, JSON.stringify(e)); } + const message = getMessage(jsonResponse); if (response.status >= 400) { logError( 'Error while performing request: ' + JSON.stringify(jsonResponse) ); if (response.status === 401) { - throw new DataConnectError( - Code.UNAUTHORIZED, - JSON.stringify(jsonResponse) - ); + throw new DataConnectError(Code.UNAUTHORIZED, message); } - throw new DataConnectError(Code.OTHER, JSON.stringify(jsonResponse)); + throw new DataConnectError(Code.OTHER, message); } return jsonResponse; }) @@ -87,3 +85,12 @@ export function dcFetch( return res as { data: T; errors: Error[] }; }); } +interface MessageObject { + message?: string; +} +function getMessage(obj: MessageObject): string { + if ('message' in obj) { + return obj.message; + } + return JSON.stringify(obj); +} diff --git a/packages/data-connect/test/emulatorSeeder.ts b/packages/data-connect/test/emulatorSeeder.ts index 713e870fd57..df7071a5868 100644 --- a/packages/data-connect/test/emulatorSeeder.ts +++ b/packages/data-connect/test/emulatorSeeder.ts @@ -78,7 +78,7 @@ export async function setupQueries( }) } }, - // eslint-disable-line camelcase + // eslint-disable-next-line camelcase connection_string: 'postgresql://postgres:secretpassword@localhost:5432/postgres?sslmode=disable' }; diff --git a/packages/data-connect/test/post.gql b/packages/data-connect/test/post.gql index 79bd6688efe..d483ec10130 100644 --- a/packages/data-connect/test/post.gql +++ b/packages/data-connect/test/post.gql @@ -9,4 +9,10 @@ query listPosts @auth(level: PUBLIC) { content } } +query listPosts2 { + posts { + id, + content + } +} diff --git a/packages/data-connect/test/queries.test.ts b/packages/data-connect/test/queries.test.ts index 459b82f60df..5897063c284 100644 --- a/packages/data-connect/test/queries.test.ts +++ b/packages/data-connect/test/queries.test.ts @@ -59,6 +59,10 @@ const SEEDED_DATA = [ content: 'task 2' } ]; +const REAL_DATA = SEEDED_DATA.map(obj => ({ + ...obj, + id: obj.id.replace(/-/g, '') +})); function seedDatabase(instance: DataConnect): Promise { // call mutation query that adds SEEDED_DATA to database return new Promise((resolve, reject) => { @@ -100,7 +104,7 @@ describe('DataConnect Tests', async () => { const taskListQuery = queryRef(dc, 'listPosts'); const taskListRes = await executeQuery(taskListQuery); expect(taskListRes.data).to.deep.eq({ - posts: SEEDED_DATA + posts: REAL_DATA }); }); it(`instantly executes a query if one hasn't been subscribed to`, async () => { @@ -121,7 +125,7 @@ describe('DataConnect Tests', async () => { ); const res = await promise; expect(res.data).to.deep.eq({ - posts: SEEDED_DATA + posts: REAL_DATA }); expect(res.source).to.eq(SOURCE_SERVER); }); @@ -138,7 +142,7 @@ describe('DataConnect Tests', async () => { const result = await waitForFirstEvent(taskListQuery); const serializedRef: SerializedRef = { data: { - posts: SEEDED_DATA + posts: REAL_DATA }, fetchTime: Date.now().toLocaleString(), refInfo: { @@ -149,7 +153,7 @@ describe('DataConnect Tests', async () => { name: taskListQuery.name, variables: undefined }, - source: SOURCE_SERVER + source: SOURCE_CACHE }; expect(result.toJSON()).to.deep.eq(serializedRef); expect(result.source).to.deep.eq(SOURCE_CACHE); @@ -167,6 +171,14 @@ describe('DataConnect Tests', async () => { 'ECONNREFUSED' ); }); + it('throws an error with just the message when the server responds with an error', async () => { + const invalidTaskListQuery = queryRef(dc, 'listPosts2'); + const message = + 'unauthorized: you are not authorized to perform this operation'; + await expect( + executeQuery(invalidTaskListQuery) + ).to.eventually.be.rejectedWith(message); + }); }); async function waitForFirstEvent( query: QueryRef diff --git a/packages/data-connect/test/unit/dataconnect.test.ts b/packages/data-connect/test/unit/dataconnect.test.ts index c390ae6c7e7..314c8a068dc 100644 --- a/packages/data-connect/test/unit/dataconnect.test.ts +++ b/packages/data-connect/test/unit/dataconnect.test.ts @@ -22,9 +22,9 @@ import { ConnectorConfig, getDataConnect } from '../../src'; describe('Data Connect Test', () => { it('should throw an error if `projectId` is not provided', async () => { - const app = initializeApp({}); + const app = initializeApp({ projectId: undefined }, 'a'); expect(() => - getDataConnect({ connector: 'c', location: 'l', service: 's' }) + getDataConnect(app, { connector: 'c', location: 'l', service: 's' }) ).to.throw( 'Project ID must be provided. Did you pass in a proper projectId to initializeApp?' ); diff --git a/packages/data-connect/test/unit/fetch.test.ts b/packages/data-connect/test/unit/fetch.test.ts new file mode 100644 index 00000000000..9f856abcdcf --- /dev/null +++ b/packages/data-connect/test/unit/fetch.test.ts @@ -0,0 +1,57 @@ +/** + * @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 { expect, use } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import * as sinon from 'sinon'; + +import { dcFetch, initializeFetch } from '../../src/network/fetch'; +use(chaiAsPromised); +function mockFetch(json: object): void { + const fakeFetchImpl = sinon.stub().returns( + Promise.resolve({ + json: () => { + return Promise.resolve(json); + }, + status: 401 + } as Response) + ); + initializeFetch(fakeFetchImpl); +} +describe('fetch', () => { + it('should throw an error with just the message when the server responds with an error with a message property in the body', async () => { + const message = 'Failed to connect to Postgres instance'; + mockFetch({ + code: 401, + message + }); + await expect( + dcFetch('http://localhost', {}, {} as AbortController, null) + ).to.eventually.be.rejectedWith(message); + }); + it('should throw a stringified message when the server responds with an error without a message property in the body', async () => { + const message = 'Failed to connect to Postgres instance'; + const json = { + code: 401, + message1: message + }; + mockFetch(json); + await expect( + dcFetch('http://localhost', {}, {} as AbortController, null) + ).to.eventually.be.rejectedWith(JSON.stringify(json)); + }); +}); diff --git a/packages/data-connect/test/unit/queries.test.ts b/packages/data-connect/test/unit/queries.test.ts index 3d9ef848f29..aa94428a6d7 100644 --- a/packages/data-connect/test/unit/queries.test.ts +++ b/packages/data-connect/test/unit/queries.test.ts @@ -70,7 +70,7 @@ describe('Queries', () => { const authProvider = new FakeAuthProvider(); const rt = new RESTTransport(options, undefined, authProvider); await expect(rt.invokeQuery('test', null)).to.eventually.be.rejectedWith( - JSON.stringify(json) + json.message ); expect(fakeFetchImpl.callCount).to.eq(2); }); @@ -79,7 +79,7 @@ describe('Queries', () => { const authProvider = new FakeAuthProvider(); const rt = new RESTTransport(options, undefined, authProvider); await expect(rt.invokeMutation('test', null)).to.eventually.be.rejectedWith( - JSON.stringify(json) + json.message ); expect(fakeFetchImpl.callCount).to.eq(2); }); @@ -90,7 +90,7 @@ describe('Queries', () => { rt._setLastToken('initial token'); await expect( rt.invokeQuery('test', null) as Promise - ).to.eventually.be.rejectedWith(JSON.stringify(json)); + ).to.eventually.be.rejectedWith(json.message); expect(fakeFetchImpl.callCount).to.eq(1); }); }); From c060e2e80483fd93682fd3211ce3a54c3f38f126 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Tue, 30 Jul 2024 11:34:02 -0700 Subject: [PATCH 30/74] Updated package.json --- packages/data-connect/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/data-connect/package.json b/packages/data-connect/package.json index 1bdb149aad8..9384b162a99 100644 --- a/packages/data-connect/package.json +++ b/packages/data-connect/package.json @@ -57,7 +57,7 @@ "tslib": "^2.1.0" }, "devDependencies": { - "@firebase/app": "0.10.6", + "@firebase/app": "0.10.7", "rollup": "2.79.1", "rollup-plugin-typescript2": "0.31.2", "typescript": "4.7.4" From 6aae5f5a6cc22996647ad9b32b77488cec264e56 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Tue, 30 Jul 2024 13:36:26 -0700 Subject: [PATCH 31/74] Moved validateArgs to core SDK (#8370) --- common/api-review/data-connect.api.md | 3 - docs-devsite/_toc.yaml | 51 +++++++++++++ .../data-connect.firebaseauthprovider.md | 2 +- docs-devsite/data-connect.md | 4 +- packages/data-connect/CHANGELOG.md | 1 + packages/data-connect/notes.md | 74 ------------------- packages/data-connect/src/api/DataConnect.ts | 6 ++ packages/data-connect/src/api/index.ts | 1 + .../data-connect/src/util/validateArgs.ts | 58 +++++++++++++++ packages/data-connect/test/queries.test.ts | 4 +- packages/data-connect/test/unit/utils.test.ts | 48 ++++++++++++ 11 files changed, 170 insertions(+), 82 deletions(-) delete mode 100644 packages/data-connect/notes.md create mode 100644 packages/data-connect/src/util/validateArgs.ts create mode 100644 packages/data-connect/test/unit/utils.test.ts diff --git a/common/api-review/data-connect.api.md b/common/api-review/data-connect.api.md index f06dbb0e337..4935b9b766e 100644 --- a/common/api-review/data-connect.api.md +++ b/common/api-review/data-connect.api.md @@ -295,8 +295,5 @@ export interface TransportOptions { sslEnabled?: boolean; } -// @public (undocumented) -export function validateDCOptions(dcOptions: ConnectorConfig): boolean; - ``` diff --git a/docs-devsite/_toc.yaml b/docs-devsite/_toc.yaml index 7412d572013..9bd5cc801e8 100644 --- a/docs-devsite/_toc.yaml +++ b/docs-devsite/_toc.yaml @@ -173,6 +173,57 @@ toc: path: /docs/reference/js/auth.userinfo.md - title: UserMetadata path: /docs/reference/js/auth.usermetadata.md + - title: data-connect + path: /docs/reference/js/data-connect.md + section: + - title: AuthTokenProvider + path: /docs/reference/js/data-connect.authtokenprovider.md + - title: CancellableOperation + path: /docs/reference/js/data-connect.cancellableoperation.md + - title: ConnectorConfig + path: /docs/reference/js/data-connect.connectorconfig.md + - title: DataConnect + path: /docs/reference/js/data-connect.dataconnect.md + - title: DataConnectOptions + path: /docs/reference/js/data-connect.dataconnectoptions.md + - title: DataConnectResult + path: /docs/reference/js/data-connect.dataconnectresult.md + - title: DataConnectSubscription + path: /docs/reference/js/data-connect.dataconnectsubscription.md + - title: DataConnectTransport + path: /docs/reference/js/data-connect.dataconnecttransport.md + - title: FirebaseAuthProvider + path: /docs/reference/js/data-connect.firebaseauthprovider.md + - title: MutationPromise + path: /docs/reference/js/data-connect.mutationpromise.md + - title: MutationRef + path: /docs/reference/js/data-connect.mutationref.md + - title: MutationResponse + path: /docs/reference/js/data-connect.mutationresponse.md + - title: MutationResult + path: /docs/reference/js/data-connect.mutationresult.md + - title: OperationRef + path: /docs/reference/js/data-connect.operationref.md + - title: OpResult + path: /docs/reference/js/data-connect.opresult.md + - title: QueryPromise + path: /docs/reference/js/data-connect.querypromise.md + - title: QueryRef + path: /docs/reference/js/data-connect.queryref.md + - title: QueryResponse + path: /docs/reference/js/data-connect.queryresponse.md + - title: QueryResult + path: /docs/reference/js/data-connect.queryresult.md + - title: RefInfo + path: /docs/reference/js/data-connect.refinfo.md + - title: Sender + path: /docs/reference/js/data-connect.sender.md + - title: SerializedRef + path: /docs/reference/js/data-connect.serializedref.md + - title: SubscriptionOptions + path: /docs/reference/js/data-connect.subscriptionoptions.md + - title: TransportOptions + path: /docs/reference/js/data-connect.transportoptions.md - title: database path: /docs/reference/js/database.md section: diff --git a/docs-devsite/data-connect.firebaseauthprovider.md b/docs-devsite/data-connect.firebaseauthprovider.md index 27d2a7be277..49580885def 100644 --- a/docs-devsite/data-connect.firebaseauthprovider.md +++ b/docs-devsite/data-connect.firebaseauthprovider.md @@ -46,7 +46,7 @@ constructor(_appName: string, _options: FirebaseOptions, _authProvider: Provider | Parameter | Type | Description | | --- | --- | --- | | \_appName | string | | -| \_options | [FirebaseOptions](./app.firebaseoptions.md#firebaseoptions_interface) | | +| \_options | FirebaseOptions | | | \_authProvider | Provider<FirebaseAuthInternalName> | | ## FirebaseAuthProvider.addTokenChangeListener() diff --git a/docs-devsite/data-connect.md b/docs-devsite/data-connect.md index 36603f6dcd7..26c3a11f90d 100644 --- a/docs-devsite/data-connect.md +++ b/docs-devsite/data-connect.md @@ -408,7 +408,7 @@ Converts serialized ref to query ref Signature: ```typescript -export declare function toQueryRef(serializedRef: SerializedRef): QueryRef; +export declare function toQueryRef(serializedRef: SerializedRef): QueryRef; ``` #### Parameters @@ -419,7 +419,7 @@ export declare function toQueryRef(serializedRef: SerializedRef Returns: -[QueryRef](./data-connect.queryref.md#queryref_interface)<unknown, Variables> +[QueryRef](./data-connect.queryref.md#queryref_interface)<Data, Variables> `QueryRef` diff --git a/packages/data-connect/CHANGELOG.md b/packages/data-connect/CHANGELOG.md index 47a284d0412..48490801892 100644 --- a/packages/data-connect/CHANGELOG.md +++ b/packages/data-connect/CHANGELOG.md @@ -2,4 +2,5 @@ ## UNRELEASED * Updated reporting to use @firebase/data-connect instead of @firebase/connect * Added functionality to retry queries and mutations if the server responds with UNAUTHENTICATED. +* Moved `validateArgs` to core SDK * Updated errors to only show relevant details to the user. diff --git a/packages/data-connect/notes.md b/packages/data-connect/notes.md deleted file mode 100644 index caa61c49696..00000000000 --- a/packages/data-connect/notes.md +++ /dev/null @@ -1,74 +0,0 @@ -# Android SDK notes - -## Structure -* Maybe we should have each query look up its own cache in the QueryManager. This should be a very easy lookup and we wouldn't have to replicate data in two places. -* `QueryManager` handles actual execution and subscription callbacks. -* Add a `QueryManager` to each of the `DataConnect` objects. -* What happens if you have multiple query references and then each of them has a different serializer? - -For example: -```typescript -const query1 = queryRef('myQuery', myConverter1); -const query2 = queryRef('myQuery', myConverter2); -subscribe(query1, () => {}); -subscribe(query2, () => {}); -execute(query1); - -``` -* What if there are two query refs with the same function callback and then you unsubscribe one? -```typescript -const query1 = queryRef('myQuery', myConverter1); -const query2 = queryRef('myQuery', myConverter2); -function mySub() { } -const unsubscribe1 = subscribe(query1, mySub); -const unsubscribe2 = subscribe(query2, mySub); -unsubscribe1(); // would this unsubscribe twice? -``` - -* Potentially, what could happen is that for each reference, it stores the converter and then it queries the querymanager for cache changes? - * Any time you call `new` on the `Query`, it adds an instance to the `QueryManager` to update when it gets data back. -* What if the converter was on the subscription itself? - - -``` typescript -interface Query { - name: string; - variables: Variables; -} -interface DataConnectSubscription { - userCallback: (data: Response, error: Error) => void; - converter: Converter; - unsubscribe: () => void; -} -interface TrackedQuery { - queryRef: Query; - subscriptions: DataConnectSubscription[]; - currentCache: Response | null; -} -interface QueryManager { - // adds a query to track - track(queryName: string, args: T) - // subscribe to a query - subscribe(queryName, args: T) -} - -// Examples -function HomeComponent() { - const getAllMoviesRef = queryRef('getAllMovies'); - const [movies, setMovieData] = useState([]); - function onMovieSubscribe(data: Movie[], error: Error) { - setMovieData(data); - } - function movieConverter(snapshot: DataConnectSnapshot): Movie[] { - return snapshot.data as Movie[]; - } - subscribe(getAllMoviesRef, onMovieSubscribe, movieConverter) -} -// Separate Page, but still needs movie information -function ListComponent() { - // Calls `queryManager`, which would return an existing query ref - const getAllMoviesRef = queryRef('getAllMovies'); -} - -// Two queries with different types? Should be fine. Weird, but fine. -``` diff --git a/packages/data-connect/src/api/DataConnect.ts b/packages/data-connect/src/api/DataConnect.ts index 50941f32f3e..9e9fddf1c67 100644 --- a/packages/data-connect/src/api/DataConnect.ts +++ b/packages/data-connect/src/api/DataConnect.ts @@ -234,6 +234,12 @@ export function getDataConnect( }); } +/** + * + * @param dcOptions + * @returns {void} + * @internal + */ export function validateDCOptions(dcOptions: ConnectorConfig): boolean { const fields = ['connector', 'location', 'service']; if (!dcOptions) { diff --git a/packages/data-connect/src/api/index.ts b/packages/data-connect/src/api/index.ts index cd8e4c195a6..885dac5a923 100644 --- a/packages/data-connect/src/api/index.ts +++ b/packages/data-connect/src/api/index.ts @@ -21,3 +21,4 @@ export * from './Reference'; export * from './Mutation'; export * from './query'; export { setLogLevel } from '../logger'; +export { validateArgs } from '../util/validateArgs'; diff --git a/packages/data-connect/src/util/validateArgs.ts b/packages/data-connect/src/util/validateArgs.ts new file mode 100644 index 00000000000..15d1effa3da --- /dev/null +++ b/packages/data-connect/src/util/validateArgs.ts @@ -0,0 +1,58 @@ +/** + * @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 { + ConnectorConfig, + DataConnect, + getDataConnect +} from '../api/DataConnect'; +import { Code, DataConnectError } from '../core/error'; +interface ParsedArgs { + dc: DataConnect; + vars: Variables; +} + +/** + * The generated SDK will allow the user to pass in either the variable or the data connect instance with the variable, + * and this function validates the variables and returns back the DataConnect instance and variables based on the arguments passed in. + * @param connectorConfig + * @param dcOrVars + * @param vars + * @param validateVars + * @returns {DataConnect} and {Variables} instance + * @internal + */ +export function validateArgs( + connectorConfig: ConnectorConfig, + dcOrVars?: DataConnect | Variables, + vars?: Variables, + validateVars?: boolean +): ParsedArgs { + let dcInstance: DataConnect; + let realVars: Variables; + if (dcOrVars && 'enableEmulator' in dcOrVars) { + dcInstance = dcOrVars as DataConnect; + realVars = vars; + } else { + dcInstance = getDataConnect(connectorConfig); + realVars = dcOrVars as Variables; + } + if (!dcInstance || (!realVars && validateVars)) { + throw new DataConnectError(Code.INVALID_ARGUMENT, 'Variables required.'); + } + return { dc: dcInstance, vars: realVars }; +} diff --git a/packages/data-connect/test/queries.test.ts b/packages/data-connect/test/queries.test.ts index 5897063c284..dd7e4e6c9e3 100644 --- a/packages/data-connect/test/queries.test.ts +++ b/packages/data-connect/test/queries.test.ts @@ -165,8 +165,8 @@ describe('DataConnect Tests', async () => { location: 'wrong', service: 'wrong' }); - connectDataConnectEmulator(fakeInstance, 'localhost', Number(0)); - const taskListQuery = queryRef(dc, 'listPosts'); + connectDataConnectEmulator(fakeInstance, 'localhost', 3512); + const taskListQuery = queryRef(fakeInstance, 'listPosts'); await expect(executeQuery(taskListQuery)).to.eventually.be.rejectedWith( 'ECONNREFUSED' ); diff --git a/packages/data-connect/test/unit/utils.test.ts b/packages/data-connect/test/unit/utils.test.ts new file mode 100644 index 00000000000..99e9d30fc3e --- /dev/null +++ b/packages/data-connect/test/unit/utils.test.ts @@ -0,0 +1,48 @@ +/** + * @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 { expect } from 'chai'; + +import { getDataConnect } from '../../src'; +import { validateArgs } from '../../src/util/validateArgs'; +describe('Utils', () => { + it('[Vars required: true] should throw if no arguments are provided', () => { + const connectorConfig = { connector: 'c', location: 'l', service: 's' }; + expect(() => + validateArgs(connectorConfig, undefined, false, true) + ).to.throw('Variables required'); + }); + it('[vars required: false, vars provided: false] should return data connect instance and no variables', () => { + const connectorConfig = { connector: 'c', location: 'l', service: 's' }; + const dc = getDataConnect(connectorConfig); + expect(validateArgs(connectorConfig)).to.deep.eq({ dc, vars: undefined }); + }); + it('[vars required: false, vars provided: false, data connect provided: true] should return data connect instance and no variables', () => { + const connectorConfig = { connector: 'c', location: 'l', service: 's' }; + const dc = getDataConnect(connectorConfig); + expect(validateArgs(connectorConfig, dc)).to.deep.eq({ + dc, + vars: undefined + }); + }); + it('[vars required: true, vars provided: true, data connect provided: true] should return data connect instance and variables', () => { + const connectorConfig = { connector: 'c', location: 'l', service: 's' }; + const dc = getDataConnect(connectorConfig); + const vars = { a: 1 }; + expect(validateArgs(connectorConfig, dc, vars)).to.deep.eq({ dc, vars }); + }); +}); From e58bf6b9708792a1fc134ea2ad3865f8fc3fe433 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Wed, 31 Jul 2024 16:04:50 -0700 Subject: [PATCH 32/74] Add logging capability to track whether the user is using Core or Gen SDK (#8385) * Wrote tests for user agent * Added another test * Update API reports * Fixed linting errors * Fixed tests * Updated changelog * Updated changelog again * Updated formatting * Updated docgen --- common/api-review/data-connect.api.md | 2 +- docs-devsite/data-connect.md | 2 +- packages/data-connect/CHANGELOG.md | 5 +- packages/data-connect/rollup.config.react.js | 238 ------------------ packages/data-connect/src/api/DataConnect.ts | 13 +- packages/data-connect/src/network/fetch.ts | 13 +- .../src/network/transport/index.ts | 3 +- .../src/network/transport/rest.ts | 12 +- packages/data-connect/test/unit/fetch.test.ts | 4 +- .../data-connect/test/unit/userAgent.test.ts | 73 ++++++ packages/data-connect/test/unit/utils.test.ts | 2 +- packages/data-connect/tsconfig.json | 2 +- 12 files changed, 113 insertions(+), 256 deletions(-) delete mode 100644 packages/data-connect/rollup.config.react.js create mode 100644 packages/data-connect/test/unit/userAgent.test.ts diff --git a/common/api-review/data-connect.api.md b/common/api-review/data-connect.api.md index 4935b9b766e..41f9cb4bb60 100644 --- a/common/api-review/data-connect.api.md +++ b/common/api-review/data-connect.api.md @@ -283,7 +283,7 @@ export function terminate(dataConnect: DataConnect): Promise; export function toQueryRef(serializedRef: SerializedRef): QueryRef; // @public (undocumented) -export type TransportClass = new (options: DataConnectOptions, apiKey?: string, authProvider?: AuthTokenProvider, transportOptions?: TransportOptions) => DataConnectTransport; +export type TransportClass = new (options: DataConnectOptions, apiKey?: string, authProvider?: AuthTokenProvider, transportOptions?: TransportOptions, _isUsingGen?: boolean) => DataConnectTransport; // @public export interface TransportOptions { diff --git a/docs-devsite/data-connect.md b/docs-devsite/data-connect.md index 26c3a11f90d..cbd8b16d370 100644 --- a/docs-devsite/data-connect.md +++ b/docs-devsite/data-connect.md @@ -532,5 +532,5 @@ export declare type ReferenceType = typeof QUERY_STR | typeof MUTATION_STR; Signature: ```typescript -export declare type TransportClass = new (options: DataConnectOptions, apiKey?: string, authProvider?: AuthTokenProvider, transportOptions?: TransportOptions) => DataConnectTransport; +export declare type TransportClass = new (options: DataConnectOptions, apiKey?: string, authProvider?: AuthTokenProvider, transportOptions?: TransportOptions, _isUsingGen?: boolean) => DataConnectTransport; ``` diff --git a/packages/data-connect/CHANGELOG.md b/packages/data-connect/CHANGELOG.md index 48490801892..60b90cb5bc7 100644 --- a/packages/data-connect/CHANGELOG.md +++ b/packages/data-connect/CHANGELOG.md @@ -1,6 +1,7 @@ # @firebase/data-connect ## UNRELEASED -* Updated reporting to use @firebase/data-connect instead of @firebase/connect +* Updated reporting to use @firebase/data-connect instead of @firebase/connect. * Added functionality to retry queries and mutations if the server responds with UNAUTHENTICATED. -* Moved `validateArgs` to core SDK +* Moved `validateArgs` to core SDK. * Updated errors to only show relevant details to the user. +* Added ability to track whether user is calling core sdk or generated sdk. diff --git a/packages/data-connect/rollup.config.react.js b/packages/data-connect/rollup.config.react.js deleted file mode 100644 index a61ac8a640d..00000000000 --- a/packages/data-connect/rollup.config.react.js +++ /dev/null @@ -1,238 +0,0 @@ -/** - * @license - * Copyright 2020 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 tmp from 'tmp'; -import path from 'path'; -import json from '@rollup/plugin-json'; -import alias from '@rollup/plugin-alias'; -import typescriptPlugin from 'rollup-plugin-typescript2'; -import typescript from 'typescript'; -import sourcemaps from 'rollup-plugin-sourcemaps'; -import replace from 'rollup-plugin-replace'; -import { terser } from 'rollup-plugin-terser'; - -import pkg from './react/package.json'; -import { generateBuildTargetReplaceConfig } from '../../scripts/build/rollup_replace_build_target'; - -const util = require('./rollup.shared'); - -const nodePlugins = function () { - return [ - typescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - target: 'es2017' - } - }, - cacheDir: tmp.dirSync(), - abortOnError: true, - transformers: [util.removeAssertTransformer] - }), - json({ preferConst: true }) - ]; -}; - -const browserPlugins = function () { - return [ - typescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - target: 'es2017' - } - }, - cacheDir: tmp.dirSync(), - abortOnError: true, - transformers: [util.removeAssertAndPrefixInternalTransformer] - }), - json({ preferConst: true }), - terser(util.manglePrivatePropertiesOptions) - ]; -}; - -const allBuilds = [ - // Intermidiate Node ESM build without build target reporting - // this is an intermidiate build used to generate the actual esm and cjs builds - // which add build target reporting - { - input: './react/index.tsx', - output: { - file: path.resolve('./react', pkg['main-esm']), - format: 'es', - sourcemap: true - }, - plugins: [ - alias(util.generateAliasConfig('node_react')), - ...nodePlugins(), - replace({ - '__RUNTIME_ENV__': 'node' - }) - ], - external: util.resolveNodeExterns, - treeshake: { - moduleSideEffects: false - }, - onwarn: util.onwarn - }, - // Node CJS build - { - input: path.resolve('./react', pkg['main-esm']), - output: { - file: path.resolve('./react', pkg.main), - format: 'cjs', - sourcemap: true - }, - plugins: [ - typescriptPlugin({ - typescript, - compilerOptions: { - allowJs: true, - target: 'es5' - }, - include: ['dist/react/*.js'] - }), - json(), - sourcemaps(), - replace(generateBuildTargetReplaceConfig('cjs', 5)) - ], - external: util.resolveNodeExterns, - treeshake: { - moduleSideEffects: false - } - }, - // Node ESM build - { - input: path.resolve('./react', pkg['main-esm']), - output: { - file: path.resolve('./react', pkg['main-esm']), - format: 'es', - sourcemap: true - }, - plugins: [ - sourcemaps(), - replace(generateBuildTargetReplaceConfig('esm', 2017)) - ], - external: util.resolveNodeExterns, - treeshake: { - moduleSideEffects: false - } - }, - // Intermidiate browser build without build target reporting - // this is an intermidiate build used to generate the actual esm and cjs builds - // which add build target reporting - { - input: './react/index.tsx', - output: { - file: path.resolve('./react', pkg.browser), - format: 'es', - sourcemap: true - }, - plugins: [ - alias(util.generateAliasConfig('browser_react')), - ...browserPlugins(), - // setting it to empty string because browser is the default env - replace({ - '__RUNTIME_ENV__': '' - }) - ], - external: util.resolveBrowserExterns, - treeshake: { - moduleSideEffects: false - } - }, - // Convert es2017 build to ES5 - { - input: path.resolve('./react', pkg.browser), - output: [ - { - file: path.resolve('./react', pkg.esm5), - format: 'es', - sourcemap: true - } - ], - plugins: [ - ...util.es2017ToEs5Plugins(/* mangled= */ true), - replace(generateBuildTargetReplaceConfig('esm', 5)) - ], - external: util.resolveBrowserExterns, - treeshake: { - moduleSideEffects: false - } - }, - // Convert es2017 build to CJS - { - input: path.resolve('./react', pkg.browser), - output: [ - { - file: './dist/react/index.cjs.js', - format: 'es', - sourcemap: true - } - ], - plugins: [ - sourcemaps(), - replace(generateBuildTargetReplaceConfig('cjs', 2017)) - ], - external: util.resolveBrowserExterns, - treeshake: { - moduleSideEffects: false - } - }, - // Browser es2017 build - { - input: path.resolve('./react', pkg.browser), - output: [ - { - file: path.resolve('./react', pkg.browser), - format: 'es', - sourcemap: true - } - ], - plugins: [ - sourcemaps(), - replace(generateBuildTargetReplaceConfig('esm', 2017)) - ], - external: util.resolveBrowserExterns, - treeshake: { - moduleSideEffects: false - } - }, - // RN build - { - input: './react/index.ts', - output: { - file: path.resolve('./react', pkg['react-native']), - format: 'es', - sourcemap: true - }, - plugins: [ - alias(util.generateAliasConfig('rn_react')), - ...browserPlugins(), - replace({ - ...generateBuildTargetReplaceConfig('esm', 2017), - '__RUNTIME_ENV__': 'rn' - }) - ], - external: util.resolveBrowserExterns, - treeshake: { - moduleSideEffects: false - } - } -]; - -export default allBuilds; diff --git a/packages/data-connect/src/api/DataConnect.ts b/packages/data-connect/src/api/DataConnect.ts index 9e9fddf1c67..a16d7ba84fe 100644 --- a/packages/data-connect/src/api/DataConnect.ts +++ b/packages/data-connect/src/api/DataConnect.ts @@ -89,6 +89,7 @@ export class DataConnect { private _transportClass: TransportClass | undefined; private _transportOptions?: TransportOptions; private _authTokenProvider?: AuthTokenProvider; + _isUsingGeneratedSdk: boolean = false; constructor( public readonly app: FirebaseApp, // TODO(mtewani): Replace with _dataConnectOptions in the future @@ -104,6 +105,14 @@ export class DataConnect { } } } + /* + @internal + */ + _useGeneratedSdk(): void { + if (!this._isUsingGeneratedSdk) { + this._isUsingGeneratedSdk = true; + } + } _delete(): Promise { _removeServiceInstance( this.app, @@ -140,7 +149,9 @@ export class DataConnect { this._transport = new this._transportClass( this.dataConnectOptions, this.app.options.apiKey, - this._authTokenProvider + this._authTokenProvider, + undefined, + this._isUsingGeneratedSdk ); if (this._transportOptions) { this._transport.useEmulator( diff --git a/packages/data-connect/src/network/fetch.ts b/packages/data-connect/src/network/fetch.ts index 6ecaf8fdcfe..0a202bf90df 100644 --- a/packages/data-connect/src/network/fetch.ts +++ b/packages/data-connect/src/network/fetch.ts @@ -23,21 +23,26 @@ let connectFetch: typeof fetch | null = globalThis.fetch; export function initializeFetch(fetchImpl: typeof fetch): void { connectFetch = fetchImpl; } -function getGoogApiClientValue(): string { - return 'gl-js/ fire/' + SDK_VERSION; +function getGoogApiClientValue(_isUsingGen: boolean): string { + let str = 'gl-js/ fire/' + SDK_VERSION; + if (_isUsingGen) { + str += ' web/gen'; + } + return str; } export function dcFetch( url: string, body: U, { signal }: AbortController, - accessToken: string | null + accessToken: string | null, + _isUsingGen: boolean ): Promise<{ data: T; errors: Error[] }> { if (!connectFetch) { throw new DataConnectError(Code.OTHER, 'No Fetch Implementation detected!'); } const headers: HeadersInit = { 'Content-Type': 'application/json', - 'X-Goog-Api-Client': getGoogApiClientValue() + 'X-Goog-Api-Client': getGoogApiClientValue(_isUsingGen) }; if (accessToken) { headers['X-Firebase-Auth-Token'] = accessToken; diff --git a/packages/data-connect/src/network/transport/index.ts b/packages/data-connect/src/network/transport/index.ts index a557f843513..1b0233ea013 100644 --- a/packages/data-connect/src/network/transport/index.ts +++ b/packages/data-connect/src/network/transport/index.ts @@ -52,6 +52,7 @@ export type TransportClass = new ( options: DataConnectOptions, apiKey?: string, authProvider?: AuthTokenProvider, - transportOptions?: TransportOptions + transportOptions?: TransportOptions, + _isUsingGen?: boolean ) => DataConnectTransport; export * from '../../core/FirebaseAuthProvider'; diff --git a/packages/data-connect/src/network/transport/rest.ts b/packages/data-connect/src/network/transport/rest.ts index f35c6b11695..342348f604f 100644 --- a/packages/data-connect/src/network/transport/rest.ts +++ b/packages/data-connect/src/network/transport/rest.ts @@ -39,7 +39,8 @@ export class RESTTransport implements DataConnectTransport { options: DataConnectOptions, private apiKey?: string | undefined, private authProvider?: AuthTokenProvider | undefined, - transportOptions?: TransportOptions | undefined + transportOptions?: TransportOptions | undefined, + private _isUsingGen = false ) { if (transportOptions) { if (typeof transportOptions.port === 'number') { @@ -166,12 +167,14 @@ export class RESTTransport implements DataConnectTransport { variables: body } as unknown as U, // TODO(mtewani): This is a patch, fix this. abortController, - this._accessToken + this._accessToken, + this._isUsingGen ) ); return { - then: withAuth.then.bind(withAuth) + then: withAuth.then.bind(withAuth), + catch: withAuth.catch.bind(withAuth) }; }; invokeMutation: ( @@ -191,7 +194,8 @@ export class RESTTransport implements DataConnectTransport { variables: body } as unknown as U, abortController, - this._accessToken + this._accessToken, + this._isUsingGen ); }); diff --git a/packages/data-connect/test/unit/fetch.test.ts b/packages/data-connect/test/unit/fetch.test.ts index 9f856abcdcf..8010b08b790 100644 --- a/packages/data-connect/test/unit/fetch.test.ts +++ b/packages/data-connect/test/unit/fetch.test.ts @@ -40,7 +40,7 @@ describe('fetch', () => { message }); await expect( - dcFetch('http://localhost', {}, {} as AbortController, null) + dcFetch('http://localhost', {}, {} as AbortController, null, false) ).to.eventually.be.rejectedWith(message); }); it('should throw a stringified message when the server responds with an error without a message property in the body', async () => { @@ -51,7 +51,7 @@ describe('fetch', () => { }; mockFetch(json); await expect( - dcFetch('http://localhost', {}, {} as AbortController, null) + dcFetch('http://localhost', {}, {} as AbortController, null, false) ).to.eventually.be.rejectedWith(JSON.stringify(json)); }); }); diff --git a/packages/data-connect/test/unit/userAgent.test.ts b/packages/data-connect/test/unit/userAgent.test.ts new file mode 100644 index 00000000000..696f0bf60e9 --- /dev/null +++ b/packages/data-connect/test/unit/userAgent.test.ts @@ -0,0 +1,73 @@ +/** + * @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 { expect, use } from 'chai'; +import * as sinon from 'sinon'; +import sinonChai from 'sinon-chai'; + +import { DataConnect, executeQuery, getDataConnect, queryRef } from '../../src'; +import { SDK_VERSION } from '../../src/core/version'; +import { initializeFetch } from '../../src/network/fetch'; + +use(sinonChai); +const json = { + message: 'unauthorized' +}; +const fakeFetchImpl = sinon.stub().returns( + Promise.resolve({ + json: () => { + return Promise.resolve(json); + }, + status: 401 + } as Response) +); + +describe('User Agent Tests', () => { + let dc: DataConnect; + beforeEach(() => { + initializeFetch(fakeFetchImpl); + dc = getDataConnect({ connector: 'c', location: 'l', service: 's' }); + }); + afterEach(async () => { + await dc._delete(); + }); + it('should send a request with the corresponding user agent if using the generated SDK', async () => { + dc._useGeneratedSdk(); + // @ts-ignore + await executeQuery(queryRef(dc, '')).catch(() => {}); + expect(fakeFetchImpl).to.be.calledWithMatch( + 'https://firebasedataconnect.googleapis.com/v1alpha/projects/p/locations/l/services/s/connectors/c:executeQuery', + { + headers: { + ['X-Goog-Api-Client']: 'gl-js/ fire/' + SDK_VERSION + ' web/gen' + } + } + ); + }); + it('should send a request with the corresponding user agent if using the generated SDK', async () => { + // @ts-ignore + await executeQuery(queryRef(dc, '')).catch(() => {}); + expect(fakeFetchImpl).to.be.calledWithMatch( + 'https://firebasedataconnect.googleapis.com/v1alpha/projects/p/locations/l/services/s/connectors/c:executeQuery', + { + headers: { + ['X-Goog-Api-Client']: 'gl-js/ fire/' + SDK_VERSION + } + } + ); + }); +}); diff --git a/packages/data-connect/test/unit/utils.test.ts b/packages/data-connect/test/unit/utils.test.ts index 99e9d30fc3e..c69c1c8f511 100644 --- a/packages/data-connect/test/unit/utils.test.ts +++ b/packages/data-connect/test/unit/utils.test.ts @@ -23,7 +23,7 @@ describe('Utils', () => { it('[Vars required: true] should throw if no arguments are provided', () => { const connectorConfig = { connector: 'c', location: 'l', service: 's' }; expect(() => - validateArgs(connectorConfig, undefined, false, true) + validateArgs(connectorConfig, undefined, undefined, true) ).to.throw('Variables required'); }); it('[vars required: false, vars provided: false] should return data connect instance and no variables', () => { diff --git a/packages/data-connect/tsconfig.json b/packages/data-connect/tsconfig.json index 2355409eed4..838f5c0d3c3 100644 --- a/packages/data-connect/tsconfig.json +++ b/packages/data-connect/tsconfig.json @@ -6,6 +6,6 @@ }, "exclude": [ "dist/**/*", - "./test/**/*" + "test/**/*" ] } From d986d4bf24b1d17bb2ab89bbcd11e281611c6eda Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Wed, 31 Jul 2024 16:09:24 -0700 Subject: [PATCH 33/74] 0.0.3 release --- packages/data-connect/CHANGELOG.md | 2 +- packages/data-connect/package.json | 2 +- packages/firebase/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/data-connect/CHANGELOG.md b/packages/data-connect/CHANGELOG.md index 60b90cb5bc7..871c3b02f00 100644 --- a/packages/data-connect/CHANGELOG.md +++ b/packages/data-connect/CHANGELOG.md @@ -1,5 +1,5 @@ # @firebase/data-connect -## UNRELEASED +## 0.0.3 * Updated reporting to use @firebase/data-connect instead of @firebase/connect. * Added functionality to retry queries and mutations if the server responds with UNAUTHENTICATED. * Moved `validateArgs` to core SDK. diff --git a/packages/data-connect/package.json b/packages/data-connect/package.json index 9384b162a99..699d9c5f32e 100644 --- a/packages/data-connect/package.json +++ b/packages/data-connect/package.json @@ -1,6 +1,6 @@ { "name": "@firebase/data-connect", - "version": "0.0.2", + "version": "0.0.3", "description": "", "author": "Firebase (https://firebase.google.com/)", "main": "dist/index.node.cjs.js", diff --git a/packages/firebase/package.json b/packages/firebase/package.json index 9a41fde8674..c7c8b14a6ed 100644 --- a/packages/firebase/package.json +++ b/packages/firebase/package.json @@ -404,7 +404,7 @@ "@firebase/app-types": "0.9.2", "@firebase/auth": "1.7.5", "@firebase/auth-compat": "0.5.10", - "@firebase/data-connect": "0.0.2", + "@firebase/data-connect": "0.0.3", "@firebase/database": "1.0.6", "@firebase/database-compat": "1.0.6", "@firebase/firestore": "4.6.4", From 994892ce1e91c5536a664850262365f96448e785 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Tue, 3 Sep 2024 19:49:10 -0700 Subject: [PATCH 34/74] Fixed appcheck implementation --- .changeset/README.md | 8 - .changeset/config.json | 29 - .changeset/late-humans-tan.md | 12 - .changeset/spicy-dragons-pay.md | 5 - .changeset/tender-apes-clap.md | 6 - .opensource/project.json | 20 - .yarn/releases/yarn-1.22.11.cjs | 147406 --------------- .yarnrc | 2 - CHANGELOG.md | 5 - LICENSE | 257 - config/.eslintrc.js | 237 - config/api-extractor.json | 365 - config/ci.config.json | 9 - config/database.rules.json | 9 - config/firebase.json | 12 - config/firestore.indexes.json | 3 - config/firestore.rules | 7 - config/functions/index.js | 158 - config/functions/package.json | 13 - config/karma.base.js | 91 - config/karma.saucelabs.js | 237 - config/mocha.browser.opts | 25 - config/mocharc.node.js | 37 - config/tsconfig.base.json | 36 - config/webpack.test.js | 123 - docs-devsite/_toc.yaml | 606 - docs-devsite/analytics.analytics.md | 35 - .../analytics.analyticscalloptions.md | 35 - docs-devsite/analytics.analyticssettings.md | 35 - docs-devsite/analytics.consentsettings.md | 101 - docs-devsite/analytics.controlparams.md | 60 - docs-devsite/analytics.customparams.md | 19 - docs-devsite/analytics.eventparams.md | 316 - docs-devsite/analytics.gtagconfigparams.md | 134 - docs-devsite/analytics.item.md | 278 - docs-devsite/analytics.md | 1024 - docs-devsite/analytics.promotion.md | 65 - docs-devsite/analytics.settingsoptions.md | 46 - docs-devsite/app-check.appcheck.md | 35 - docs-devsite/app-check.appcheckoptions.md | 46 - docs-devsite/app-check.appchecktoken.md | 44 - docs-devsite/app-check.appchecktokenresult.md | 35 - docs-devsite/app-check.customprovider.md | 43 - .../app-check.customprovideroptions.md | 35 - docs-devsite/app-check.md | 201 - .../app-check.recaptchaenterpriseprovider.md | 43 - docs-devsite/app-check.recaptchav3provider.md | 43 - docs-devsite/app.firebaseapp.md | 91 - docs-devsite/app.firebaseappsettings.md | 46 - docs-devsite/app.firebaseoptions.md | 112 - docs-devsite/app.firebaseserverapp.md | 59 - docs-devsite/app.firebaseserverappsettings.md | 59 - docs-devsite/app.md | 371 - docs-devsite/auth.actioncodeinfo.md | 58 - docs-devsite/auth.actioncodesettings.md | 95 - docs-devsite/auth.actioncodeurl.md | 121 - docs-devsite/auth.additionaluserinfo.md | 68 - docs-devsite/auth.applicationverifier.md | 59 - docs-devsite/auth.auth.md | 317 - docs-devsite/auth.authcredential.md | 76 - docs-devsite/auth.autherror.md | 41 - docs-devsite/auth.autherrormap.md | 27 - docs-devsite/auth.authprovider.md | 35 - docs-devsite/auth.authsettings.md | 41 - docs-devsite/auth.config.md | 90 - docs-devsite/auth.confirmationresult.md | 74 - docs-devsite/auth.dependencies.md | 63 - docs-devsite/auth.emailauthcredential.md | 69 - docs-devsite/auth.emailauthprovider.md | 159 - docs-devsite/auth.emulatorconfig.md | 70 - docs-devsite/auth.facebookauthprovider.md | 182 - docs-devsite/auth.githubauthprovider.md | 174 - docs-devsite/auth.googleauthprovider.md | 185 - docs-devsite/auth.idtokenresult.md | 109 - docs-devsite/auth.md | 2148 - docs-devsite/auth.multifactorassertion.md | 37 - docs-devsite/auth.multifactorerror.md | 67 - docs-devsite/auth.multifactorinfo.md | 68 - docs-devsite/auth.multifactorresolver.md | 128 - docs-devsite/auth.multifactorsession.md | 19 - docs-devsite/auth.multifactoruser.md | 161 - docs-devsite/auth.oauthcredential.md | 105 - docs-devsite/auth.oauthcredentialoptions.md | 61 - docs-devsite/auth.oauthprovider.md | 168 - docs-devsite/auth.parsedtoken.md | 83 - docs-devsite/auth.passwordpolicy.md | 75 - docs-devsite/auth.passwordvalidationstatus.md | 112 - docs-devsite/auth.persistence.md | 35 - docs-devsite/auth.phoneauthcredential.md | 63 - docs-devsite/auth.phoneauthprovider.md | 258 - .../auth.phonemultifactorassertion.md | 21 - .../auth.phonemultifactorenrollinfooptions.md | 46 - .../auth.phonemultifactorgenerator.md | 66 - docs-devsite/auth.phonemultifactorinfo.md | 36 - .../auth.phonemultifactorsignininfooptions.md | 61 - .../auth.phonesinglefactorinfooptions.md | 35 - docs-devsite/auth.popupredirectresolver.md | 19 - docs-devsite/auth.reactnativeasyncstorage.md | 89 - docs-devsite/auth.recaptchaparameters.md | 23 - docs-devsite/auth.recaptchaverifier.md | 118 - docs-devsite/auth.samlauthprovider.md | 119 - docs-devsite/auth.totpmultifactorassertion.md | 21 - docs-devsite/auth.totpmultifactorgenerator.md | 112 - docs-devsite/auth.totpmultifactorinfo.md | 21 - docs-devsite/auth.totpsecret.md | 111 - docs-devsite/auth.twitterauthprovider.md | 173 - docs-devsite/auth.user.md | 206 - docs-devsite/auth.usercredential.md | 59 - docs-devsite/auth.userinfo.md | 92 - docs-devsite/auth.usermetadata.md | 46 - .../data-connect.authtokenprovider.md | 61 - .../data-connect.cancellableoperation.md | 43 - docs-devsite/data-connect.connectorconfig.md | 51 - docs-devsite/data-connect.dataconnect.md | 124 - .../data-connect.dataconnectoptions.md | 34 - .../data-connect.dataconnectresult.md | 32 - .../data-connect.dataconnectsubscription.md | 51 - .../data-connect.dataconnecttransport.md | 104 - .../data-connect.firebaseauthprovider.md | 105 - docs-devsite/data-connect.md | 536 - docs-devsite/data-connect.mutationpromise.md | 21 - docs-devsite/data-connect.mutationref.md | 32 - docs-devsite/data-connect.mutationresponse.md | 19 - docs-devsite/data-connect.mutationresult.md | 34 - docs-devsite/data-connect.operationref.md | 58 - docs-devsite/data-connect.opresult.md | 49 - docs-devsite/data-connect.querypromise.md | 21 - docs-devsite/data-connect.queryref.md | 34 - docs-devsite/data-connect.queryresponse.md | 19 - docs-devsite/data-connect.queryresult.md | 43 - docs-devsite/data-connect.refinfo.md | 51 - docs-devsite/data-connect.sender.md | 40 - docs-devsite/data-connect.serializedref.md | 34 - .../data-connect.subscriptionoptions.md | 51 - docs-devsite/data-connect.transportoptions.md | 51 - docs-devsite/database.database.md | 46 - docs-devsite/database.databasereference.md | 68 - docs-devsite/database.datasnapshot.md | 237 - docs-devsite/database.iterateddatasnapshot.md | 34 - docs-devsite/database.listenoptions.md | 35 - docs-devsite/database.md | 1430 - docs-devsite/database.ondisconnect.md | 143 - docs-devsite/database.query.md | 108 - docs-devsite/database.queryconstraint.md | 35 - docs-devsite/database.thenablereference.md | 21 - docs-devsite/database.transactionoptions.md | 35 - docs-devsite/database.transactionresult.md | 66 - docs-devsite/firestore.md | 19 - docs-devsite/firestore_.aggregatefield.md | 46 - .../firestore_.aggregatequerysnapshot.md | 70 - docs-devsite/firestore_.aggregatespec.md | 19 - docs-devsite/firestore_.bytes.md | 138 - .../firestore_.collectionreference.md | 121 - docs-devsite/firestore_.documentchange.md | 68 - docs-devsite/firestore_.documentdata.md | 19 - docs-devsite/firestore_.documentreference.md | 142 - docs-devsite/firestore_.documentsnapshot.md | 146 - ...restore_.experimentallongpollingoptions.md | 43 - docs-devsite/firestore_.fieldpath.md | 72 - docs-devsite/firestore_.fieldvalue.md | 46 - docs-devsite/firestore_.firestore.md | 68 - .../firestore_.firestoredataconverter.md | 264 - docs-devsite/firestore_.firestoreerror.md | 58 - docs-devsite/firestore_.firestoresettings.md | 130 - docs-devsite/firestore_.geopoint.md | 117 - docs-devsite/firestore_.index.md | 60 - docs-devsite/firestore_.indexconfiguration.md | 48 - docs-devsite/firestore_.indexfield.md | 78 - docs-devsite/firestore_.loadbundletask.md | 94 - .../firestore_.loadbundletaskprogress.md | 79 - docs-devsite/firestore_.md | 2731 - .../firestore_.memorycachesettings.md | 35 - .../firestore_.memoryeagergarbagecollector.md | 37 - docs-devsite/firestore_.memorylocalcache.md | 35 - .../firestore_.memorylrugarbagecollector.md | 37 - .../firestore_.persistencesettings.md | 37 - .../firestore_.persistentcacheindexmanager.md | 37 - .../firestore_.persistentcachesettings.md | 50 - .../firestore_.persistentlocalcache.md | 35 - ...firestore_.persistentmultipletabmanager.md | 33 - .../firestore_.persistentsingletabmanager.md | 33 - ...ore_.persistentsingletabmanagersettings.md | 35 - docs-devsite/firestore_.query.md | 125 - ...restore_.querycompositefilterconstraint.md | 35 - docs-devsite/firestore_.queryconstraint.md | 35 - .../firestore_.querydocumentsnapshot.md | 54 - .../firestore_.queryendatconstraint.md | 36 - .../firestore_.queryfieldfilterconstraint.md | 36 - .../firestore_.querylimitconstraint.md | 36 - .../firestore_.queryorderbyconstraint.md | 38 - docs-devsite/firestore_.querysnapshot.md | 128 - .../firestore_.querystartatconstraint.md | 36 - .../firestore_.snapshotlistenoptions.md | 46 - docs-devsite/firestore_.snapshotmetadata.md | 75 - docs-devsite/firestore_.snapshotoptions.md | 41 - docs-devsite/firestore_.timestamp.md | 243 - docs-devsite/firestore_.transaction.md | 190 - docs-devsite/firestore_.transactionoptions.md | 35 - docs-devsite/firestore_.unsubscribe.md | 19 - docs-devsite/firestore_.writebatch.md | 181 - docs-devsite/firestore_lite.aggregatefield.md | 46 - .../firestore_lite.aggregatequerysnapshot.md | 70 - docs-devsite/firestore_lite.aggregatespec.md | 19 - docs-devsite/firestore_lite.bytes.md | 138 - .../firestore_lite.collectionreference.md | 121 - docs-devsite/firestore_lite.documentdata.md | 19 - .../firestore_lite.documentreference.md | 142 - .../firestore_lite.documentsnapshot.md | 125 - docs-devsite/firestore_lite.fieldpath.md | 72 - docs-devsite/firestore_lite.fieldvalue.md | 46 - docs-devsite/firestore_lite.firestore.md | 68 - .../firestore_lite.firestoredataconverter.md | 261 - docs-devsite/firestore_lite.firestoreerror.md | 58 - docs-devsite/firestore_lite.geopoint.md | 117 - docs-devsite/firestore_lite.md | 1773 - docs-devsite/firestore_lite.query.md | 125 - ...ore_lite.querycompositefilterconstraint.md | 35 - .../firestore_lite.queryconstraint.md | 35 - .../firestore_lite.querydocumentsnapshot.md | 45 - .../firestore_lite.queryendatconstraint.md | 36 - ...restore_lite.queryfieldfilterconstraint.md | 36 - .../firestore_lite.querylimitconstraint.md | 36 - .../firestore_lite.queryorderbyconstraint.md | 38 - docs-devsite/firestore_lite.querysnapshot.md | 96 - .../firestore_lite.querystartatconstraint.md | 36 - docs-devsite/firestore_lite.settings.md | 57 - docs-devsite/firestore_lite.timestamp.md | 243 - docs-devsite/firestore_lite.transaction.md | 190 - .../firestore_lite.transactionoptions.md | 35 - docs-devsite/firestore_lite.writebatch.md | 181 - docs-devsite/functions.functions.md | 57 - docs-devsite/functions.functionserror.md | 47 - .../functions.httpscallableoptions.md | 46 - docs-devsite/functions.httpscallableresult.md | 35 - docs-devsite/functions.md | 166 - docs-devsite/index.md | 32 - docs-devsite/installations.installations.md | 35 - docs-devsite/installations.md | 166 - docs-devsite/messaging.md | 19 - docs-devsite/messaging_.fcmoptions.md | 46 - docs-devsite/messaging_.gettokenoptions.md | 46 - docs-devsite/messaging_.md | 149 - docs-devsite/messaging_.messagepayload.md | 92 - docs-devsite/messaging_.messaging.md | 35 - .../messaging_.notificationpayload.md | 68 - docs-devsite/messaging_sw.fcmoptions.md | 46 - docs-devsite/messaging_sw.gettokenoptions.md | 46 - docs-devsite/messaging_sw.md | 122 - docs-devsite/messaging_sw.messagepayload.md | 92 - docs-devsite/messaging_sw.messaging.md | 35 - .../messaging_sw.notificationpayload.md | 68 - .../performance.firebaseperformance.md | 57 - docs-devsite/performance.md | 98 - .../performance.performancesettings.md | 46 - docs-devsite/performance.performancetrace.md | 228 - docs-devsite/remote-config.md | 351 - docs-devsite/remote-config.remoteconfig.md | 81 - .../remote-config.remoteconfigsettings.md | 46 - docs-devsite/remote-config.value.md | 83 - docs-devsite/storage.firebasestorage.md | 58 - docs-devsite/storage.fullmetadata.md | 135 - docs-devsite/storage.listoptions.md | 46 - docs-devsite/storage.listresult.md | 59 - docs-devsite/storage.md | 558 - docs-devsite/storage.settablemetadata.md | 92 - docs-devsite/storage.storageerror.md | 113 - docs-devsite/storage.storageobserver.md | 51 - docs-devsite/storage.storagereference.md | 112 - docs-devsite/storage.uploadmetadata.md | 36 - docs-devsite/storage.uploadresult.md | 46 - docs-devsite/storage.uploadtask.md | 255 - docs-devsite/storage.uploadtasksnapshot.md | 90 - docs-devsite/vertexai-preview.baseparams.md | 42 - docs-devsite/vertexai-preview.chatsession.md | 138 - docs-devsite/vertexai-preview.citation.md | 78 - .../vertexai-preview.citationmetadata.md | 33 - docs-devsite/vertexai-preview.content.md | 42 - .../vertexai-preview.counttokensrequest.md | 33 - .../vertexai-preview.counttokensresponse.md | 46 - .../vertexai-preview.customerrordata.md | 68 - docs-devsite/vertexai-preview.date_2.md | 51 - ...preview.enhancedgeneratecontentresponse.md | 45 - docs-devsite/vertexai-preview.errordetails.md | 66 - docs-devsite/vertexai-preview.filedata.md | 42 - docs-devsite/vertexai-preview.filedatapart.md | 69 - docs-devsite/vertexai-preview.functioncall.md | 42 - .../vertexai-preview.functioncallingconfig.md | 41 - .../vertexai-preview.functioncallpart.md | 60 - .../vertexai-preview.functiondeclaration.md | 57 - ...texai-preview.functiondeclarationschema.md | 70 - ...eview.functiondeclarationschemaproperty.md | 125 - ...rtexai-preview.functiondeclarationstool.md | 35 - .../vertexai-preview.functionresponse.md | 42 - .../vertexai-preview.functionresponsepart.md | 60 - ...rtexai-preview.generatecontentcandidate.md | 87 - ...vertexai-preview.generatecontentrequest.md | 61 - ...ertexai-preview.generatecontentresponse.md | 51 - .../vertexai-preview.generatecontentresult.md | 33 - ...xai-preview.generatecontentstreamresult.md | 42 - .../vertexai-preview.generationconfig.md | 107 - .../vertexai-preview.generativecontentblob.md | 44 - .../vertexai-preview.generativemodel.md | 201 - .../vertexai-preview.groundingattribution.md | 59 - .../vertexai-preview.groundingmetadata.md | 51 - .../vertexai-preview.inlinedatapart.md | 71 - docs-devsite/vertexai-preview.md | 397 - docs-devsite/vertexai-preview.modelparams.md | 61 - .../vertexai-preview.promptfeedback.md | 51 - .../vertexai-preview.requestoptions.md | 46 - ...xai-preview.retrievedcontextattribution.md | 41 - docs-devsite/vertexai-preview.safetyrating.md | 78 - .../vertexai-preview.safetysetting.md | 51 - docs-devsite/vertexai-preview.segment.md | 50 - .../vertexai-preview.startchatparams.md | 61 - docs-devsite/vertexai-preview.textpart.md | 60 - docs-devsite/vertexai-preview.toolconfig.md | 33 - .../vertexai-preview.usagemetadata.md | 51 - docs-devsite/vertexai-preview.vertexai.md | 44 - .../vertexai-preview.vertexaierror.md | 76 - .../vertexai-preview.vertexaioptions.md | 33 - .../vertexai-preview.videometadata.md | 46 - .../vertexai-preview.webattribution.md | 41 - integration/compat-interop/analytics.test.ts | 35 - integration/compat-interop/app.test.ts | 50 - integration/compat-interop/auth.test.ts | 49 - integration/compat-interop/functions.test.ts | 35 - integration/compat-interop/karma.conf.js | 36 - integration/compat-interop/messaging.test.ts | 35 - integration/compat-interop/package.json | 29 - .../compat-interop/performance.test.ts | 60 - .../compat-interop/remote-config.test.ts | 35 - integration/compat-interop/tsconfig.json | 19 - integration/compat-interop/util.ts | 18 - integration/compat-typings/package.json | 15 - integration/compat-typings/tsconfig.json | 11 - integration/compat-typings/typings.ts | 85 - integration/firebase/karma.conf.js | 36 - integration/firebase/package.json | 25 - integration/firebase/test/namespace.test.ts | 38 - .../firebase/test/namespaceDefinition.json | 231 - integration/firebase/test/typings.d.ts | 21 - integration/firebase/test/validator.js | 109 - integration/firebase/tsconfig.json | 19 - integration/firestore/.gitignore | 1 - integration/firestore/README.md | 14 - integration/firestore/firebase_export.ts | 53 - integration/firestore/gulpfile.js | 102 - integration/firestore/karma.conf.js | 36 - integration/firestore/package.json | 36 - integration/firestore/tsconfig.json | 6 - integration/messaging/download-browsers.js | 30 - integration/messaging/manual-test-server.js | 22 - integration/messaging/package.json | 21 - integration/messaging/test/static/app.js | 127 - .../messaging/test/static/constants.js | 35 - .../test/static/default-sw/index.html | 35 - .../test/static/firebase-messaging-sw.js | 18 - integration/messaging/test/static/helpers.js | 52 - integration/messaging/test/static/sw-base.js | 37 - .../test/static/valid-manifest/index.html | 34 - .../test/static/valid-manifest/manifest.json | 3 - .../valid-vapid-key-modern-sw/index.html | 44 - .../static/valid-vapid-key-modern-sw/sw.js | 37 - .../test/static/valid-vapid-key/index.html | 44 - .../test/static/valid-vapid-key/sw.js | 18 - .../messaging/test/test-receive-background.js | 157 - .../messaging/test/test-receive-foreground.js | 177 - .../messaging/test/test-token-delete.js | 82 - .../messaging/test/test-token-update.js | 88 - .../test/test-useDefaultServiceWorker.js | 64 - .../messaging/test/test-useValidManifest.js | 52 - .../test/utils/checkMessageReceived.js | 66 - .../messaging/test/utils/checkSendResponse.js | 22 - .../messaging/test/utils/clearAppForTest.js | 30 - .../test/utils/createPermittedWebDriver.js | 50 - .../messaging/test/utils/deleteToken.js | 29 - .../messaging/test/utils/forwardTime.js | 29 - integration/messaging/test/utils/getErrors.js | 30 - .../utils/getReceivedBackgroundMessages.js | 55 - .../utils/getReceivedForegroundMessages.js | 31 - .../messaging/test/utils/openNewTab.js | 24 - .../messaging/test/utils/retrieveToken.js | 30 - .../messaging/test/utils/sendMessage.js | 43 - .../messaging/test/utils/test-server.js | 74 - .../messaging/test/utils/triggerGetToken.js | 32 - patches/karma-webpack+5.0.0.patch | 23 - renovate.json | 33 - repo-scripts/api-documenter/CHANGELOG.md | 21 - repo-scripts/api-documenter/README.md | 16 - repo-scripts/api-documenter/gulpfile.js | 22 - repo-scripts/api-documenter/package.json | 38 - .../src/cli/ApiDocumenterCommandLine.ts | 45 - .../api-documenter/src/cli/BaseAction.ts | 197 - .../api-documenter/src/cli/MarkdownAction.ts | 71 - .../api-documenter/src/cli/TocAction.ts | 77 - .../src/documenters/DocumenterConfig.ts | 84 - .../src/documenters/IConfigFile.ts | 112 - .../src/documenters/MarkdownDocumenter.ts | 1228 - .../documenters/MarkdownDocumenterHelpers.ts | 449 - repo-scripts/api-documenter/src/index.ts | 44 - .../src/markdown/CustomMarkdownEmitter.ts | 232 - .../src/markdown/MarkdownEmitter.ts | 319 - .../test/CustomMarkdownEmitter.test.ts | 237 - .../CustomMarkdownEmitter.test.ts.snap | 62 - .../src/nodes/CustomDocNodeKind.ts | 89 - .../src/nodes/DocEmphasisSpan.ts | 58 - .../api-documenter/src/nodes/DocHeading.ts | 62 - .../api-documenter/src/nodes/DocNoteBox.ts | 55 - .../api-documenter/src/nodes/DocTable.ts | 101 - .../api-documenter/src/nodes/DocTableCell.ts | 51 - .../api-documenter/src/nodes/DocTableRow.ts | 86 - .../plugin/IApiDocumenterPluginManifest.ts | 96 - .../src/plugin/MarkdownDocumenterAccessor.ts | 55 - .../src/plugin/MarkdownDocumenterFeature.ts | 124 - .../src/plugin/PluginFeature.ts | 85 - .../api-documenter/src/plugin/PluginLoader.ts | 163 - .../src/schemas/api-documenter-template.json | 92 - .../src/schemas/api-documenter.schema.json | 42 - repo-scripts/api-documenter/src/start.ts | 39 - repo-scripts/api-documenter/src/toc.ts | 108 - .../src/utils/IndentedWriter.ts | 243 - .../api-documenter/src/utils/Utilities.ts | 48 - .../src/utils/test/IndentedWriter.test.ts | 99 - .../__snapshots__/IndentedWriter.test.ts.snap | 46 - repo-scripts/api-documenter/tsconfig.json | 12 - repo-scripts/changelog-generator/.eslintrc.js | 26 - repo-scripts/changelog-generator/README.md | 3 - repo-scripts/changelog-generator/index.ts | 120 - repo-scripts/changelog-generator/package.json | 43 - .../changelog-generator/tsconfig.json | 14 - repo-scripts/prune-dts/.run/AllTests.run.xml | 35 - repo-scripts/prune-dts/extract-public-api.ts | 237 - repo-scripts/prune-dts/package.json | 39 - repo-scripts/prune-dts/prune-dts.test.ts | 99 - repo-scripts/prune-dts/prune-dts.ts | 561 - repo-scripts/prune-dts/tests/dom.input.d.ts | 29 - repo-scripts/prune-dts/tests/dom.output.d.ts | 29 - repo-scripts/prune-dts/tests/error.input.d.ts | 23 - .../prune-dts/tests/error.output.d.ts | 21 - .../prune-dts/tests/firestore.input.d.ts | 5779 - .../prune-dts/tests/firestore.output.d.ts | 2078 - .../tests/hide-constructor.input.d.ts | 22 - .../tests/hide-constructor.output.d.ts | 20 - .../inherits-non-exported-members.input.d.ts | 24 - .../inherits-non-exported-members.output.d.ts | 21 - .../tests/keeps-inheritance.input.d.ts | 27 - .../tests/keeps-inheritance.output.d.ts | 24 - ...nly-modifies-non-exported-types.input.d.ts | 25 - ...ly-modifies-non-exported-types.output.d.ts | 24 - .../tests/private-interface.input.d.ts | 24 - .../tests/private-interface.output.d.ts | 20 - .../tests/propagates-comments.input.d.ts | 41 - .../tests/propagates-comments.output.d.ts | 41 - .../tests/propagates-members.input.d.ts | 27 - .../tests/propagates-members.output.d.ts | 24 - .../prunes-non-exported-types.input.d.ts | 22 - .../prunes-non-exported-types.output.d.ts | 19 - .../tests/prunes-underscores.input.d.ts | 25 - .../tests/prunes-underscores.output.d.ts | 20 - .../references-public-interfaces.input.d.ts | 23 - .../references-public-interfaces.output.d.ts | 21 - .../tests/references-public-types.input.d.ts | 28 - .../tests/references-public-types.output.d.ts | 26 - ...esolves-generics-different-name.input.d.ts | 24 - ...solves-generics-different-name.output.d.ts | 21 - ...es-generics-through-inheritance.input.d.ts | 25 - ...s-generics-through-inheritence.output.d.ts | 24 - .../tests/resolves-generics.input.d.ts | 22 - .../tests/resolves-generics.output.d.ts | 20 - .../prune-dts/tests/swaps-generics.input.d.ts | 24 - .../tests/swaps-generics.output.d.ts | 21 - repo-scripts/prune-dts/tsconfig.eslint.json | 4 - repo-scripts/prune-dts/tsconfig.json | 14 - repo-scripts/size-analysis/.eslintrc.js | 26 - repo-scripts/size-analysis/README.md | 117 - repo-scripts/size-analysis/analysis-helper.ts | 670 - .../size-analysis/analyze-all-bundles.ts | 117 - repo-scripts/size-analysis/bundle-analysis.ts | 500 - .../bundle-definitions/analytics.json | 32 - .../bundle-definitions/app-check.json | 95 - .../bundle-definitions/auth.json | 201 - .../bundle-definitions/database.json | 288 - .../bundle-definitions/firestore-lite.json | 166 - .../bundle-definitions/firestore.json | 328 - .../bundle-definitions/functions.json | 32 - .../bundle-definitions/messaging.json | 33 - .../bundle-definitions/performance.json | 32 - .../bundle-definitions/remote-config.json | 33 - .../bundle-definitions/storage.json | 251 - repo-scripts/size-analysis/bundle/minify.ts | 30 - repo-scripts/size-analysis/bundle/rollup.ts | 57 - repo-scripts/size-analysis/bundle/webpack.ts | 85 - repo-scripts/size-analysis/cli.ts | 92 - .../size-analysis/package-analysis.ts | 134 - repo-scripts/size-analysis/package.json | 61 - repo-scripts/size-analysis/rollup.config.js | 83 - .../size-analysis/test/size-analysis.test.ts | 489 - .../test/test-inputs/assortedImports.ts | 32 - .../size-analysis/test/test-inputs/bar.ts | 60 - .../size-analysis/test/test-inputs/far.ts | 77 - .../size-analysis/test/test-inputs/index.ts | 117 - .../test/test-inputs/subsetExports.ts | 24 - .../size-analysis/test/test-inputs/tar.ts | 24 - .../test/test-inputs/tsconfig.json | 14 - repo-scripts/size-analysis/test/utils.ts | 26 - repo-scripts/size-analysis/tsconfig.json | 16 - repo-scripts/size-analysis/util.ts | 34 - tools/config.js | 103 - tools/pretest.js | 98 - tools/repl.js | 61 - 510 files changed, 204975 deletions(-) delete mode 100644 .changeset/README.md delete mode 100644 .changeset/config.json delete mode 100644 .changeset/late-humans-tan.md delete mode 100644 .changeset/spicy-dragons-pay.md delete mode 100644 .changeset/tender-apes-clap.md delete mode 100644 .opensource/project.json delete mode 100755 .yarn/releases/yarn-1.22.11.cjs delete mode 100644 .yarnrc delete mode 100644 CHANGELOG.md delete mode 100644 LICENSE delete mode 100644 config/.eslintrc.js delete mode 100644 config/api-extractor.json delete mode 100644 config/ci.config.json delete mode 100644 config/database.rules.json delete mode 100644 config/firebase.json delete mode 100644 config/firestore.indexes.json delete mode 100644 config/firestore.rules delete mode 100644 config/functions/index.js delete mode 100644 config/functions/package.json delete mode 100644 config/karma.base.js delete mode 100644 config/karma.saucelabs.js delete mode 100644 config/mocha.browser.opts delete mode 100644 config/mocharc.node.js delete mode 100644 config/tsconfig.base.json delete mode 100644 config/webpack.test.js delete mode 100644 docs-devsite/_toc.yaml delete mode 100644 docs-devsite/analytics.analytics.md delete mode 100644 docs-devsite/analytics.analyticscalloptions.md delete mode 100644 docs-devsite/analytics.analyticssettings.md delete mode 100644 docs-devsite/analytics.consentsettings.md delete mode 100644 docs-devsite/analytics.controlparams.md delete mode 100644 docs-devsite/analytics.customparams.md delete mode 100644 docs-devsite/analytics.eventparams.md delete mode 100644 docs-devsite/analytics.gtagconfigparams.md delete mode 100644 docs-devsite/analytics.item.md delete mode 100644 docs-devsite/analytics.md delete mode 100644 docs-devsite/analytics.promotion.md delete mode 100644 docs-devsite/analytics.settingsoptions.md delete mode 100644 docs-devsite/app-check.appcheck.md delete mode 100644 docs-devsite/app-check.appcheckoptions.md delete mode 100644 docs-devsite/app-check.appchecktoken.md delete mode 100644 docs-devsite/app-check.appchecktokenresult.md delete mode 100644 docs-devsite/app-check.customprovider.md delete mode 100644 docs-devsite/app-check.customprovideroptions.md delete mode 100644 docs-devsite/app-check.md delete mode 100644 docs-devsite/app-check.recaptchaenterpriseprovider.md delete mode 100644 docs-devsite/app-check.recaptchav3provider.md delete mode 100644 docs-devsite/app.firebaseapp.md delete mode 100644 docs-devsite/app.firebaseappsettings.md delete mode 100644 docs-devsite/app.firebaseoptions.md delete mode 100644 docs-devsite/app.firebaseserverapp.md delete mode 100644 docs-devsite/app.firebaseserverappsettings.md delete mode 100644 docs-devsite/app.md delete mode 100644 docs-devsite/auth.actioncodeinfo.md delete mode 100644 docs-devsite/auth.actioncodesettings.md delete mode 100644 docs-devsite/auth.actioncodeurl.md delete mode 100644 docs-devsite/auth.additionaluserinfo.md delete mode 100644 docs-devsite/auth.applicationverifier.md delete mode 100644 docs-devsite/auth.auth.md delete mode 100644 docs-devsite/auth.authcredential.md delete mode 100644 docs-devsite/auth.autherror.md delete mode 100644 docs-devsite/auth.autherrormap.md delete mode 100644 docs-devsite/auth.authprovider.md delete mode 100644 docs-devsite/auth.authsettings.md delete mode 100644 docs-devsite/auth.config.md delete mode 100644 docs-devsite/auth.confirmationresult.md delete mode 100644 docs-devsite/auth.dependencies.md delete mode 100644 docs-devsite/auth.emailauthcredential.md delete mode 100644 docs-devsite/auth.emailauthprovider.md delete mode 100644 docs-devsite/auth.emulatorconfig.md delete mode 100644 docs-devsite/auth.facebookauthprovider.md delete mode 100644 docs-devsite/auth.githubauthprovider.md delete mode 100644 docs-devsite/auth.googleauthprovider.md delete mode 100644 docs-devsite/auth.idtokenresult.md delete mode 100644 docs-devsite/auth.md delete mode 100644 docs-devsite/auth.multifactorassertion.md delete mode 100644 docs-devsite/auth.multifactorerror.md delete mode 100644 docs-devsite/auth.multifactorinfo.md delete mode 100644 docs-devsite/auth.multifactorresolver.md delete mode 100644 docs-devsite/auth.multifactorsession.md delete mode 100644 docs-devsite/auth.multifactoruser.md delete mode 100644 docs-devsite/auth.oauthcredential.md delete mode 100644 docs-devsite/auth.oauthcredentialoptions.md delete mode 100644 docs-devsite/auth.oauthprovider.md delete mode 100644 docs-devsite/auth.parsedtoken.md delete mode 100644 docs-devsite/auth.passwordpolicy.md delete mode 100644 docs-devsite/auth.passwordvalidationstatus.md delete mode 100644 docs-devsite/auth.persistence.md delete mode 100644 docs-devsite/auth.phoneauthcredential.md delete mode 100644 docs-devsite/auth.phoneauthprovider.md delete mode 100644 docs-devsite/auth.phonemultifactorassertion.md delete mode 100644 docs-devsite/auth.phonemultifactorenrollinfooptions.md delete mode 100644 docs-devsite/auth.phonemultifactorgenerator.md delete mode 100644 docs-devsite/auth.phonemultifactorinfo.md delete mode 100644 docs-devsite/auth.phonemultifactorsignininfooptions.md delete mode 100644 docs-devsite/auth.phonesinglefactorinfooptions.md delete mode 100644 docs-devsite/auth.popupredirectresolver.md delete mode 100644 docs-devsite/auth.reactnativeasyncstorage.md delete mode 100644 docs-devsite/auth.recaptchaparameters.md delete mode 100644 docs-devsite/auth.recaptchaverifier.md delete mode 100644 docs-devsite/auth.samlauthprovider.md delete mode 100644 docs-devsite/auth.totpmultifactorassertion.md delete mode 100644 docs-devsite/auth.totpmultifactorgenerator.md delete mode 100644 docs-devsite/auth.totpmultifactorinfo.md delete mode 100644 docs-devsite/auth.totpsecret.md delete mode 100644 docs-devsite/auth.twitterauthprovider.md delete mode 100644 docs-devsite/auth.user.md delete mode 100644 docs-devsite/auth.usercredential.md delete mode 100644 docs-devsite/auth.userinfo.md delete mode 100644 docs-devsite/auth.usermetadata.md delete mode 100644 docs-devsite/data-connect.authtokenprovider.md delete mode 100644 docs-devsite/data-connect.cancellableoperation.md delete mode 100644 docs-devsite/data-connect.connectorconfig.md delete mode 100644 docs-devsite/data-connect.dataconnect.md delete mode 100644 docs-devsite/data-connect.dataconnectoptions.md delete mode 100644 docs-devsite/data-connect.dataconnectresult.md delete mode 100644 docs-devsite/data-connect.dataconnectsubscription.md delete mode 100644 docs-devsite/data-connect.dataconnecttransport.md delete mode 100644 docs-devsite/data-connect.firebaseauthprovider.md delete mode 100644 docs-devsite/data-connect.md delete mode 100644 docs-devsite/data-connect.mutationpromise.md delete mode 100644 docs-devsite/data-connect.mutationref.md delete mode 100644 docs-devsite/data-connect.mutationresponse.md delete mode 100644 docs-devsite/data-connect.mutationresult.md delete mode 100644 docs-devsite/data-connect.operationref.md delete mode 100644 docs-devsite/data-connect.opresult.md delete mode 100644 docs-devsite/data-connect.querypromise.md delete mode 100644 docs-devsite/data-connect.queryref.md delete mode 100644 docs-devsite/data-connect.queryresponse.md delete mode 100644 docs-devsite/data-connect.queryresult.md delete mode 100644 docs-devsite/data-connect.refinfo.md delete mode 100644 docs-devsite/data-connect.sender.md delete mode 100644 docs-devsite/data-connect.serializedref.md delete mode 100644 docs-devsite/data-connect.subscriptionoptions.md delete mode 100644 docs-devsite/data-connect.transportoptions.md delete mode 100644 docs-devsite/database.database.md delete mode 100644 docs-devsite/database.databasereference.md delete mode 100644 docs-devsite/database.datasnapshot.md delete mode 100644 docs-devsite/database.iterateddatasnapshot.md delete mode 100644 docs-devsite/database.listenoptions.md delete mode 100644 docs-devsite/database.md delete mode 100644 docs-devsite/database.ondisconnect.md delete mode 100644 docs-devsite/database.query.md delete mode 100644 docs-devsite/database.queryconstraint.md delete mode 100644 docs-devsite/database.thenablereference.md delete mode 100644 docs-devsite/database.transactionoptions.md delete mode 100644 docs-devsite/database.transactionresult.md delete mode 100644 docs-devsite/firestore.md delete mode 100644 docs-devsite/firestore_.aggregatefield.md delete mode 100644 docs-devsite/firestore_.aggregatequerysnapshot.md delete mode 100644 docs-devsite/firestore_.aggregatespec.md delete mode 100644 docs-devsite/firestore_.bytes.md delete mode 100644 docs-devsite/firestore_.collectionreference.md delete mode 100644 docs-devsite/firestore_.documentchange.md delete mode 100644 docs-devsite/firestore_.documentdata.md delete mode 100644 docs-devsite/firestore_.documentreference.md delete mode 100644 docs-devsite/firestore_.documentsnapshot.md delete mode 100644 docs-devsite/firestore_.experimentallongpollingoptions.md delete mode 100644 docs-devsite/firestore_.fieldpath.md delete mode 100644 docs-devsite/firestore_.fieldvalue.md delete mode 100644 docs-devsite/firestore_.firestore.md delete mode 100644 docs-devsite/firestore_.firestoredataconverter.md delete mode 100644 docs-devsite/firestore_.firestoreerror.md delete mode 100644 docs-devsite/firestore_.firestoresettings.md delete mode 100644 docs-devsite/firestore_.geopoint.md delete mode 100644 docs-devsite/firestore_.index.md delete mode 100644 docs-devsite/firestore_.indexconfiguration.md delete mode 100644 docs-devsite/firestore_.indexfield.md delete mode 100644 docs-devsite/firestore_.loadbundletask.md delete mode 100644 docs-devsite/firestore_.loadbundletaskprogress.md delete mode 100644 docs-devsite/firestore_.md delete mode 100644 docs-devsite/firestore_.memorycachesettings.md delete mode 100644 docs-devsite/firestore_.memoryeagergarbagecollector.md delete mode 100644 docs-devsite/firestore_.memorylocalcache.md delete mode 100644 docs-devsite/firestore_.memorylrugarbagecollector.md delete mode 100644 docs-devsite/firestore_.persistencesettings.md delete mode 100644 docs-devsite/firestore_.persistentcacheindexmanager.md delete mode 100644 docs-devsite/firestore_.persistentcachesettings.md delete mode 100644 docs-devsite/firestore_.persistentlocalcache.md delete mode 100644 docs-devsite/firestore_.persistentmultipletabmanager.md delete mode 100644 docs-devsite/firestore_.persistentsingletabmanager.md delete mode 100644 docs-devsite/firestore_.persistentsingletabmanagersettings.md delete mode 100644 docs-devsite/firestore_.query.md delete mode 100644 docs-devsite/firestore_.querycompositefilterconstraint.md delete mode 100644 docs-devsite/firestore_.queryconstraint.md delete mode 100644 docs-devsite/firestore_.querydocumentsnapshot.md delete mode 100644 docs-devsite/firestore_.queryendatconstraint.md delete mode 100644 docs-devsite/firestore_.queryfieldfilterconstraint.md delete mode 100644 docs-devsite/firestore_.querylimitconstraint.md delete mode 100644 docs-devsite/firestore_.queryorderbyconstraint.md delete mode 100644 docs-devsite/firestore_.querysnapshot.md delete mode 100644 docs-devsite/firestore_.querystartatconstraint.md delete mode 100644 docs-devsite/firestore_.snapshotlistenoptions.md delete mode 100644 docs-devsite/firestore_.snapshotmetadata.md delete mode 100644 docs-devsite/firestore_.snapshotoptions.md delete mode 100644 docs-devsite/firestore_.timestamp.md delete mode 100644 docs-devsite/firestore_.transaction.md delete mode 100644 docs-devsite/firestore_.transactionoptions.md delete mode 100644 docs-devsite/firestore_.unsubscribe.md delete mode 100644 docs-devsite/firestore_.writebatch.md delete mode 100644 docs-devsite/firestore_lite.aggregatefield.md delete mode 100644 docs-devsite/firestore_lite.aggregatequerysnapshot.md delete mode 100644 docs-devsite/firestore_lite.aggregatespec.md delete mode 100644 docs-devsite/firestore_lite.bytes.md delete mode 100644 docs-devsite/firestore_lite.collectionreference.md delete mode 100644 docs-devsite/firestore_lite.documentdata.md delete mode 100644 docs-devsite/firestore_lite.documentreference.md delete mode 100644 docs-devsite/firestore_lite.documentsnapshot.md delete mode 100644 docs-devsite/firestore_lite.fieldpath.md delete mode 100644 docs-devsite/firestore_lite.fieldvalue.md delete mode 100644 docs-devsite/firestore_lite.firestore.md delete mode 100644 docs-devsite/firestore_lite.firestoredataconverter.md delete mode 100644 docs-devsite/firestore_lite.firestoreerror.md delete mode 100644 docs-devsite/firestore_lite.geopoint.md delete mode 100644 docs-devsite/firestore_lite.md delete mode 100644 docs-devsite/firestore_lite.query.md delete mode 100644 docs-devsite/firestore_lite.querycompositefilterconstraint.md delete mode 100644 docs-devsite/firestore_lite.queryconstraint.md delete mode 100644 docs-devsite/firestore_lite.querydocumentsnapshot.md delete mode 100644 docs-devsite/firestore_lite.queryendatconstraint.md delete mode 100644 docs-devsite/firestore_lite.queryfieldfilterconstraint.md delete mode 100644 docs-devsite/firestore_lite.querylimitconstraint.md delete mode 100644 docs-devsite/firestore_lite.queryorderbyconstraint.md delete mode 100644 docs-devsite/firestore_lite.querysnapshot.md delete mode 100644 docs-devsite/firestore_lite.querystartatconstraint.md delete mode 100644 docs-devsite/firestore_lite.settings.md delete mode 100644 docs-devsite/firestore_lite.timestamp.md delete mode 100644 docs-devsite/firestore_lite.transaction.md delete mode 100644 docs-devsite/firestore_lite.transactionoptions.md delete mode 100644 docs-devsite/firestore_lite.writebatch.md delete mode 100644 docs-devsite/functions.functions.md delete mode 100644 docs-devsite/functions.functionserror.md delete mode 100644 docs-devsite/functions.httpscallableoptions.md delete mode 100644 docs-devsite/functions.httpscallableresult.md delete mode 100644 docs-devsite/functions.md delete mode 100644 docs-devsite/index.md delete mode 100644 docs-devsite/installations.installations.md delete mode 100644 docs-devsite/installations.md delete mode 100644 docs-devsite/messaging.md delete mode 100644 docs-devsite/messaging_.fcmoptions.md delete mode 100644 docs-devsite/messaging_.gettokenoptions.md delete mode 100644 docs-devsite/messaging_.md delete mode 100644 docs-devsite/messaging_.messagepayload.md delete mode 100644 docs-devsite/messaging_.messaging.md delete mode 100644 docs-devsite/messaging_.notificationpayload.md delete mode 100644 docs-devsite/messaging_sw.fcmoptions.md delete mode 100644 docs-devsite/messaging_sw.gettokenoptions.md delete mode 100644 docs-devsite/messaging_sw.md delete mode 100644 docs-devsite/messaging_sw.messagepayload.md delete mode 100644 docs-devsite/messaging_sw.messaging.md delete mode 100644 docs-devsite/messaging_sw.notificationpayload.md delete mode 100644 docs-devsite/performance.firebaseperformance.md delete mode 100644 docs-devsite/performance.md delete mode 100644 docs-devsite/performance.performancesettings.md delete mode 100644 docs-devsite/performance.performancetrace.md delete mode 100644 docs-devsite/remote-config.md delete mode 100644 docs-devsite/remote-config.remoteconfig.md delete mode 100644 docs-devsite/remote-config.remoteconfigsettings.md delete mode 100644 docs-devsite/remote-config.value.md delete mode 100644 docs-devsite/storage.firebasestorage.md delete mode 100644 docs-devsite/storage.fullmetadata.md delete mode 100644 docs-devsite/storage.listoptions.md delete mode 100644 docs-devsite/storage.listresult.md delete mode 100644 docs-devsite/storage.md delete mode 100644 docs-devsite/storage.settablemetadata.md delete mode 100644 docs-devsite/storage.storageerror.md delete mode 100644 docs-devsite/storage.storageobserver.md delete mode 100644 docs-devsite/storage.storagereference.md delete mode 100644 docs-devsite/storage.uploadmetadata.md delete mode 100644 docs-devsite/storage.uploadresult.md delete mode 100644 docs-devsite/storage.uploadtask.md delete mode 100644 docs-devsite/storage.uploadtasksnapshot.md delete mode 100644 docs-devsite/vertexai-preview.baseparams.md delete mode 100644 docs-devsite/vertexai-preview.chatsession.md delete mode 100644 docs-devsite/vertexai-preview.citation.md delete mode 100644 docs-devsite/vertexai-preview.citationmetadata.md delete mode 100644 docs-devsite/vertexai-preview.content.md delete mode 100644 docs-devsite/vertexai-preview.counttokensrequest.md delete mode 100644 docs-devsite/vertexai-preview.counttokensresponse.md delete mode 100644 docs-devsite/vertexai-preview.customerrordata.md delete mode 100644 docs-devsite/vertexai-preview.date_2.md delete mode 100644 docs-devsite/vertexai-preview.enhancedgeneratecontentresponse.md delete mode 100644 docs-devsite/vertexai-preview.errordetails.md delete mode 100644 docs-devsite/vertexai-preview.filedata.md delete mode 100644 docs-devsite/vertexai-preview.filedatapart.md delete mode 100644 docs-devsite/vertexai-preview.functioncall.md delete mode 100644 docs-devsite/vertexai-preview.functioncallingconfig.md delete mode 100644 docs-devsite/vertexai-preview.functioncallpart.md delete mode 100644 docs-devsite/vertexai-preview.functiondeclaration.md delete mode 100644 docs-devsite/vertexai-preview.functiondeclarationschema.md delete mode 100644 docs-devsite/vertexai-preview.functiondeclarationschemaproperty.md delete mode 100644 docs-devsite/vertexai-preview.functiondeclarationstool.md delete mode 100644 docs-devsite/vertexai-preview.functionresponse.md delete mode 100644 docs-devsite/vertexai-preview.functionresponsepart.md delete mode 100644 docs-devsite/vertexai-preview.generatecontentcandidate.md delete mode 100644 docs-devsite/vertexai-preview.generatecontentrequest.md delete mode 100644 docs-devsite/vertexai-preview.generatecontentresponse.md delete mode 100644 docs-devsite/vertexai-preview.generatecontentresult.md delete mode 100644 docs-devsite/vertexai-preview.generatecontentstreamresult.md delete mode 100644 docs-devsite/vertexai-preview.generationconfig.md delete mode 100644 docs-devsite/vertexai-preview.generativecontentblob.md delete mode 100644 docs-devsite/vertexai-preview.generativemodel.md delete mode 100644 docs-devsite/vertexai-preview.groundingattribution.md delete mode 100644 docs-devsite/vertexai-preview.groundingmetadata.md delete mode 100644 docs-devsite/vertexai-preview.inlinedatapart.md delete mode 100644 docs-devsite/vertexai-preview.md delete mode 100644 docs-devsite/vertexai-preview.modelparams.md delete mode 100644 docs-devsite/vertexai-preview.promptfeedback.md delete mode 100644 docs-devsite/vertexai-preview.requestoptions.md delete mode 100644 docs-devsite/vertexai-preview.retrievedcontextattribution.md delete mode 100644 docs-devsite/vertexai-preview.safetyrating.md delete mode 100644 docs-devsite/vertexai-preview.safetysetting.md delete mode 100644 docs-devsite/vertexai-preview.segment.md delete mode 100644 docs-devsite/vertexai-preview.startchatparams.md delete mode 100644 docs-devsite/vertexai-preview.textpart.md delete mode 100644 docs-devsite/vertexai-preview.toolconfig.md delete mode 100644 docs-devsite/vertexai-preview.usagemetadata.md delete mode 100644 docs-devsite/vertexai-preview.vertexai.md delete mode 100644 docs-devsite/vertexai-preview.vertexaierror.md delete mode 100644 docs-devsite/vertexai-preview.vertexaioptions.md delete mode 100644 docs-devsite/vertexai-preview.videometadata.md delete mode 100644 docs-devsite/vertexai-preview.webattribution.md delete mode 100644 integration/compat-interop/analytics.test.ts delete mode 100644 integration/compat-interop/app.test.ts delete mode 100644 integration/compat-interop/auth.test.ts delete mode 100644 integration/compat-interop/functions.test.ts delete mode 100644 integration/compat-interop/karma.conf.js delete mode 100644 integration/compat-interop/messaging.test.ts delete mode 100644 integration/compat-interop/package.json delete mode 100644 integration/compat-interop/performance.test.ts delete mode 100644 integration/compat-interop/remote-config.test.ts delete mode 100644 integration/compat-interop/tsconfig.json delete mode 100644 integration/compat-interop/util.ts delete mode 100644 integration/compat-typings/package.json delete mode 100644 integration/compat-typings/tsconfig.json delete mode 100644 integration/compat-typings/typings.ts delete mode 100644 integration/firebase/karma.conf.js delete mode 100644 integration/firebase/package.json delete mode 100644 integration/firebase/test/namespace.test.ts delete mode 100644 integration/firebase/test/namespaceDefinition.json delete mode 100644 integration/firebase/test/typings.d.ts delete mode 100644 integration/firebase/test/validator.js delete mode 100644 integration/firebase/tsconfig.json delete mode 100644 integration/firestore/.gitignore delete mode 100644 integration/firestore/README.md delete mode 100644 integration/firestore/firebase_export.ts delete mode 100644 integration/firestore/gulpfile.js delete mode 100644 integration/firestore/karma.conf.js delete mode 100644 integration/firestore/package.json delete mode 100644 integration/firestore/tsconfig.json delete mode 100644 integration/messaging/download-browsers.js delete mode 100644 integration/messaging/manual-test-server.js delete mode 100644 integration/messaging/package.json delete mode 100644 integration/messaging/test/static/app.js delete mode 100644 integration/messaging/test/static/constants.js delete mode 100644 integration/messaging/test/static/default-sw/index.html delete mode 100644 integration/messaging/test/static/firebase-messaging-sw.js delete mode 100644 integration/messaging/test/static/helpers.js delete mode 100644 integration/messaging/test/static/sw-base.js delete mode 100644 integration/messaging/test/static/valid-manifest/index.html delete mode 100644 integration/messaging/test/static/valid-manifest/manifest.json delete mode 100644 integration/messaging/test/static/valid-vapid-key-modern-sw/index.html delete mode 100644 integration/messaging/test/static/valid-vapid-key-modern-sw/sw.js delete mode 100644 integration/messaging/test/static/valid-vapid-key/index.html delete mode 100644 integration/messaging/test/static/valid-vapid-key/sw.js delete mode 100644 integration/messaging/test/test-receive-background.js delete mode 100644 integration/messaging/test/test-receive-foreground.js delete mode 100644 integration/messaging/test/test-token-delete.js delete mode 100644 integration/messaging/test/test-token-update.js delete mode 100644 integration/messaging/test/test-useDefaultServiceWorker.js delete mode 100644 integration/messaging/test/test-useValidManifest.js delete mode 100644 integration/messaging/test/utils/checkMessageReceived.js delete mode 100644 integration/messaging/test/utils/checkSendResponse.js delete mode 100644 integration/messaging/test/utils/clearAppForTest.js delete mode 100644 integration/messaging/test/utils/createPermittedWebDriver.js delete mode 100644 integration/messaging/test/utils/deleteToken.js delete mode 100644 integration/messaging/test/utils/forwardTime.js delete mode 100644 integration/messaging/test/utils/getErrors.js delete mode 100644 integration/messaging/test/utils/getReceivedBackgroundMessages.js delete mode 100644 integration/messaging/test/utils/getReceivedForegroundMessages.js delete mode 100644 integration/messaging/test/utils/openNewTab.js delete mode 100644 integration/messaging/test/utils/retrieveToken.js delete mode 100644 integration/messaging/test/utils/sendMessage.js delete mode 100644 integration/messaging/test/utils/test-server.js delete mode 100644 integration/messaging/test/utils/triggerGetToken.js delete mode 100644 patches/karma-webpack+5.0.0.patch delete mode 100644 renovate.json delete mode 100644 repo-scripts/api-documenter/CHANGELOG.md delete mode 100644 repo-scripts/api-documenter/README.md delete mode 100644 repo-scripts/api-documenter/gulpfile.js delete mode 100644 repo-scripts/api-documenter/package.json delete mode 100644 repo-scripts/api-documenter/src/cli/ApiDocumenterCommandLine.ts delete mode 100644 repo-scripts/api-documenter/src/cli/BaseAction.ts delete mode 100644 repo-scripts/api-documenter/src/cli/MarkdownAction.ts delete mode 100644 repo-scripts/api-documenter/src/cli/TocAction.ts delete mode 100644 repo-scripts/api-documenter/src/documenters/DocumenterConfig.ts delete mode 100644 repo-scripts/api-documenter/src/documenters/IConfigFile.ts delete mode 100644 repo-scripts/api-documenter/src/documenters/MarkdownDocumenter.ts delete mode 100644 repo-scripts/api-documenter/src/documenters/MarkdownDocumenterHelpers.ts delete mode 100644 repo-scripts/api-documenter/src/index.ts delete mode 100644 repo-scripts/api-documenter/src/markdown/CustomMarkdownEmitter.ts delete mode 100644 repo-scripts/api-documenter/src/markdown/MarkdownEmitter.ts delete mode 100644 repo-scripts/api-documenter/src/markdown/test/CustomMarkdownEmitter.test.ts delete mode 100644 repo-scripts/api-documenter/src/markdown/test/__snapshots__/CustomMarkdownEmitter.test.ts.snap delete mode 100644 repo-scripts/api-documenter/src/nodes/CustomDocNodeKind.ts delete mode 100644 repo-scripts/api-documenter/src/nodes/DocEmphasisSpan.ts delete mode 100644 repo-scripts/api-documenter/src/nodes/DocHeading.ts delete mode 100644 repo-scripts/api-documenter/src/nodes/DocNoteBox.ts delete mode 100644 repo-scripts/api-documenter/src/nodes/DocTable.ts delete mode 100644 repo-scripts/api-documenter/src/nodes/DocTableCell.ts delete mode 100644 repo-scripts/api-documenter/src/nodes/DocTableRow.ts delete mode 100644 repo-scripts/api-documenter/src/plugin/IApiDocumenterPluginManifest.ts delete mode 100644 repo-scripts/api-documenter/src/plugin/MarkdownDocumenterAccessor.ts delete mode 100644 repo-scripts/api-documenter/src/plugin/MarkdownDocumenterFeature.ts delete mode 100644 repo-scripts/api-documenter/src/plugin/PluginFeature.ts delete mode 100644 repo-scripts/api-documenter/src/plugin/PluginLoader.ts delete mode 100644 repo-scripts/api-documenter/src/schemas/api-documenter-template.json delete mode 100644 repo-scripts/api-documenter/src/schemas/api-documenter.schema.json delete mode 100644 repo-scripts/api-documenter/src/start.ts delete mode 100644 repo-scripts/api-documenter/src/toc.ts delete mode 100644 repo-scripts/api-documenter/src/utils/IndentedWriter.ts delete mode 100644 repo-scripts/api-documenter/src/utils/Utilities.ts delete mode 100644 repo-scripts/api-documenter/src/utils/test/IndentedWriter.test.ts delete mode 100644 repo-scripts/api-documenter/src/utils/test/__snapshots__/IndentedWriter.test.ts.snap delete mode 100644 repo-scripts/api-documenter/tsconfig.json delete mode 100644 repo-scripts/changelog-generator/.eslintrc.js delete mode 100644 repo-scripts/changelog-generator/README.md delete mode 100644 repo-scripts/changelog-generator/index.ts delete mode 100644 repo-scripts/changelog-generator/package.json delete mode 100644 repo-scripts/changelog-generator/tsconfig.json delete mode 100644 repo-scripts/prune-dts/.run/AllTests.run.xml delete mode 100644 repo-scripts/prune-dts/extract-public-api.ts delete mode 100644 repo-scripts/prune-dts/package.json delete mode 100644 repo-scripts/prune-dts/prune-dts.test.ts delete mode 100644 repo-scripts/prune-dts/prune-dts.ts delete mode 100644 repo-scripts/prune-dts/tests/dom.input.d.ts delete mode 100644 repo-scripts/prune-dts/tests/dom.output.d.ts delete mode 100644 repo-scripts/prune-dts/tests/error.input.d.ts delete mode 100644 repo-scripts/prune-dts/tests/error.output.d.ts delete mode 100644 repo-scripts/prune-dts/tests/firestore.input.d.ts delete mode 100644 repo-scripts/prune-dts/tests/firestore.output.d.ts delete mode 100644 repo-scripts/prune-dts/tests/hide-constructor.input.d.ts delete mode 100644 repo-scripts/prune-dts/tests/hide-constructor.output.d.ts delete mode 100644 repo-scripts/prune-dts/tests/inherits-non-exported-members.input.d.ts delete mode 100644 repo-scripts/prune-dts/tests/inherits-non-exported-members.output.d.ts delete mode 100644 repo-scripts/prune-dts/tests/keeps-inheritance.input.d.ts delete mode 100644 repo-scripts/prune-dts/tests/keeps-inheritance.output.d.ts delete mode 100644 repo-scripts/prune-dts/tests/only-modifies-non-exported-types.input.d.ts delete mode 100644 repo-scripts/prune-dts/tests/only-modifies-non-exported-types.output.d.ts delete mode 100644 repo-scripts/prune-dts/tests/private-interface.input.d.ts delete mode 100644 repo-scripts/prune-dts/tests/private-interface.output.d.ts delete mode 100644 repo-scripts/prune-dts/tests/propagates-comments.input.d.ts delete mode 100644 repo-scripts/prune-dts/tests/propagates-comments.output.d.ts delete mode 100644 repo-scripts/prune-dts/tests/propagates-members.input.d.ts delete mode 100644 repo-scripts/prune-dts/tests/propagates-members.output.d.ts delete mode 100644 repo-scripts/prune-dts/tests/prunes-non-exported-types.input.d.ts delete mode 100644 repo-scripts/prune-dts/tests/prunes-non-exported-types.output.d.ts delete mode 100644 repo-scripts/prune-dts/tests/prunes-underscores.input.d.ts delete mode 100644 repo-scripts/prune-dts/tests/prunes-underscores.output.d.ts delete mode 100644 repo-scripts/prune-dts/tests/references-public-interfaces.input.d.ts delete mode 100644 repo-scripts/prune-dts/tests/references-public-interfaces.output.d.ts delete mode 100644 repo-scripts/prune-dts/tests/references-public-types.input.d.ts delete mode 100644 repo-scripts/prune-dts/tests/references-public-types.output.d.ts delete mode 100644 repo-scripts/prune-dts/tests/resolves-generics-different-name.input.d.ts delete mode 100644 repo-scripts/prune-dts/tests/resolves-generics-different-name.output.d.ts delete mode 100644 repo-scripts/prune-dts/tests/resolves-generics-through-inheritance.input.d.ts delete mode 100644 repo-scripts/prune-dts/tests/resolves-generics-through-inheritence.output.d.ts delete mode 100644 repo-scripts/prune-dts/tests/resolves-generics.input.d.ts delete mode 100644 repo-scripts/prune-dts/tests/resolves-generics.output.d.ts delete mode 100644 repo-scripts/prune-dts/tests/swaps-generics.input.d.ts delete mode 100644 repo-scripts/prune-dts/tests/swaps-generics.output.d.ts delete mode 100644 repo-scripts/prune-dts/tsconfig.eslint.json delete mode 100644 repo-scripts/prune-dts/tsconfig.json delete mode 100644 repo-scripts/size-analysis/.eslintrc.js delete mode 100644 repo-scripts/size-analysis/README.md delete mode 100644 repo-scripts/size-analysis/analysis-helper.ts delete mode 100644 repo-scripts/size-analysis/analyze-all-bundles.ts delete mode 100644 repo-scripts/size-analysis/bundle-analysis.ts delete mode 100644 repo-scripts/size-analysis/bundle-definitions/analytics.json delete mode 100644 repo-scripts/size-analysis/bundle-definitions/app-check.json delete mode 100644 repo-scripts/size-analysis/bundle-definitions/auth.json delete mode 100644 repo-scripts/size-analysis/bundle-definitions/database.json delete mode 100644 repo-scripts/size-analysis/bundle-definitions/firestore-lite.json delete mode 100644 repo-scripts/size-analysis/bundle-definitions/firestore.json delete mode 100644 repo-scripts/size-analysis/bundle-definitions/functions.json delete mode 100644 repo-scripts/size-analysis/bundle-definitions/messaging.json delete mode 100644 repo-scripts/size-analysis/bundle-definitions/performance.json delete mode 100644 repo-scripts/size-analysis/bundle-definitions/remote-config.json delete mode 100644 repo-scripts/size-analysis/bundle-definitions/storage.json delete mode 100644 repo-scripts/size-analysis/bundle/minify.ts delete mode 100644 repo-scripts/size-analysis/bundle/rollup.ts delete mode 100644 repo-scripts/size-analysis/bundle/webpack.ts delete mode 100644 repo-scripts/size-analysis/cli.ts delete mode 100644 repo-scripts/size-analysis/package-analysis.ts delete mode 100644 repo-scripts/size-analysis/package.json delete mode 100644 repo-scripts/size-analysis/rollup.config.js delete mode 100644 repo-scripts/size-analysis/test/size-analysis.test.ts delete mode 100644 repo-scripts/size-analysis/test/test-inputs/assortedImports.ts delete mode 100644 repo-scripts/size-analysis/test/test-inputs/bar.ts delete mode 100644 repo-scripts/size-analysis/test/test-inputs/far.ts delete mode 100644 repo-scripts/size-analysis/test/test-inputs/index.ts delete mode 100644 repo-scripts/size-analysis/test/test-inputs/subsetExports.ts delete mode 100644 repo-scripts/size-analysis/test/test-inputs/tar.ts delete mode 100644 repo-scripts/size-analysis/test/test-inputs/tsconfig.json delete mode 100644 repo-scripts/size-analysis/test/utils.ts delete mode 100644 repo-scripts/size-analysis/tsconfig.json delete mode 100644 repo-scripts/size-analysis/util.ts delete mode 100644 tools/config.js delete mode 100644 tools/pretest.js delete mode 100644 tools/repl.js diff --git a/.changeset/README.md b/.changeset/README.md deleted file mode 100644 index 4f3b76b096b..00000000000 --- a/.changeset/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Changesets - -Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works -with multi-package repos, or single-package repos to help you version and publish your code. You can -find the full documentation for it [in our repository](https://github.com/changesets/changesets) - -We have a quick list of common questions to get you started engaging with this project in -[our documentation](https://github.com/changesets/changesets/blob/master/docs/common-questions.md) diff --git a/.changeset/config.json b/.changeset/config.json deleted file mode 100644 index 936ef9d7512..00000000000 --- a/.changeset/config.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "$schema": "https://unpkg.com/@changesets/config@1.1.0/schema.json", - "changelog": [ - "../repo-scripts/changelog-generator", - { "repo": "firebase/firebase-js-sdk" } - ], - "commit": false, - "linked": [], - "access": "public", - "baseBranch": "main", - "updateInternalDependencies": "patch", - "ignore": [ - "firebase-namespace-integration-test", - "firebase-firestore-integration-test", - "firebase-messaging-integration-test", - "firebase-compat-interop-test", - "firebase-compat-typings-test", - "@firebase/changelog-generator", - "firebase-size-analysis", - "@firebase/api-documenter", - "firebase-repo-scripts-prune-dts" - ], - "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": { - "onlyUpdatePeerDependentsWhenOutOfRange": true - }, - "snapshot": { - "useCalculatedVersion": true - } -} diff --git a/.changeset/late-humans-tan.md b/.changeset/late-humans-tan.md deleted file mode 100644 index bd2b2cbaa0e..00000000000 --- a/.changeset/late-humans-tan.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -'@firebase/firestore-compat': patch -'@firebase/database-compat': patch -'@firebase/auth-compat': patch -'@firebase/app-compat': patch -'@firebase/firestore': patch -'@firebase/database': patch -'firebase': patch -'@firebase/auth': patch ---- - -Fixed typos in documentation and some internal variables and parameters. \ No newline at end of file diff --git a/.changeset/spicy-dragons-pay.md b/.changeset/spicy-dragons-pay.md deleted file mode 100644 index 280406da0f0..00000000000 --- a/.changeset/spicy-dragons-pay.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@firebase/app-compat': patch ---- - -Properly handle the case in app-compat checks where `window` exists but `self` does not. (This occurs in Ionic Stencil's Jest preset.) diff --git a/.changeset/tender-apes-clap.md b/.changeset/tender-apes-clap.md deleted file mode 100644 index d3629a245b8..00000000000 --- a/.changeset/tender-apes-clap.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@firebase/analytics': patch -'@firebase/app-check': patch ---- - -Revert introduction of safevalues to prevent issues from arising in Browser CommonJS environments due to ES5 incompatibility. For more information, see [GitHub PR #8395](https://github.com/firebase/firebase-js-sdk/pull/8395) diff --git a/.opensource/project.json b/.opensource/project.json deleted file mode 100644 index 09cb26174a2..00000000000 --- a/.opensource/project.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "Firebase JavaScript SDK", - "type": "library", - "platforms": [ - "Web" - ], - "content": "README.md", - "pages": { - "packages/rxfire/README.md": "RxFire" - }, - "related": [ - "firebase/quickstart-js" - ], - "tabs": [ - { - "title": "Reference", - "href": "https://firebase.google.com/docs/reference/js/" - } - ] -} diff --git a/.yarn/releases/yarn-1.22.11.cjs b/.yarn/releases/yarn-1.22.11.cjs deleted file mode 100755 index 41236bd841f..00000000000 --- a/.yarn/releases/yarn-1.22.11.cjs +++ /dev/null @@ -1,147406 +0,0 @@ -#!/usr/bin/env node -module.exports = -/******/ (function(modules) { // webpackBootstrap -/******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // The require function -/******/ function __webpack_require__(moduleId) { -/******/ -/******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; -/******/ } -/******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, -/******/ exports: {} -/******/ }; -/******/ -/******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ -/******/ // Return the exports of the module -/******/ return module.exports; -/******/ } -/******/ -/******/ -/******/ // expose the modules object (__webpack_modules__) -/******/ __webpack_require__.m = modules; -/******/ -/******/ // expose the module cache -/******/ __webpack_require__.c = installedModules; -/******/ -/******/ // identity function for calling harmony imports with the correct context -/******/ __webpack_require__.i = function(value) { return value; }; -/******/ -/******/ // define getter function for harmony exports -/******/ __webpack_require__.d = function(exports, name, getter) { -/******/ if(!__webpack_require__.o(exports, name)) { -/******/ Object.defineProperty(exports, name, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); -/******/ } -/******/ }; -/******/ -/******/ // getDefaultExport function for compatibility with non-harmony modules -/******/ __webpack_require__.n = function(module) { -/******/ var getter = module && module.__esModule ? -/******/ function getDefault() { return module['default']; } : -/******/ function getModuleExports() { return module; }; -/******/ __webpack_require__.d(getter, 'a', getter); -/******/ return getter; -/******/ }; -/******/ -/******/ // Object.prototype.hasOwnProperty.call -/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; -/******/ -/******/ // __webpack_public_path__ -/******/ __webpack_require__.p = ""; -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 549); -/******/ }) -/************************************************************************/ -/******/ ([ -/* 0 */ -/***/ (function(module, exports) { - -module.exports = require("path"); - -/***/ }), -/* 1 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export (immutable) */ __webpack_exports__["a"] = __extends; -/* unused harmony export __assign */ -/* unused harmony export __rest */ -/* unused harmony export __decorate */ -/* unused harmony export __param */ -/* unused harmony export __metadata */ -/* unused harmony export __awaiter */ -/* unused harmony export __generator */ -/* unused harmony export __exportStar */ -/* unused harmony export __values */ -/* unused harmony export __read */ -/* unused harmony export __spread */ -/* unused harmony export __await */ -/* unused harmony export __asyncGenerator */ -/* unused harmony export __asyncDelegator */ -/* unused harmony export __asyncValues */ -/* unused harmony export __makeTemplateObject */ -/* unused harmony export __importStar */ -/* unused harmony export __importDefault */ -/*! ***************************************************************************** -Copyright (c) Microsoft Corporation. All rights reserved. -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 - -THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED -WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, -MERCHANTABLITY OR NON-INFRINGEMENT. - -See the Apache Version 2.0 License for specific language governing permissions -and limitations under the License. -***************************************************************************** */ -/* global Reflect, Promise */ - -var extendStatics = function(d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; - return extendStatics(d, b); -}; - -function __extends(d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); -} - -var __assign = function() { - __assign = Object.assign || function __assign(t) { - for (var s, i = 1, n = arguments.length; i < n; i++) { - s = arguments[i]; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; - } - return t; - } - return __assign.apply(this, arguments); -} - -function __rest(s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) - t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0) - t[p[i]] = s[p[i]]; - return t; -} - -function __decorate(decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -} - -function __param(paramIndex, decorator) { - return function (target, key) { decorator(target, key, paramIndex); } -} - -function __metadata(metadataKey, metadataValue) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue); -} - -function __awaiter(thisArg, _arguments, P, generator) { - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -} - -function __generator(thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; - return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while (_) try { - if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; - if (y = 0, t) op = [op[0] & 2, t.value]; - switch (op[0]) { - case 0: case 1: t = op; break; - case 4: _.label++; return { value: op[1], done: false }; - case 5: _.label++; y = op[1]; op = [0]; continue; - case 7: op = _.ops.pop(); _.trys.pop(); continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; - } - op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; - } -} - -function __exportStar(m, exports) { - for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; -} - -function __values(o) { - var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0; - if (m) return m.call(o); - return { - next: function () { - if (o && i >= o.length) o = void 0; - return { value: o && o[i++], done: !o }; - } - }; -} - -function __read(o, n) { - var m = typeof Symbol === "function" && o[Symbol.iterator]; - if (!m) return o; - var i = m.call(o), r, ar = [], e; - try { - while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); - } - catch (error) { e = { error: error }; } - finally { - try { - if (r && !r.done && (m = i["return"])) m.call(i); - } - finally { if (e) throw e.error; } - } - return ar; -} - -function __spread() { - for (var ar = [], i = 0; i < arguments.length; i++) - ar = ar.concat(__read(arguments[i])); - return ar; -} - -function __await(v) { - return this instanceof __await ? (this.v = v, this) : new __await(v); -} - -function __asyncGenerator(thisArg, _arguments, generator) { - if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); - var g = generator.apply(thisArg, _arguments || []), i, q = []; - return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i; - function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; } - function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } } - function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); } - function fulfill(value) { resume("next", value); } - function reject(value) { resume("throw", value); } - function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); } -} - -function __asyncDelegator(o) { - var i, p; - return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i; - function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v; } : f; } -} - -function __asyncValues(o) { - if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); - var m = o[Symbol.asyncIterator], i; - return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); - function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } - function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } -} - -function __makeTemplateObject(cooked, raw) { - if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } - return cooked; -}; - -function __importStar(mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result.default = mod; - return result; -} - -function __importDefault(mod) { - return (mod && mod.__esModule) ? mod : { default: mod }; -} - - -/***/ }), -/* 2 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -exports.__esModule = true; - -var _promise = __webpack_require__(227); - -var _promise2 = _interopRequireDefault(_promise); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -exports.default = function (fn) { - return function () { - var gen = fn.apply(this, arguments); - return new _promise2.default(function (resolve, reject) { - function step(key, arg) { - try { - var info = gen[key](arg); - var value = info.value; - } catch (error) { - reject(error); - return; - } - - if (info.done) { - resolve(value); - } else { - return _promise2.default.resolve(value).then(function (value) { - step("next", value); - }, function (err) { - step("throw", err); - }); - } - } - - return step("next"); - }); - }; -}; - -/***/ }), -/* 3 */ -/***/ (function(module, exports) { - -module.exports = require("util"); - -/***/ }), -/* 4 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.getFirstSuitableFolder = exports.readFirstAvailableStream = exports.makeTempDir = exports.hardlinksWork = exports.writeFilePreservingEol = exports.getFileSizeOnDisk = exports.walk = exports.symlink = exports.find = exports.readJsonAndFile = exports.readJson = exports.readFileAny = exports.hardlinkBulk = exports.copyBulk = exports.unlink = exports.glob = exports.link = exports.chmod = exports.lstat = exports.exists = exports.mkdirp = exports.stat = exports.access = exports.rename = exports.readdir = exports.realpath = exports.readlink = exports.writeFile = exports.open = exports.readFileBuffer = exports.lockQueue = exports.constants = undefined; - -var _asyncToGenerator2; - -function _load_asyncToGenerator() { - return _asyncToGenerator2 = _interopRequireDefault(__webpack_require__(2)); -} - -let buildActionsForCopy = (() => { - var _ref = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (queue, events, possibleExtraneous, reporter) { - - // - let build = (() => { - var _ref5 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (data) { - const src = data.src, - dest = data.dest, - type = data.type; - - const onFresh = data.onFresh || noop; - const onDone = data.onDone || noop; - - // TODO https://github.com/yarnpkg/yarn/issues/3751 - // related to bundled dependencies handling - if (files.has(dest.toLowerCase())) { - reporter.verbose(`The case-insensitive file ${dest} shouldn't be copied twice in one bulk copy`); - } else { - files.add(dest.toLowerCase()); - } - - if (type === 'symlink') { - yield mkdirp((_path || _load_path()).default.dirname(dest)); - onFresh(); - actions.symlink.push({ - dest, - linkname: src - }); - onDone(); - return; - } - - if (events.ignoreBasenames.indexOf((_path || _load_path()).default.basename(src)) >= 0) { - // ignored file - return; - } - - const srcStat = yield lstat(src); - let srcFiles; - - if (srcStat.isDirectory()) { - srcFiles = yield readdir(src); - } - - let destStat; - try { - // try accessing the destination - destStat = yield lstat(dest); - } catch (e) { - // proceed if destination doesn't exist, otherwise error - if (e.code !== 'ENOENT') { - throw e; - } - } - - // if destination exists - if (destStat) { - const bothSymlinks = srcStat.isSymbolicLink() && destStat.isSymbolicLink(); - const bothFolders = srcStat.isDirectory() && destStat.isDirectory(); - const bothFiles = srcStat.isFile() && destStat.isFile(); - - // EINVAL access errors sometimes happen which shouldn't because node shouldn't be giving - // us modes that aren't valid. investigate this, it's generally safe to proceed. - - /* if (srcStat.mode !== destStat.mode) { - try { - await access(dest, srcStat.mode); - } catch (err) {} - } */ - - if (bothFiles && artifactFiles.has(dest)) { - // this file gets changed during build, likely by a custom install script. Don't bother checking it. - onDone(); - reporter.verbose(reporter.lang('verboseFileSkipArtifact', src)); - return; - } - - if (bothFiles && srcStat.size === destStat.size && (0, (_fsNormalized || _load_fsNormalized()).fileDatesEqual)(srcStat.mtime, destStat.mtime)) { - // we can safely assume this is the same file - onDone(); - reporter.verbose(reporter.lang('verboseFileSkip', src, dest, srcStat.size, +srcStat.mtime)); - return; - } - - if (bothSymlinks) { - const srcReallink = yield readlink(src); - if (srcReallink === (yield readlink(dest))) { - // if both symlinks are the same then we can continue on - onDone(); - reporter.verbose(reporter.lang('verboseFileSkipSymlink', src, dest, srcReallink)); - return; - } - } - - if (bothFolders) { - // mark files that aren't in this folder as possibly extraneous - const destFiles = yield readdir(dest); - invariant(srcFiles, 'src files not initialised'); - - for (var _iterator4 = destFiles, _isArray4 = Array.isArray(_iterator4), _i4 = 0, _iterator4 = _isArray4 ? _iterator4 : _iterator4[Symbol.iterator]();;) { - var _ref6; - - if (_isArray4) { - if (_i4 >= _iterator4.length) break; - _ref6 = _iterator4[_i4++]; - } else { - _i4 = _iterator4.next(); - if (_i4.done) break; - _ref6 = _i4.value; - } - - const file = _ref6; - - if (srcFiles.indexOf(file) < 0) { - const loc = (_path || _load_path()).default.join(dest, file); - possibleExtraneous.add(loc); - - if ((yield lstat(loc)).isDirectory()) { - for (var _iterator5 = yield readdir(loc), _isArray5 = Array.isArray(_iterator5), _i5 = 0, _iterator5 = _isArray5 ? _iterator5 : _iterator5[Symbol.iterator]();;) { - var _ref7; - - if (_isArray5) { - if (_i5 >= _iterator5.length) break; - _ref7 = _iterator5[_i5++]; - } else { - _i5 = _iterator5.next(); - if (_i5.done) break; - _ref7 = _i5.value; - } - - const file = _ref7; - - possibleExtraneous.add((_path || _load_path()).default.join(loc, file)); - } - } - } - } - } - } - - if (destStat && destStat.isSymbolicLink()) { - yield (0, (_fsNormalized || _load_fsNormalized()).unlink)(dest); - destStat = null; - } - - if (srcStat.isSymbolicLink()) { - onFresh(); - const linkname = yield readlink(src); - actions.symlink.push({ - dest, - linkname - }); - onDone(); - } else if (srcStat.isDirectory()) { - if (!destStat) { - reporter.verbose(reporter.lang('verboseFileFolder', dest)); - yield mkdirp(dest); - } - - const destParts = dest.split((_path || _load_path()).default.sep); - while (destParts.length) { - files.add(destParts.join((_path || _load_path()).default.sep).toLowerCase()); - destParts.pop(); - } - - // push all files to queue - invariant(srcFiles, 'src files not initialised'); - let remaining = srcFiles.length; - if (!remaining) { - onDone(); - } - for (var _iterator6 = srcFiles, _isArray6 = Array.isArray(_iterator6), _i6 = 0, _iterator6 = _isArray6 ? _iterator6 : _iterator6[Symbol.iterator]();;) { - var _ref8; - - if (_isArray6) { - if (_i6 >= _iterator6.length) break; - _ref8 = _iterator6[_i6++]; - } else { - _i6 = _iterator6.next(); - if (_i6.done) break; - _ref8 = _i6.value; - } - - const file = _ref8; - - queue.push({ - dest: (_path || _load_path()).default.join(dest, file), - onFresh, - onDone: function (_onDone) { - function onDone() { - return _onDone.apply(this, arguments); - } - - onDone.toString = function () { - return _onDone.toString(); - }; - - return onDone; - }(function () { - if (--remaining === 0) { - onDone(); - } - }), - src: (_path || _load_path()).default.join(src, file) - }); - } - } else if (srcStat.isFile()) { - onFresh(); - actions.file.push({ - src, - dest, - atime: srcStat.atime, - mtime: srcStat.mtime, - mode: srcStat.mode - }); - onDone(); - } else { - throw new Error(`unsure how to copy this: ${src}`); - } - }); - - return function build(_x5) { - return _ref5.apply(this, arguments); - }; - })(); - - const artifactFiles = new Set(events.artifactFiles || []); - const files = new Set(); - - // initialise events - for (var _iterator = queue, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { - var _ref2; - - if (_isArray) { - if (_i >= _iterator.length) break; - _ref2 = _iterator[_i++]; - } else { - _i = _iterator.next(); - if (_i.done) break; - _ref2 = _i.value; - } - - const item = _ref2; - - const onDone = item.onDone; - item.onDone = function () { - events.onProgress(item.dest); - if (onDone) { - onDone(); - } - }; - } - events.onStart(queue.length); - - // start building actions - const actions = { - file: [], - symlink: [], - link: [] - }; - - // custom concurrency logic as we're always executing stacks of CONCURRENT_QUEUE_ITEMS queue items - // at a time due to the requirement to push items onto the queue - while (queue.length) { - const items = queue.splice(0, CONCURRENT_QUEUE_ITEMS); - yield Promise.all(items.map(build)); - } - - // simulate the existence of some files to prevent considering them extraneous - for (var _iterator2 = artifactFiles, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) { - var _ref3; - - if (_isArray2) { - if (_i2 >= _iterator2.length) break; - _ref3 = _iterator2[_i2++]; - } else { - _i2 = _iterator2.next(); - if (_i2.done) break; - _ref3 = _i2.value; - } - - const file = _ref3; - - if (possibleExtraneous.has(file)) { - reporter.verbose(reporter.lang('verboseFilePhantomExtraneous', file)); - possibleExtraneous.delete(file); - } - } - - for (var _iterator3 = possibleExtraneous, _isArray3 = Array.isArray(_iterator3), _i3 = 0, _iterator3 = _isArray3 ? _iterator3 : _iterator3[Symbol.iterator]();;) { - var _ref4; - - if (_isArray3) { - if (_i3 >= _iterator3.length) break; - _ref4 = _iterator3[_i3++]; - } else { - _i3 = _iterator3.next(); - if (_i3.done) break; - _ref4 = _i3.value; - } - - const loc = _ref4; - - if (files.has(loc.toLowerCase())) { - possibleExtraneous.delete(loc); - } - } - - return actions; - }); - - return function buildActionsForCopy(_x, _x2, _x3, _x4) { - return _ref.apply(this, arguments); - }; -})(); - -let buildActionsForHardlink = (() => { - var _ref9 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (queue, events, possibleExtraneous, reporter) { - - // - let build = (() => { - var _ref13 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (data) { - const src = data.src, - dest = data.dest; - - const onFresh = data.onFresh || noop; - const onDone = data.onDone || noop; - if (files.has(dest.toLowerCase())) { - // Fixes issue https://github.com/yarnpkg/yarn/issues/2734 - // When bulk hardlinking we have A -> B structure that we want to hardlink to A1 -> B1, - // package-linker passes that modules A1 and B1 need to be hardlinked, - // the recursive linking algorithm of A1 ends up scheduling files in B1 to be linked twice which will case - // an exception. - onDone(); - return; - } - files.add(dest.toLowerCase()); - - if (events.ignoreBasenames.indexOf((_path || _load_path()).default.basename(src)) >= 0) { - // ignored file - return; - } - - const srcStat = yield lstat(src); - let srcFiles; - - if (srcStat.isDirectory()) { - srcFiles = yield readdir(src); - } - - const destExists = yield exists(dest); - if (destExists) { - const destStat = yield lstat(dest); - - const bothSymlinks = srcStat.isSymbolicLink() && destStat.isSymbolicLink(); - const bothFolders = srcStat.isDirectory() && destStat.isDirectory(); - const bothFiles = srcStat.isFile() && destStat.isFile(); - - if (srcStat.mode !== destStat.mode) { - try { - yield access(dest, srcStat.mode); - } catch (err) { - // EINVAL access errors sometimes happen which shouldn't because node shouldn't be giving - // us modes that aren't valid. investigate this, it's generally safe to proceed. - reporter.verbose(err); - } - } - - if (bothFiles && artifactFiles.has(dest)) { - // this file gets changed during build, likely by a custom install script. Don't bother checking it. - onDone(); - reporter.verbose(reporter.lang('verboseFileSkipArtifact', src)); - return; - } - - // correct hardlink - if (bothFiles && srcStat.ino !== null && srcStat.ino === destStat.ino) { - onDone(); - reporter.verbose(reporter.lang('verboseFileSkip', src, dest, srcStat.ino)); - return; - } - - if (bothSymlinks) { - const srcReallink = yield readlink(src); - if (srcReallink === (yield readlink(dest))) { - // if both symlinks are the same then we can continue on - onDone(); - reporter.verbose(reporter.lang('verboseFileSkipSymlink', src, dest, srcReallink)); - return; - } - } - - if (bothFolders) { - // mark files that aren't in this folder as possibly extraneous - const destFiles = yield readdir(dest); - invariant(srcFiles, 'src files not initialised'); - - for (var _iterator10 = destFiles, _isArray10 = Array.isArray(_iterator10), _i10 = 0, _iterator10 = _isArray10 ? _iterator10 : _iterator10[Symbol.iterator]();;) { - var _ref14; - - if (_isArray10) { - if (_i10 >= _iterator10.length) break; - _ref14 = _iterator10[_i10++]; - } else { - _i10 = _iterator10.next(); - if (_i10.done) break; - _ref14 = _i10.value; - } - - const file = _ref14; - - if (srcFiles.indexOf(file) < 0) { - const loc = (_path || _load_path()).default.join(dest, file); - possibleExtraneous.add(loc); - - if ((yield lstat(loc)).isDirectory()) { - for (var _iterator11 = yield readdir(loc), _isArray11 = Array.isArray(_iterator11), _i11 = 0, _iterator11 = _isArray11 ? _iterator11 : _iterator11[Symbol.iterator]();;) { - var _ref15; - - if (_isArray11) { - if (_i11 >= _iterator11.length) break; - _ref15 = _iterator11[_i11++]; - } else { - _i11 = _iterator11.next(); - if (_i11.done) break; - _ref15 = _i11.value; - } - - const file = _ref15; - - possibleExtraneous.add((_path || _load_path()).default.join(loc, file)); - } - } - } - } - } - } - - if (srcStat.isSymbolicLink()) { - onFresh(); - const linkname = yield readlink(src); - actions.symlink.push({ - dest, - linkname - }); - onDone(); - } else if (srcStat.isDirectory()) { - reporter.verbose(reporter.lang('verboseFileFolder', dest)); - yield mkdirp(dest); - - const destParts = dest.split((_path || _load_path()).default.sep); - while (destParts.length) { - files.add(destParts.join((_path || _load_path()).default.sep).toLowerCase()); - destParts.pop(); - } - - // push all files to queue - invariant(srcFiles, 'src files not initialised'); - let remaining = srcFiles.length; - if (!remaining) { - onDone(); - } - for (var _iterator12 = srcFiles, _isArray12 = Array.isArray(_iterator12), _i12 = 0, _iterator12 = _isArray12 ? _iterator12 : _iterator12[Symbol.iterator]();;) { - var _ref16; - - if (_isArray12) { - if (_i12 >= _iterator12.length) break; - _ref16 = _iterator12[_i12++]; - } else { - _i12 = _iterator12.next(); - if (_i12.done) break; - _ref16 = _i12.value; - } - - const file = _ref16; - - queue.push({ - onFresh, - src: (_path || _load_path()).default.join(src, file), - dest: (_path || _load_path()).default.join(dest, file), - onDone: function (_onDone2) { - function onDone() { - return _onDone2.apply(this, arguments); - } - - onDone.toString = function () { - return _onDone2.toString(); - }; - - return onDone; - }(function () { - if (--remaining === 0) { - onDone(); - } - }) - }); - } - } else if (srcStat.isFile()) { - onFresh(); - actions.link.push({ - src, - dest, - removeDest: destExists - }); - onDone(); - } else { - throw new Error(`unsure how to copy this: ${src}`); - } - }); - - return function build(_x10) { - return _ref13.apply(this, arguments); - }; - })(); - - const artifactFiles = new Set(events.artifactFiles || []); - const files = new Set(); - - // initialise events - for (var _iterator7 = queue, _isArray7 = Array.isArray(_iterator7), _i7 = 0, _iterator7 = _isArray7 ? _iterator7 : _iterator7[Symbol.iterator]();;) { - var _ref10; - - if (_isArray7) { - if (_i7 >= _iterator7.length) break; - _ref10 = _iterator7[_i7++]; - } else { - _i7 = _iterator7.next(); - if (_i7.done) break; - _ref10 = _i7.value; - } - - const item = _ref10; - - const onDone = item.onDone || noop; - item.onDone = function () { - events.onProgress(item.dest); - onDone(); - }; - } - events.onStart(queue.length); - - // start building actions - const actions = { - file: [], - symlink: [], - link: [] - }; - - // custom concurrency logic as we're always executing stacks of CONCURRENT_QUEUE_ITEMS queue items - // at a time due to the requirement to push items onto the queue - while (queue.length) { - const items = queue.splice(0, CONCURRENT_QUEUE_ITEMS); - yield Promise.all(items.map(build)); - } - - // simulate the existence of some files to prevent considering them extraneous - for (var _iterator8 = artifactFiles, _isArray8 = Array.isArray(_iterator8), _i8 = 0, _iterator8 = _isArray8 ? _iterator8 : _iterator8[Symbol.iterator]();;) { - var _ref11; - - if (_isArray8) { - if (_i8 >= _iterator8.length) break; - _ref11 = _iterator8[_i8++]; - } else { - _i8 = _iterator8.next(); - if (_i8.done) break; - _ref11 = _i8.value; - } - - const file = _ref11; - - if (possibleExtraneous.has(file)) { - reporter.verbose(reporter.lang('verboseFilePhantomExtraneous', file)); - possibleExtraneous.delete(file); - } - } - - for (var _iterator9 = possibleExtraneous, _isArray9 = Array.isArray(_iterator9), _i9 = 0, _iterator9 = _isArray9 ? _iterator9 : _iterator9[Symbol.iterator]();;) { - var _ref12; - - if (_isArray9) { - if (_i9 >= _iterator9.length) break; - _ref12 = _iterator9[_i9++]; - } else { - _i9 = _iterator9.next(); - if (_i9.done) break; - _ref12 = _i9.value; - } - - const loc = _ref12; - - if (files.has(loc.toLowerCase())) { - possibleExtraneous.delete(loc); - } - } - - return actions; - }); - - return function buildActionsForHardlink(_x6, _x7, _x8, _x9) { - return _ref9.apply(this, arguments); - }; -})(); - -let copyBulk = exports.copyBulk = (() => { - var _ref17 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (queue, reporter, _events) { - const events = { - onStart: _events && _events.onStart || noop, - onProgress: _events && _events.onProgress || noop, - possibleExtraneous: _events ? _events.possibleExtraneous : new Set(), - ignoreBasenames: _events && _events.ignoreBasenames || [], - artifactFiles: _events && _events.artifactFiles || [] - }; - - const actions = yield buildActionsForCopy(queue, events, events.possibleExtraneous, reporter); - events.onStart(actions.file.length + actions.symlink.length + actions.link.length); - - const fileActions = actions.file; - - const currentlyWriting = new Map(); - - yield (_promise || _load_promise()).queue(fileActions, (() => { - var _ref18 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (data) { - let writePromise; - while (writePromise = currentlyWriting.get(data.dest)) { - yield writePromise; - } - - reporter.verbose(reporter.lang('verboseFileCopy', data.src, data.dest)); - const copier = (0, (_fsNormalized || _load_fsNormalized()).copyFile)(data, function () { - return currentlyWriting.delete(data.dest); - }); - currentlyWriting.set(data.dest, copier); - events.onProgress(data.dest); - return copier; - }); - - return function (_x14) { - return _ref18.apply(this, arguments); - }; - })(), CONCURRENT_QUEUE_ITEMS); - - // we need to copy symlinks last as they could reference files we were copying - const symlinkActions = actions.symlink; - yield (_promise || _load_promise()).queue(symlinkActions, function (data) { - const linkname = (_path || _load_path()).default.resolve((_path || _load_path()).default.dirname(data.dest), data.linkname); - reporter.verbose(reporter.lang('verboseFileSymlink', data.dest, linkname)); - return symlink(linkname, data.dest); - }); - }); - - return function copyBulk(_x11, _x12, _x13) { - return _ref17.apply(this, arguments); - }; -})(); - -let hardlinkBulk = exports.hardlinkBulk = (() => { - var _ref19 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (queue, reporter, _events) { - const events = { - onStart: _events && _events.onStart || noop, - onProgress: _events && _events.onProgress || noop, - possibleExtraneous: _events ? _events.possibleExtraneous : new Set(), - artifactFiles: _events && _events.artifactFiles || [], - ignoreBasenames: [] - }; - - const actions = yield buildActionsForHardlink(queue, events, events.possibleExtraneous, reporter); - events.onStart(actions.file.length + actions.symlink.length + actions.link.length); - - const fileActions = actions.link; - - yield (_promise || _load_promise()).queue(fileActions, (() => { - var _ref20 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (data) { - reporter.verbose(reporter.lang('verboseFileLink', data.src, data.dest)); - if (data.removeDest) { - yield (0, (_fsNormalized || _load_fsNormalized()).unlink)(data.dest); - } - yield link(data.src, data.dest); - }); - - return function (_x18) { - return _ref20.apply(this, arguments); - }; - })(), CONCURRENT_QUEUE_ITEMS); - - // we need to copy symlinks last as they could reference files we were copying - const symlinkActions = actions.symlink; - yield (_promise || _load_promise()).queue(symlinkActions, function (data) { - const linkname = (_path || _load_path()).default.resolve((_path || _load_path()).default.dirname(data.dest), data.linkname); - reporter.verbose(reporter.lang('verboseFileSymlink', data.dest, linkname)); - return symlink(linkname, data.dest); - }); - }); - - return function hardlinkBulk(_x15, _x16, _x17) { - return _ref19.apply(this, arguments); - }; -})(); - -let readFileAny = exports.readFileAny = (() => { - var _ref21 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (files) { - for (var _iterator13 = files, _isArray13 = Array.isArray(_iterator13), _i13 = 0, _iterator13 = _isArray13 ? _iterator13 : _iterator13[Symbol.iterator]();;) { - var _ref22; - - if (_isArray13) { - if (_i13 >= _iterator13.length) break; - _ref22 = _iterator13[_i13++]; - } else { - _i13 = _iterator13.next(); - if (_i13.done) break; - _ref22 = _i13.value; - } - - const file = _ref22; - - if (yield exists(file)) { - return readFile(file); - } - } - return null; - }); - - return function readFileAny(_x19) { - return _ref21.apply(this, arguments); - }; -})(); - -let readJson = exports.readJson = (() => { - var _ref23 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (loc) { - return (yield readJsonAndFile(loc)).object; - }); - - return function readJson(_x20) { - return _ref23.apply(this, arguments); - }; -})(); - -let readJsonAndFile = exports.readJsonAndFile = (() => { - var _ref24 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (loc) { - const file = yield readFile(loc); - try { - return { - object: (0, (_map || _load_map()).default)(JSON.parse(stripBOM(file))), - content: file - }; - } catch (err) { - err.message = `${loc}: ${err.message}`; - throw err; - } - }); - - return function readJsonAndFile(_x21) { - return _ref24.apply(this, arguments); - }; -})(); - -let find = exports.find = (() => { - var _ref25 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (filename, dir) { - const parts = dir.split((_path || _load_path()).default.sep); - - while (parts.length) { - const loc = parts.concat(filename).join((_path || _load_path()).default.sep); - - if (yield exists(loc)) { - return loc; - } else { - parts.pop(); - } - } - - return false; - }); - - return function find(_x22, _x23) { - return _ref25.apply(this, arguments); - }; -})(); - -let symlink = exports.symlink = (() => { - var _ref26 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (src, dest) { - if (process.platform !== 'win32') { - // use relative paths otherwise which will be retained if the directory is moved - src = (_path || _load_path()).default.relative((_path || _load_path()).default.dirname(dest), src); - // When path.relative returns an empty string for the current directory, we should instead use - // '.', which is a valid fs.symlink target. - src = src || '.'; - } - - try { - const stats = yield lstat(dest); - if (stats.isSymbolicLink()) { - const resolved = dest; - if (resolved === src) { - return; - } - } - } catch (err) { - if (err.code !== 'ENOENT') { - throw err; - } - } - - // We use rimraf for unlink which never throws an ENOENT on missing target - yield (0, (_fsNormalized || _load_fsNormalized()).unlink)(dest); - - if (process.platform === 'win32') { - // use directory junctions if possible on win32, this requires absolute paths - yield fsSymlink(src, dest, 'junction'); - } else { - yield fsSymlink(src, dest); - } - }); - - return function symlink(_x24, _x25) { - return _ref26.apply(this, arguments); - }; -})(); - -let walk = exports.walk = (() => { - var _ref27 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (dir, relativeDir, ignoreBasenames = new Set()) { - let files = []; - - let filenames = yield readdir(dir); - if (ignoreBasenames.size) { - filenames = filenames.filter(function (name) { - return !ignoreBasenames.has(name); - }); - } - - for (var _iterator14 = filenames, _isArray14 = Array.isArray(_iterator14), _i14 = 0, _iterator14 = _isArray14 ? _iterator14 : _iterator14[Symbol.iterator]();;) { - var _ref28; - - if (_isArray14) { - if (_i14 >= _iterator14.length) break; - _ref28 = _iterator14[_i14++]; - } else { - _i14 = _iterator14.next(); - if (_i14.done) break; - _ref28 = _i14.value; - } - - const name = _ref28; - - const relative = relativeDir ? (_path || _load_path()).default.join(relativeDir, name) : name; - const loc = (_path || _load_path()).default.join(dir, name); - const stat = yield lstat(loc); - - files.push({ - relative, - basename: name, - absolute: loc, - mtime: +stat.mtime - }); - - if (stat.isDirectory()) { - files = files.concat((yield walk(loc, relative, ignoreBasenames))); - } - } - - return files; - }); - - return function walk(_x26, _x27) { - return _ref27.apply(this, arguments); - }; -})(); - -let getFileSizeOnDisk = exports.getFileSizeOnDisk = (() => { - var _ref29 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (loc) { - const stat = yield lstat(loc); - const size = stat.size, - blockSize = stat.blksize; - - - return Math.ceil(size / blockSize) * blockSize; - }); - - return function getFileSizeOnDisk(_x28) { - return _ref29.apply(this, arguments); - }; -})(); - -let getEolFromFile = (() => { - var _ref30 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (path) { - if (!(yield exists(path))) { - return undefined; - } - - const buffer = yield readFileBuffer(path); - - for (let i = 0; i < buffer.length; ++i) { - if (buffer[i] === cr) { - return '\r\n'; - } - if (buffer[i] === lf) { - return '\n'; - } - } - return undefined; - }); - - return function getEolFromFile(_x29) { - return _ref30.apply(this, arguments); - }; -})(); - -let writeFilePreservingEol = exports.writeFilePreservingEol = (() => { - var _ref31 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (path, data) { - const eol = (yield getEolFromFile(path)) || (_os || _load_os()).default.EOL; - if (eol !== '\n') { - data = data.replace(/\n/g, eol); - } - yield writeFile(path, data); - }); - - return function writeFilePreservingEol(_x30, _x31) { - return _ref31.apply(this, arguments); - }; -})(); - -let hardlinksWork = exports.hardlinksWork = (() => { - var _ref32 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (dir) { - const filename = 'test-file' + Math.random(); - const file = (_path || _load_path()).default.join(dir, filename); - const fileLink = (_path || _load_path()).default.join(dir, filename + '-link'); - try { - yield writeFile(file, 'test'); - yield link(file, fileLink); - } catch (err) { - return false; - } finally { - yield (0, (_fsNormalized || _load_fsNormalized()).unlink)(file); - yield (0, (_fsNormalized || _load_fsNormalized()).unlink)(fileLink); - } - return true; - }); - - return function hardlinksWork(_x32) { - return _ref32.apply(this, arguments); - }; -})(); - -// not a strict polyfill for Node's fs.mkdtemp - - -let makeTempDir = exports.makeTempDir = (() => { - var _ref33 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (prefix) { - const dir = (_path || _load_path()).default.join((_os || _load_os()).default.tmpdir(), `yarn-${prefix || ''}-${Date.now()}-${Math.random()}`); - yield (0, (_fsNormalized || _load_fsNormalized()).unlink)(dir); - yield mkdirp(dir); - return dir; - }); - - return function makeTempDir(_x33) { - return _ref33.apply(this, arguments); - }; -})(); - -let readFirstAvailableStream = exports.readFirstAvailableStream = (() => { - var _ref34 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (paths) { - for (var _iterator15 = paths, _isArray15 = Array.isArray(_iterator15), _i15 = 0, _iterator15 = _isArray15 ? _iterator15 : _iterator15[Symbol.iterator]();;) { - var _ref35; - - if (_isArray15) { - if (_i15 >= _iterator15.length) break; - _ref35 = _iterator15[_i15++]; - } else { - _i15 = _iterator15.next(); - if (_i15.done) break; - _ref35 = _i15.value; - } - - const path = _ref35; - - try { - const fd = yield open(path, 'r'); - return (_fs || _load_fs()).default.createReadStream(path, { fd }); - } catch (err) { - // Try the next one - } - } - return null; - }); - - return function readFirstAvailableStream(_x34) { - return _ref34.apply(this, arguments); - }; -})(); - -let getFirstSuitableFolder = exports.getFirstSuitableFolder = (() => { - var _ref36 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (paths, mode = constants.W_OK | constants.X_OK) { - const result = { - skipped: [], - folder: null - }; - - for (var _iterator16 = paths, _isArray16 = Array.isArray(_iterator16), _i16 = 0, _iterator16 = _isArray16 ? _iterator16 : _iterator16[Symbol.iterator]();;) { - var _ref37; - - if (_isArray16) { - if (_i16 >= _iterator16.length) break; - _ref37 = _iterator16[_i16++]; - } else { - _i16 = _iterator16.next(); - if (_i16.done) break; - _ref37 = _i16.value; - } - - const folder = _ref37; - - try { - yield mkdirp(folder); - yield access(folder, mode); - - result.folder = folder; - - return result; - } catch (error) { - result.skipped.push({ - error, - folder - }); - } - } - return result; - }); - - return function getFirstSuitableFolder(_x35) { - return _ref36.apply(this, arguments); - }; -})(); - -exports.copy = copy; -exports.readFile = readFile; -exports.readFileRaw = readFileRaw; -exports.normalizeOS = normalizeOS; - -var _fs; - -function _load_fs() { - return _fs = _interopRequireDefault(__webpack_require__(5)); -} - -var _glob; - -function _load_glob() { - return _glob = _interopRequireDefault(__webpack_require__(99)); -} - -var _os; - -function _load_os() { - return _os = _interopRequireDefault(__webpack_require__(46)); -} - -var _path; - -function _load_path() { - return _path = _interopRequireDefault(__webpack_require__(0)); -} - -var _blockingQueue; - -function _load_blockingQueue() { - return _blockingQueue = _interopRequireDefault(__webpack_require__(110)); -} - -var _promise; - -function _load_promise() { - return _promise = _interopRequireWildcard(__webpack_require__(50)); -} - -var _promise2; - -function _load_promise2() { - return _promise2 = __webpack_require__(50); -} - -var _map; - -function _load_map() { - return _map = _interopRequireDefault(__webpack_require__(29)); -} - -var _fsNormalized; - -function _load_fsNormalized() { - return _fsNormalized = __webpack_require__(218); -} - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -const constants = exports.constants = typeof (_fs || _load_fs()).default.constants !== 'undefined' ? (_fs || _load_fs()).default.constants : { - R_OK: (_fs || _load_fs()).default.R_OK, - W_OK: (_fs || _load_fs()).default.W_OK, - X_OK: (_fs || _load_fs()).default.X_OK -}; - -const lockQueue = exports.lockQueue = new (_blockingQueue || _load_blockingQueue()).default('fs lock'); - -const readFileBuffer = exports.readFileBuffer = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.readFile); -const open = exports.open = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.open); -const writeFile = exports.writeFile = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.writeFile); -const readlink = exports.readlink = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.readlink); -const realpath = exports.realpath = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.realpath); -const readdir = exports.readdir = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.readdir); -const rename = exports.rename = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.rename); -const access = exports.access = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.access); -const stat = exports.stat = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.stat); -const mkdirp = exports.mkdirp = (0, (_promise2 || _load_promise2()).promisify)(__webpack_require__(145)); -const exists = exports.exists = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.exists, true); -const lstat = exports.lstat = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.lstat); -const chmod = exports.chmod = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.chmod); -const link = exports.link = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.link); -const glob = exports.glob = (0, (_promise2 || _load_promise2()).promisify)((_glob || _load_glob()).default); -exports.unlink = (_fsNormalized || _load_fsNormalized()).unlink; - -// fs.copyFile uses the native file copying instructions on the system, performing much better -// than any JS-based solution and consumes fewer resources. Repeated testing to fine tune the -// concurrency level revealed 128 as the sweet spot on a quad-core, 16 CPU Intel system with SSD. - -const CONCURRENT_QUEUE_ITEMS = (_fs || _load_fs()).default.copyFile ? 128 : 4; - -const fsSymlink = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.symlink); -const invariant = __webpack_require__(9); -const stripBOM = __webpack_require__(160); - -const noop = () => {}; - -function copy(src, dest, reporter) { - return copyBulk([{ src, dest }], reporter); -} - -function _readFile(loc, encoding) { - return new Promise((resolve, reject) => { - (_fs || _load_fs()).default.readFile(loc, encoding, function (err, content) { - if (err) { - reject(err); - } else { - resolve(content); - } - }); - }); -} - -function readFile(loc) { - return _readFile(loc, 'utf8').then(normalizeOS); -} - -function readFileRaw(loc) { - return _readFile(loc, 'binary'); -} - -function normalizeOS(body) { - return body.replace(/\r\n/g, '\n'); -} - -const cr = '\r'.charCodeAt(0); -const lf = '\n'.charCodeAt(0); - -/***/ }), -/* 5 */ -/***/ (function(module, exports) { - -module.exports = require("fs"); - -/***/ }), -/* 6 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -class MessageError extends Error { - constructor(msg, code) { - super(msg); - this.code = code; - } - -} - -exports.MessageError = MessageError; -class ProcessSpawnError extends MessageError { - constructor(msg, code, process) { - super(msg, code); - this.process = process; - } - -} - -exports.ProcessSpawnError = ProcessSpawnError; -class SecurityError extends MessageError {} - -exports.SecurityError = SecurityError; -class ProcessTermError extends MessageError {} - -exports.ProcessTermError = ProcessTermError; -class ResponseError extends Error { - constructor(msg, responseCode) { - super(msg); - this.responseCode = responseCode; - } - -} - -exports.ResponseError = ResponseError; -class OneTimePasswordError extends Error {} -exports.OneTimePasswordError = OneTimePasswordError; - -/***/ }), -/* 7 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return Subscriber; }); -/* unused harmony export SafeSubscriber */ -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_tslib__ = __webpack_require__(1); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__util_isFunction__ = __webpack_require__(154); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__Observer__ = __webpack_require__(420); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__Subscription__ = __webpack_require__(25); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__internal_symbol_rxSubscriber__ = __webpack_require__(321); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__config__ = __webpack_require__(185); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_6__util_hostReportError__ = __webpack_require__(323); -/** PURE_IMPORTS_START tslib,_util_isFunction,_Observer,_Subscription,_internal_symbol_rxSubscriber,_config,_util_hostReportError PURE_IMPORTS_END */ - - - - - - - -var Subscriber = /*@__PURE__*/ (function (_super) { - __WEBPACK_IMPORTED_MODULE_0_tslib__["a" /* __extends */](Subscriber, _super); - function Subscriber(destinationOrNext, error, complete) { - var _this = _super.call(this) || this; - _this.syncErrorValue = null; - _this.syncErrorThrown = false; - _this.syncErrorThrowable = false; - _this.isStopped = false; - _this._parentSubscription = null; - switch (arguments.length) { - case 0: - _this.destination = __WEBPACK_IMPORTED_MODULE_2__Observer__["a" /* empty */]; - break; - case 1: - if (!destinationOrNext) { - _this.destination = __WEBPACK_IMPORTED_MODULE_2__Observer__["a" /* empty */]; - break; - } - if (typeof destinationOrNext === 'object') { - if (destinationOrNext instanceof Subscriber) { - _this.syncErrorThrowable = destinationOrNext.syncErrorThrowable; - _this.destination = destinationOrNext; - destinationOrNext.add(_this); - } - else { - _this.syncErrorThrowable = true; - _this.destination = new SafeSubscriber(_this, destinationOrNext); - } - break; - } - default: - _this.syncErrorThrowable = true; - _this.destination = new SafeSubscriber(_this, destinationOrNext, error, complete); - break; - } - return _this; - } - Subscriber.prototype[__WEBPACK_IMPORTED_MODULE_4__internal_symbol_rxSubscriber__["a" /* rxSubscriber */]] = function () { return this; }; - Subscriber.create = function (next, error, complete) { - var subscriber = new Subscriber(next, error, complete); - subscriber.syncErrorThrowable = false; - return subscriber; - }; - Subscriber.prototype.next = function (value) { - if (!this.isStopped) { - this._next(value); - } - }; - Subscriber.prototype.error = function (err) { - if (!this.isStopped) { - this.isStopped = true; - this._error(err); - } - }; - Subscriber.prototype.complete = function () { - if (!this.isStopped) { - this.isStopped = true; - this._complete(); - } - }; - Subscriber.prototype.unsubscribe = function () { - if (this.closed) { - return; - } - this.isStopped = true; - _super.prototype.unsubscribe.call(this); - }; - Subscriber.prototype._next = function (value) { - this.destination.next(value); - }; - Subscriber.prototype._error = function (err) { - this.destination.error(err); - this.unsubscribe(); - }; - Subscriber.prototype._complete = function () { - this.destination.complete(); - this.unsubscribe(); - }; - Subscriber.prototype._unsubscribeAndRecycle = function () { - var _a = this, _parent = _a._parent, _parents = _a._parents; - this._parent = null; - this._parents = null; - this.unsubscribe(); - this.closed = false; - this.isStopped = false; - this._parent = _parent; - this._parents = _parents; - this._parentSubscription = null; - return this; - }; - return Subscriber; -}(__WEBPACK_IMPORTED_MODULE_3__Subscription__["a" /* Subscription */])); - -var SafeSubscriber = /*@__PURE__*/ (function (_super) { - __WEBPACK_IMPORTED_MODULE_0_tslib__["a" /* __extends */](SafeSubscriber, _super); - function SafeSubscriber(_parentSubscriber, observerOrNext, error, complete) { - var _this = _super.call(this) || this; - _this._parentSubscriber = _parentSubscriber; - var next; - var context = _this; - if (__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_1__util_isFunction__["a" /* isFunction */])(observerOrNext)) { - next = observerOrNext; - } - else if (observerOrNext) { - next = observerOrNext.next; - error = observerOrNext.error; - complete = observerOrNext.complete; - if (observerOrNext !== __WEBPACK_IMPORTED_MODULE_2__Observer__["a" /* empty */]) { - context = Object.create(observerOrNext); - if (__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_1__util_isFunction__["a" /* isFunction */])(context.unsubscribe)) { - _this.add(context.unsubscribe.bind(context)); - } - context.unsubscribe = _this.unsubscribe.bind(_this); - } - } - _this._context = context; - _this._next = next; - _this._error = error; - _this._complete = complete; - return _this; - } - SafeSubscriber.prototype.next = function (value) { - if (!this.isStopped && this._next) { - var _parentSubscriber = this._parentSubscriber; - if (!__WEBPACK_IMPORTED_MODULE_5__config__["a" /* config */].useDeprecatedSynchronousErrorHandling || !_parentSubscriber.syncErrorThrowable) { - this.__tryOrUnsub(this._next, value); - } - else if (this.__tryOrSetError(_parentSubscriber, this._next, value)) { - this.unsubscribe(); - } - } - }; - SafeSubscriber.prototype.error = function (err) { - if (!this.isStopped) { - var _parentSubscriber = this._parentSubscriber; - var useDeprecatedSynchronousErrorHandling = __WEBPACK_IMPORTED_MODULE_5__config__["a" /* config */].useDeprecatedSynchronousErrorHandling; - if (this._error) { - if (!useDeprecatedSynchronousErrorHandling || !_parentSubscriber.syncErrorThrowable) { - this.__tryOrUnsub(this._error, err); - this.unsubscribe(); - } - else { - this.__tryOrSetError(_parentSubscriber, this._error, err); - this.unsubscribe(); - } - } - else if (!_parentSubscriber.syncErrorThrowable) { - this.unsubscribe(); - if (useDeprecatedSynchronousErrorHandling) { - throw err; - } - __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_6__util_hostReportError__["a" /* hostReportError */])(err); - } - else { - if (useDeprecatedSynchronousErrorHandling) { - _parentSubscriber.syncErrorValue = err; - _parentSubscriber.syncErrorThrown = true; - } - else { - __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_6__util_hostReportError__["a" /* hostReportError */])(err); - } - this.unsubscribe(); - } - } - }; - SafeSubscriber.prototype.complete = function () { - var _this = this; - if (!this.isStopped) { - var _parentSubscriber = this._parentSubscriber; - if (this._complete) { - var wrappedComplete = function () { return _this._complete.call(_this._context); }; - if (!__WEBPACK_IMPORTED_MODULE_5__config__["a" /* config */].useDeprecatedSynchronousErrorHandling || !_parentSubscriber.syncErrorThrowable) { - this.__tryOrUnsub(wrappedComplete); - this.unsubscribe(); - } - else { - this.__tryOrSetError(_parentSubscriber, wrappedComplete); - this.unsubscribe(); - } - } - else { - this.unsubscribe(); - } - } - }; - SafeSubscriber.prototype.__tryOrUnsub = function (fn, value) { - try { - fn.call(this._context, value); - } - catch (err) { - this.unsubscribe(); - if (__WEBPACK_IMPORTED_MODULE_5__config__["a" /* config */].useDeprecatedSynchronousErrorHandling) { - throw err; - } - else { - __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_6__util_hostReportError__["a" /* hostReportError */])(err); - } - } - }; - SafeSubscriber.prototype.__tryOrSetError = function (parent, fn, value) { - if (!__WEBPACK_IMPORTED_MODULE_5__config__["a" /* config */].useDeprecatedSynchronousErrorHandling) { - throw new Error('bad call'); - } - try { - fn.call(this._context, value); - } - catch (err) { - if (__WEBPACK_IMPORTED_MODULE_5__config__["a" /* config */].useDeprecatedSynchronousErrorHandling) { - parent.syncErrorValue = err; - parent.syncErrorThrown = true; - return true; - } - else { - __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_6__util_hostReportError__["a" /* hostReportError */])(err); - return true; - } - } - return false; - }; - SafeSubscriber.prototype._unsubscribe = function () { - var _parentSubscriber = this._parentSubscriber; - this._context = null; - this._parentSubscriber = null; - _parentSubscriber.unsubscribe(); - }; - return SafeSubscriber; -}(Subscriber)); - -//# sourceMappingURL=Subscriber.js.map - - -/***/ }), -/* 8 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.getPathKey = getPathKey; -const os = __webpack_require__(46); -const path = __webpack_require__(0); -const userHome = __webpack_require__(67).default; - -var _require = __webpack_require__(225); - -const getCacheDir = _require.getCacheDir, - getConfigDir = _require.getConfigDir, - getDataDir = _require.getDataDir; - -const isWebpackBundle = __webpack_require__(278); - -const DEPENDENCY_TYPES = exports.DEPENDENCY_TYPES = ['devDependencies', 'dependencies', 'optionalDependencies', 'peerDependencies']; -const OWNED_DEPENDENCY_TYPES = exports.OWNED_DEPENDENCY_TYPES = ['devDependencies', 'dependencies', 'optionalDependencies']; - -const RESOLUTIONS = exports.RESOLUTIONS = 'resolutions'; -const MANIFEST_FIELDS = exports.MANIFEST_FIELDS = [RESOLUTIONS, ...DEPENDENCY_TYPES]; - -const SUPPORTED_NODE_VERSIONS = exports.SUPPORTED_NODE_VERSIONS = '^4.8.0 || ^5.7.0 || ^6.2.2 || >=8.0.0'; - -const YARN_REGISTRY = exports.YARN_REGISTRY = 'https://registry.yarnpkg.com'; -const NPM_REGISTRY_RE = exports.NPM_REGISTRY_RE = /https?:\/\/registry\.npmjs\.org/g; - -const YARN_DOCS = exports.YARN_DOCS = 'https://yarnpkg.com/en/docs/cli/'; -const YARN_INSTALLER_SH = exports.YARN_INSTALLER_SH = 'https://yarnpkg.com/install.sh'; -const YARN_INSTALLER_MSI = exports.YARN_INSTALLER_MSI = 'https://yarnpkg.com/latest.msi'; - -const SELF_UPDATE_VERSION_URL = exports.SELF_UPDATE_VERSION_URL = 'https://yarnpkg.com/latest-version'; - -// cache version, bump whenever we make backwards incompatible changes -const CACHE_VERSION = exports.CACHE_VERSION = 6; - -// lockfile version, bump whenever we make backwards incompatible changes -const LOCKFILE_VERSION = exports.LOCKFILE_VERSION = 1; - -// max amount of network requests to perform concurrently -const NETWORK_CONCURRENCY = exports.NETWORK_CONCURRENCY = 8; - -// HTTP timeout used when downloading packages -const NETWORK_TIMEOUT = exports.NETWORK_TIMEOUT = 30 * 1000; // in milliseconds - -// max amount of child processes to execute concurrently -const CHILD_CONCURRENCY = exports.CHILD_CONCURRENCY = 5; - -const REQUIRED_PACKAGE_KEYS = exports.REQUIRED_PACKAGE_KEYS = ['name', 'version', '_uid']; - -function getPreferredCacheDirectories() { - const preferredCacheDirectories = [getCacheDir()]; - - if (process.getuid) { - // $FlowFixMe: process.getuid exists, dammit - preferredCacheDirectories.push(path.join(os.tmpdir(), `.yarn-cache-${process.getuid()}`)); - } - - preferredCacheDirectories.push(path.join(os.tmpdir(), `.yarn-cache`)); - - return preferredCacheDirectories; -} - -const PREFERRED_MODULE_CACHE_DIRECTORIES = exports.PREFERRED_MODULE_CACHE_DIRECTORIES = getPreferredCacheDirectories(); -const CONFIG_DIRECTORY = exports.CONFIG_DIRECTORY = getConfigDir(); -const DATA_DIRECTORY = exports.DATA_DIRECTORY = getDataDir(); -const LINK_REGISTRY_DIRECTORY = exports.LINK_REGISTRY_DIRECTORY = path.join(DATA_DIRECTORY, 'link'); -const GLOBAL_MODULE_DIRECTORY = exports.GLOBAL_MODULE_DIRECTORY = path.join(DATA_DIRECTORY, 'global'); - -const NODE_BIN_PATH = exports.NODE_BIN_PATH = process.execPath; -const YARN_BIN_PATH = exports.YARN_BIN_PATH = getYarnBinPath(); - -// Webpack needs to be configured with node.__dirname/__filename = false -function getYarnBinPath() { - if (isWebpackBundle) { - return __filename; - } else { - return path.join(__dirname, '..', 'bin', 'yarn.js'); - } -} - -const NODE_MODULES_FOLDER = exports.NODE_MODULES_FOLDER = 'node_modules'; -const NODE_PACKAGE_JSON = exports.NODE_PACKAGE_JSON = 'package.json'; - -const PNP_FILENAME = exports.PNP_FILENAME = '.pnp.js'; - -const POSIX_GLOBAL_PREFIX = exports.POSIX_GLOBAL_PREFIX = `${process.env.DESTDIR || ''}/usr/local`; -const FALLBACK_GLOBAL_PREFIX = exports.FALLBACK_GLOBAL_PREFIX = path.join(userHome, '.yarn'); - -const META_FOLDER = exports.META_FOLDER = '.yarn-meta'; -const INTEGRITY_FILENAME = exports.INTEGRITY_FILENAME = '.yarn-integrity'; -const LOCKFILE_FILENAME = exports.LOCKFILE_FILENAME = 'yarn.lock'; -const METADATA_FILENAME = exports.METADATA_FILENAME = '.yarn-metadata.json'; -const TARBALL_FILENAME = exports.TARBALL_FILENAME = '.yarn-tarball.tgz'; -const CLEAN_FILENAME = exports.CLEAN_FILENAME = '.yarnclean'; - -const NPM_LOCK_FILENAME = exports.NPM_LOCK_FILENAME = 'package-lock.json'; -const NPM_SHRINKWRAP_FILENAME = exports.NPM_SHRINKWRAP_FILENAME = 'npm-shrinkwrap.json'; - -const DEFAULT_INDENT = exports.DEFAULT_INDENT = ' '; -const SINGLE_INSTANCE_PORT = exports.SINGLE_INSTANCE_PORT = 31997; -const SINGLE_INSTANCE_FILENAME = exports.SINGLE_INSTANCE_FILENAME = '.yarn-single-instance'; - -const ENV_PATH_KEY = exports.ENV_PATH_KEY = getPathKey(process.platform, process.env); - -function getPathKey(platform, env) { - let pathKey = 'PATH'; - - // windows calls its path "Path" usually, but this is not guaranteed. - if (platform === 'win32') { - pathKey = 'Path'; - - for (const key in env) { - if (key.toLowerCase() === 'path') { - pathKey = key; - } - } - } - - return pathKey; -} - -const VERSION_COLOR_SCHEME = exports.VERSION_COLOR_SCHEME = { - major: 'red', - premajor: 'red', - minor: 'yellow', - preminor: 'yellow', - patch: 'green', - prepatch: 'green', - prerelease: 'red', - unchanged: 'white', - unknown: 'red' -}; - -/***/ }), -/* 9 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/** - * Copyright (c) 2013-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - - - -/** - * Use invariant() to assert state which your program assumes to be true. - * - * Provide sprintf-style format (only %s is supported) and arguments - * to provide information about what broke and what you were - * expecting. - * - * The invariant message will be stripped in production, but the invariant - * will remain to ensure logic does not differ in production. - */ - -var NODE_ENV = process.env.NODE_ENV; - -var invariant = function(condition, format, a, b, c, d, e, f) { - if (NODE_ENV !== 'production') { - if (format === undefined) { - throw new Error('invariant requires an error message argument'); - } - } - - if (!condition) { - var error; - if (format === undefined) { - error = new Error( - 'Minified exception occurred; use the non-minified dev environment ' + - 'for the full error message and additional helpful warnings.' - ); - } else { - var args = [a, b, c, d, e, f]; - var argIndex = 0; - error = new Error( - format.replace(/%s/g, function() { return args[argIndex++]; }) - ); - error.name = 'Invariant Violation'; - } - - error.framesToPop = 1; // we don't care about invariant's own frame - throw error; - } -}; - -module.exports = invariant; - - -/***/ }), -/* 10 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var YAMLException = __webpack_require__(54); - -var TYPE_CONSTRUCTOR_OPTIONS = [ - 'kind', - 'resolve', - 'construct', - 'instanceOf', - 'predicate', - 'represent', - 'defaultStyle', - 'styleAliases' -]; - -var YAML_NODE_KINDS = [ - 'scalar', - 'sequence', - 'mapping' -]; - -function compileStyleAliases(map) { - var result = {}; - - if (map !== null) { - Object.keys(map).forEach(function (style) { - map[style].forEach(function (alias) { - result[String(alias)] = style; - }); - }); - } - - return result; -} - -function Type(tag, options) { - options = options || {}; - - Object.keys(options).forEach(function (name) { - if (TYPE_CONSTRUCTOR_OPTIONS.indexOf(name) === -1) { - throw new YAMLException('Unknown option "' + name + '" is met in definition of "' + tag + '" YAML type.'); - } - }); - - // TODO: Add tag format check. - this.tag = tag; - this.kind = options['kind'] || null; - this.resolve = options['resolve'] || function () { return true; }; - this.construct = options['construct'] || function (data) { return data; }; - this.instanceOf = options['instanceOf'] || null; - this.predicate = options['predicate'] || null; - this.represent = options['represent'] || null; - this.defaultStyle = options['defaultStyle'] || null; - this.styleAliases = compileStyleAliases(options['styleAliases'] || null); - - if (YAML_NODE_KINDS.indexOf(this.kind) === -1) { - throw new YAMLException('Unknown kind "' + this.kind + '" is specified for "' + tag + '" YAML type.'); - } -} - -module.exports = Type; - - -/***/ }), -/* 11 */ -/***/ (function(module, exports) { - -module.exports = require("crypto"); - -/***/ }), -/* 12 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return Observable; }); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__util_canReportError__ = __webpack_require__(322); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__util_toSubscriber__ = __webpack_require__(932); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__internal_symbol_observable__ = __webpack_require__(117); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__util_pipe__ = __webpack_require__(324); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__config__ = __webpack_require__(185); -/** PURE_IMPORTS_START _util_canReportError,_util_toSubscriber,_internal_symbol_observable,_util_pipe,_config PURE_IMPORTS_END */ - - - - - -var Observable = /*@__PURE__*/ (function () { - function Observable(subscribe) { - this._isScalar = false; - if (subscribe) { - this._subscribe = subscribe; - } - } - Observable.prototype.lift = function (operator) { - var observable = new Observable(); - observable.source = this; - observable.operator = operator; - return observable; - }; - Observable.prototype.subscribe = function (observerOrNext, error, complete) { - var operator = this.operator; - var sink = __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_1__util_toSubscriber__["a" /* toSubscriber */])(observerOrNext, error, complete); - if (operator) { - operator.call(sink, this.source); - } - else { - sink.add(this.source || (__WEBPACK_IMPORTED_MODULE_4__config__["a" /* config */].useDeprecatedSynchronousErrorHandling && !sink.syncErrorThrowable) ? - this._subscribe(sink) : - this._trySubscribe(sink)); - } - if (__WEBPACK_IMPORTED_MODULE_4__config__["a" /* config */].useDeprecatedSynchronousErrorHandling) { - if (sink.syncErrorThrowable) { - sink.syncErrorThrowable = false; - if (sink.syncErrorThrown) { - throw sink.syncErrorValue; - } - } - } - return sink; - }; - Observable.prototype._trySubscribe = function (sink) { - try { - return this._subscribe(sink); - } - catch (err) { - if (__WEBPACK_IMPORTED_MODULE_4__config__["a" /* config */].useDeprecatedSynchronousErrorHandling) { - sink.syncErrorThrown = true; - sink.syncErrorValue = err; - } - if (__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__util_canReportError__["a" /* canReportError */])(sink)) { - sink.error(err); - } - else { - console.warn(err); - } - } - }; - Observable.prototype.forEach = function (next, promiseCtor) { - var _this = this; - promiseCtor = getPromiseCtor(promiseCtor); - return new promiseCtor(function (resolve, reject) { - var subscription; - subscription = _this.subscribe(function (value) { - try { - next(value); - } - catch (err) { - reject(err); - if (subscription) { - subscription.unsubscribe(); - } - } - }, reject, resolve); - }); - }; - Observable.prototype._subscribe = function (subscriber) { - var source = this.source; - return source && source.subscribe(subscriber); - }; - Observable.prototype[__WEBPACK_IMPORTED_MODULE_2__internal_symbol_observable__["a" /* observable */]] = function () { - return this; - }; - Observable.prototype.pipe = function () { - var operations = []; - for (var _i = 0; _i < arguments.length; _i++) { - operations[_i] = arguments[_i]; - } - if (operations.length === 0) { - return this; - } - return __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_3__util_pipe__["b" /* pipeFromArray */])(operations)(this); - }; - Observable.prototype.toPromise = function (promiseCtor) { - var _this = this; - promiseCtor = getPromiseCtor(promiseCtor); - return new promiseCtor(function (resolve, reject) { - var value; - _this.subscribe(function (x) { return value = x; }, function (err) { return reject(err); }, function () { return resolve(value); }); - }); - }; - Observable.create = function (subscribe) { - return new Observable(subscribe); - }; - return Observable; -}()); - -function getPromiseCtor(promiseCtor) { - if (!promiseCtor) { - promiseCtor = __WEBPACK_IMPORTED_MODULE_4__config__["a" /* config */].Promise || Promise; - } - if (!promiseCtor) { - throw new Error('no Promise impl found'); - } - return promiseCtor; -} -//# sourceMappingURL=Observable.js.map - - -/***/ }), -/* 13 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return OuterSubscriber; }); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_tslib__ = __webpack_require__(1); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__Subscriber__ = __webpack_require__(7); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ - - -var OuterSubscriber = /*@__PURE__*/ (function (_super) { - __WEBPACK_IMPORTED_MODULE_0_tslib__["a" /* __extends */](OuterSubscriber, _super); - function OuterSubscriber() { - return _super !== null && _super.apply(this, arguments) || this; - } - OuterSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { - this.destination.next(innerValue); - }; - OuterSubscriber.prototype.notifyError = function (error, innerSub) { - this.destination.error(error); - }; - OuterSubscriber.prototype.notifyComplete = function (innerSub) { - this.destination.complete(); - }; - return OuterSubscriber; -}(__WEBPACK_IMPORTED_MODULE_1__Subscriber__["a" /* Subscriber */])); - -//# sourceMappingURL=OuterSubscriber.js.map - - -/***/ }), -/* 14 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export (immutable) */ __webpack_exports__["a"] = subscribeToResult; -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__InnerSubscriber__ = __webpack_require__(84); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__subscribeTo__ = __webpack_require__(446); -/** PURE_IMPORTS_START _InnerSubscriber,_subscribeTo PURE_IMPORTS_END */ - - -function subscribeToResult(outerSubscriber, result, outerValue, outerIndex, destination) { - if (destination === void 0) { - destination = new __WEBPACK_IMPORTED_MODULE_0__InnerSubscriber__["a" /* InnerSubscriber */](outerSubscriber, outerValue, outerIndex); - } - if (destination.closed) { - return; - } - return __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_1__subscribeTo__["a" /* subscribeTo */])(result)(destination); -} -//# sourceMappingURL=subscribeToResult.js.map - - -/***/ }), -/* 15 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/* eslint-disable node/no-deprecated-api */ - - - -var buffer = __webpack_require__(64) -var Buffer = buffer.Buffer - -var safer = {} - -var key - -for (key in buffer) { - if (!buffer.hasOwnProperty(key)) continue - if (key === 'SlowBuffer' || key === 'Buffer') continue - safer[key] = buffer[key] -} - -var Safer = safer.Buffer = {} -for (key in Buffer) { - if (!Buffer.hasOwnProperty(key)) continue - if (key === 'allocUnsafe' || key === 'allocUnsafeSlow') continue - Safer[key] = Buffer[key] -} - -safer.Buffer.prototype = Buffer.prototype - -if (!Safer.from || Safer.from === Uint8Array.from) { - Safer.from = function (value, encodingOrOffset, length) { - if (typeof value === 'number') { - throw new TypeError('The "value" argument must not be of type number. Received type ' + typeof value) - } - if (value && typeof value.length === 'undefined') { - throw new TypeError('The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type ' + typeof value) - } - return Buffer(value, encodingOrOffset, length) - } -} - -if (!Safer.alloc) { - Safer.alloc = function (size, fill, encoding) { - if (typeof size !== 'number') { - throw new TypeError('The "size" argument must be of type number. Received type ' + typeof size) - } - if (size < 0 || size >= 2 * (1 << 30)) { - throw new RangeError('The value "' + size + '" is invalid for option "size"') - } - var buf = Buffer(size) - if (!fill || fill.length === 0) { - buf.fill(0) - } else if (typeof encoding === 'string') { - buf.fill(fill, encoding) - } else { - buf.fill(fill) - } - return buf - } -} - -if (!safer.kStringMaxLength) { - try { - safer.kStringMaxLength = process.binding('buffer').kStringMaxLength - } catch (e) { - // we can't determine kStringMaxLength in environments where process.binding - // is unsupported, so let's not set it - } -} - -if (!safer.constants) { - safer.constants = { - MAX_LENGTH: safer.kMaxLength - } - if (safer.kStringMaxLength) { - safer.constants.MAX_STRING_LENGTH = safer.kStringMaxLength - } -} - -module.exports = safer - - -/***/ }), -/* 16 */ -/***/ (function(module, exports, __webpack_require__) { - -// Copyright (c) 2012, Mark Cavage. All rights reserved. -// Copyright 2015 Joyent, Inc. - -var assert = __webpack_require__(28); -var Stream = __webpack_require__(23).Stream; -var util = __webpack_require__(3); - - -///--- Globals - -/* JSSTYLED */ -var UUID_REGEXP = /^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$/; - - -///--- Internal - -function _capitalize(str) { - return (str.charAt(0).toUpperCase() + str.slice(1)); -} - -function _toss(name, expected, oper, arg, actual) { - throw new assert.AssertionError({ - message: util.format('%s (%s) is required', name, expected), - actual: (actual === undefined) ? typeof (arg) : actual(arg), - expected: expected, - operator: oper || '===', - stackStartFunction: _toss.caller - }); -} - -function _getClass(arg) { - return (Object.prototype.toString.call(arg).slice(8, -1)); -} - -function noop() { - // Why even bother with asserts? -} - - -///--- Exports - -var types = { - bool: { - check: function (arg) { return typeof (arg) === 'boolean'; } - }, - func: { - check: function (arg) { return typeof (arg) === 'function'; } - }, - string: { - check: function (arg) { return typeof (arg) === 'string'; } - }, - object: { - check: function (arg) { - return typeof (arg) === 'object' && arg !== null; - } - }, - number: { - check: function (arg) { - return typeof (arg) === 'number' && !isNaN(arg); - } - }, - finite: { - check: function (arg) { - return typeof (arg) === 'number' && !isNaN(arg) && isFinite(arg); - } - }, - buffer: { - check: function (arg) { return Buffer.isBuffer(arg); }, - operator: 'Buffer.isBuffer' - }, - array: { - check: function (arg) { return Array.isArray(arg); }, - operator: 'Array.isArray' - }, - stream: { - check: function (arg) { return arg instanceof Stream; }, - operator: 'instanceof', - actual: _getClass - }, - date: { - check: function (arg) { return arg instanceof Date; }, - operator: 'instanceof', - actual: _getClass - }, - regexp: { - check: function (arg) { return arg instanceof RegExp; }, - operator: 'instanceof', - actual: _getClass - }, - uuid: { - check: function (arg) { - return typeof (arg) === 'string' && UUID_REGEXP.test(arg); - }, - operator: 'isUUID' - } -}; - -function _setExports(ndebug) { - var keys = Object.keys(types); - var out; - - /* re-export standard assert */ - if (process.env.NODE_NDEBUG) { - out = noop; - } else { - out = function (arg, msg) { - if (!arg) { - _toss(msg, 'true', arg); - } - }; - } - - /* standard checks */ - keys.forEach(function (k) { - if (ndebug) { - out[k] = noop; - return; - } - var type = types[k]; - out[k] = function (arg, msg) { - if (!type.check(arg)) { - _toss(msg, k, type.operator, arg, type.actual); - } - }; - }); - - /* optional checks */ - keys.forEach(function (k) { - var name = 'optional' + _capitalize(k); - if (ndebug) { - out[name] = noop; - return; - } - var type = types[k]; - out[name] = function (arg, msg) { - if (arg === undefined || arg === null) { - return; - } - if (!type.check(arg)) { - _toss(msg, k, type.operator, arg, type.actual); - } - }; - }); - - /* arrayOf checks */ - keys.forEach(function (k) { - var name = 'arrayOf' + _capitalize(k); - if (ndebug) { - out[name] = noop; - return; - } - var type = types[k]; - var expected = '[' + k + ']'; - out[name] = function (arg, msg) { - if (!Array.isArray(arg)) { - _toss(msg, expected, type.operator, arg, type.actual); - } - var i; - for (i = 0; i < arg.length; i++) { - if (!type.check(arg[i])) { - _toss(msg, expected, type.operator, arg, type.actual); - } - } - }; - }); - - /* optionalArrayOf checks */ - keys.forEach(function (k) { - var name = 'optionalArrayOf' + _capitalize(k); - if (ndebug) { - out[name] = noop; - return; - } - var type = types[k]; - var expected = '[' + k + ']'; - out[name] = function (arg, msg) { - if (arg === undefined || arg === null) { - return; - } - if (!Array.isArray(arg)) { - _toss(msg, expected, type.operator, arg, type.actual); - } - var i; - for (i = 0; i < arg.length; i++) { - if (!type.check(arg[i])) { - _toss(msg, expected, type.operator, arg, type.actual); - } - } - }; - }); - - /* re-export built-in assertions */ - Object.keys(assert).forEach(function (k) { - if (k === 'AssertionError') { - out[k] = assert[k]; - return; - } - if (ndebug) { - out[k] = noop; - return; - } - out[k] = assert[k]; - }); - - /* export ourselves (for unit tests _only_) */ - out._setExports = _setExports; - - return out; -} - -module.exports = _setExports(process.env.NODE_NDEBUG); - - -/***/ }), -/* 17 */ -/***/ (function(module, exports) { - -// https://github.com/zloirock/core-js/issues/86#issuecomment-115759028 -var global = module.exports = typeof window != 'undefined' && window.Math == Math - ? window : typeof self != 'undefined' && self.Math == Math ? self - // eslint-disable-next-line no-new-func - : Function('return this')(); -if (typeof __g == 'number') __g = global; // eslint-disable-line no-undef - - -/***/ }), -/* 18 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.sortAlpha = sortAlpha; -exports.sortOptionsByFlags = sortOptionsByFlags; -exports.entries = entries; -exports.removePrefix = removePrefix; -exports.removeSuffix = removeSuffix; -exports.addSuffix = addSuffix; -exports.hyphenate = hyphenate; -exports.camelCase = camelCase; -exports.compareSortedArrays = compareSortedArrays; -exports.sleep = sleep; -const _camelCase = __webpack_require__(230); - -function sortAlpha(a, b) { - // sort alphabetically in a deterministic way - const shortLen = Math.min(a.length, b.length); - for (let i = 0; i < shortLen; i++) { - const aChar = a.charCodeAt(i); - const bChar = b.charCodeAt(i); - if (aChar !== bChar) { - return aChar - bChar; - } - } - return a.length - b.length; -} - -function sortOptionsByFlags(a, b) { - const aOpt = a.flags.replace(/-/g, ''); - const bOpt = b.flags.replace(/-/g, ''); - return sortAlpha(aOpt, bOpt); -} - -function entries(obj) { - const entries = []; - if (obj) { - for (const key in obj) { - entries.push([key, obj[key]]); - } - } - return entries; -} - -function removePrefix(pattern, prefix) { - if (pattern.startsWith(prefix)) { - pattern = pattern.slice(prefix.length); - } - - return pattern; -} - -function removeSuffix(pattern, suffix) { - if (pattern.endsWith(suffix)) { - return pattern.slice(0, -suffix.length); - } - - return pattern; -} - -function addSuffix(pattern, suffix) { - if (!pattern.endsWith(suffix)) { - return pattern + suffix; - } - - return pattern; -} - -function hyphenate(str) { - return str.replace(/[A-Z]/g, match => { - return '-' + match.charAt(0).toLowerCase(); - }); -} - -function camelCase(str) { - if (/[A-Z]/.test(str)) { - return null; - } else { - return _camelCase(str); - } -} - -function compareSortedArrays(array1, array2) { - if (array1.length !== array2.length) { - return false; - } - for (let i = 0, len = array1.length; i < len; i++) { - if (array1[i] !== array2[i]) { - return false; - } - } - return true; -} - -function sleep(ms) { - return new Promise(resolve => { - setTimeout(resolve, ms); - }); -} - -/***/ }), -/* 19 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.stringify = exports.parse = undefined; - -var _asyncToGenerator2; - -function _load_asyncToGenerator() { - return _asyncToGenerator2 = _interopRequireDefault(__webpack_require__(2)); -} - -var _parse; - -function _load_parse() { - return _parse = __webpack_require__(105); -} - -Object.defineProperty(exports, 'parse', { - enumerable: true, - get: function get() { - return _interopRequireDefault(_parse || _load_parse()).default; - } -}); - -var _stringify; - -function _load_stringify() { - return _stringify = __webpack_require__(199); -} - -Object.defineProperty(exports, 'stringify', { - enumerable: true, - get: function get() { - return _interopRequireDefault(_stringify || _load_stringify()).default; - } -}); -exports.implodeEntry = implodeEntry; -exports.explodeEntry = explodeEntry; - -var _misc; - -function _load_misc() { - return _misc = __webpack_require__(18); -} - -var _normalizePattern; - -function _load_normalizePattern() { - return _normalizePattern = __webpack_require__(37); -} - -var _parse2; - -function _load_parse2() { - return _parse2 = _interopRequireDefault(__webpack_require__(105)); -} - -var _constants; - -function _load_constants() { - return _constants = __webpack_require__(8); -} - -var _fs; - -function _load_fs() { - return _fs = _interopRequireWildcard(__webpack_require__(4)); -} - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -const invariant = __webpack_require__(9); - -const path = __webpack_require__(0); -const ssri = __webpack_require__(65); - -function getName(pattern) { - return (0, (_normalizePattern || _load_normalizePattern()).normalizePattern)(pattern).name; -} - -function blankObjectUndefined(obj) { - return obj && Object.keys(obj).length ? obj : undefined; -} - -function keyForRemote(remote) { - return remote.resolved || (remote.reference && remote.hash ? `${remote.reference}#${remote.hash}` : null); -} - -function serializeIntegrity(integrity) { - // We need this because `Integrity.toString()` does not use sorting to ensure a stable string output - // See https://git.io/vx2Hy - return integrity.toString().split(' ').sort().join(' '); -} - -function implodeEntry(pattern, obj) { - const inferredName = getName(pattern); - const integrity = obj.integrity ? serializeIntegrity(obj.integrity) : ''; - const imploded = { - name: inferredName === obj.name ? undefined : obj.name, - version: obj.version, - uid: obj.uid === obj.version ? undefined : obj.uid, - resolved: obj.resolved, - registry: obj.registry === 'npm' ? undefined : obj.registry, - dependencies: blankObjectUndefined(obj.dependencies), - optionalDependencies: blankObjectUndefined(obj.optionalDependencies), - permissions: blankObjectUndefined(obj.permissions), - prebuiltVariants: blankObjectUndefined(obj.prebuiltVariants) - }; - if (integrity) { - imploded.integrity = integrity; - } - return imploded; -} - -function explodeEntry(pattern, obj) { - obj.optionalDependencies = obj.optionalDependencies || {}; - obj.dependencies = obj.dependencies || {}; - obj.uid = obj.uid || obj.version; - obj.permissions = obj.permissions || {}; - obj.registry = obj.registry || 'npm'; - obj.name = obj.name || getName(pattern); - const integrity = obj.integrity; - if (integrity && integrity.isIntegrity) { - obj.integrity = ssri.parse(integrity); - } - return obj; -} - -class Lockfile { - constructor({ cache, source, parseResultType } = {}) { - this.source = source || ''; - this.cache = cache; - this.parseResultType = parseResultType; - } - - // source string if the `cache` was parsed - - - // if true, we're parsing an old yarn file and need to update integrity fields - hasEntriesExistWithoutIntegrity() { - if (!this.cache) { - return false; - } - - for (const key in this.cache) { - // $FlowFixMe - `this.cache` is clearly defined at this point - if (!/^.*@(file:|http)/.test(key) && this.cache[key] && !this.cache[key].integrity) { - return true; - } - } - - return false; - } - - static fromDirectory(dir, reporter) { - return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - // read the manifest in this directory - const lockfileLoc = path.join(dir, (_constants || _load_constants()).LOCKFILE_FILENAME); - - let lockfile; - let rawLockfile = ''; - let parseResult; - - if (yield (_fs || _load_fs()).exists(lockfileLoc)) { - rawLockfile = yield (_fs || _load_fs()).readFile(lockfileLoc); - parseResult = (0, (_parse2 || _load_parse2()).default)(rawLockfile, lockfileLoc); - - if (reporter) { - if (parseResult.type === 'merge') { - reporter.info(reporter.lang('lockfileMerged')); - } else if (parseResult.type === 'conflict') { - reporter.warn(reporter.lang('lockfileConflict')); - } - } - - lockfile = parseResult.object; - } else if (reporter) { - reporter.info(reporter.lang('noLockfileFound')); - } - - if (lockfile && lockfile.__metadata) { - const lockfilev2 = lockfile; - lockfile = {}; - } - - return new Lockfile({ cache: lockfile, source: rawLockfile, parseResultType: parseResult && parseResult.type }); - })(); - } - - getLocked(pattern) { - const cache = this.cache; - if (!cache) { - return undefined; - } - - const shrunk = pattern in cache && cache[pattern]; - - if (typeof shrunk === 'string') { - return this.getLocked(shrunk); - } else if (shrunk) { - explodeEntry(pattern, shrunk); - return shrunk; - } - - return undefined; - } - - removePattern(pattern) { - const cache = this.cache; - if (!cache) { - return; - } - delete cache[pattern]; - } - - getLockfile(patterns) { - const lockfile = {}; - const seen = new Map(); - - // order by name so that lockfile manifest is assigned to the first dependency with this manifest - // the others that have the same remoteKey will just refer to the first - // ordering allows for consistency in lockfile when it is serialized - const sortedPatternsKeys = Object.keys(patterns).sort((_misc || _load_misc()).sortAlpha); - - for (var _iterator = sortedPatternsKeys, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { - var _ref; - - if (_isArray) { - if (_i >= _iterator.length) break; - _ref = _iterator[_i++]; - } else { - _i = _iterator.next(); - if (_i.done) break; - _ref = _i.value; - } - - const pattern = _ref; - - const pkg = patterns[pattern]; - const remote = pkg._remote, - ref = pkg._reference; - - invariant(ref, 'Package is missing a reference'); - invariant(remote, 'Package is missing a remote'); - - const remoteKey = keyForRemote(remote); - const seenPattern = remoteKey && seen.get(remoteKey); - if (seenPattern) { - // no point in duplicating it - lockfile[pattern] = seenPattern; - - // if we're relying on our name being inferred and two of the patterns have - // different inferred names then we need to set it - if (!seenPattern.name && getName(pattern) !== pkg.name) { - seenPattern.name = pkg.name; - } - continue; - } - const obj = implodeEntry(pattern, { - name: pkg.name, - version: pkg.version, - uid: pkg._uid, - resolved: remote.resolved, - integrity: remote.integrity, - registry: remote.registry, - dependencies: pkg.dependencies, - peerDependencies: pkg.peerDependencies, - optionalDependencies: pkg.optionalDependencies, - permissions: ref.permissions, - prebuiltVariants: pkg.prebuiltVariants - }); - - lockfile[pattern] = obj; - - if (remoteKey) { - seen.set(remoteKey, obj); - } - } - - return lockfile; - } -} -exports.default = Lockfile; - -/***/ }), -/* 20 */ -/***/ (function(module, exports, __webpack_require__) { - -var store = __webpack_require__(133)('wks'); -var uid = __webpack_require__(137); -var Symbol = __webpack_require__(17).Symbol; -var USE_SYMBOL = typeof Symbol == 'function'; - -var $exports = module.exports = function (name) { - return store[name] || (store[name] = - USE_SYMBOL && Symbol[name] || (USE_SYMBOL ? Symbol : uid)('Symbol.' + name)); -}; - -$exports.store = store; - - -/***/ }), -/* 21 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -exports.__esModule = true; - -var _assign = __webpack_require__(591); - -var _assign2 = _interopRequireDefault(_assign); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -exports.default = _assign2.default || function (target) { - for (var i = 1; i < arguments.length; i++) { - var source = arguments[i]; - - for (var key in source) { - if (Object.prototype.hasOwnProperty.call(source, key)) { - target[key] = source[key]; - } - } - } - - return target; -}; - -/***/ }), -/* 22 */ -/***/ (function(module, exports) { - -exports = module.exports = SemVer; - -// The debug function is excluded entirely from the minified version. -/* nomin */ var debug; -/* nomin */ if (typeof process === 'object' && - /* nomin */ process.env && - /* nomin */ process.env.NODE_DEBUG && - /* nomin */ /\bsemver\b/i.test(process.env.NODE_DEBUG)) - /* nomin */ debug = function() { - /* nomin */ var args = Array.prototype.slice.call(arguments, 0); - /* nomin */ args.unshift('SEMVER'); - /* nomin */ console.log.apply(console, args); - /* nomin */ }; -/* nomin */ else - /* nomin */ debug = function() {}; - -// Note: this is the semver.org version of the spec that it implements -// Not necessarily the package version of this code. -exports.SEMVER_SPEC_VERSION = '2.0.0'; - -var MAX_LENGTH = 256; -var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; - -// Max safe segment length for coercion. -var MAX_SAFE_COMPONENT_LENGTH = 16; - -// The actual regexps go on exports.re -var re = exports.re = []; -var src = exports.src = []; -var R = 0; - -// The following Regular Expressions can be used for tokenizing, -// validating, and parsing SemVer version strings. - -// ## Numeric Identifier -// A single `0`, or a non-zero digit followed by zero or more digits. - -var NUMERICIDENTIFIER = R++; -src[NUMERICIDENTIFIER] = '0|[1-9]\\d*'; -var NUMERICIDENTIFIERLOOSE = R++; -src[NUMERICIDENTIFIERLOOSE] = '[0-9]+'; - - -// ## Non-numeric Identifier -// Zero or more digits, followed by a letter or hyphen, and then zero or -// more letters, digits, or hyphens. - -var NONNUMERICIDENTIFIER = R++; -src[NONNUMERICIDENTIFIER] = '\\d*[a-zA-Z-][a-zA-Z0-9-]*'; - - -// ## Main Version -// Three dot-separated numeric identifiers. - -var MAINVERSION = R++; -src[MAINVERSION] = '(' + src[NUMERICIDENTIFIER] + ')\\.' + - '(' + src[NUMERICIDENTIFIER] + ')\\.' + - '(' + src[NUMERICIDENTIFIER] + ')'; - -var MAINVERSIONLOOSE = R++; -src[MAINVERSIONLOOSE] = '(' + src[NUMERICIDENTIFIERLOOSE] + ')\\.' + - '(' + src[NUMERICIDENTIFIERLOOSE] + ')\\.' + - '(' + src[NUMERICIDENTIFIERLOOSE] + ')'; - -// ## Pre-release Version Identifier -// A numeric identifier, or a non-numeric identifier. - -var PRERELEASEIDENTIFIER = R++; -src[PRERELEASEIDENTIFIER] = '(?:' + src[NUMERICIDENTIFIER] + - '|' + src[NONNUMERICIDENTIFIER] + ')'; - -var PRERELEASEIDENTIFIERLOOSE = R++; -src[PRERELEASEIDENTIFIERLOOSE] = '(?:' + src[NUMERICIDENTIFIERLOOSE] + - '|' + src[NONNUMERICIDENTIFIER] + ')'; - - -// ## Pre-release Version -// Hyphen, followed by one or more dot-separated pre-release version -// identifiers. - -var PRERELEASE = R++; -src[PRERELEASE] = '(?:-(' + src[PRERELEASEIDENTIFIER] + - '(?:\\.' + src[PRERELEASEIDENTIFIER] + ')*))'; - -var PRERELEASELOOSE = R++; -src[PRERELEASELOOSE] = '(?:-?(' + src[PRERELEASEIDENTIFIERLOOSE] + - '(?:\\.' + src[PRERELEASEIDENTIFIERLOOSE] + ')*))'; - -// ## Build Metadata Identifier -// Any combination of digits, letters, or hyphens. - -var BUILDIDENTIFIER = R++; -src[BUILDIDENTIFIER] = '[0-9A-Za-z-]+'; - -// ## Build Metadata -// Plus sign, followed by one or more period-separated build metadata -// identifiers. - -var BUILD = R++; -src[BUILD] = '(?:\\+(' + src[BUILDIDENTIFIER] + - '(?:\\.' + src[BUILDIDENTIFIER] + ')*))'; - - -// ## Full Version String -// A main version, followed optionally by a pre-release version and -// build metadata. - -// Note that the only major, minor, patch, and pre-release sections of -// the version string are capturing groups. The build metadata is not a -// capturing group, because it should not ever be used in version -// comparison. - -var FULL = R++; -var FULLPLAIN = 'v?' + src[MAINVERSION] + - src[PRERELEASE] + '?' + - src[BUILD] + '?'; - -src[FULL] = '^' + FULLPLAIN + '$'; - -// like full, but allows v1.2.3 and =1.2.3, which people do sometimes. -// also, 1.0.0alpha1 (prerelease without the hyphen) which is pretty -// common in the npm registry. -var LOOSEPLAIN = '[v=\\s]*' + src[MAINVERSIONLOOSE] + - src[PRERELEASELOOSE] + '?' + - src[BUILD] + '?'; - -var LOOSE = R++; -src[LOOSE] = '^' + LOOSEPLAIN + '$'; - -var GTLT = R++; -src[GTLT] = '((?:<|>)?=?)'; - -// Something like "2.*" or "1.2.x". -// Note that "x.x" is a valid xRange identifer, meaning "any version" -// Only the first item is strictly required. -var XRANGEIDENTIFIERLOOSE = R++; -src[XRANGEIDENTIFIERLOOSE] = src[NUMERICIDENTIFIERLOOSE] + '|x|X|\\*'; -var XRANGEIDENTIFIER = R++; -src[XRANGEIDENTIFIER] = src[NUMERICIDENTIFIER] + '|x|X|\\*'; - -var XRANGEPLAIN = R++; -src[XRANGEPLAIN] = '[v=\\s]*(' + src[XRANGEIDENTIFIER] + ')' + - '(?:\\.(' + src[XRANGEIDENTIFIER] + ')' + - '(?:\\.(' + src[XRANGEIDENTIFIER] + ')' + - '(?:' + src[PRERELEASE] + ')?' + - src[BUILD] + '?' + - ')?)?'; - -var XRANGEPLAINLOOSE = R++; -src[XRANGEPLAINLOOSE] = '[v=\\s]*(' + src[XRANGEIDENTIFIERLOOSE] + ')' + - '(?:\\.(' + src[XRANGEIDENTIFIERLOOSE] + ')' + - '(?:\\.(' + src[XRANGEIDENTIFIERLOOSE] + ')' + - '(?:' + src[PRERELEASELOOSE] + ')?' + - src[BUILD] + '?' + - ')?)?'; - -var XRANGE = R++; -src[XRANGE] = '^' + src[GTLT] + '\\s*' + src[XRANGEPLAIN] + '$'; -var XRANGELOOSE = R++; -src[XRANGELOOSE] = '^' + src[GTLT] + '\\s*' + src[XRANGEPLAINLOOSE] + '$'; - -// Coercion. -// Extract anything that could conceivably be a part of a valid semver -var COERCE = R++; -src[COERCE] = '(?:^|[^\\d])' + - '(\\d{1,' + MAX_SAFE_COMPONENT_LENGTH + '})' + - '(?:\\.(\\d{1,' + MAX_SAFE_COMPONENT_LENGTH + '}))?' + - '(?:\\.(\\d{1,' + MAX_SAFE_COMPONENT_LENGTH + '}))?' + - '(?:$|[^\\d])'; - -// Tilde ranges. -// Meaning is "reasonably at or greater than" -var LONETILDE = R++; -src[LONETILDE] = '(?:~>?)'; - -var TILDETRIM = R++; -src[TILDETRIM] = '(\\s*)' + src[LONETILDE] + '\\s+'; -re[TILDETRIM] = new RegExp(src[TILDETRIM], 'g'); -var tildeTrimReplace = '$1~'; - -var TILDE = R++; -src[TILDE] = '^' + src[LONETILDE] + src[XRANGEPLAIN] + '$'; -var TILDELOOSE = R++; -src[TILDELOOSE] = '^' + src[LONETILDE] + src[XRANGEPLAINLOOSE] + '$'; - -// Caret ranges. -// Meaning is "at least and backwards compatible with" -var LONECARET = R++; -src[LONECARET] = '(?:\\^)'; - -var CARETTRIM = R++; -src[CARETTRIM] = '(\\s*)' + src[LONECARET] + '\\s+'; -re[CARETTRIM] = new RegExp(src[CARETTRIM], 'g'); -var caretTrimReplace = '$1^'; - -var CARET = R++; -src[CARET] = '^' + src[LONECARET] + src[XRANGEPLAIN] + '$'; -var CARETLOOSE = R++; -src[CARETLOOSE] = '^' + src[LONECARET] + src[XRANGEPLAINLOOSE] + '$'; - -// A simple gt/lt/eq thing, or just "" to indicate "any version" -var COMPARATORLOOSE = R++; -src[COMPARATORLOOSE] = '^' + src[GTLT] + '\\s*(' + LOOSEPLAIN + ')$|^$'; -var COMPARATOR = R++; -src[COMPARATOR] = '^' + src[GTLT] + '\\s*(' + FULLPLAIN + ')$|^$'; - - -// An expression to strip any whitespace between the gtlt and the thing -// it modifies, so that `> 1.2.3` ==> `>1.2.3` -var COMPARATORTRIM = R++; -src[COMPARATORTRIM] = '(\\s*)' + src[GTLT] + - '\\s*(' + LOOSEPLAIN + '|' + src[XRANGEPLAIN] + ')'; - -// this one has to use the /g flag -re[COMPARATORTRIM] = new RegExp(src[COMPARATORTRIM], 'g'); -var comparatorTrimReplace = '$1$2$3'; - - -// Something like `1.2.3 - 1.2.4` -// Note that these all use the loose form, because they'll be -// checked against either the strict or loose comparator form -// later. -var HYPHENRANGE = R++; -src[HYPHENRANGE] = '^\\s*(' + src[XRANGEPLAIN] + ')' + - '\\s+-\\s+' + - '(' + src[XRANGEPLAIN] + ')' + - '\\s*$'; - -var HYPHENRANGELOOSE = R++; -src[HYPHENRANGELOOSE] = '^\\s*(' + src[XRANGEPLAINLOOSE] + ')' + - '\\s+-\\s+' + - '(' + src[XRANGEPLAINLOOSE] + ')' + - '\\s*$'; - -// Star ranges basically just allow anything at all. -var STAR = R++; -src[STAR] = '(<|>)?=?\\s*\\*'; - -// Compile to actual regexp objects. -// All are flag-free, unless they were created above with a flag. -for (var i = 0; i < R; i++) { - debug(i, src[i]); - if (!re[i]) - re[i] = new RegExp(src[i]); -} - -exports.parse = parse; -function parse(version, loose) { - if (version instanceof SemVer) - return version; - - if (typeof version !== 'string') - return null; - - if (version.length > MAX_LENGTH) - return null; - - var r = loose ? re[LOOSE] : re[FULL]; - if (!r.test(version)) - return null; - - try { - return new SemVer(version, loose); - } catch (er) { - return null; - } -} - -exports.valid = valid; -function valid(version, loose) { - var v = parse(version, loose); - return v ? v.version : null; -} - - -exports.clean = clean; -function clean(version, loose) { - var s = parse(version.trim().replace(/^[=v]+/, ''), loose); - return s ? s.version : null; -} - -exports.SemVer = SemVer; - -function SemVer(version, loose) { - if (version instanceof SemVer) { - if (version.loose === loose) - return version; - else - version = version.version; - } else if (typeof version !== 'string') { - throw new TypeError('Invalid Version: ' + version); - } - - if (version.length > MAX_LENGTH) - throw new TypeError('version is longer than ' + MAX_LENGTH + ' characters') - - if (!(this instanceof SemVer)) - return new SemVer(version, loose); - - debug('SemVer', version, loose); - this.loose = loose; - var m = version.trim().match(loose ? re[LOOSE] : re[FULL]); - - if (!m) - throw new TypeError('Invalid Version: ' + version); - - this.raw = version; - - // these are actually numbers - this.major = +m[1]; - this.minor = +m[2]; - this.patch = +m[3]; - - if (this.major > MAX_SAFE_INTEGER || this.major < 0) - throw new TypeError('Invalid major version') - - if (this.minor > MAX_SAFE_INTEGER || this.minor < 0) - throw new TypeError('Invalid minor version') - - if (this.patch > MAX_SAFE_INTEGER || this.patch < 0) - throw new TypeError('Invalid patch version') - - // numberify any prerelease numeric ids - if (!m[4]) - this.prerelease = []; - else - this.prerelease = m[4].split('.').map(function(id) { - if (/^[0-9]+$/.test(id)) { - var num = +id; - if (num >= 0 && num < MAX_SAFE_INTEGER) - return num; - } - return id; - }); - - this.build = m[5] ? m[5].split('.') : []; - this.format(); -} - -SemVer.prototype.format = function() { - this.version = this.major + '.' + this.minor + '.' + this.patch; - if (this.prerelease.length) - this.version += '-' + this.prerelease.join('.'); - return this.version; -}; - -SemVer.prototype.toString = function() { - return this.version; -}; - -SemVer.prototype.compare = function(other) { - debug('SemVer.compare', this.version, this.loose, other); - if (!(other instanceof SemVer)) - other = new SemVer(other, this.loose); - - return this.compareMain(other) || this.comparePre(other); -}; - -SemVer.prototype.compareMain = function(other) { - if (!(other instanceof SemVer)) - other = new SemVer(other, this.loose); - - return compareIdentifiers(this.major, other.major) || - compareIdentifiers(this.minor, other.minor) || - compareIdentifiers(this.patch, other.patch); -}; - -SemVer.prototype.comparePre = function(other) { - if (!(other instanceof SemVer)) - other = new SemVer(other, this.loose); - - // NOT having a prerelease is > having one - if (this.prerelease.length && !other.prerelease.length) - return -1; - else if (!this.prerelease.length && other.prerelease.length) - return 1; - else if (!this.prerelease.length && !other.prerelease.length) - return 0; - - var i = 0; - do { - var a = this.prerelease[i]; - var b = other.prerelease[i]; - debug('prerelease compare', i, a, b); - if (a === undefined && b === undefined) - return 0; - else if (b === undefined) - return 1; - else if (a === undefined) - return -1; - else if (a === b) - continue; - else - return compareIdentifiers(a, b); - } while (++i); -}; - -// preminor will bump the version up to the next minor release, and immediately -// down to pre-release. premajor and prepatch work the same way. -SemVer.prototype.inc = function(release, identifier) { - switch (release) { - case 'premajor': - this.prerelease.length = 0; - this.patch = 0; - this.minor = 0; - this.major++; - this.inc('pre', identifier); - break; - case 'preminor': - this.prerelease.length = 0; - this.patch = 0; - this.minor++; - this.inc('pre', identifier); - break; - case 'prepatch': - // If this is already a prerelease, it will bump to the next version - // drop any prereleases that might already exist, since they are not - // relevant at this point. - this.prerelease.length = 0; - this.inc('patch', identifier); - this.inc('pre', identifier); - break; - // If the input is a non-prerelease version, this acts the same as - // prepatch. - case 'prerelease': - if (this.prerelease.length === 0) - this.inc('patch', identifier); - this.inc('pre', identifier); - break; - - case 'major': - // If this is a pre-major version, bump up to the same major version. - // Otherwise increment major. - // 1.0.0-5 bumps to 1.0.0 - // 1.1.0 bumps to 2.0.0 - if (this.minor !== 0 || this.patch !== 0 || this.prerelease.length === 0) - this.major++; - this.minor = 0; - this.patch = 0; - this.prerelease = []; - break; - case 'minor': - // If this is a pre-minor version, bump up to the same minor version. - // Otherwise increment minor. - // 1.2.0-5 bumps to 1.2.0 - // 1.2.1 bumps to 1.3.0 - if (this.patch !== 0 || this.prerelease.length === 0) - this.minor++; - this.patch = 0; - this.prerelease = []; - break; - case 'patch': - // If this is not a pre-release version, it will increment the patch. - // If it is a pre-release it will bump up to the same patch version. - // 1.2.0-5 patches to 1.2.0 - // 1.2.0 patches to 1.2.1 - if (this.prerelease.length === 0) - this.patch++; - this.prerelease = []; - break; - // This probably shouldn't be used publicly. - // 1.0.0 "pre" would become 1.0.0-0 which is the wrong direction. - case 'pre': - if (this.prerelease.length === 0) - this.prerelease = [0]; - else { - var i = this.prerelease.length; - while (--i >= 0) { - if (typeof this.prerelease[i] === 'number') { - this.prerelease[i]++; - i = -2; - } - } - if (i === -1) // didn't increment anything - this.prerelease.push(0); - } - if (identifier) { - // 1.2.0-beta.1 bumps to 1.2.0-beta.2, - // 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0 - if (this.prerelease[0] === identifier) { - if (isNaN(this.prerelease[1])) - this.prerelease = [identifier, 0]; - } else - this.prerelease = [identifier, 0]; - } - break; - - default: - throw new Error('invalid increment argument: ' + release); - } - this.format(); - this.raw = this.version; - return this; -}; - -exports.inc = inc; -function inc(version, release, loose, identifier) { - if (typeof(loose) === 'string') { - identifier = loose; - loose = undefined; - } - - try { - return new SemVer(version, loose).inc(release, identifier).version; - } catch (er) { - return null; - } -} - -exports.diff = diff; -function diff(version1, version2) { - if (eq(version1, version2)) { - return null; - } else { - var v1 = parse(version1); - var v2 = parse(version2); - if (v1.prerelease.length || v2.prerelease.length) { - for (var key in v1) { - if (key === 'major' || key === 'minor' || key === 'patch') { - if (v1[key] !== v2[key]) { - return 'pre'+key; - } - } - } - return 'prerelease'; - } - for (var key in v1) { - if (key === 'major' || key === 'minor' || key === 'patch') { - if (v1[key] !== v2[key]) { - return key; - } - } - } - } -} - -exports.compareIdentifiers = compareIdentifiers; - -var numeric = /^[0-9]+$/; -function compareIdentifiers(a, b) { - var anum = numeric.test(a); - var bnum = numeric.test(b); - - if (anum && bnum) { - a = +a; - b = +b; - } - - return (anum && !bnum) ? -1 : - (bnum && !anum) ? 1 : - a < b ? -1 : - a > b ? 1 : - 0; -} - -exports.rcompareIdentifiers = rcompareIdentifiers; -function rcompareIdentifiers(a, b) { - return compareIdentifiers(b, a); -} - -exports.major = major; -function major(a, loose) { - return new SemVer(a, loose).major; -} - -exports.minor = minor; -function minor(a, loose) { - return new SemVer(a, loose).minor; -} - -exports.patch = patch; -function patch(a, loose) { - return new SemVer(a, loose).patch; -} - -exports.compare = compare; -function compare(a, b, loose) { - return new SemVer(a, loose).compare(new SemVer(b, loose)); -} - -exports.compareLoose = compareLoose; -function compareLoose(a, b) { - return compare(a, b, true); -} - -exports.rcompare = rcompare; -function rcompare(a, b, loose) { - return compare(b, a, loose); -} - -exports.sort = sort; -function sort(list, loose) { - return list.sort(function(a, b) { - return exports.compare(a, b, loose); - }); -} - -exports.rsort = rsort; -function rsort(list, loose) { - return list.sort(function(a, b) { - return exports.rcompare(a, b, loose); - }); -} - -exports.gt = gt; -function gt(a, b, loose) { - return compare(a, b, loose) > 0; -} - -exports.lt = lt; -function lt(a, b, loose) { - return compare(a, b, loose) < 0; -} - -exports.eq = eq; -function eq(a, b, loose) { - return compare(a, b, loose) === 0; -} - -exports.neq = neq; -function neq(a, b, loose) { - return compare(a, b, loose) !== 0; -} - -exports.gte = gte; -function gte(a, b, loose) { - return compare(a, b, loose) >= 0; -} - -exports.lte = lte; -function lte(a, b, loose) { - return compare(a, b, loose) <= 0; -} - -exports.cmp = cmp; -function cmp(a, op, b, loose) { - var ret; - switch (op) { - case '===': - if (typeof a === 'object') a = a.version; - if (typeof b === 'object') b = b.version; - ret = a === b; - break; - case '!==': - if (typeof a === 'object') a = a.version; - if (typeof b === 'object') b = b.version; - ret = a !== b; - break; - case '': case '=': case '==': ret = eq(a, b, loose); break; - case '!=': ret = neq(a, b, loose); break; - case '>': ret = gt(a, b, loose); break; - case '>=': ret = gte(a, b, loose); break; - case '<': ret = lt(a, b, loose); break; - case '<=': ret = lte(a, b, loose); break; - default: throw new TypeError('Invalid operator: ' + op); - } - return ret; -} - -exports.Comparator = Comparator; -function Comparator(comp, loose) { - if (comp instanceof Comparator) { - if (comp.loose === loose) - return comp; - else - comp = comp.value; - } - - if (!(this instanceof Comparator)) - return new Comparator(comp, loose); - - debug('comparator', comp, loose); - this.loose = loose; - this.parse(comp); - - if (this.semver === ANY) - this.value = ''; - else - this.value = this.operator + this.semver.version; - - debug('comp', this); -} - -var ANY = {}; -Comparator.prototype.parse = function(comp) { - var r = this.loose ? re[COMPARATORLOOSE] : re[COMPARATOR]; - var m = comp.match(r); - - if (!m) - throw new TypeError('Invalid comparator: ' + comp); - - this.operator = m[1]; - if (this.operator === '=') - this.operator = ''; - - // if it literally is just '>' or '' then allow anything. - if (!m[2]) - this.semver = ANY; - else - this.semver = new SemVer(m[2], this.loose); -}; - -Comparator.prototype.toString = function() { - return this.value; -}; - -Comparator.prototype.test = function(version) { - debug('Comparator.test', version, this.loose); - - if (this.semver === ANY) - return true; - - if (typeof version === 'string') - version = new SemVer(version, this.loose); - - return cmp(version, this.operator, this.semver, this.loose); -}; - -Comparator.prototype.intersects = function(comp, loose) { - if (!(comp instanceof Comparator)) { - throw new TypeError('a Comparator is required'); - } - - var rangeTmp; - - if (this.operator === '') { - rangeTmp = new Range(comp.value, loose); - return satisfies(this.value, rangeTmp, loose); - } else if (comp.operator === '') { - rangeTmp = new Range(this.value, loose); - return satisfies(comp.semver, rangeTmp, loose); - } - - var sameDirectionIncreasing = - (this.operator === '>=' || this.operator === '>') && - (comp.operator === '>=' || comp.operator === '>'); - var sameDirectionDecreasing = - (this.operator === '<=' || this.operator === '<') && - (comp.operator === '<=' || comp.operator === '<'); - var sameSemVer = this.semver.version === comp.semver.version; - var differentDirectionsInclusive = - (this.operator === '>=' || this.operator === '<=') && - (comp.operator === '>=' || comp.operator === '<='); - var oppositeDirectionsLessThan = - cmp(this.semver, '<', comp.semver, loose) && - ((this.operator === '>=' || this.operator === '>') && - (comp.operator === '<=' || comp.operator === '<')); - var oppositeDirectionsGreaterThan = - cmp(this.semver, '>', comp.semver, loose) && - ((this.operator === '<=' || this.operator === '<') && - (comp.operator === '>=' || comp.operator === '>')); - - return sameDirectionIncreasing || sameDirectionDecreasing || - (sameSemVer && differentDirectionsInclusive) || - oppositeDirectionsLessThan || oppositeDirectionsGreaterThan; -}; - - -exports.Range = Range; -function Range(range, loose) { - if (range instanceof Range) { - if (range.loose === loose) { - return range; - } else { - return new Range(range.raw, loose); - } - } - - if (range instanceof Comparator) { - return new Range(range.value, loose); - } - - if (!(this instanceof Range)) - return new Range(range, loose); - - this.loose = loose; - - // First, split based on boolean or || - this.raw = range; - this.set = range.split(/\s*\|\|\s*/).map(function(range) { - return this.parseRange(range.trim()); - }, this).filter(function(c) { - // throw out any that are not relevant for whatever reason - return c.length; - }); - - if (!this.set.length) { - throw new TypeError('Invalid SemVer Range: ' + range); - } - - this.format(); -} - -Range.prototype.format = function() { - this.range = this.set.map(function(comps) { - return comps.join(' ').trim(); - }).join('||').trim(); - return this.range; -}; - -Range.prototype.toString = function() { - return this.range; -}; - -Range.prototype.parseRange = function(range) { - var loose = this.loose; - range = range.trim(); - debug('range', range, loose); - // `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4` - var hr = loose ? re[HYPHENRANGELOOSE] : re[HYPHENRANGE]; - range = range.replace(hr, hyphenReplace); - debug('hyphen replace', range); - // `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5` - range = range.replace(re[COMPARATORTRIM], comparatorTrimReplace); - debug('comparator trim', range, re[COMPARATORTRIM]); - - // `~ 1.2.3` => `~1.2.3` - range = range.replace(re[TILDETRIM], tildeTrimReplace); - - // `^ 1.2.3` => `^1.2.3` - range = range.replace(re[CARETTRIM], caretTrimReplace); - - // normalize spaces - range = range.split(/\s+/).join(' '); - - // At this point, the range is completely trimmed and - // ready to be split into comparators. - - var compRe = loose ? re[COMPARATORLOOSE] : re[COMPARATOR]; - var set = range.split(' ').map(function(comp) { - return parseComparator(comp, loose); - }).join(' ').split(/\s+/); - if (this.loose) { - // in loose mode, throw out any that are not valid comparators - set = set.filter(function(comp) { - return !!comp.match(compRe); - }); - } - set = set.map(function(comp) { - return new Comparator(comp, loose); - }); - - return set; -}; - -Range.prototype.intersects = function(range, loose) { - if (!(range instanceof Range)) { - throw new TypeError('a Range is required'); - } - - return this.set.some(function(thisComparators) { - return thisComparators.every(function(thisComparator) { - return range.set.some(function(rangeComparators) { - return rangeComparators.every(function(rangeComparator) { - return thisComparator.intersects(rangeComparator, loose); - }); - }); - }); - }); -}; - -// Mostly just for testing and legacy API reasons -exports.toComparators = toComparators; -function toComparators(range, loose) { - return new Range(range, loose).set.map(function(comp) { - return comp.map(function(c) { - return c.value; - }).join(' ').trim().split(' '); - }); -} - -// comprised of xranges, tildes, stars, and gtlt's at this point. -// already replaced the hyphen ranges -// turn into a set of JUST comparators. -function parseComparator(comp, loose) { - debug('comp', comp); - comp = replaceCarets(comp, loose); - debug('caret', comp); - comp = replaceTildes(comp, loose); - debug('tildes', comp); - comp = replaceXRanges(comp, loose); - debug('xrange', comp); - comp = replaceStars(comp, loose); - debug('stars', comp); - return comp; -} - -function isX(id) { - return !id || id.toLowerCase() === 'x' || id === '*'; -} - -// ~, ~> --> * (any, kinda silly) -// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0 -// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0 -// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0 -// ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0 -// ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0 -function replaceTildes(comp, loose) { - return comp.trim().split(/\s+/).map(function(comp) { - return replaceTilde(comp, loose); - }).join(' '); -} - -function replaceTilde(comp, loose) { - var r = loose ? re[TILDELOOSE] : re[TILDE]; - return comp.replace(r, function(_, M, m, p, pr) { - debug('tilde', comp, _, M, m, p, pr); - var ret; - - if (isX(M)) - ret = ''; - else if (isX(m)) - ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0'; - else if (isX(p)) - // ~1.2 == >=1.2.0 <1.3.0 - ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0'; - else if (pr) { - debug('replaceTilde pr', pr); - if (pr.charAt(0) !== '-') - pr = '-' + pr; - ret = '>=' + M + '.' + m + '.' + p + pr + - ' <' + M + '.' + (+m + 1) + '.0'; - } else - // ~1.2.3 == >=1.2.3 <1.3.0 - ret = '>=' + M + '.' + m + '.' + p + - ' <' + M + '.' + (+m + 1) + '.0'; - - debug('tilde return', ret); - return ret; - }); -} - -// ^ --> * (any, kinda silly) -// ^2, ^2.x, ^2.x.x --> >=2.0.0 <3.0.0 -// ^2.0, ^2.0.x --> >=2.0.0 <3.0.0 -// ^1.2, ^1.2.x --> >=1.2.0 <2.0.0 -// ^1.2.3 --> >=1.2.3 <2.0.0 -// ^1.2.0 --> >=1.2.0 <2.0.0 -function replaceCarets(comp, loose) { - return comp.trim().split(/\s+/).map(function(comp) { - return replaceCaret(comp, loose); - }).join(' '); -} - -function replaceCaret(comp, loose) { - debug('caret', comp, loose); - var r = loose ? re[CARETLOOSE] : re[CARET]; - return comp.replace(r, function(_, M, m, p, pr) { - debug('caret', comp, _, M, m, p, pr); - var ret; - - if (isX(M)) - ret = ''; - else if (isX(m)) - ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0'; - else if (isX(p)) { - if (M === '0') - ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0'; - else - ret = '>=' + M + '.' + m + '.0 <' + (+M + 1) + '.0.0'; - } else if (pr) { - debug('replaceCaret pr', pr); - if (pr.charAt(0) !== '-') - pr = '-' + pr; - if (M === '0') { - if (m === '0') - ret = '>=' + M + '.' + m + '.' + p + pr + - ' <' + M + '.' + m + '.' + (+p + 1); - else - ret = '>=' + M + '.' + m + '.' + p + pr + - ' <' + M + '.' + (+m + 1) + '.0'; - } else - ret = '>=' + M + '.' + m + '.' + p + pr + - ' <' + (+M + 1) + '.0.0'; - } else { - debug('no pr'); - if (M === '0') { - if (m === '0') - ret = '>=' + M + '.' + m + '.' + p + - ' <' + M + '.' + m + '.' + (+p + 1); - else - ret = '>=' + M + '.' + m + '.' + p + - ' <' + M + '.' + (+m + 1) + '.0'; - } else - ret = '>=' + M + '.' + m + '.' + p + - ' <' + (+M + 1) + '.0.0'; - } - - debug('caret return', ret); - return ret; - }); -} - -function replaceXRanges(comp, loose) { - debug('replaceXRanges', comp, loose); - return comp.split(/\s+/).map(function(comp) { - return replaceXRange(comp, loose); - }).join(' '); -} - -function replaceXRange(comp, loose) { - comp = comp.trim(); - var r = loose ? re[XRANGELOOSE] : re[XRANGE]; - return comp.replace(r, function(ret, gtlt, M, m, p, pr) { - debug('xRange', comp, ret, gtlt, M, m, p, pr); - var xM = isX(M); - var xm = xM || isX(m); - var xp = xm || isX(p); - var anyX = xp; - - if (gtlt === '=' && anyX) - gtlt = ''; - - if (xM) { - if (gtlt === '>' || gtlt === '<') { - // nothing is allowed - ret = '<0.0.0'; - } else { - // nothing is forbidden - ret = '*'; - } - } else if (gtlt && anyX) { - // replace X with 0 - if (xm) - m = 0; - if (xp) - p = 0; - - if (gtlt === '>') { - // >1 => >=2.0.0 - // >1.2 => >=1.3.0 - // >1.2.3 => >= 1.2.4 - gtlt = '>='; - if (xm) { - M = +M + 1; - m = 0; - p = 0; - } else if (xp) { - m = +m + 1; - p = 0; - } - } else if (gtlt === '<=') { - // <=0.7.x is actually <0.8.0, since any 0.7.x should - // pass. Similarly, <=7.x is actually <8.0.0, etc. - gtlt = '<'; - if (xm) - M = +M + 1; - else - m = +m + 1; - } - - ret = gtlt + M + '.' + m + '.' + p; - } else if (xm) { - ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0'; - } else if (xp) { - ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0'; - } - - debug('xRange return', ret); - - return ret; - }); -} - -// Because * is AND-ed with everything else in the comparator, -// and '' means "any version", just remove the *s entirely. -function replaceStars(comp, loose) { - debug('replaceStars', comp, loose); - // Looseness is ignored here. star is always as loose as it gets! - return comp.trim().replace(re[STAR], ''); -} - -// This function is passed to string.replace(re[HYPHENRANGE]) -// M, m, patch, prerelease, build -// 1.2 - 3.4.5 => >=1.2.0 <=3.4.5 -// 1.2.3 - 3.4 => >=1.2.0 <3.5.0 Any 3.4.x will do -// 1.2 - 3.4 => >=1.2.0 <3.5.0 -function hyphenReplace($0, - from, fM, fm, fp, fpr, fb, - to, tM, tm, tp, tpr, tb) { - - if (isX(fM)) - from = ''; - else if (isX(fm)) - from = '>=' + fM + '.0.0'; - else if (isX(fp)) - from = '>=' + fM + '.' + fm + '.0'; - else - from = '>=' + from; - - if (isX(tM)) - to = ''; - else if (isX(tm)) - to = '<' + (+tM + 1) + '.0.0'; - else if (isX(tp)) - to = '<' + tM + '.' + (+tm + 1) + '.0'; - else if (tpr) - to = '<=' + tM + '.' + tm + '.' + tp + '-' + tpr; - else - to = '<=' + to; - - return (from + ' ' + to).trim(); -} - - -// if ANY of the sets match ALL of its comparators, then pass -Range.prototype.test = function(version) { - if (!version) - return false; - - if (typeof version === 'string') - version = new SemVer(version, this.loose); - - for (var i = 0; i < this.set.length; i++) { - if (testSet(this.set[i], version)) - return true; - } - return false; -}; - -function testSet(set, version) { - for (var i = 0; i < set.length; i++) { - if (!set[i].test(version)) - return false; - } - - if (version.prerelease.length) { - // Find the set of versions that are allowed to have prereleases - // For example, ^1.2.3-pr.1 desugars to >=1.2.3-pr.1 <2.0.0 - // That should allow `1.2.3-pr.2` to pass. - // However, `1.2.4-alpha.notready` should NOT be allowed, - // even though it's within the range set by the comparators. - for (var i = 0; i < set.length; i++) { - debug(set[i].semver); - if (set[i].semver === ANY) - continue; - - if (set[i].semver.prerelease.length > 0) { - var allowed = set[i].semver; - if (allowed.major === version.major && - allowed.minor === version.minor && - allowed.patch === version.patch) - return true; - } - } - - // Version has a -pre, but it's not one of the ones we like. - return false; - } - - return true; -} - -exports.satisfies = satisfies; -function satisfies(version, range, loose) { - try { - range = new Range(range, loose); - } catch (er) { - return false; - } - return range.test(version); -} - -exports.maxSatisfying = maxSatisfying; -function maxSatisfying(versions, range, loose) { - var max = null; - var maxSV = null; - try { - var rangeObj = new Range(range, loose); - } catch (er) { - return null; - } - versions.forEach(function (v) { - if (rangeObj.test(v)) { // satisfies(v, range, loose) - if (!max || maxSV.compare(v) === -1) { // compare(max, v, true) - max = v; - maxSV = new SemVer(max, loose); - } - } - }) - return max; -} - -exports.minSatisfying = minSatisfying; -function minSatisfying(versions, range, loose) { - var min = null; - var minSV = null; - try { - var rangeObj = new Range(range, loose); - } catch (er) { - return null; - } - versions.forEach(function (v) { - if (rangeObj.test(v)) { // satisfies(v, range, loose) - if (!min || minSV.compare(v) === 1) { // compare(min, v, true) - min = v; - minSV = new SemVer(min, loose); - } - } - }) - return min; -} - -exports.validRange = validRange; -function validRange(range, loose) { - try { - // Return '*' instead of '' so that truthiness works. - // This will throw if it's invalid anyway - return new Range(range, loose).range || '*'; - } catch (er) { - return null; - } -} - -// Determine if version is less than all the versions possible in the range -exports.ltr = ltr; -function ltr(version, range, loose) { - return outside(version, range, '<', loose); -} - -// Determine if version is greater than all the versions possible in the range. -exports.gtr = gtr; -function gtr(version, range, loose) { - return outside(version, range, '>', loose); -} - -exports.outside = outside; -function outside(version, range, hilo, loose) { - version = new SemVer(version, loose); - range = new Range(range, loose); - - var gtfn, ltefn, ltfn, comp, ecomp; - switch (hilo) { - case '>': - gtfn = gt; - ltefn = lte; - ltfn = lt; - comp = '>'; - ecomp = '>='; - break; - case '<': - gtfn = lt; - ltefn = gte; - ltfn = gt; - comp = '<'; - ecomp = '<='; - break; - default: - throw new TypeError('Must provide a hilo val of "<" or ">"'); - } - - // If it satisifes the range it is not outside - if (satisfies(version, range, loose)) { - return false; - } - - // From now on, variable terms are as if we're in "gtr" mode. - // but note that everything is flipped for the "ltr" function. - - for (var i = 0; i < range.set.length; ++i) { - var comparators = range.set[i]; - - var high = null; - var low = null; - - comparators.forEach(function(comparator) { - if (comparator.semver === ANY) { - comparator = new Comparator('>=0.0.0') - } - high = high || comparator; - low = low || comparator; - if (gtfn(comparator.semver, high.semver, loose)) { - high = comparator; - } else if (ltfn(comparator.semver, low.semver, loose)) { - low = comparator; - } - }); - - // If the edge version comparator has a operator then our version - // isn't outside it - if (high.operator === comp || high.operator === ecomp) { - return false; - } - - // If the lowest version comparator has an operator and our version - // is less than it then it isn't higher than the range - if ((!low.operator || low.operator === comp) && - ltefn(version, low.semver)) { - return false; - } else if (low.operator === ecomp && ltfn(version, low.semver)) { - return false; - } - } - return true; -} - -exports.prerelease = prerelease; -function prerelease(version, loose) { - var parsed = parse(version, loose); - return (parsed && parsed.prerelease.length) ? parsed.prerelease : null; -} - -exports.intersects = intersects; -function intersects(r1, r2, loose) { - r1 = new Range(r1, loose) - r2 = new Range(r2, loose) - return r1.intersects(r2) -} - -exports.coerce = coerce; -function coerce(version) { - if (version instanceof SemVer) - return version; - - if (typeof version !== 'string') - return null; - - var match = version.match(re[COERCE]); - - if (match == null) - return null; - - return parse((match[1] || '0') + '.' + (match[2] || '0') + '.' + (match[3] || '0')); -} - - -/***/ }), -/* 23 */ -/***/ (function(module, exports) { - -module.exports = require("stream"); - -/***/ }), -/* 24 */ -/***/ (function(module, exports) { - -module.exports = require("url"); - -/***/ }), -/* 25 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return Subscription; }); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__util_isArray__ = __webpack_require__(41); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__util_isObject__ = __webpack_require__(444); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__util_isFunction__ = __webpack_require__(154); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__util_tryCatch__ = __webpack_require__(56); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__util_errorObject__ = __webpack_require__(48); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__util_UnsubscriptionError__ = __webpack_require__(441); -/** PURE_IMPORTS_START _util_isArray,_util_isObject,_util_isFunction,_util_tryCatch,_util_errorObject,_util_UnsubscriptionError PURE_IMPORTS_END */ - - - - - - -var Subscription = /*@__PURE__*/ (function () { - function Subscription(unsubscribe) { - this.closed = false; - this._parent = null; - this._parents = null; - this._subscriptions = null; - if (unsubscribe) { - this._unsubscribe = unsubscribe; - } - } - Subscription.prototype.unsubscribe = function () { - var hasErrors = false; - var errors; - if (this.closed) { - return; - } - var _a = this, _parent = _a._parent, _parents = _a._parents, _unsubscribe = _a._unsubscribe, _subscriptions = _a._subscriptions; - this.closed = true; - this._parent = null; - this._parents = null; - this._subscriptions = null; - var index = -1; - var len = _parents ? _parents.length : 0; - while (_parent) { - _parent.remove(this); - _parent = ++index < len && _parents[index] || null; - } - if (__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_2__util_isFunction__["a" /* isFunction */])(_unsubscribe)) { - var trial = __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_3__util_tryCatch__["a" /* tryCatch */])(_unsubscribe).call(this); - if (trial === __WEBPACK_IMPORTED_MODULE_4__util_errorObject__["a" /* errorObject */]) { - hasErrors = true; - errors = errors || (__WEBPACK_IMPORTED_MODULE_4__util_errorObject__["a" /* errorObject */].e instanceof __WEBPACK_IMPORTED_MODULE_5__util_UnsubscriptionError__["a" /* UnsubscriptionError */] ? - flattenUnsubscriptionErrors(__WEBPACK_IMPORTED_MODULE_4__util_errorObject__["a" /* errorObject */].e.errors) : [__WEBPACK_IMPORTED_MODULE_4__util_errorObject__["a" /* errorObject */].e]); - } - } - if (__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__util_isArray__["a" /* isArray */])(_subscriptions)) { - index = -1; - len = _subscriptions.length; - while (++index < len) { - var sub = _subscriptions[index]; - if (__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_1__util_isObject__["a" /* isObject */])(sub)) { - var trial = __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_3__util_tryCatch__["a" /* tryCatch */])(sub.unsubscribe).call(sub); - if (trial === __WEBPACK_IMPORTED_MODULE_4__util_errorObject__["a" /* errorObject */]) { - hasErrors = true; - errors = errors || []; - var err = __WEBPACK_IMPORTED_MODULE_4__util_errorObject__["a" /* errorObject */].e; - if (err instanceof __WEBPACK_IMPORTED_MODULE_5__util_UnsubscriptionError__["a" /* UnsubscriptionError */]) { - errors = errors.concat(flattenUnsubscriptionErrors(err.errors)); - } - else { - errors.push(err); - } - } - } - } - } - if (hasErrors) { - throw new __WEBPACK_IMPORTED_MODULE_5__util_UnsubscriptionError__["a" /* UnsubscriptionError */](errors); - } - }; - Subscription.prototype.add = function (teardown) { - if (!teardown || (teardown === Subscription.EMPTY)) { - return Subscription.EMPTY; - } - if (teardown === this) { - return this; - } - var subscription = teardown; - switch (typeof teardown) { - case 'function': - subscription = new Subscription(teardown); - case 'object': - if (subscription.closed || typeof subscription.unsubscribe !== 'function') { - return subscription; - } - else if (this.closed) { - subscription.unsubscribe(); - return subscription; - } - else if (typeof subscription._addParent !== 'function') { - var tmp = subscription; - subscription = new Subscription(); - subscription._subscriptions = [tmp]; - } - break; - default: - throw new Error('unrecognized teardown ' + teardown + ' added to Subscription.'); - } - var subscriptions = this._subscriptions || (this._subscriptions = []); - subscriptions.push(subscription); - subscription._addParent(this); - return subscription; - }; - Subscription.prototype.remove = function (subscription) { - var subscriptions = this._subscriptions; - if (subscriptions) { - var subscriptionIndex = subscriptions.indexOf(subscription); - if (subscriptionIndex !== -1) { - subscriptions.splice(subscriptionIndex, 1); - } - } - }; - Subscription.prototype._addParent = function (parent) { - var _a = this, _parent = _a._parent, _parents = _a._parents; - if (!_parent || _parent === parent) { - this._parent = parent; - } - else if (!_parents) { - this._parents = [parent]; - } - else if (_parents.indexOf(parent) === -1) { - _parents.push(parent); - } - }; - Subscription.EMPTY = (function (empty) { - empty.closed = true; - return empty; - }(new Subscription())); - return Subscription; -}()); - -function flattenUnsubscriptionErrors(errors) { - return errors.reduce(function (errs, err) { return errs.concat((err instanceof __WEBPACK_IMPORTED_MODULE_5__util_UnsubscriptionError__["a" /* UnsubscriptionError */]) ? err.errors : err); }, []); -} -//# sourceMappingURL=Subscription.js.map - - -/***/ }), -/* 26 */ -/***/ (function(module, exports, __webpack_require__) { - -// Copyright 2015 Joyent, Inc. - -module.exports = { - bufferSplit: bufferSplit, - addRSAMissing: addRSAMissing, - calculateDSAPublic: calculateDSAPublic, - calculateED25519Public: calculateED25519Public, - calculateX25519Public: calculateX25519Public, - mpNormalize: mpNormalize, - mpDenormalize: mpDenormalize, - ecNormalize: ecNormalize, - countZeros: countZeros, - assertCompatible: assertCompatible, - isCompatible: isCompatible, - opensslKeyDeriv: opensslKeyDeriv, - opensshCipherInfo: opensshCipherInfo, - publicFromPrivateECDSA: publicFromPrivateECDSA, - zeroPadToLength: zeroPadToLength, - writeBitString: writeBitString, - readBitString: readBitString -}; - -var assert = __webpack_require__(16); -var Buffer = __webpack_require__(15).Buffer; -var PrivateKey = __webpack_require__(33); -var Key = __webpack_require__(27); -var crypto = __webpack_require__(11); -var algs = __webpack_require__(32); -var asn1 = __webpack_require__(66); - -var ec, jsbn; -var nacl; - -var MAX_CLASS_DEPTH = 3; - -function isCompatible(obj, klass, needVer) { - if (obj === null || typeof (obj) !== 'object') - return (false); - if (needVer === undefined) - needVer = klass.prototype._sshpkApiVersion; - if (obj instanceof klass && - klass.prototype._sshpkApiVersion[0] == needVer[0]) - return (true); - var proto = Object.getPrototypeOf(obj); - var depth = 0; - while (proto.constructor.name !== klass.name) { - proto = Object.getPrototypeOf(proto); - if (!proto || ++depth > MAX_CLASS_DEPTH) - return (false); - } - if (proto.constructor.name !== klass.name) - return (false); - var ver = proto._sshpkApiVersion; - if (ver === undefined) - ver = klass._oldVersionDetect(obj); - if (ver[0] != needVer[0] || ver[1] < needVer[1]) - return (false); - return (true); -} - -function assertCompatible(obj, klass, needVer, name) { - if (name === undefined) - name = 'object'; - assert.ok(obj, name + ' must not be null'); - assert.object(obj, name + ' must be an object'); - if (needVer === undefined) - needVer = klass.prototype._sshpkApiVersion; - if (obj instanceof klass && - klass.prototype._sshpkApiVersion[0] == needVer[0]) - return; - var proto = Object.getPrototypeOf(obj); - var depth = 0; - while (proto.constructor.name !== klass.name) { - proto = Object.getPrototypeOf(proto); - assert.ok(proto && ++depth <= MAX_CLASS_DEPTH, - name + ' must be a ' + klass.name + ' instance'); - } - assert.strictEqual(proto.constructor.name, klass.name, - name + ' must be a ' + klass.name + ' instance'); - var ver = proto._sshpkApiVersion; - if (ver === undefined) - ver = klass._oldVersionDetect(obj); - assert.ok(ver[0] == needVer[0] && ver[1] >= needVer[1], - name + ' must be compatible with ' + klass.name + ' klass ' + - 'version ' + needVer[0] + '.' + needVer[1]); -} - -var CIPHER_LEN = { - 'des-ede3-cbc': { key: 7, iv: 8 }, - 'aes-128-cbc': { key: 16, iv: 16 } -}; -var PKCS5_SALT_LEN = 8; - -function opensslKeyDeriv(cipher, salt, passphrase, count) { - assert.buffer(salt, 'salt'); - assert.buffer(passphrase, 'passphrase'); - assert.number(count, 'iteration count'); - - var clen = CIPHER_LEN[cipher]; - assert.object(clen, 'supported cipher'); - - salt = salt.slice(0, PKCS5_SALT_LEN); - - var D, D_prev, bufs; - var material = Buffer.alloc(0); - while (material.length < clen.key + clen.iv) { - bufs = []; - if (D_prev) - bufs.push(D_prev); - bufs.push(passphrase); - bufs.push(salt); - D = Buffer.concat(bufs); - for (var j = 0; j < count; ++j) - D = crypto.createHash('md5').update(D).digest(); - material = Buffer.concat([material, D]); - D_prev = D; - } - - return ({ - key: material.slice(0, clen.key), - iv: material.slice(clen.key, clen.key + clen.iv) - }); -} - -/* Count leading zero bits on a buffer */ -function countZeros(buf) { - var o = 0, obit = 8; - while (o < buf.length) { - var mask = (1 << obit); - if ((buf[o] & mask) === mask) - break; - obit--; - if (obit < 0) { - o++; - obit = 8; - } - } - return (o*8 + (8 - obit) - 1); -} - -function bufferSplit(buf, chr) { - assert.buffer(buf); - assert.string(chr); - - var parts = []; - var lastPart = 0; - var matches = 0; - for (var i = 0; i < buf.length; ++i) { - if (buf[i] === chr.charCodeAt(matches)) - ++matches; - else if (buf[i] === chr.charCodeAt(0)) - matches = 1; - else - matches = 0; - - if (matches >= chr.length) { - var newPart = i + 1; - parts.push(buf.slice(lastPart, newPart - matches)); - lastPart = newPart; - matches = 0; - } - } - if (lastPart <= buf.length) - parts.push(buf.slice(lastPart, buf.length)); - - return (parts); -} - -function ecNormalize(buf, addZero) { - assert.buffer(buf); - if (buf[0] === 0x00 && buf[1] === 0x04) { - if (addZero) - return (buf); - return (buf.slice(1)); - } else if (buf[0] === 0x04) { - if (!addZero) - return (buf); - } else { - while (buf[0] === 0x00) - buf = buf.slice(1); - if (buf[0] === 0x02 || buf[0] === 0x03) - throw (new Error('Compressed elliptic curve points ' + - 'are not supported')); - if (buf[0] !== 0x04) - throw (new Error('Not a valid elliptic curve point')); - if (!addZero) - return (buf); - } - var b = Buffer.alloc(buf.length + 1); - b[0] = 0x0; - buf.copy(b, 1); - return (b); -} - -function readBitString(der, tag) { - if (tag === undefined) - tag = asn1.Ber.BitString; - var buf = der.readString(tag, true); - assert.strictEqual(buf[0], 0x00, 'bit strings with unused bits are ' + - 'not supported (0x' + buf[0].toString(16) + ')'); - return (buf.slice(1)); -} - -function writeBitString(der, buf, tag) { - if (tag === undefined) - tag = asn1.Ber.BitString; - var b = Buffer.alloc(buf.length + 1); - b[0] = 0x00; - buf.copy(b, 1); - der.writeBuffer(b, tag); -} - -function mpNormalize(buf) { - assert.buffer(buf); - while (buf.length > 1 && buf[0] === 0x00 && (buf[1] & 0x80) === 0x00) - buf = buf.slice(1); - if ((buf[0] & 0x80) === 0x80) { - var b = Buffer.alloc(buf.length + 1); - b[0] = 0x00; - buf.copy(b, 1); - buf = b; - } - return (buf); -} - -function mpDenormalize(buf) { - assert.buffer(buf); - while (buf.length > 1 && buf[0] === 0x00) - buf = buf.slice(1); - return (buf); -} - -function zeroPadToLength(buf, len) { - assert.buffer(buf); - assert.number(len); - while (buf.length > len) { - assert.equal(buf[0], 0x00); - buf = buf.slice(1); - } - while (buf.length < len) { - var b = Buffer.alloc(buf.length + 1); - b[0] = 0x00; - buf.copy(b, 1); - buf = b; - } - return (buf); -} - -function bigintToMpBuf(bigint) { - var buf = Buffer.from(bigint.toByteArray()); - buf = mpNormalize(buf); - return (buf); -} - -function calculateDSAPublic(g, p, x) { - assert.buffer(g); - assert.buffer(p); - assert.buffer(x); - try { - var bigInt = __webpack_require__(81).BigInteger; - } catch (e) { - throw (new Error('To load a PKCS#8 format DSA private key, ' + - 'the node jsbn library is required.')); - } - g = new bigInt(g); - p = new bigInt(p); - x = new bigInt(x); - var y = g.modPow(x, p); - var ybuf = bigintToMpBuf(y); - return (ybuf); -} - -function calculateED25519Public(k) { - assert.buffer(k); - - if (nacl === undefined) - nacl = __webpack_require__(76); - - var kp = nacl.sign.keyPair.fromSeed(new Uint8Array(k)); - return (Buffer.from(kp.publicKey)); -} - -function calculateX25519Public(k) { - assert.buffer(k); - - if (nacl === undefined) - nacl = __webpack_require__(76); - - var kp = nacl.box.keyPair.fromSeed(new Uint8Array(k)); - return (Buffer.from(kp.publicKey)); -} - -function addRSAMissing(key) { - assert.object(key); - assertCompatible(key, PrivateKey, [1, 1]); - try { - var bigInt = __webpack_require__(81).BigInteger; - } catch (e) { - throw (new Error('To write a PEM private key from ' + - 'this source, the node jsbn lib is required.')); - } - - var d = new bigInt(key.part.d.data); - var buf; - - if (!key.part.dmodp) { - var p = new bigInt(key.part.p.data); - var dmodp = d.mod(p.subtract(1)); - - buf = bigintToMpBuf(dmodp); - key.part.dmodp = {name: 'dmodp', data: buf}; - key.parts.push(key.part.dmodp); - } - if (!key.part.dmodq) { - var q = new bigInt(key.part.q.data); - var dmodq = d.mod(q.subtract(1)); - - buf = bigintToMpBuf(dmodq); - key.part.dmodq = {name: 'dmodq', data: buf}; - key.parts.push(key.part.dmodq); - } -} - -function publicFromPrivateECDSA(curveName, priv) { - assert.string(curveName, 'curveName'); - assert.buffer(priv); - if (ec === undefined) - ec = __webpack_require__(139); - if (jsbn === undefined) - jsbn = __webpack_require__(81).BigInteger; - var params = algs.curves[curveName]; - var p = new jsbn(params.p); - var a = new jsbn(params.a); - var b = new jsbn(params.b); - var curve = new ec.ECCurveFp(p, a, b); - var G = curve.decodePointHex(params.G.toString('hex')); - - var d = new jsbn(mpNormalize(priv)); - var pub = G.multiply(d); - pub = Buffer.from(curve.encodePointHex(pub), 'hex'); - - var parts = []; - parts.push({name: 'curve', data: Buffer.from(curveName)}); - parts.push({name: 'Q', data: pub}); - - var key = new Key({type: 'ecdsa', curve: curve, parts: parts}); - return (key); -} - -function opensshCipherInfo(cipher) { - var inf = {}; - switch (cipher) { - case '3des-cbc': - inf.keySize = 24; - inf.blockSize = 8; - inf.opensslName = 'des-ede3-cbc'; - break; - case 'blowfish-cbc': - inf.keySize = 16; - inf.blockSize = 8; - inf.opensslName = 'bf-cbc'; - break; - case 'aes128-cbc': - case 'aes128-ctr': - case 'aes128-gcm@openssh.com': - inf.keySize = 16; - inf.blockSize = 16; - inf.opensslName = 'aes-128-' + cipher.slice(7, 10); - break; - case 'aes192-cbc': - case 'aes192-ctr': - case 'aes192-gcm@openssh.com': - inf.keySize = 24; - inf.blockSize = 16; - inf.opensslName = 'aes-192-' + cipher.slice(7, 10); - break; - case 'aes256-cbc': - case 'aes256-ctr': - case 'aes256-gcm@openssh.com': - inf.keySize = 32; - inf.blockSize = 16; - inf.opensslName = 'aes-256-' + cipher.slice(7, 10); - break; - default: - throw (new Error( - 'Unsupported openssl cipher "' + cipher + '"')); - } - return (inf); -} - - -/***/ }), -/* 27 */ -/***/ (function(module, exports, __webpack_require__) { - -// Copyright 2017 Joyent, Inc. - -module.exports = Key; - -var assert = __webpack_require__(16); -var algs = __webpack_require__(32); -var crypto = __webpack_require__(11); -var Fingerprint = __webpack_require__(156); -var Signature = __webpack_require__(75); -var DiffieHellman = __webpack_require__(325).DiffieHellman; -var errs = __webpack_require__(74); -var utils = __webpack_require__(26); -var PrivateKey = __webpack_require__(33); -var edCompat; - -try { - edCompat = __webpack_require__(454); -} catch (e) { - /* Just continue through, and bail out if we try to use it. */ -} - -var InvalidAlgorithmError = errs.InvalidAlgorithmError; -var KeyParseError = errs.KeyParseError; - -var formats = {}; -formats['auto'] = __webpack_require__(455); -formats['pem'] = __webpack_require__(86); -formats['pkcs1'] = __webpack_require__(327); -formats['pkcs8'] = __webpack_require__(157); -formats['rfc4253'] = __webpack_require__(103); -formats['ssh'] = __webpack_require__(456); -formats['ssh-private'] = __webpack_require__(192); -formats['openssh'] = formats['ssh-private']; -formats['dnssec'] = __webpack_require__(326); - -function Key(opts) { - assert.object(opts, 'options'); - assert.arrayOfObject(opts.parts, 'options.parts'); - assert.string(opts.type, 'options.type'); - assert.optionalString(opts.comment, 'options.comment'); - - var algInfo = algs.info[opts.type]; - if (typeof (algInfo) !== 'object') - throw (new InvalidAlgorithmError(opts.type)); - - var partLookup = {}; - for (var i = 0; i < opts.parts.length; ++i) { - var part = opts.parts[i]; - partLookup[part.name] = part; - } - - this.type = opts.type; - this.parts = opts.parts; - this.part = partLookup; - this.comment = undefined; - this.source = opts.source; - - /* for speeding up hashing/fingerprint operations */ - this._rfc4253Cache = opts._rfc4253Cache; - this._hashCache = {}; - - var sz; - this.curve = undefined; - if (this.type === 'ecdsa') { - var curve = this.part.curve.data.toString(); - this.curve = curve; - sz = algs.curves[curve].size; - } else if (this.type === 'ed25519' || this.type === 'curve25519') { - sz = 256; - this.curve = 'curve25519'; - } else { - var szPart = this.part[algInfo.sizePart]; - sz = szPart.data.length; - sz = sz * 8 - utils.countZeros(szPart.data); - } - this.size = sz; -} - -Key.formats = formats; - -Key.prototype.toBuffer = function (format, options) { - if (format === undefined) - format = 'ssh'; - assert.string(format, 'format'); - assert.object(formats[format], 'formats[format]'); - assert.optionalObject(options, 'options'); - - if (format === 'rfc4253') { - if (this._rfc4253Cache === undefined) - this._rfc4253Cache = formats['rfc4253'].write(this); - return (this._rfc4253Cache); - } - - return (formats[format].write(this, options)); -}; - -Key.prototype.toString = function (format, options) { - return (this.toBuffer(format, options).toString()); -}; - -Key.prototype.hash = function (algo) { - assert.string(algo, 'algorithm'); - algo = algo.toLowerCase(); - if (algs.hashAlgs[algo] === undefined) - throw (new InvalidAlgorithmError(algo)); - - if (this._hashCache[algo]) - return (this._hashCache[algo]); - var hash = crypto.createHash(algo). - update(this.toBuffer('rfc4253')).digest(); - this._hashCache[algo] = hash; - return (hash); -}; - -Key.prototype.fingerprint = function (algo) { - if (algo === undefined) - algo = 'sha256'; - assert.string(algo, 'algorithm'); - var opts = { - type: 'key', - hash: this.hash(algo), - algorithm: algo - }; - return (new Fingerprint(opts)); -}; - -Key.prototype.defaultHashAlgorithm = function () { - var hashAlgo = 'sha1'; - if (this.type === 'rsa') - hashAlgo = 'sha256'; - if (this.type === 'dsa' && this.size > 1024) - hashAlgo = 'sha256'; - if (this.type === 'ed25519') - hashAlgo = 'sha512'; - if (this.type === 'ecdsa') { - if (this.size <= 256) - hashAlgo = 'sha256'; - else if (this.size <= 384) - hashAlgo = 'sha384'; - else - hashAlgo = 'sha512'; - } - return (hashAlgo); -}; - -Key.prototype.createVerify = function (hashAlgo) { - if (hashAlgo === undefined) - hashAlgo = this.defaultHashAlgorithm(); - assert.string(hashAlgo, 'hash algorithm'); - - /* ED25519 is not supported by OpenSSL, use a javascript impl. */ - if (this.type === 'ed25519' && edCompat !== undefined) - return (new edCompat.Verifier(this, hashAlgo)); - if (this.type === 'curve25519') - throw (new Error('Curve25519 keys are not suitable for ' + - 'signing or verification')); - - var v, nm, err; - try { - nm = hashAlgo.toUpperCase(); - v = crypto.createVerify(nm); - } catch (e) { - err = e; - } - if (v === undefined || (err instanceof Error && - err.message.match(/Unknown message digest/))) { - nm = 'RSA-'; - nm += hashAlgo.toUpperCase(); - v = crypto.createVerify(nm); - } - assert.ok(v, 'failed to create verifier'); - var oldVerify = v.verify.bind(v); - var key = this.toBuffer('pkcs8'); - var curve = this.curve; - var self = this; - v.verify = function (signature, fmt) { - if (Signature.isSignature(signature, [2, 0])) { - if (signature.type !== self.type) - return (false); - if (signature.hashAlgorithm && - signature.hashAlgorithm !== hashAlgo) - return (false); - if (signature.curve && self.type === 'ecdsa' && - signature.curve !== curve) - return (false); - return (oldVerify(key, signature.toBuffer('asn1'))); - - } else if (typeof (signature) === 'string' || - Buffer.isBuffer(signature)) { - return (oldVerify(key, signature, fmt)); - - /* - * Avoid doing this on valid arguments, walking the prototype - * chain can be quite slow. - */ - } else if (Signature.isSignature(signature, [1, 0])) { - throw (new Error('signature was created by too old ' + - 'a version of sshpk and cannot be verified')); - - } else { - throw (new TypeError('signature must be a string, ' + - 'Buffer, or Signature object')); - } - }; - return (v); -}; - -Key.prototype.createDiffieHellman = function () { - if (this.type === 'rsa') - throw (new Error('RSA keys do not support Diffie-Hellman')); - - return (new DiffieHellman(this)); -}; -Key.prototype.createDH = Key.prototype.createDiffieHellman; - -Key.parse = function (data, format, options) { - if (typeof (data) !== 'string') - assert.buffer(data, 'data'); - if (format === undefined) - format = 'auto'; - assert.string(format, 'format'); - if (typeof (options) === 'string') - options = { filename: options }; - assert.optionalObject(options, 'options'); - if (options === undefined) - options = {}; - assert.optionalString(options.filename, 'options.filename'); - if (options.filename === undefined) - options.filename = '(unnamed)'; - - assert.object(formats[format], 'formats[format]'); - - try { - var k = formats[format].read(data, options); - if (k instanceof PrivateKey) - k = k.toPublic(); - if (!k.comment) - k.comment = options.filename; - return (k); - } catch (e) { - if (e.name === 'KeyEncryptedError') - throw (e); - throw (new KeyParseError(options.filename, format, e)); - } -}; - -Key.isKey = function (obj, ver) { - return (utils.isCompatible(obj, Key, ver)); -}; - -/* - * API versions for Key: - * [1,0] -- initial ver, may take Signature for createVerify or may not - * [1,1] -- added pkcs1, pkcs8 formats - * [1,2] -- added auto, ssh-private, openssh formats - * [1,3] -- added defaultHashAlgorithm - * [1,4] -- added ed support, createDH - * [1,5] -- first explicitly tagged version - * [1,6] -- changed ed25519 part names - */ -Key.prototype._sshpkApiVersion = [1, 6]; - -Key._oldVersionDetect = function (obj) { - assert.func(obj.toBuffer); - assert.func(obj.fingerprint); - if (obj.createDH) - return ([1, 4]); - if (obj.defaultHashAlgorithm) - return ([1, 3]); - if (obj.formats['auto']) - return ([1, 2]); - if (obj.formats['pkcs1']) - return ([1, 1]); - return ([1, 0]); -}; - - -/***/ }), -/* 28 */ -/***/ (function(module, exports) { - -module.exports = require("assert"); - -/***/ }), -/* 29 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = nullify; -function nullify(obj = {}) { - if (Array.isArray(obj)) { - for (var _iterator = obj, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { - var _ref; - - if (_isArray) { - if (_i >= _iterator.length) break; - _ref = _iterator[_i++]; - } else { - _i = _iterator.next(); - if (_i.done) break; - _ref = _i.value; - } - - const item = _ref; - - nullify(item); - } - } else if (obj !== null && typeof obj === 'object' || typeof obj === 'function') { - Object.setPrototypeOf(obj, null); - - // for..in can only be applied to 'object', not 'function' - if (typeof obj === 'object') { - for (const key in obj) { - nullify(obj[key]); - } - } - } - - return obj; -} - -/***/ }), -/* 30 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -const escapeStringRegexp = __webpack_require__(388); -const ansiStyles = __webpack_require__(506); -const stdoutColor = __webpack_require__(598).stdout; - -const template = __webpack_require__(599); - -const isSimpleWindowsTerm = process.platform === 'win32' && !(process.env.TERM || '').toLowerCase().startsWith('xterm'); - -// `supportsColor.level` → `ansiStyles.color[name]` mapping -const levelMapping = ['ansi', 'ansi', 'ansi256', 'ansi16m']; - -// `color-convert` models to exclude from the Chalk API due to conflicts and such -const skipModels = new Set(['gray']); - -const styles = Object.create(null); - -function applyOptions(obj, options) { - options = options || {}; - - // Detect level if not set manually - const scLevel = stdoutColor ? stdoutColor.level : 0; - obj.level = options.level === undefined ? scLevel : options.level; - obj.enabled = 'enabled' in options ? options.enabled : obj.level > 0; -} - -function Chalk(options) { - // We check for this.template here since calling `chalk.constructor()` - // by itself will have a `this` of a previously constructed chalk object - if (!this || !(this instanceof Chalk) || this.template) { - const chalk = {}; - applyOptions(chalk, options); - - chalk.template = function () { - const args = [].slice.call(arguments); - return chalkTag.apply(null, [chalk.template].concat(args)); - }; - - Object.setPrototypeOf(chalk, Chalk.prototype); - Object.setPrototypeOf(chalk.template, chalk); - - chalk.template.constructor = Chalk; - - return chalk.template; - } - - applyOptions(this, options); -} - -// Use bright blue on Windows as the normal blue color is illegible -if (isSimpleWindowsTerm) { - ansiStyles.blue.open = '\u001B[94m'; -} - -for (const key of Object.keys(ansiStyles)) { - ansiStyles[key].closeRe = new RegExp(escapeStringRegexp(ansiStyles[key].close), 'g'); - - styles[key] = { - get() { - const codes = ansiStyles[key]; - return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, key); - } - }; -} - -styles.visible = { - get() { - return build.call(this, this._styles || [], true, 'visible'); - } -}; - -ansiStyles.color.closeRe = new RegExp(escapeStringRegexp(ansiStyles.color.close), 'g'); -for (const model of Object.keys(ansiStyles.color.ansi)) { - if (skipModels.has(model)) { - continue; - } - - styles[model] = { - get() { - const level = this.level; - return function () { - const open = ansiStyles.color[levelMapping[level]][model].apply(null, arguments); - const codes = { - open, - close: ansiStyles.color.close, - closeRe: ansiStyles.color.closeRe - }; - return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, model); - }; - } - }; -} - -ansiStyles.bgColor.closeRe = new RegExp(escapeStringRegexp(ansiStyles.bgColor.close), 'g'); -for (const model of Object.keys(ansiStyles.bgColor.ansi)) { - if (skipModels.has(model)) { - continue; - } - - const bgModel = 'bg' + model[0].toUpperCase() + model.slice(1); - styles[bgModel] = { - get() { - const level = this.level; - return function () { - const open = ansiStyles.bgColor[levelMapping[level]][model].apply(null, arguments); - const codes = { - open, - close: ansiStyles.bgColor.close, - closeRe: ansiStyles.bgColor.closeRe - }; - return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, model); - }; - } - }; -} - -const proto = Object.defineProperties(() => {}, styles); - -function build(_styles, _empty, key) { - const builder = function () { - return applyStyle.apply(builder, arguments); - }; - - builder._styles = _styles; - builder._empty = _empty; - - const self = this; - - Object.defineProperty(builder, 'level', { - enumerable: true, - get() { - return self.level; - }, - set(level) { - self.level = level; - } - }); - - Object.defineProperty(builder, 'enabled', { - enumerable: true, - get() { - return self.enabled; - }, - set(enabled) { - self.enabled = enabled; - } - }); - - // See below for fix regarding invisible grey/dim combination on Windows - builder.hasGrey = this.hasGrey || key === 'gray' || key === 'grey'; - - // `__proto__` is used because we must return a function, but there is - // no way to create a function with a different prototype - builder.__proto__ = proto; // eslint-disable-line no-proto - - return builder; -} - -function applyStyle() { - // Support varags, but simply cast to string in case there's only one arg - const args = arguments; - const argsLen = args.length; - let str = String(arguments[0]); - - if (argsLen === 0) { - return ''; - } - - if (argsLen > 1) { - // Don't slice `arguments`, it prevents V8 optimizations - for (let a = 1; a < argsLen; a++) { - str += ' ' + args[a]; - } - } - - if (!this.enabled || this.level <= 0 || !str) { - return this._empty ? '' : str; - } - - // Turns out that on Windows dimmed gray text becomes invisible in cmd.exe, - // see https://github.com/chalk/chalk/issues/58 - // If we're on Windows and we're dealing with a gray color, temporarily make 'dim' a noop. - const originalDim = ansiStyles.dim.open; - if (isSimpleWindowsTerm && this.hasGrey) { - ansiStyles.dim.open = ''; - } - - for (const code of this._styles.slice().reverse()) { - // Replace any instances already present with a re-opening code - // otherwise only the part of the string until said closing code - // will be colored, and the rest will simply be 'plain'. - str = code.open + str.replace(code.closeRe, code.open) + code.close; - - // Close the styling before a linebreak and reopen - // after next line to fix a bleed issue on macOS - // https://github.com/chalk/chalk/pull/92 - str = str.replace(/\r?\n/g, `${code.close}$&${code.open}`); - } - - // Reset the original `dim` if we changed it to work around the Windows dimmed gray issue - ansiStyles.dim.open = originalDim; - - return str; -} - -function chalkTag(chalk, strings) { - if (!Array.isArray(strings)) { - // If chalk() was called by itself or with a string, - // return the string itself as a string. - return [].slice.call(arguments, 1).join(' '); - } - - const args = [].slice.call(arguments, 2); - const parts = [strings.raw[0]]; - - for (let i = 1; i < strings.length; i++) { - parts.push(String(args[i - 1]).replace(/[{}\\]/g, '\\$&')); - parts.push(String(strings.raw[i])); - } - - return template(chalk, parts.join('')); -} - -Object.defineProperties(Chalk.prototype, styles); - -module.exports = Chalk(); // eslint-disable-line new-cap -module.exports.supportsColor = stdoutColor; -module.exports.default = module.exports; // For TypeScript - - -/***/ }), -/* 31 */ -/***/ (function(module, exports) { - -var core = module.exports = { version: '2.5.7' }; -if (typeof __e == 'number') __e = core; // eslint-disable-line no-undef - - -/***/ }), -/* 32 */ -/***/ (function(module, exports, __webpack_require__) { - -// Copyright 2015 Joyent, Inc. - -var Buffer = __webpack_require__(15).Buffer; - -var algInfo = { - 'dsa': { - parts: ['p', 'q', 'g', 'y'], - sizePart: 'p' - }, - 'rsa': { - parts: ['e', 'n'], - sizePart: 'n' - }, - 'ecdsa': { - parts: ['curve', 'Q'], - sizePart: 'Q' - }, - 'ed25519': { - parts: ['A'], - sizePart: 'A' - } -}; -algInfo['curve25519'] = algInfo['ed25519']; - -var algPrivInfo = { - 'dsa': { - parts: ['p', 'q', 'g', 'y', 'x'] - }, - 'rsa': { - parts: ['n', 'e', 'd', 'iqmp', 'p', 'q'] - }, - 'ecdsa': { - parts: ['curve', 'Q', 'd'] - }, - 'ed25519': { - parts: ['A', 'k'] - } -}; -algPrivInfo['curve25519'] = algPrivInfo['ed25519']; - -var hashAlgs = { - 'md5': true, - 'sha1': true, - 'sha256': true, - 'sha384': true, - 'sha512': true -}; - -/* - * Taken from - * http://csrc.nist.gov/groups/ST/toolkit/documents/dss/NISTReCur.pdf - */ -var curves = { - 'nistp256': { - size: 256, - pkcs8oid: '1.2.840.10045.3.1.7', - p: Buffer.from(('00' + - 'ffffffff 00000001 00000000 00000000' + - '00000000 ffffffff ffffffff ffffffff'). - replace(/ /g, ''), 'hex'), - a: Buffer.from(('00' + - 'FFFFFFFF 00000001 00000000 00000000' + - '00000000 FFFFFFFF FFFFFFFF FFFFFFFC'). - replace(/ /g, ''), 'hex'), - b: Buffer.from(( - '5ac635d8 aa3a93e7 b3ebbd55 769886bc' + - '651d06b0 cc53b0f6 3bce3c3e 27d2604b'). - replace(/ /g, ''), 'hex'), - s: Buffer.from(('00' + - 'c49d3608 86e70493 6a6678e1 139d26b7' + - '819f7e90'). - replace(/ /g, ''), 'hex'), - n: Buffer.from(('00' + - 'ffffffff 00000000 ffffffff ffffffff' + - 'bce6faad a7179e84 f3b9cac2 fc632551'). - replace(/ /g, ''), 'hex'), - G: Buffer.from(('04' + - '6b17d1f2 e12c4247 f8bce6e5 63a440f2' + - '77037d81 2deb33a0 f4a13945 d898c296' + - '4fe342e2 fe1a7f9b 8ee7eb4a 7c0f9e16' + - '2bce3357 6b315ece cbb64068 37bf51f5'). - replace(/ /g, ''), 'hex') - }, - 'nistp384': { - size: 384, - pkcs8oid: '1.3.132.0.34', - p: Buffer.from(('00' + - 'ffffffff ffffffff ffffffff ffffffff' + - 'ffffffff ffffffff ffffffff fffffffe' + - 'ffffffff 00000000 00000000 ffffffff'). - replace(/ /g, ''), 'hex'), - a: Buffer.from(('00' + - 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF' + - 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE' + - 'FFFFFFFF 00000000 00000000 FFFFFFFC'). - replace(/ /g, ''), 'hex'), - b: Buffer.from(( - 'b3312fa7 e23ee7e4 988e056b e3f82d19' + - '181d9c6e fe814112 0314088f 5013875a' + - 'c656398d 8a2ed19d 2a85c8ed d3ec2aef'). - replace(/ /g, ''), 'hex'), - s: Buffer.from(('00' + - 'a335926a a319a27a 1d00896a 6773a482' + - '7acdac73'). - replace(/ /g, ''), 'hex'), - n: Buffer.from(('00' + - 'ffffffff ffffffff ffffffff ffffffff' + - 'ffffffff ffffffff c7634d81 f4372ddf' + - '581a0db2 48b0a77a ecec196a ccc52973'). - replace(/ /g, ''), 'hex'), - G: Buffer.from(('04' + - 'aa87ca22 be8b0537 8eb1c71e f320ad74' + - '6e1d3b62 8ba79b98 59f741e0 82542a38' + - '5502f25d bf55296c 3a545e38 72760ab7' + - '3617de4a 96262c6f 5d9e98bf 9292dc29' + - 'f8f41dbd 289a147c e9da3113 b5f0b8c0' + - '0a60b1ce 1d7e819d 7a431d7c 90ea0e5f'). - replace(/ /g, ''), 'hex') - }, - 'nistp521': { - size: 521, - pkcs8oid: '1.3.132.0.35', - p: Buffer.from(( - '01ffffff ffffffff ffffffff ffffffff' + - 'ffffffff ffffffff ffffffff ffffffff' + - 'ffffffff ffffffff ffffffff ffffffff' + - 'ffffffff ffffffff ffffffff ffffffff' + - 'ffff').replace(/ /g, ''), 'hex'), - a: Buffer.from(('01FF' + - 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF' + - 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF' + - 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF' + - 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFC'). - replace(/ /g, ''), 'hex'), - b: Buffer.from(('51' + - '953eb961 8e1c9a1f 929a21a0 b68540ee' + - 'a2da725b 99b315f3 b8b48991 8ef109e1' + - '56193951 ec7e937b 1652c0bd 3bb1bf07' + - '3573df88 3d2c34f1 ef451fd4 6b503f00'). - replace(/ /g, ''), 'hex'), - s: Buffer.from(('00' + - 'd09e8800 291cb853 96cc6717 393284aa' + - 'a0da64ba').replace(/ /g, ''), 'hex'), - n: Buffer.from(('01ff' + - 'ffffffff ffffffff ffffffff ffffffff' + - 'ffffffff ffffffff ffffffff fffffffa' + - '51868783 bf2f966b 7fcc0148 f709a5d0' + - '3bb5c9b8 899c47ae bb6fb71e 91386409'). - replace(/ /g, ''), 'hex'), - G: Buffer.from(('04' + - '00c6 858e06b7 0404e9cd 9e3ecb66 2395b442' + - '9c648139 053fb521 f828af60 6b4d3dba' + - 'a14b5e77 efe75928 fe1dc127 a2ffa8de' + - '3348b3c1 856a429b f97e7e31 c2e5bd66' + - '0118 39296a78 9a3bc004 5c8a5fb4 2c7d1bd9' + - '98f54449 579b4468 17afbd17 273e662c' + - '97ee7299 5ef42640 c550b901 3fad0761' + - '353c7086 a272c240 88be9476 9fd16650'). - replace(/ /g, ''), 'hex') - } -}; - -module.exports = { - info: algInfo, - privInfo: algPrivInfo, - hashAlgs: hashAlgs, - curves: curves -}; - - -/***/ }), -/* 33 */ -/***/ (function(module, exports, __webpack_require__) { - -// Copyright 2017 Joyent, Inc. - -module.exports = PrivateKey; - -var assert = __webpack_require__(16); -var Buffer = __webpack_require__(15).Buffer; -var algs = __webpack_require__(32); -var crypto = __webpack_require__(11); -var Fingerprint = __webpack_require__(156); -var Signature = __webpack_require__(75); -var errs = __webpack_require__(74); -var util = __webpack_require__(3); -var utils = __webpack_require__(26); -var dhe = __webpack_require__(325); -var generateECDSA = dhe.generateECDSA; -var generateED25519 = dhe.generateED25519; -var edCompat; -var nacl; - -try { - edCompat = __webpack_require__(454); -} catch (e) { - /* Just continue through, and bail out if we try to use it. */ -} - -var Key = __webpack_require__(27); - -var InvalidAlgorithmError = errs.InvalidAlgorithmError; -var KeyParseError = errs.KeyParseError; -var KeyEncryptedError = errs.KeyEncryptedError; - -var formats = {}; -formats['auto'] = __webpack_require__(455); -formats['pem'] = __webpack_require__(86); -formats['pkcs1'] = __webpack_require__(327); -formats['pkcs8'] = __webpack_require__(157); -formats['rfc4253'] = __webpack_require__(103); -formats['ssh-private'] = __webpack_require__(192); -formats['openssh'] = formats['ssh-private']; -formats['ssh'] = formats['ssh-private']; -formats['dnssec'] = __webpack_require__(326); - -function PrivateKey(opts) { - assert.object(opts, 'options'); - Key.call(this, opts); - - this._pubCache = undefined; -} -util.inherits(PrivateKey, Key); - -PrivateKey.formats = formats; - -PrivateKey.prototype.toBuffer = function (format, options) { - if (format === undefined) - format = 'pkcs1'; - assert.string(format, 'format'); - assert.object(formats[format], 'formats[format]'); - assert.optionalObject(options, 'options'); - - return (formats[format].write(this, options)); -}; - -PrivateKey.prototype.hash = function (algo) { - return (this.toPublic().hash(algo)); -}; - -PrivateKey.prototype.toPublic = function () { - if (this._pubCache) - return (this._pubCache); - - var algInfo = algs.info[this.type]; - var pubParts = []; - for (var i = 0; i < algInfo.parts.length; ++i) { - var p = algInfo.parts[i]; - pubParts.push(this.part[p]); - } - - this._pubCache = new Key({ - type: this.type, - source: this, - parts: pubParts - }); - if (this.comment) - this._pubCache.comment = this.comment; - return (this._pubCache); -}; - -PrivateKey.prototype.derive = function (newType) { - assert.string(newType, 'type'); - var priv, pub, pair; - - if (this.type === 'ed25519' && newType === 'curve25519') { - if (nacl === undefined) - nacl = __webpack_require__(76); - - priv = this.part.k.data; - if (priv[0] === 0x00) - priv = priv.slice(1); - - pair = nacl.box.keyPair.fromSecretKey(new Uint8Array(priv)); - pub = Buffer.from(pair.publicKey); - - return (new PrivateKey({ - type: 'curve25519', - parts: [ - { name: 'A', data: utils.mpNormalize(pub) }, - { name: 'k', data: utils.mpNormalize(priv) } - ] - })); - } else if (this.type === 'curve25519' && newType === 'ed25519') { - if (nacl === undefined) - nacl = __webpack_require__(76); - - priv = this.part.k.data; - if (priv[0] === 0x00) - priv = priv.slice(1); - - pair = nacl.sign.keyPair.fromSeed(new Uint8Array(priv)); - pub = Buffer.from(pair.publicKey); - - return (new PrivateKey({ - type: 'ed25519', - parts: [ - { name: 'A', data: utils.mpNormalize(pub) }, - { name: 'k', data: utils.mpNormalize(priv) } - ] - })); - } - throw (new Error('Key derivation not supported from ' + this.type + - ' to ' + newType)); -}; - -PrivateKey.prototype.createVerify = function (hashAlgo) { - return (this.toPublic().createVerify(hashAlgo)); -}; - -PrivateKey.prototype.createSign = function (hashAlgo) { - if (hashAlgo === undefined) - hashAlgo = this.defaultHashAlgorithm(); - assert.string(hashAlgo, 'hash algorithm'); - - /* ED25519 is not supported by OpenSSL, use a javascript impl. */ - if (this.type === 'ed25519' && edCompat !== undefined) - return (new edCompat.Signer(this, hashAlgo)); - if (this.type === 'curve25519') - throw (new Error('Curve25519 keys are not suitable for ' + - 'signing or verification')); - - var v, nm, err; - try { - nm = hashAlgo.toUpperCase(); - v = crypto.createSign(nm); - } catch (e) { - err = e; - } - if (v === undefined || (err instanceof Error && - err.message.match(/Unknown message digest/))) { - nm = 'RSA-'; - nm += hashAlgo.toUpperCase(); - v = crypto.createSign(nm); - } - assert.ok(v, 'failed to create verifier'); - var oldSign = v.sign.bind(v); - var key = this.toBuffer('pkcs1'); - var type = this.type; - var curve = this.curve; - v.sign = function () { - var sig = oldSign(key); - if (typeof (sig) === 'string') - sig = Buffer.from(sig, 'binary'); - sig = Signature.parse(sig, type, 'asn1'); - sig.hashAlgorithm = hashAlgo; - sig.curve = curve; - return (sig); - }; - return (v); -}; - -PrivateKey.parse = function (data, format, options) { - if (typeof (data) !== 'string') - assert.buffer(data, 'data'); - if (format === undefined) - format = 'auto'; - assert.string(format, 'format'); - if (typeof (options) === 'string') - options = { filename: options }; - assert.optionalObject(options, 'options'); - if (options === undefined) - options = {}; - assert.optionalString(options.filename, 'options.filename'); - if (options.filename === undefined) - options.filename = '(unnamed)'; - - assert.object(formats[format], 'formats[format]'); - - try { - var k = formats[format].read(data, options); - assert.ok(k instanceof PrivateKey, 'key is not a private key'); - if (!k.comment) - k.comment = options.filename; - return (k); - } catch (e) { - if (e.name === 'KeyEncryptedError') - throw (e); - throw (new KeyParseError(options.filename, format, e)); - } -}; - -PrivateKey.isPrivateKey = function (obj, ver) { - return (utils.isCompatible(obj, PrivateKey, ver)); -}; - -PrivateKey.generate = function (type, options) { - if (options === undefined) - options = {}; - assert.object(options, 'options'); - - switch (type) { - case 'ecdsa': - if (options.curve === undefined) - options.curve = 'nistp256'; - assert.string(options.curve, 'options.curve'); - return (generateECDSA(options.curve)); - case 'ed25519': - return (generateED25519()); - default: - throw (new Error('Key generation not supported with key ' + - 'type "' + type + '"')); - } -}; - -/* - * API versions for PrivateKey: - * [1,0] -- initial ver - * [1,1] -- added auto, pkcs[18], openssh/ssh-private formats - * [1,2] -- added defaultHashAlgorithm - * [1,3] -- added derive, ed, createDH - * [1,4] -- first tagged version - * [1,5] -- changed ed25519 part names and format - */ -PrivateKey.prototype._sshpkApiVersion = [1, 5]; - -PrivateKey._oldVersionDetect = function (obj) { - assert.func(obj.toPublic); - assert.func(obj.createSign); - if (obj.derive) - return ([1, 3]); - if (obj.defaultHashAlgorithm) - return ([1, 2]); - if (obj.formats['auto']) - return ([1, 1]); - return ([1, 0]); -}; - - -/***/ }), -/* 34 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.wrapLifecycle = exports.run = exports.install = exports.Install = undefined; - -var _extends2; - -function _load_extends() { - return _extends2 = _interopRequireDefault(__webpack_require__(21)); -} - -var _asyncToGenerator2; - -function _load_asyncToGenerator() { - return _asyncToGenerator2 = _interopRequireDefault(__webpack_require__(2)); -} - -let install = exports.install = (() => { - var _ref29 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (config, reporter, flags, lockfile) { - yield wrapLifecycle(config, flags, (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - const install = new Install(flags, config, reporter, lockfile); - yield install.init(); - })); - }); - - return function install(_x7, _x8, _x9, _x10) { - return _ref29.apply(this, arguments); - }; -})(); - -let run = exports.run = (() => { - var _ref31 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (config, reporter, flags, args) { - let lockfile; - let error = 'installCommandRenamed'; - if (flags.lockfile === false) { - lockfile = new (_lockfile || _load_lockfile()).default(); - } else { - lockfile = yield (_lockfile || _load_lockfile()).default.fromDirectory(config.lockfileFolder, reporter); - } - - if (args.length) { - const exampleArgs = args.slice(); - - if (flags.saveDev) { - exampleArgs.push('--dev'); - } - if (flags.savePeer) { - exampleArgs.push('--peer'); - } - if (flags.saveOptional) { - exampleArgs.push('--optional'); - } - if (flags.saveExact) { - exampleArgs.push('--exact'); - } - if (flags.saveTilde) { - exampleArgs.push('--tilde'); - } - let command = 'add'; - if (flags.global) { - error = 'globalFlagRemoved'; - command = 'global add'; - } - throw new (_errors || _load_errors()).MessageError(reporter.lang(error, `yarn ${command} ${exampleArgs.join(' ')}`)); - } - - yield install(config, reporter, flags, lockfile); - }); - - return function run(_x11, _x12, _x13, _x14) { - return _ref31.apply(this, arguments); - }; -})(); - -let wrapLifecycle = exports.wrapLifecycle = (() => { - var _ref32 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (config, flags, factory) { - yield config.executeLifecycleScript('preinstall'); - - yield factory(); - - // npm behaviour, seems kinda funky but yay compatibility - yield config.executeLifecycleScript('install'); - yield config.executeLifecycleScript('postinstall'); - - if (!config.production) { - if (!config.disablePrepublish) { - yield config.executeLifecycleScript('prepublish'); - } - yield config.executeLifecycleScript('prepare'); - } - }); - - return function wrapLifecycle(_x15, _x16, _x17) { - return _ref32.apply(this, arguments); - }; -})(); - -exports.hasWrapper = hasWrapper; -exports.setFlags = setFlags; - -var _objectPath; - -function _load_objectPath() { - return _objectPath = _interopRequireDefault(__webpack_require__(304)); -} - -var _hooks; - -function _load_hooks() { - return _hooks = __webpack_require__(374); -} - -var _index; - -function _load_index() { - return _index = _interopRequireDefault(__webpack_require__(220)); -} - -var _errors; - -function _load_errors() { - return _errors = __webpack_require__(6); -} - -var _integrityChecker; - -function _load_integrityChecker() { - return _integrityChecker = _interopRequireDefault(__webpack_require__(208)); -} - -var _lockfile; - -function _load_lockfile() { - return _lockfile = _interopRequireDefault(__webpack_require__(19)); -} - -var _lockfile2; - -function _load_lockfile2() { - return _lockfile2 = __webpack_require__(19); -} - -var _packageFetcher; - -function _load_packageFetcher() { - return _packageFetcher = _interopRequireWildcard(__webpack_require__(210)); -} - -var _packageInstallScripts; - -function _load_packageInstallScripts() { - return _packageInstallScripts = _interopRequireDefault(__webpack_require__(557)); -} - -var _packageCompatibility; - -function _load_packageCompatibility() { - return _packageCompatibility = _interopRequireWildcard(__webpack_require__(209)); -} - -var _packageResolver; - -function _load_packageResolver() { - return _packageResolver = _interopRequireDefault(__webpack_require__(366)); -} - -var _packageLinker; - -function _load_packageLinker() { - return _packageLinker = _interopRequireDefault(__webpack_require__(211)); -} - -var _index2; - -function _load_index2() { - return _index2 = __webpack_require__(57); -} - -var _index3; - -function _load_index3() { - return _index3 = __webpack_require__(78); -} - -var _autoclean; - -function _load_autoclean() { - return _autoclean = __webpack_require__(354); -} - -var _constants; - -function _load_constants() { - return _constants = _interopRequireWildcard(__webpack_require__(8)); -} - -var _normalizePattern; - -function _load_normalizePattern() { - return _normalizePattern = __webpack_require__(37); -} - -var _fs; - -function _load_fs() { - return _fs = _interopRequireWildcard(__webpack_require__(4)); -} - -var _map; - -function _load_map() { - return _map = _interopRequireDefault(__webpack_require__(29)); -} - -var _yarnVersion; - -function _load_yarnVersion() { - return _yarnVersion = __webpack_require__(120); -} - -var _generatePnpMap; - -function _load_generatePnpMap() { - return _generatePnpMap = __webpack_require__(579); -} - -var _workspaceLayout; - -function _load_workspaceLayout() { - return _workspaceLayout = _interopRequireDefault(__webpack_require__(90)); -} - -var _resolutionMap; - -function _load_resolutionMap() { - return _resolutionMap = _interopRequireDefault(__webpack_require__(214)); -} - -var _guessName; - -function _load_guessName() { - return _guessName = _interopRequireDefault(__webpack_require__(169)); -} - -var _audit; - -function _load_audit() { - return _audit = _interopRequireDefault(__webpack_require__(353)); -} - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -const deepEqual = __webpack_require__(631); - -const emoji = __webpack_require__(302); -const invariant = __webpack_require__(9); -const path = __webpack_require__(0); -const semver = __webpack_require__(22); -const uuid = __webpack_require__(119); -const ssri = __webpack_require__(65); - -const ONE_DAY = 1000 * 60 * 60 * 24; - -/** - * Try and detect the installation method for Yarn and provide a command to update it with. - */ - -function getUpdateCommand(installationMethod) { - if (installationMethod === 'tar') { - return `curl --compressed -o- -L ${(_constants || _load_constants()).YARN_INSTALLER_SH} | bash`; - } - - if (installationMethod === 'homebrew') { - return 'brew upgrade yarn'; - } - - if (installationMethod === 'deb') { - return 'sudo apt-get update && sudo apt-get install yarn'; - } - - if (installationMethod === 'rpm') { - return 'sudo yum install yarn'; - } - - if (installationMethod === 'npm') { - return 'npm install --global yarn'; - } - - if (installationMethod === 'chocolatey') { - return 'choco upgrade yarn'; - } - - if (installationMethod === 'apk') { - return 'apk update && apk add -u yarn'; - } - - if (installationMethod === 'portage') { - return 'sudo emerge --sync && sudo emerge -au sys-apps/yarn'; - } - - return null; -} - -function getUpdateInstaller(installationMethod) { - // Windows - if (installationMethod === 'msi') { - return (_constants || _load_constants()).YARN_INSTALLER_MSI; - } - - return null; -} - -function normalizeFlags(config, rawFlags) { - const flags = { - // install - har: !!rawFlags.har, - ignorePlatform: !!rawFlags.ignorePlatform, - ignoreEngines: !!rawFlags.ignoreEngines, - ignoreScripts: !!rawFlags.ignoreScripts, - ignoreOptional: !!rawFlags.ignoreOptional, - force: !!rawFlags.force, - flat: !!rawFlags.flat, - lockfile: rawFlags.lockfile !== false, - pureLockfile: !!rawFlags.pureLockfile, - updateChecksums: !!rawFlags.updateChecksums, - skipIntegrityCheck: !!rawFlags.skipIntegrityCheck, - frozenLockfile: !!rawFlags.frozenLockfile, - linkDuplicates: !!rawFlags.linkDuplicates, - checkFiles: !!rawFlags.checkFiles, - audit: !!rawFlags.audit, - - // add - peer: !!rawFlags.peer, - dev: !!rawFlags.dev, - optional: !!rawFlags.optional, - exact: !!rawFlags.exact, - tilde: !!rawFlags.tilde, - ignoreWorkspaceRootCheck: !!rawFlags.ignoreWorkspaceRootCheck, - - // outdated, update-interactive - includeWorkspaceDeps: !!rawFlags.includeWorkspaceDeps, - - // add, remove, update - workspaceRootIsCwd: rawFlags.workspaceRootIsCwd !== false - }; - - if (config.getOption('ignore-scripts')) { - flags.ignoreScripts = true; - } - - if (config.getOption('ignore-platform')) { - flags.ignorePlatform = true; - } - - if (config.getOption('ignore-engines')) { - flags.ignoreEngines = true; - } - - if (config.getOption('ignore-optional')) { - flags.ignoreOptional = true; - } - - if (config.getOption('force')) { - flags.force = true; - } - - return flags; -} - -class Install { - constructor(flags, config, reporter, lockfile) { - this.rootManifestRegistries = []; - this.rootPatternsToOrigin = (0, (_map || _load_map()).default)(); - this.lockfile = lockfile; - this.reporter = reporter; - this.config = config; - this.flags = normalizeFlags(config, flags); - this.resolutions = (0, (_map || _load_map()).default)(); // Legacy resolutions field used for flat install mode - this.resolutionMap = new (_resolutionMap || _load_resolutionMap()).default(config); // Selective resolutions for nested dependencies - this.resolver = new (_packageResolver || _load_packageResolver()).default(config, lockfile, this.resolutionMap); - this.integrityChecker = new (_integrityChecker || _load_integrityChecker()).default(config); - this.linker = new (_packageLinker || _load_packageLinker()).default(config, this.resolver); - this.scripts = new (_packageInstallScripts || _load_packageInstallScripts()).default(config, this.resolver, this.flags.force); - } - - /** - * Create a list of dependency requests from the current directories manifests. - */ - - fetchRequestFromCwd(excludePatterns = [], ignoreUnusedPatterns = false) { - var _this = this; - - return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - const patterns = []; - const deps = []; - let resolutionDeps = []; - const manifest = {}; - - const ignorePatterns = []; - const usedPatterns = []; - let workspaceLayout; - - // some commands should always run in the context of the entire workspace - const cwd = _this.flags.includeWorkspaceDeps || _this.flags.workspaceRootIsCwd ? _this.config.lockfileFolder : _this.config.cwd; - - // non-workspaces are always root, otherwise check for workspace root - const cwdIsRoot = !_this.config.workspaceRootFolder || _this.config.lockfileFolder === cwd; - - // exclude package names that are in install args - const excludeNames = []; - for (var _iterator = excludePatterns, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { - var _ref; - - if (_isArray) { - if (_i >= _iterator.length) break; - _ref = _iterator[_i++]; - } else { - _i = _iterator.next(); - if (_i.done) break; - _ref = _i.value; - } - - const pattern = _ref; - - if ((0, (_index3 || _load_index3()).getExoticResolver)(pattern)) { - excludeNames.push((0, (_guessName || _load_guessName()).default)(pattern)); - } else { - // extract the name - const parts = (0, (_normalizePattern || _load_normalizePattern()).normalizePattern)(pattern); - excludeNames.push(parts.name); - } - } - - const stripExcluded = function stripExcluded(manifest) { - for (var _iterator2 = excludeNames, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) { - var _ref2; - - if (_isArray2) { - if (_i2 >= _iterator2.length) break; - _ref2 = _iterator2[_i2++]; - } else { - _i2 = _iterator2.next(); - if (_i2.done) break; - _ref2 = _i2.value; - } - - const exclude = _ref2; - - if (manifest.dependencies && manifest.dependencies[exclude]) { - delete manifest.dependencies[exclude]; - } - if (manifest.devDependencies && manifest.devDependencies[exclude]) { - delete manifest.devDependencies[exclude]; - } - if (manifest.optionalDependencies && manifest.optionalDependencies[exclude]) { - delete manifest.optionalDependencies[exclude]; - } - } - }; - - for (var _iterator3 = Object.keys((_index2 || _load_index2()).registries), _isArray3 = Array.isArray(_iterator3), _i3 = 0, _iterator3 = _isArray3 ? _iterator3 : _iterator3[Symbol.iterator]();;) { - var _ref3; - - if (_isArray3) { - if (_i3 >= _iterator3.length) break; - _ref3 = _iterator3[_i3++]; - } else { - _i3 = _iterator3.next(); - if (_i3.done) break; - _ref3 = _i3.value; - } - - const registry = _ref3; - - const filename = (_index2 || _load_index2()).registries[registry].filename; - - const loc = path.join(cwd, filename); - if (!(yield (_fs || _load_fs()).exists(loc))) { - continue; - } - - _this.rootManifestRegistries.push(registry); - - const projectManifestJson = yield _this.config.readJson(loc); - yield (0, (_index || _load_index()).default)(projectManifestJson, cwd, _this.config, cwdIsRoot); - - Object.assign(_this.resolutions, projectManifestJson.resolutions); - Object.assign(manifest, projectManifestJson); - - _this.resolutionMap.init(_this.resolutions); - for (var _iterator4 = Object.keys(_this.resolutionMap.resolutionsByPackage), _isArray4 = Array.isArray(_iterator4), _i4 = 0, _iterator4 = _isArray4 ? _iterator4 : _iterator4[Symbol.iterator]();;) { - var _ref4; - - if (_isArray4) { - if (_i4 >= _iterator4.length) break; - _ref4 = _iterator4[_i4++]; - } else { - _i4 = _iterator4.next(); - if (_i4.done) break; - _ref4 = _i4.value; - } - - const packageName = _ref4; - - const optional = (_objectPath || _load_objectPath()).default.has(manifest.optionalDependencies, packageName) && _this.flags.ignoreOptional; - for (var _iterator8 = _this.resolutionMap.resolutionsByPackage[packageName], _isArray8 = Array.isArray(_iterator8), _i8 = 0, _iterator8 = _isArray8 ? _iterator8 : _iterator8[Symbol.iterator]();;) { - var _ref9; - - if (_isArray8) { - if (_i8 >= _iterator8.length) break; - _ref9 = _iterator8[_i8++]; - } else { - _i8 = _iterator8.next(); - if (_i8.done) break; - _ref9 = _i8.value; - } - - const _ref8 = _ref9; - const pattern = _ref8.pattern; - - resolutionDeps = [...resolutionDeps, { registry, pattern, optional, hint: 'resolution' }]; - } - } - - const pushDeps = function pushDeps(depType, manifest, { hint, optional }, isUsed) { - if (ignoreUnusedPatterns && !isUsed) { - return; - } - // We only take unused dependencies into consideration to get deterministic hoisting. - // Since flat mode doesn't care about hoisting and everything is top level and specified then we can safely - // leave these out. - if (_this.flags.flat && !isUsed) { - return; - } - const depMap = manifest[depType]; - for (const name in depMap) { - if (excludeNames.indexOf(name) >= 0) { - continue; - } - - let pattern = name; - if (!_this.lockfile.getLocked(pattern)) { - // when we use --save we save the dependency to the lockfile with just the name rather than the - // version combo - pattern += '@' + depMap[name]; - } - - // normalization made sure packages are mentioned only once - if (isUsed) { - usedPatterns.push(pattern); - } else { - ignorePatterns.push(pattern); - } - - _this.rootPatternsToOrigin[pattern] = depType; - patterns.push(pattern); - deps.push({ pattern, registry, hint, optional, workspaceName: manifest.name, workspaceLoc: manifest._loc }); - } - }; - - if (cwdIsRoot) { - pushDeps('dependencies', projectManifestJson, { hint: null, optional: false }, true); - pushDeps('devDependencies', projectManifestJson, { hint: 'dev', optional: false }, !_this.config.production); - pushDeps('optionalDependencies', projectManifestJson, { hint: 'optional', optional: true }, true); - } - - if (_this.config.workspaceRootFolder) { - const workspaceLoc = cwdIsRoot ? loc : path.join(_this.config.lockfileFolder, filename); - const workspacesRoot = path.dirname(workspaceLoc); - - let workspaceManifestJson = projectManifestJson; - if (!cwdIsRoot) { - // the manifest we read before was a child workspace, so get the root - workspaceManifestJson = yield _this.config.readJson(workspaceLoc); - yield (0, (_index || _load_index()).default)(workspaceManifestJson, workspacesRoot, _this.config, true); - } - - const workspaces = yield _this.config.resolveWorkspaces(workspacesRoot, workspaceManifestJson); - workspaceLayout = new (_workspaceLayout || _load_workspaceLayout()).default(workspaces, _this.config); - - // add virtual manifest that depends on all workspaces, this way package hoisters and resolvers will work fine - const workspaceDependencies = (0, (_extends2 || _load_extends()).default)({}, workspaceManifestJson.dependencies); - for (var _iterator5 = Object.keys(workspaces), _isArray5 = Array.isArray(_iterator5), _i5 = 0, _iterator5 = _isArray5 ? _iterator5 : _iterator5[Symbol.iterator]();;) { - var _ref5; - - if (_isArray5) { - if (_i5 >= _iterator5.length) break; - _ref5 = _iterator5[_i5++]; - } else { - _i5 = _iterator5.next(); - if (_i5.done) break; - _ref5 = _i5.value; - } - - const workspaceName = _ref5; - - const workspaceManifest = workspaces[workspaceName].manifest; - workspaceDependencies[workspaceName] = workspaceManifest.version; - - // include dependencies from all workspaces - if (_this.flags.includeWorkspaceDeps) { - pushDeps('dependencies', workspaceManifest, { hint: null, optional: false }, true); - pushDeps('devDependencies', workspaceManifest, { hint: 'dev', optional: false }, !_this.config.production); - pushDeps('optionalDependencies', workspaceManifest, { hint: 'optional', optional: true }, true); - } - } - const virtualDependencyManifest = { - _uid: '', - name: `workspace-aggregator-${uuid.v4()}`, - version: '1.0.0', - _registry: 'npm', - _loc: workspacesRoot, - dependencies: workspaceDependencies, - devDependencies: (0, (_extends2 || _load_extends()).default)({}, workspaceManifestJson.devDependencies), - optionalDependencies: (0, (_extends2 || _load_extends()).default)({}, workspaceManifestJson.optionalDependencies), - private: workspaceManifestJson.private, - workspaces: workspaceManifestJson.workspaces - }; - workspaceLayout.virtualManifestName = virtualDependencyManifest.name; - const virtualDep = {}; - virtualDep[virtualDependencyManifest.name] = virtualDependencyManifest.version; - workspaces[virtualDependencyManifest.name] = { loc: workspacesRoot, manifest: virtualDependencyManifest }; - - // ensure dependencies that should be excluded are stripped from the correct manifest - stripExcluded(cwdIsRoot ? virtualDependencyManifest : workspaces[projectManifestJson.name].manifest); - - pushDeps('workspaces', { workspaces: virtualDep }, { hint: 'workspaces', optional: false }, true); - - const implicitWorkspaceDependencies = (0, (_extends2 || _load_extends()).default)({}, workspaceDependencies); - - for (var _iterator6 = (_constants || _load_constants()).OWNED_DEPENDENCY_TYPES, _isArray6 = Array.isArray(_iterator6), _i6 = 0, _iterator6 = _isArray6 ? _iterator6 : _iterator6[Symbol.iterator]();;) { - var _ref6; - - if (_isArray6) { - if (_i6 >= _iterator6.length) break; - _ref6 = _iterator6[_i6++]; - } else { - _i6 = _iterator6.next(); - if (_i6.done) break; - _ref6 = _i6.value; - } - - const type = _ref6; - - for (var _iterator7 = Object.keys(projectManifestJson[type] || {}), _isArray7 = Array.isArray(_iterator7), _i7 = 0, _iterator7 = _isArray7 ? _iterator7 : _iterator7[Symbol.iterator]();;) { - var _ref7; - - if (_isArray7) { - if (_i7 >= _iterator7.length) break; - _ref7 = _iterator7[_i7++]; - } else { - _i7 = _iterator7.next(); - if (_i7.done) break; - _ref7 = _i7.value; - } - - const dependencyName = _ref7; - - delete implicitWorkspaceDependencies[dependencyName]; - } - } - - pushDeps('dependencies', { dependencies: implicitWorkspaceDependencies }, { hint: 'workspaces', optional: false }, true); - } - - break; - } - - // inherit root flat flag - if (manifest.flat) { - _this.flags.flat = true; - } - - return { - requests: [...resolutionDeps, ...deps], - patterns, - manifest, - usedPatterns, - ignorePatterns, - workspaceLayout - }; - })(); - } - - /** - * TODO description - */ - - prepareRequests(requests) { - return requests; - } - - preparePatterns(patterns) { - return patterns; - } - preparePatternsForLinking(patterns, cwdManifest, cwdIsRoot) { - return patterns; - } - - prepareManifests() { - var _this2 = this; - - return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - const manifests = yield _this2.config.getRootManifests(); - return manifests; - })(); - } - - bailout(patterns, workspaceLayout) { - var _this3 = this; - - return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - // We don't want to skip the audit - it could yield important errors - if (_this3.flags.audit) { - return false; - } - // PNP is so fast that the integrity check isn't pertinent - if (_this3.config.plugnplayEnabled) { - return false; - } - if (_this3.flags.skipIntegrityCheck || _this3.flags.force) { - return false; - } - const lockfileCache = _this3.lockfile.cache; - if (!lockfileCache) { - return false; - } - const lockfileClean = _this3.lockfile.parseResultType === 'success'; - const match = yield _this3.integrityChecker.check(patterns, lockfileCache, _this3.flags, workspaceLayout); - if (_this3.flags.frozenLockfile && (!lockfileClean || match.missingPatterns.length > 0)) { - throw new (_errors || _load_errors()).MessageError(_this3.reporter.lang('frozenLockfileError')); - } - - const haveLockfile = yield (_fs || _load_fs()).exists(path.join(_this3.config.lockfileFolder, (_constants || _load_constants()).LOCKFILE_FILENAME)); - - const lockfileIntegrityPresent = !_this3.lockfile.hasEntriesExistWithoutIntegrity(); - const integrityBailout = lockfileIntegrityPresent || !_this3.config.autoAddIntegrity; - - if (match.integrityMatches && haveLockfile && lockfileClean && integrityBailout) { - _this3.reporter.success(_this3.reporter.lang('upToDate')); - return true; - } - - if (match.integrityFileMissing && haveLockfile) { - // Integrity file missing, force script installations - _this3.scripts.setForce(true); - return false; - } - - if (match.hardRefreshRequired) { - // e.g. node version doesn't match, force script installations - _this3.scripts.setForce(true); - return false; - } - - if (!patterns.length && !match.integrityFileMissing) { - _this3.reporter.success(_this3.reporter.lang('nothingToInstall')); - yield _this3.createEmptyManifestFolders(); - yield _this3.saveLockfileAndIntegrity(patterns, workspaceLayout); - return true; - } - - return false; - })(); - } - - /** - * Produce empty folders for all used root manifests. - */ - - createEmptyManifestFolders() { - var _this4 = this; - - return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - if (_this4.config.modulesFolder) { - // already created - return; - } - - for (var _iterator9 = _this4.rootManifestRegistries, _isArray9 = Array.isArray(_iterator9), _i9 = 0, _iterator9 = _isArray9 ? _iterator9 : _iterator9[Symbol.iterator]();;) { - var _ref10; - - if (_isArray9) { - if (_i9 >= _iterator9.length) break; - _ref10 = _iterator9[_i9++]; - } else { - _i9 = _iterator9.next(); - if (_i9.done) break; - _ref10 = _i9.value; - } - - const registryName = _ref10; - const folder = _this4.config.registries[registryName].folder; - - yield (_fs || _load_fs()).mkdirp(path.join(_this4.config.lockfileFolder, folder)); - } - })(); - } - - /** - * TODO description - */ - - markIgnored(patterns) { - for (var _iterator10 = patterns, _isArray10 = Array.isArray(_iterator10), _i10 = 0, _iterator10 = _isArray10 ? _iterator10 : _iterator10[Symbol.iterator]();;) { - var _ref11; - - if (_isArray10) { - if (_i10 >= _iterator10.length) break; - _ref11 = _iterator10[_i10++]; - } else { - _i10 = _iterator10.next(); - if (_i10.done) break; - _ref11 = _i10.value; - } - - const pattern = _ref11; - - const manifest = this.resolver.getStrictResolvedPattern(pattern); - const ref = manifest._reference; - invariant(ref, 'expected package reference'); - - // just mark the package as ignored. if the package is used by a required package, the hoister - // will take care of that. - ref.ignore = true; - } - } - - /** - * helper method that gets only recent manifests - * used by global.ls command - */ - getFlattenedDeps() { - var _this5 = this; - - return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - var _ref12 = yield _this5.fetchRequestFromCwd(); - - const depRequests = _ref12.requests, - rawPatterns = _ref12.patterns; - - - yield _this5.resolver.init(depRequests, {}); - - const manifests = yield (_packageFetcher || _load_packageFetcher()).fetch(_this5.resolver.getManifests(), _this5.config); - _this5.resolver.updateManifests(manifests); - - return _this5.flatten(rawPatterns); - })(); - } - - /** - * TODO description - */ - - init() { - var _this6 = this; - - return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - _this6.checkUpdate(); - - // warn if we have a shrinkwrap - if (yield (_fs || _load_fs()).exists(path.join(_this6.config.lockfileFolder, (_constants || _load_constants()).NPM_SHRINKWRAP_FILENAME))) { - _this6.reporter.warn(_this6.reporter.lang('shrinkwrapWarning')); - } - - // warn if we have an npm lockfile - if (yield (_fs || _load_fs()).exists(path.join(_this6.config.lockfileFolder, (_constants || _load_constants()).NPM_LOCK_FILENAME))) { - _this6.reporter.warn(_this6.reporter.lang('npmLockfileWarning')); - } - - if (_this6.config.plugnplayEnabled) { - _this6.reporter.info(_this6.reporter.lang('plugnplaySuggestV2L1')); - _this6.reporter.info(_this6.reporter.lang('plugnplaySuggestV2L2')); - } - - let flattenedTopLevelPatterns = []; - const steps = []; - - var _ref13 = yield _this6.fetchRequestFromCwd(); - - const depRequests = _ref13.requests, - rawPatterns = _ref13.patterns, - ignorePatterns = _ref13.ignorePatterns, - workspaceLayout = _ref13.workspaceLayout, - manifest = _ref13.manifest; - - let topLevelPatterns = []; - - const artifacts = yield _this6.integrityChecker.getArtifacts(); - if (artifacts) { - _this6.linker.setArtifacts(artifacts); - _this6.scripts.setArtifacts(artifacts); - } - - if ((_packageCompatibility || _load_packageCompatibility()).shouldCheck(manifest, _this6.flags)) { - steps.push((() => { - var _ref14 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (curr, total) { - _this6.reporter.step(curr, total, _this6.reporter.lang('checkingManifest'), emoji.get('mag')); - yield _this6.checkCompatibility(); - }); - - return function (_x, _x2) { - return _ref14.apply(this, arguments); - }; - })()); - } - - const audit = new (_audit || _load_audit()).default(_this6.config, _this6.reporter, { groups: (_constants || _load_constants()).OWNED_DEPENDENCY_TYPES }); - let auditFoundProblems = false; - - steps.push(function (curr, total) { - return (0, (_hooks || _load_hooks()).callThroughHook)('resolveStep', (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - _this6.reporter.step(curr, total, _this6.reporter.lang('resolvingPackages'), emoji.get('mag')); - yield _this6.resolver.init(_this6.prepareRequests(depRequests), { - isFlat: _this6.flags.flat, - isFrozen: _this6.flags.frozenLockfile, - workspaceLayout - }); - topLevelPatterns = _this6.preparePatterns(rawPatterns); - flattenedTopLevelPatterns = yield _this6.flatten(topLevelPatterns); - return { bailout: !_this6.flags.audit && (yield _this6.bailout(topLevelPatterns, workspaceLayout)) }; - })); - }); - - if (_this6.flags.audit) { - steps.push(function (curr, total) { - return (0, (_hooks || _load_hooks()).callThroughHook)('auditStep', (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - _this6.reporter.step(curr, total, _this6.reporter.lang('auditRunning'), emoji.get('mag')); - if (_this6.flags.offline) { - _this6.reporter.warn(_this6.reporter.lang('auditOffline')); - return { bailout: false }; - } - const preparedManifests = yield _this6.prepareManifests(); - // $FlowFixMe - Flow considers `m` in the map operation to be "mixed", so does not recognize `m.object` - const mergedManifest = Object.assign({}, ...Object.values(preparedManifests).map(function (m) { - return m.object; - })); - const auditVulnerabilityCounts = yield audit.performAudit(mergedManifest, _this6.lockfile, _this6.resolver, _this6.linker, topLevelPatterns); - auditFoundProblems = auditVulnerabilityCounts.info || auditVulnerabilityCounts.low || auditVulnerabilityCounts.moderate || auditVulnerabilityCounts.high || auditVulnerabilityCounts.critical; - return { bailout: yield _this6.bailout(topLevelPatterns, workspaceLayout) }; - })); - }); - } - - steps.push(function (curr, total) { - return (0, (_hooks || _load_hooks()).callThroughHook)('fetchStep', (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - _this6.markIgnored(ignorePatterns); - _this6.reporter.step(curr, total, _this6.reporter.lang('fetchingPackages'), emoji.get('truck')); - const manifests = yield (_packageFetcher || _load_packageFetcher()).fetch(_this6.resolver.getManifests(), _this6.config); - _this6.resolver.updateManifests(manifests); - yield (_packageCompatibility || _load_packageCompatibility()).check(_this6.resolver.getManifests(), _this6.config, _this6.flags.ignoreEngines); - })); - }); - - steps.push(function (curr, total) { - return (0, (_hooks || _load_hooks()).callThroughHook)('linkStep', (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - // remove integrity hash to make this operation atomic - yield _this6.integrityChecker.removeIntegrityFile(); - _this6.reporter.step(curr, total, _this6.reporter.lang('linkingDependencies'), emoji.get('link')); - flattenedTopLevelPatterns = _this6.preparePatternsForLinking(flattenedTopLevelPatterns, manifest, _this6.config.lockfileFolder === _this6.config.cwd); - yield _this6.linker.init(flattenedTopLevelPatterns, workspaceLayout, { - linkDuplicates: _this6.flags.linkDuplicates, - ignoreOptional: _this6.flags.ignoreOptional - }); - })); - }); - - if (_this6.config.plugnplayEnabled) { - steps.push(function (curr, total) { - return (0, (_hooks || _load_hooks()).callThroughHook)('pnpStep', (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - const pnpPath = `${_this6.config.lockfileFolder}/${(_constants || _load_constants()).PNP_FILENAME}`; - - const code = yield (0, (_generatePnpMap || _load_generatePnpMap()).generatePnpMap)(_this6.config, flattenedTopLevelPatterns, { - resolver: _this6.resolver, - reporter: _this6.reporter, - targetPath: pnpPath, - workspaceLayout - }); - - try { - const file = yield (_fs || _load_fs()).readFile(pnpPath); - if (file === code) { - return; - } - } catch (error) {} - - yield (_fs || _load_fs()).writeFile(pnpPath, code); - yield (_fs || _load_fs()).chmod(pnpPath, 0o755); - })); - }); - } - - steps.push(function (curr, total) { - return (0, (_hooks || _load_hooks()).callThroughHook)('buildStep', (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - _this6.reporter.step(curr, total, _this6.flags.force ? _this6.reporter.lang('rebuildingPackages') : _this6.reporter.lang('buildingFreshPackages'), emoji.get('hammer')); - - if (_this6.config.ignoreScripts) { - _this6.reporter.warn(_this6.reporter.lang('ignoredScripts')); - } else { - yield _this6.scripts.init(flattenedTopLevelPatterns); - } - })); - }); - - if (_this6.flags.har) { - steps.push((() => { - var _ref21 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (curr, total) { - const formattedDate = new Date().toISOString().replace(/:/g, '-'); - const filename = `yarn-install_${formattedDate}.har`; - _this6.reporter.step(curr, total, _this6.reporter.lang('savingHar', filename), emoji.get('black_circle_for_record')); - yield _this6.config.requestManager.saveHar(filename); - }); - - return function (_x3, _x4) { - return _ref21.apply(this, arguments); - }; - })()); - } - - if (yield _this6.shouldClean()) { - steps.push((() => { - var _ref22 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (curr, total) { - _this6.reporter.step(curr, total, _this6.reporter.lang('cleaningModules'), emoji.get('recycle')); - yield (0, (_autoclean || _load_autoclean()).clean)(_this6.config, _this6.reporter); - }); - - return function (_x5, _x6) { - return _ref22.apply(this, arguments); - }; - })()); - } - - let currentStep = 0; - for (var _iterator11 = steps, _isArray11 = Array.isArray(_iterator11), _i11 = 0, _iterator11 = _isArray11 ? _iterator11 : _iterator11[Symbol.iterator]();;) { - var _ref23; - - if (_isArray11) { - if (_i11 >= _iterator11.length) break; - _ref23 = _iterator11[_i11++]; - } else { - _i11 = _iterator11.next(); - if (_i11.done) break; - _ref23 = _i11.value; - } - - const step = _ref23; - - const stepResult = yield step(++currentStep, steps.length); - if (stepResult && stepResult.bailout) { - if (_this6.flags.audit) { - audit.summary(); - } - if (auditFoundProblems) { - _this6.reporter.warn(_this6.reporter.lang('auditRunAuditForDetails')); - } - _this6.maybeOutputUpdate(); - return flattenedTopLevelPatterns; - } - } - - // fin! - if (_this6.flags.audit) { - audit.summary(); - } - if (auditFoundProblems) { - _this6.reporter.warn(_this6.reporter.lang('auditRunAuditForDetails')); - } - yield _this6.saveLockfileAndIntegrity(topLevelPatterns, workspaceLayout); - yield _this6.persistChanges(); - _this6.maybeOutputUpdate(); - _this6.config.requestManager.clearCache(); - return flattenedTopLevelPatterns; - })(); - } - - checkCompatibility() { - var _this7 = this; - - return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - var _ref24 = yield _this7.fetchRequestFromCwd(); - - const manifest = _ref24.manifest; - - yield (_packageCompatibility || _load_packageCompatibility()).checkOne(manifest, _this7.config, _this7.flags.ignoreEngines); - })(); - } - - persistChanges() { - var _this8 = this; - - return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - // get all the different registry manifests in this folder - const manifests = yield _this8.config.getRootManifests(); - - if (yield _this8.applyChanges(manifests)) { - yield _this8.config.saveRootManifests(manifests); - } - })(); - } - - applyChanges(manifests) { - let hasChanged = false; - - if (this.config.plugnplayPersist) { - const object = manifests.npm.object; - - - if (typeof object.installConfig !== 'object') { - object.installConfig = {}; - } - - if (this.config.plugnplayEnabled && object.installConfig.pnp !== true) { - object.installConfig.pnp = true; - hasChanged = true; - } else if (!this.config.plugnplayEnabled && typeof object.installConfig.pnp !== 'undefined') { - delete object.installConfig.pnp; - hasChanged = true; - } - - if (Object.keys(object.installConfig).length === 0) { - delete object.installConfig; - } - } - - return Promise.resolve(hasChanged); - } - - /** - * Check if we should run the cleaning step. - */ - - shouldClean() { - return (_fs || _load_fs()).exists(path.join(this.config.lockfileFolder, (_constants || _load_constants()).CLEAN_FILENAME)); - } - - /** - * TODO - */ - - flatten(patterns) { - var _this9 = this; - - return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - if (!_this9.flags.flat) { - return patterns; - } - - const flattenedPatterns = []; - - for (var _iterator12 = _this9.resolver.getAllDependencyNamesByLevelOrder(patterns), _isArray12 = Array.isArray(_iterator12), _i12 = 0, _iterator12 = _isArray12 ? _iterator12 : _iterator12[Symbol.iterator]();;) { - var _ref25; - - if (_isArray12) { - if (_i12 >= _iterator12.length) break; - _ref25 = _iterator12[_i12++]; - } else { - _i12 = _iterator12.next(); - if (_i12.done) break; - _ref25 = _i12.value; - } - - const name = _ref25; - - const infos = _this9.resolver.getAllInfoForPackageName(name).filter(function (manifest) { - const ref = manifest._reference; - invariant(ref, 'expected package reference'); - return !ref.ignore; - }); - - if (infos.length === 0) { - continue; - } - - if (infos.length === 1) { - // single version of this package - // take out a single pattern as multiple patterns may have resolved to this package - flattenedPatterns.push(_this9.resolver.patternsByPackage[name][0]); - continue; - } - - const options = infos.map(function (info) { - const ref = info._reference; - invariant(ref, 'expected reference'); - return { - // TODO `and is required by {PARENT}`, - name: _this9.reporter.lang('manualVersionResolutionOption', ref.patterns.join(', '), info.version), - - value: info.version - }; - }); - const versions = infos.map(function (info) { - return info.version; - }); - let version; - - const resolutionVersion = _this9.resolutions[name]; - if (resolutionVersion && versions.indexOf(resolutionVersion) >= 0) { - // use json `resolution` version - version = resolutionVersion; - } else { - version = yield _this9.reporter.select(_this9.reporter.lang('manualVersionResolution', name), _this9.reporter.lang('answer'), options); - _this9.resolutions[name] = version; - } - - flattenedPatterns.push(_this9.resolver.collapseAllVersionsOfPackage(name, version)); - } - - // save resolutions to their appropriate root manifest - if (Object.keys(_this9.resolutions).length) { - const manifests = yield _this9.config.getRootManifests(); - - for (const name in _this9.resolutions) { - const version = _this9.resolutions[name]; - - const patterns = _this9.resolver.patternsByPackage[name]; - if (!patterns) { - continue; - } - - let manifest; - for (var _iterator13 = patterns, _isArray13 = Array.isArray(_iterator13), _i13 = 0, _iterator13 = _isArray13 ? _iterator13 : _iterator13[Symbol.iterator]();;) { - var _ref26; - - if (_isArray13) { - if (_i13 >= _iterator13.length) break; - _ref26 = _iterator13[_i13++]; - } else { - _i13 = _iterator13.next(); - if (_i13.done) break; - _ref26 = _i13.value; - } - - const pattern = _ref26; - - manifest = _this9.resolver.getResolvedPattern(pattern); - if (manifest) { - break; - } - } - invariant(manifest, 'expected manifest'); - - const ref = manifest._reference; - invariant(ref, 'expected reference'); - - const object = manifests[ref.registry].object; - object.resolutions = object.resolutions || {}; - object.resolutions[name] = version; - } - - yield _this9.config.saveRootManifests(manifests); - } - - return flattenedPatterns; - })(); - } - - /** - * Remove offline tarballs that are no longer required - */ - - pruneOfflineMirror(lockfile) { - var _this10 = this; - - return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - const mirror = _this10.config.getOfflineMirrorPath(); - if (!mirror) { - return; - } - - const requiredTarballs = new Set(); - for (const dependency in lockfile) { - const resolved = lockfile[dependency].resolved; - if (resolved) { - const basename = path.basename(resolved.split('#')[0]); - if (dependency[0] === '@' && basename[0] !== '@') { - requiredTarballs.add(`${dependency.split('/')[0]}-${basename}`); - } - requiredTarballs.add(basename); - } - } - - const mirrorFiles = yield (_fs || _load_fs()).walk(mirror); - for (var _iterator14 = mirrorFiles, _isArray14 = Array.isArray(_iterator14), _i14 = 0, _iterator14 = _isArray14 ? _iterator14 : _iterator14[Symbol.iterator]();;) { - var _ref27; - - if (_isArray14) { - if (_i14 >= _iterator14.length) break; - _ref27 = _iterator14[_i14++]; - } else { - _i14 = _iterator14.next(); - if (_i14.done) break; - _ref27 = _i14.value; - } - - const file = _ref27; - - const isTarball = path.extname(file.basename) === '.tgz'; - // if using experimental-pack-script-packages-in-mirror flag, don't unlink prebuilt packages - const hasPrebuiltPackage = file.relative.startsWith('prebuilt/'); - if (isTarball && !hasPrebuiltPackage && !requiredTarballs.has(file.basename)) { - yield (_fs || _load_fs()).unlink(file.absolute); - } - } - })(); - } - - /** - * Save updated integrity and lockfiles. - */ - - saveLockfileAndIntegrity(patterns, workspaceLayout) { - var _this11 = this; - - return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - const resolvedPatterns = {}; - Object.keys(_this11.resolver.patterns).forEach(function (pattern) { - if (!workspaceLayout || !workspaceLayout.getManifestByPattern(pattern)) { - resolvedPatterns[pattern] = _this11.resolver.patterns[pattern]; - } - }); - - // TODO this code is duplicated in a few places, need a common way to filter out workspace patterns from lockfile - patterns = patterns.filter(function (p) { - return !workspaceLayout || !workspaceLayout.getManifestByPattern(p); - }); - - const lockfileBasedOnResolver = _this11.lockfile.getLockfile(resolvedPatterns); - - if (_this11.config.pruneOfflineMirror) { - yield _this11.pruneOfflineMirror(lockfileBasedOnResolver); - } - - // write integrity hash - if (!_this11.config.plugnplayEnabled) { - yield _this11.integrityChecker.save(patterns, lockfileBasedOnResolver, _this11.flags, workspaceLayout, _this11.scripts.getArtifacts()); - } - - // --no-lockfile or --pure-lockfile or --frozen-lockfile - if (_this11.flags.lockfile === false || _this11.flags.pureLockfile || _this11.flags.frozenLockfile) { - return; - } - - const lockFileHasAllPatterns = patterns.every(function (p) { - return _this11.lockfile.getLocked(p); - }); - const lockfilePatternsMatch = Object.keys(_this11.lockfile.cache || {}).every(function (p) { - return lockfileBasedOnResolver[p]; - }); - const resolverPatternsAreSameAsInLockfile = Object.keys(lockfileBasedOnResolver).every(function (pattern) { - const manifest = _this11.lockfile.getLocked(pattern); - return manifest && manifest.resolved === lockfileBasedOnResolver[pattern].resolved && deepEqual(manifest.prebuiltVariants, lockfileBasedOnResolver[pattern].prebuiltVariants); - }); - const integrityPatternsAreSameAsInLockfile = Object.keys(lockfileBasedOnResolver).every(function (pattern) { - const existingIntegrityInfo = lockfileBasedOnResolver[pattern].integrity; - if (!existingIntegrityInfo) { - // if this entry does not have an integrity, no need to re-write the lockfile because of it - return true; - } - const manifest = _this11.lockfile.getLocked(pattern); - if (manifest && manifest.integrity) { - const manifestIntegrity = ssri.stringify(manifest.integrity); - return manifestIntegrity === existingIntegrityInfo; - } - return false; - }); - - // remove command is followed by install with force, lockfile will be rewritten in any case then - if (!_this11.flags.force && _this11.lockfile.parseResultType === 'success' && lockFileHasAllPatterns && lockfilePatternsMatch && resolverPatternsAreSameAsInLockfile && integrityPatternsAreSameAsInLockfile && patterns.length) { - return; - } - - // build lockfile location - const loc = path.join(_this11.config.lockfileFolder, (_constants || _load_constants()).LOCKFILE_FILENAME); - - // write lockfile - const lockSource = (0, (_lockfile2 || _load_lockfile2()).stringify)(lockfileBasedOnResolver, false, _this11.config.enableLockfileVersions); - yield (_fs || _load_fs()).writeFilePreservingEol(loc, lockSource); - - _this11._logSuccessSaveLockfile(); - })(); - } - - _logSuccessSaveLockfile() { - this.reporter.success(this.reporter.lang('savedLockfile')); - } - - /** - * Load the dependency graph of the current install. Only does package resolving and wont write to the cwd. - */ - hydrate(ignoreUnusedPatterns) { - var _this12 = this; - - return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - const request = yield _this12.fetchRequestFromCwd([], ignoreUnusedPatterns); - const depRequests = request.requests, - rawPatterns = request.patterns, - ignorePatterns = request.ignorePatterns, - workspaceLayout = request.workspaceLayout; - - - yield _this12.resolver.init(depRequests, { - isFlat: _this12.flags.flat, - isFrozen: _this12.flags.frozenLockfile, - workspaceLayout - }); - yield _this12.flatten(rawPatterns); - _this12.markIgnored(ignorePatterns); - - // fetch packages, should hit cache most of the time - const manifests = yield (_packageFetcher || _load_packageFetcher()).fetch(_this12.resolver.getManifests(), _this12.config); - _this12.resolver.updateManifests(manifests); - yield (_packageCompatibility || _load_packageCompatibility()).check(_this12.resolver.getManifests(), _this12.config, _this12.flags.ignoreEngines); - - // expand minimal manifests - for (var _iterator15 = _this12.resolver.getManifests(), _isArray15 = Array.isArray(_iterator15), _i15 = 0, _iterator15 = _isArray15 ? _iterator15 : _iterator15[Symbol.iterator]();;) { - var _ref28; - - if (_isArray15) { - if (_i15 >= _iterator15.length) break; - _ref28 = _iterator15[_i15++]; - } else { - _i15 = _iterator15.next(); - if (_i15.done) break; - _ref28 = _i15.value; - } - - const manifest = _ref28; - - const ref = manifest._reference; - invariant(ref, 'expected reference'); - const type = ref.remote.type; - // link specifier won't ever hit cache - - let loc = ''; - if (type === 'link') { - continue; - } else if (type === 'workspace') { - if (!ref.remote.reference) { - continue; - } - loc = ref.remote.reference; - } else { - loc = _this12.config.generateModuleCachePath(ref); - } - const newPkg = yield _this12.config.readManifest(loc); - yield _this12.resolver.updateManifest(ref, newPkg); - } - - return request; - })(); - } - - /** - * Check for updates every day and output a nag message if there's a newer version. - */ - - checkUpdate() { - if (this.config.nonInteractive) { - // don't show upgrade dialog on CI or non-TTY terminals - return; - } - - // don't check if disabled - if (this.config.getOption('disable-self-update-check')) { - return; - } - - // only check for updates once a day - const lastUpdateCheck = Number(this.config.getOption('lastUpdateCheck')) || 0; - if (lastUpdateCheck && Date.now() - lastUpdateCheck < ONE_DAY) { - return; - } - - // don't bug for updates on tagged releases - if ((_yarnVersion || _load_yarnVersion()).version.indexOf('-') >= 0) { - return; - } - - this._checkUpdate().catch(() => { - // swallow errors - }); - } - - _checkUpdate() { - var _this13 = this; - - return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { - let latestVersion = yield _this13.config.requestManager.request({ - url: (_constants || _load_constants()).SELF_UPDATE_VERSION_URL - }); - invariant(typeof latestVersion === 'string', 'expected string'); - latestVersion = latestVersion.trim(); - if (!semver.valid(latestVersion)) { - return; - } - - // ensure we only check for updates periodically - _this13.config.registries.yarn.saveHomeConfig({ - lastUpdateCheck: Date.now() - }); - - if (semver.gt(latestVersion, (_yarnVersion || _load_yarnVersion()).version)) { - const installationMethod = yield (0, (_yarnVersion || _load_yarnVersion()).getInstallationMethod)(); - _this13.maybeOutputUpdate = function () { - _this13.reporter.warn(_this13.reporter.lang('yarnOutdated', latestVersion, (_yarnVersion || _load_yarnVersion()).version)); - - const command = getUpdateCommand(installationMethod); - if (command) { - _this13.reporter.info(_this13.reporter.lang('yarnOutdatedCommand')); - _this13.reporter.command(command); - } else { - const installer = getUpdateInstaller(installationMethod); - if (installer) { - _this13.reporter.info(_this13.reporter.lang('yarnOutdatedInstaller', installer)); - } - } - }; - } - })(); - } - - /** - * Method to override with a possible upgrade message. - */ - - maybeOutputUpdate() {} -} - -exports.Install = Install; -function hasWrapper(commander, args) { - return true; -} - -function setFlags(commander) { - commander.description('Yarn install is used to install all dependencies for a project.'); - commander.usage('install [flags]'); - commander.option('-A, --audit', 'Run vulnerability audit on installed packages'); - commander.option('-g, --global', 'DEPRECATED'); - commander.option('-S, --save', 'DEPRECATED - save package to your `dependencies`'); - commander.option('-D, --save-dev', 'DEPRECATED - save package to your `devDependencies`'); - commander.option('-P, --save-peer', 'DEPRECATED - save package to your `peerDependencies`'); - commander.option('-O, --save-optional', 'DEPRECATED - save package to your `optionalDependencies`'); - commander.option('-E, --save-exact', 'DEPRECATED'); - commander.option('-T, --save-tilde', 'DEPRECATED'); -} - -/***/ }), -/* 35 */ -/***/ (function(module, exports, __webpack_require__) { - -var isObject = __webpack_require__(52); -module.exports = function (it) { - if (!isObject(it)) throw TypeError(it + ' is not an object!'); - return it; -}; - - -/***/ }), -/* 36 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return SubjectSubscriber; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return Subject; }); -/* unused harmony export AnonymousSubject */ -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_tslib__ = __webpack_require__(1); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__Observable__ = __webpack_require__(12); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__Subscriber__ = __webpack_require__(7); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__Subscription__ = __webpack_require__(25); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__util_ObjectUnsubscribedError__ = __webpack_require__(189); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__SubjectSubscription__ = __webpack_require__(422); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_6__internal_symbol_rxSubscriber__ = __webpack_require__(321); -/** PURE_IMPORTS_START tslib,_Observable,_Subscriber,_Subscription,_util_ObjectUnsubscribedError,_SubjectSubscription,_internal_symbol_rxSubscriber PURE_IMPORTS_END */ - - - - - - - -var SubjectSubscriber = /*@__PURE__*/ (function (_super) { - __WEBPACK_IMPORTED_MODULE_0_tslib__["a" /* __extends */](SubjectSubscriber, _super); - function SubjectSubscriber(destination) { - var _this = _super.call(this, destination) || this; - _this.destination = destination; - return _this; - } - return SubjectSubscriber; -}(__WEBPACK_IMPORTED_MODULE_2__Subscriber__["a" /* Subscriber */])); - -var Subject = /*@__PURE__*/ (function (_super) { - __WEBPACK_IMPORTED_MODULE_0_tslib__["a" /* __extends */](Subject, _super); - function Subject() { - var _this = _super.call(this) || this; - _this.observers = []; - _this.closed = false; - _this.isStopped = false; - _this.hasError = false; - _this.thrownError = null; - return _this; - } - Subject.prototype[__WEBPACK_IMPORTED_MODULE_6__internal_symbol_rxSubscriber__["a" /* rxSubscriber */]] = function () { - return new SubjectSubscriber(this); - }; - Subject.prototype.lift = function (operator) { - var subject = new AnonymousSubject(this, this); - subject.operator = operator; - return subject; - }; - Subject.prototype.next = function (value) { - if (this.closed) { - throw new __WEBPACK_IMPORTED_MODULE_4__util_ObjectUnsubscribedError__["a" /* ObjectUnsubscribedError */](); - } - if (!this.isStopped) { - var observers = this.observers; - var len = observers.length; - var copy = observers.slice(); - for (var i = 0; i < len; i++) { - copy[i].next(value); - } - } - }; - Subject.prototype.error = function (err) { - if (this.closed) { - throw new __WEBPACK_IMPORTED_MODULE_4__util_ObjectUnsubscribedError__["a" /* ObjectUnsubscribedError */](); - } - this.hasError = true; - this.thrownError = err; - this.isStopped = true; - var observers = this.observers; - var len = observers.length; - var copy = observers.slice(); - for (var i = 0; i < len; i++) { - copy[i].error(err); - } - this.observers.length = 0; - }; - Subject.prototype.complete = function () { - if (this.closed) { - throw new __WEBPACK_IMPORTED_MODULE_4__util_ObjectUnsubscribedError__["a" /* ObjectUnsubscribedError */](); - } - this.isStopped = true; - var observers = this.observers; - var len = observers.length; - var copy = observers.slice(); - for (var i = 0; i < len; i++) { - copy[i].complete(); - } - this.observers.length = 0; - }; - Subject.prototype.unsubscribe = function () { - this.isStopped = true; - this.closed = true; - this.observers = null; - }; - Subject.prototype._trySubscribe = function (subscriber) { - if (this.closed) { - throw new __WEBPACK_IMPORTED_MODULE_4__util_ObjectUnsubscribedError__["a" /* ObjectUnsubscribedError */](); - } - else { - return _super.prototype._trySubscribe.call(this, subscriber); - } - }; - Subject.prototype._subscribe = function (subscriber) { - if (this.closed) { - throw new __WEBPACK_IMPORTED_MODULE_4__util_ObjectUnsubscribedError__["a" /* ObjectUnsubscribedError */](); - } - else if (this.hasError) { - subscriber.error(this.thrownError); - return __WEBPACK_IMPORTED_MODULE_3__Subscription__["a" /* Subscription */].EMPTY; - } - else if (this.isStopped) { - subscriber.complete(); - return __WEBPACK_IMPORTED_MODULE_3__Subscription__["a" /* Subscription */].EMPTY; - } - else { - this.observers.push(subscriber); - return new __WEBPACK_IMPORTED_MODULE_5__SubjectSubscription__["a" /* SubjectSubscription */](this, subscriber); - } - }; - Subject.prototype.asObservable = function () { - var observable = new __WEBPACK_IMPORTED_MODULE_1__Observable__["a" /* Observable */](); - observable.source = this; - return observable; - }; - Subject.create = function (destination, source) { - return new AnonymousSubject(destination, source); - }; - return Subject; -}(__WEBPACK_IMPORTED_MODULE_1__Observable__["a" /* Observable */])); - -var AnonymousSubject = /*@__PURE__*/ (function (_super) { - __WEBPACK_IMPORTED_MODULE_0_tslib__["a" /* __extends */](AnonymousSubject, _super); - function AnonymousSubject(destination, source) { - var _this = _super.call(this) || this; - _this.destination = destination; - _this.source = source; - return _this; - } - AnonymousSubject.prototype.next = function (value) { - var destination = this.destination; - if (destination && destination.next) { - destination.next(value); - } - }; - AnonymousSubject.prototype.error = function (err) { - var destination = this.destination; - if (destination && destination.error) { - this.destination.error(err); - } - }; - AnonymousSubject.prototype.complete = function () { - var destination = this.destination; - if (destination && destination.complete) { - this.destination.complete(); - } - }; - AnonymousSubject.prototype._subscribe = function (subscriber) { - var source = this.source; - if (source) { - return this.source.subscribe(subscriber); - } - else { - return __WEBPACK_IMPORTED_MODULE_3__Subscription__["a" /* Subscription */].EMPTY; - } - }; - return AnonymousSubject; -}(Subject)); - -//# sourceMappingURL=Subject.js.map - - -/***/ }), -/* 37 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.normalizePattern = normalizePattern; - -/** - * Explode and normalize a pattern into its name and range. - */ - -function normalizePattern(pattern) { - let hasVersion = false; - let range = 'latest'; - let name = pattern; - - // if we're a scope then remove the @ and add it back later - let isScoped = false; - if (name[0] === '@') { - isScoped = true; - name = name.slice(1); - } - - // take first part as the name - const parts = name.split('@'); - if (parts.length > 1) { - name = parts.shift(); - range = parts.join('@'); - - if (range) { - hasVersion = true; - } else { - range = '*'; - } - } - - // add back @ scope suffix - if (isScoped) { - name = `@${name}`; - } - - return { name, range, hasVersion }; -} - -/***/ }), -/* 38 */ -/***/ (function(module, exports, __webpack_require__) { - -/* WEBPACK VAR INJECTION */(function(module) {var __WEBPACK_AMD_DEFINE_RESULT__;/** - * @license - * Lodash - * Copyright JS Foundation and other contributors - * Released under MIT license - * Based on Underscore.js 1.8.3 - * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - */ -;(function() { - - /** Used as a safe reference for `undefined` in pre-ES5 environments. */ - var undefined; - - /** Used as the semantic version number. */ - var VERSION = '4.17.10'; - - /** Used as the size to enable large array optimizations. */ - var LARGE_ARRAY_SIZE = 200; - - /** Error message constants. */ - var CORE_ERROR_TEXT = 'Unsupported core-js use. Try https://npms.io/search?q=ponyfill.', - FUNC_ERROR_TEXT = 'Expected a function'; - - /** Used to stand-in for `undefined` hash values. */ - var HASH_UNDEFINED = '__lodash_hash_undefined__'; - - /** Used as the maximum memoize cache size. */ - var MAX_MEMOIZE_SIZE = 500; - - /** Used as the internal argument placeholder. */ - var PLACEHOLDER = '__lodash_placeholder__'; - - /** Used to compose bitmasks for cloning. */ - var CLONE_DEEP_FLAG = 1, - CLONE_FLAT_FLAG = 2, - CLONE_SYMBOLS_FLAG = 4; - - /** Used to compose bitmasks for value comparisons. */ - var COMPARE_PARTIAL_FLAG = 1, - COMPARE_UNORDERED_FLAG = 2; - - /** Used to compose bitmasks for function metadata. */ - var WRAP_BIND_FLAG = 1, - WRAP_BIND_KEY_FLAG = 2, - WRAP_CURRY_BOUND_FLAG = 4, - WRAP_CURRY_FLAG = 8, - WRAP_CURRY_RIGHT_FLAG = 16, - WRAP_PARTIAL_FLAG = 32, - WRAP_PARTIAL_RIGHT_FLAG = 64, - WRAP_ARY_FLAG = 128, - WRAP_REARG_FLAG = 256, - WRAP_FLIP_FLAG = 512; - - /** Used as default options for `_.truncate`. */ - var DEFAULT_TRUNC_LENGTH = 30, - DEFAULT_TRUNC_OMISSION = '...'; - - /** Used to detect hot functions by number of calls within a span of milliseconds. */ - var HOT_COUNT = 800, - HOT_SPAN = 16; - - /** Used to indicate the type of lazy iteratees. */ - var LAZY_FILTER_FLAG = 1, - LAZY_MAP_FLAG = 2, - LAZY_WHILE_FLAG = 3; - - /** Used as references for various `Number` constants. */ - var INFINITY = 1 / 0, - MAX_SAFE_INTEGER = 9007199254740991, - MAX_INTEGER = 1.7976931348623157e+308, - NAN = 0 / 0; - - /** Used as references for the maximum length and index of an array. */ - var MAX_ARRAY_LENGTH = 4294967295, - MAX_ARRAY_INDEX = MAX_ARRAY_LENGTH - 1, - HALF_MAX_ARRAY_LENGTH = MAX_ARRAY_LENGTH >>> 1; - - /** Used to associate wrap methods with their bit flags. */ - var wrapFlags = [ - ['ary', WRAP_ARY_FLAG], - ['bind', WRAP_BIND_FLAG], - ['bindKey', WRAP_BIND_KEY_FLAG], - ['curry', WRAP_CURRY_FLAG], - ['curryRight', WRAP_CURRY_RIGHT_FLAG], - ['flip', WRAP_FLIP_FLAG], - ['partial', WRAP_PARTIAL_FLAG], - ['partialRight', WRAP_PARTIAL_RIGHT_FLAG], - ['rearg', WRAP_REARG_FLAG] - ]; - - /** `Object#toString` result references. */ - var argsTag = '[object Arguments]', - arrayTag = '[object Array]', - asyncTag = '[object AsyncFunction]', - boolTag = '[object Boolean]', - dateTag = '[object Date]', - domExcTag = '[object DOMException]', - errorTag = '[object Error]', - funcTag = '[object Function]', - genTag = '[object GeneratorFunction]', - mapTag = '[object Map]', - numberTag = '[object Number]', - nullTag = '[object Null]', - objectTag = '[object Object]', - promiseTag = '[object Promise]', - proxyTag = '[object Proxy]', - regexpTag = '[object RegExp]', - setTag = '[object Set]', - stringTag = '[object String]', - symbolTag = '[object Symbol]', - undefinedTag = '[object Undefined]', - weakMapTag = '[object WeakMap]', - weakSetTag = '[object WeakSet]'; - - var arrayBufferTag = '[object ArrayBuffer]', - dataViewTag = '[object DataView]', - float32Tag = '[object Float32Array]', - float64Tag = '[object Float64Array]', - int8Tag = '[object Int8Array]', - int16Tag = '[object Int16Array]', - int32Tag = '[object Int32Array]', - uint8Tag = '[object Uint8Array]', - uint8ClampedTag = '[object Uint8ClampedArray]', - uint16Tag = '[object Uint16Array]', - uint32Tag = '[object Uint32Array]'; - - /** Used to match empty string literals in compiled template source. */ - var reEmptyStringLeading = /\b__p \+= '';/g, - reEmptyStringMiddle = /\b(__p \+=) '' \+/g, - reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g; - - /** Used to match HTML entities and HTML characters. */ - var reEscapedHtml = /&(?:amp|lt|gt|quot|#39);/g, - reUnescapedHtml = /[&<>"']/g, - reHasEscapedHtml = RegExp(reEscapedHtml.source), - reHasUnescapedHtml = RegExp(reUnescapedHtml.source); - - /** Used to match template delimiters. */ - var reEscape = /<%-([\s\S]+?)%>/g, - reEvaluate = /<%([\s\S]+?)%>/g, - reInterpolate = /<%=([\s\S]+?)%>/g; - - /** Used to match property names within property paths. */ - var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/, - reIsPlainProp = /^\w*$/, - rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g; - - /** - * Used to match `RegExp` - * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns). - */ - var reRegExpChar = /[\\^$.*+?()[\]{}|]/g, - reHasRegExpChar = RegExp(reRegExpChar.source); - - /** Used to match leading and trailing whitespace. */ - var reTrim = /^\s+|\s+$/g, - reTrimStart = /^\s+/, - reTrimEnd = /\s+$/; - - /** Used to match wrap detail comments. */ - var reWrapComment = /\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/, - reWrapDetails = /\{\n\/\* \[wrapped with (.+)\] \*/, - reSplitDetails = /,? & /; - - /** Used to match words composed of alphanumeric characters. */ - var reAsciiWord = /[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g; - - /** Used to match backslashes in property paths. */ - var reEscapeChar = /\\(\\)?/g; - - /** - * Used to match - * [ES template delimiters](http://ecma-international.org/ecma-262/7.0/#sec-template-literal-lexical-components). - */ - var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g; - - /** Used to match `RegExp` flags from their coerced string values. */ - var reFlags = /\w*$/; - - /** Used to detect bad signed hexadecimal string values. */ - var reIsBadHex = /^[-+]0x[0-9a-f]+$/i; - - /** Used to detect binary string values. */ - var reIsBinary = /^0b[01]+$/i; - - /** Used to detect host constructors (Safari). */ - var reIsHostCtor = /^\[object .+?Constructor\]$/; - - /** Used to detect octal string values. */ - var reIsOctal = /^0o[0-7]+$/i; - - /** Used to detect unsigned integer values. */ - var reIsUint = /^(?:0|[1-9]\d*)$/; - - /** Used to match Latin Unicode letters (excluding mathematical operators). */ - var reLatin = /[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g; - - /** Used to ensure capturing order of template delimiters. */ - var reNoMatch = /($^)/; - - /** Used to match unescaped characters in compiled string literals. */ - var reUnescapedString = /['\n\r\u2028\u2029\\]/g; - - /** Used to compose unicode character classes. */ - var rsAstralRange = '\\ud800-\\udfff', - rsComboMarksRange = '\\u0300-\\u036f', - reComboHalfMarksRange = '\\ufe20-\\ufe2f', - rsComboSymbolsRange = '\\u20d0-\\u20ff', - rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange, - rsDingbatRange = '\\u2700-\\u27bf', - rsLowerRange = 'a-z\\xdf-\\xf6\\xf8-\\xff', - rsMathOpRange = '\\xac\\xb1\\xd7\\xf7', - rsNonCharRange = '\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf', - rsPunctuationRange = '\\u2000-\\u206f', - rsSpaceRange = ' \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000', - rsUpperRange = 'A-Z\\xc0-\\xd6\\xd8-\\xde', - rsVarRange = '\\ufe0e\\ufe0f', - rsBreakRange = rsMathOpRange + rsNonCharRange + rsPunctuationRange + rsSpaceRange; - - /** Used to compose unicode capture groups. */ - var rsApos = "['\u2019]", - rsAstral = '[' + rsAstralRange + ']', - rsBreak = '[' + rsBreakRange + ']', - rsCombo = '[' + rsComboRange + ']', - rsDigits = '\\d+', - rsDingbat = '[' + rsDingbatRange + ']', - rsLower = '[' + rsLowerRange + ']', - rsMisc = '[^' + rsAstralRange + rsBreakRange + rsDigits + rsDingbatRange + rsLowerRange + rsUpperRange + ']', - rsFitz = '\\ud83c[\\udffb-\\udfff]', - rsModifier = '(?:' + rsCombo + '|' + rsFitz + ')', - rsNonAstral = '[^' + rsAstralRange + ']', - rsRegional = '(?:\\ud83c[\\udde6-\\uddff]){2}', - rsSurrPair = '[\\ud800-\\udbff][\\udc00-\\udfff]', - rsUpper = '[' + rsUpperRange + ']', - rsZWJ = '\\u200d'; - - /** Used to compose unicode regexes. */ - var rsMiscLower = '(?:' + rsLower + '|' + rsMisc + ')', - rsMiscUpper = '(?:' + rsUpper + '|' + rsMisc + ')', - rsOptContrLower = '(?:' + rsApos + '(?:d|ll|m|re|s|t|ve))?', - rsOptContrUpper = '(?:' + rsApos + '(?:D|LL|M|RE|S|T|VE))?', - reOptMod = rsModifier + '?', - rsOptVar = '[' + rsVarRange + ']?', - rsOptJoin = '(?:' + rsZWJ + '(?:' + [rsNonAstral, rsRegional, rsSurrPair].join('|') + ')' + rsOptVar + reOptMod + ')*', - rsOrdLower = '\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])', - rsOrdUpper = '\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])', - rsSeq = rsOptVar + reOptMod + rsOptJoin, - rsEmoji = '(?:' + [rsDingbat, rsRegional, rsSurrPair].join('|') + ')' + rsSeq, - rsSymbol = '(?:' + [rsNonAstral + rsCombo + '?', rsCombo, rsRegional, rsSurrPair, rsAstral].join('|') + ')'; - - /** Used to match apostrophes. */ - var reApos = RegExp(rsApos, 'g'); - - /** - * Used to match [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks) and - * [combining diacritical marks for symbols](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks_for_Symbols). - */ - var reComboMark = RegExp(rsCombo, 'g'); - - /** Used to match [string symbols](https://mathiasbynens.be/notes/javascript-unicode). */ - var reUnicode = RegExp(rsFitz + '(?=' + rsFitz + ')|' + rsSymbol + rsSeq, 'g'); - - /** Used to match complex or compound words. */ - var reUnicodeWord = RegExp([ - rsUpper + '?' + rsLower + '+' + rsOptContrLower + '(?=' + [rsBreak, rsUpper, '$'].join('|') + ')', - rsMiscUpper + '+' + rsOptContrUpper + '(?=' + [rsBreak, rsUpper + rsMiscLower, '$'].join('|') + ')', - rsUpper + '?' + rsMiscLower + '+' + rsOptContrLower, - rsUpper + '+' + rsOptContrUpper, - rsOrdUpper, - rsOrdLower, - rsDigits, - rsEmoji - ].join('|'), 'g'); - - /** Used to detect strings with [zero-width joiners or code points from the astral planes](http://eev.ee/blog/2015/09/12/dark-corners-of-unicode/). */ - var reHasUnicode = RegExp('[' + rsZWJ + rsAstralRange + rsComboRange + rsVarRange + ']'); - - /** Used to detect strings that need a more robust regexp to match words. */ - var reHasUnicodeWord = /[a-z][A-Z]|[A-Z]{2,}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/; - - /** Used to assign default `context` object properties. */ - var contextProps = [ - 'Array', 'Buffer', 'DataView', 'Date', 'Error', 'Float32Array', 'Float64Array', - 'Function', 'Int8Array', 'Int16Array', 'Int32Array', 'Map', 'Math', 'Object', - 'Promise', 'RegExp', 'Set', 'String', 'Symbol', 'TypeError', 'Uint8Array', - 'Uint8ClampedArray', 'Uint16Array', 'Uint32Array', 'WeakMap', - '_', 'clearTimeout', 'isFinite', 'parseInt', 'setTimeout' - ]; - - /** Used to make template sourceURLs easier to identify. */ - var templateCounter = -1; - - /** Used to identify `toStringTag` values of typed arrays. */ - var typedArrayTags = {}; - typedArrayTags[float32Tag] = typedArrayTags[float64Tag] = - typedArrayTags[int8Tag] = typedArrayTags[int16Tag] = - typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] = - typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] = - typedArrayTags[uint32Tag] = true; - typedArrayTags[argsTag] = typedArrayTags[arrayTag] = - typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] = - typedArrayTags[dataViewTag] = typedArrayTags[dateTag] = - typedArrayTags[errorTag] = typedArrayTags[funcTag] = - typedArrayTags[mapTag] = typedArrayTags[numberTag] = - typedArrayTags[objectTag] = typedArrayTags[regexpTag] = - typedArrayTags[setTag] = typedArrayTags[stringTag] = - typedArrayTags[weakMapTag] = false; - - /** Used to identify `toStringTag` values supported by `_.clone`. */ - var cloneableTags = {}; - cloneableTags[argsTag] = cloneableTags[arrayTag] = - cloneableTags[arrayBufferTag] = cloneableTags[dataViewTag] = - cloneableTags[boolTag] = cloneableTags[dateTag] = - cloneableTags[float32Tag] = cloneableTags[float64Tag] = - cloneableTags[int8Tag] = cloneableTags[int16Tag] = - cloneableTags[int32Tag] = cloneableTags[mapTag] = - cloneableTags[numberTag] = cloneableTags[objectTag] = - cloneableTags[regexpTag] = cloneableTags[setTag] = - cloneableTags[stringTag] = cloneableTags[symbolTag] = - cloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] = - cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true; - cloneableTags[errorTag] = cloneableTags[funcTag] = - cloneableTags[weakMapTag] = false; - - /** Used to map Latin Unicode letters to basic Latin letters. */ - var deburredLetters = { - // Latin-1 Supplement block. - '\xc0': 'A', '\xc1': 'A', '\xc2': 'A', '\xc3': 'A', '\xc4': 'A', '\xc5': 'A', - '\xe0': 'a', '\xe1': 'a', '\xe2': 'a', '\xe3': 'a', '\xe4': 'a', '\xe5': 'a', - '\xc7': 'C', '\xe7': 'c', - '\xd0': 'D', '\xf0': 'd', - '\xc8': 'E', '\xc9': 'E', '\xca': 'E', '\xcb': 'E', - '\xe8': 'e', '\xe9': 'e', '\xea': 'e', '\xeb': 'e', - '\xcc': 'I', '\xcd': 'I', '\xce': 'I', '\xcf': 'I', - '\xec': 'i', '\xed': 'i', '\xee': 'i', '\xef': 'i', - '\xd1': 'N', '\xf1': 'n', - '\xd2': 'O', '\xd3': 'O', '\xd4': 'O', '\xd5': 'O', '\xd6': 'O', '\xd8': 'O', - '\xf2': 'o', '\xf3': 'o', '\xf4': 'o', '\xf5': 'o', '\xf6': 'o', '\xf8': 'o', - '\xd9': 'U', '\xda': 'U', '\xdb': 'U', '\xdc': 'U', - '\xf9': 'u', '\xfa': 'u', '\xfb': 'u', '\xfc': 'u', - '\xdd': 'Y', '\xfd': 'y', '\xff': 'y', - '\xc6': 'Ae', '\xe6': 'ae', - '\xde': 'Th', '\xfe': 'th', - '\xdf': 'ss', - // Latin Extended-A block. - '\u0100': 'A', '\u0102': 'A', '\u0104': 'A', - '\u0101': 'a', '\u0103': 'a', '\u0105': 'a', - '\u0106': 'C', '\u0108': 'C', '\u010a': 'C', '\u010c': 'C', - '\u0107': 'c', '\u0109': 'c', '\u010b': 'c', '\u010d': 'c', - '\u010e': 'D', '\u0110': 'D', '\u010f': 'd', '\u0111': 'd', - '\u0112': 'E', '\u0114': 'E', '\u0116': 'E', '\u0118': 'E', '\u011a': 'E', - '\u0113': 'e', '\u0115': 'e', '\u0117': 'e', '\u0119': 'e', '\u011b': 'e', - '\u011c': 'G', '\u011e': 'G', '\u0120': 'G', '\u0122': 'G', - '\u011d': 'g', '\u011f': 'g', '\u0121': 'g', '\u0123': 'g', - '\u0124': 'H', '\u0126': 'H', '\u0125': 'h', '\u0127': 'h', - '\u0128': 'I', '\u012a': 'I', '\u012c': 'I', '\u012e': 'I', '\u0130': 'I', - '\u0129': 'i', '\u012b': 'i', '\u012d': 'i', '\u012f': 'i', '\u0131': 'i', - '\u0134': 'J', '\u0135': 'j', - '\u0136': 'K', '\u0137': 'k', '\u0138': 'k', - '\u0139': 'L', '\u013b': 'L', '\u013d': 'L', '\u013f': 'L', '\u0141': 'L', - '\u013a': 'l', '\u013c': 'l', '\u013e': 'l', '\u0140': 'l', '\u0142': 'l', - '\u0143': 'N', '\u0145': 'N', '\u0147': 'N', '\u014a': 'N', - '\u0144': 'n', '\u0146': 'n', '\u0148': 'n', '\u014b': 'n', - '\u014c': 'O', '\u014e': 'O', '\u0150': 'O', - '\u014d': 'o', '\u014f': 'o', '\u0151': 'o', - '\u0154': 'R', '\u0156': 'R', '\u0158': 'R', - '\u0155': 'r', '\u0157': 'r', '\u0159': 'r', - '\u015a': 'S', '\u015c': 'S', '\u015e': 'S', '\u0160': 'S', - '\u015b': 's', '\u015d': 's', '\u015f': 's', '\u0161': 's', - '\u0162': 'T', '\u0164': 'T', '\u0166': 'T', - '\u0163': 't', '\u0165': 't', '\u0167': 't', - '\u0168': 'U', '\u016a': 'U', '\u016c': 'U', '\u016e': 'U', '\u0170': 'U', '\u0172': 'U', - '\u0169': 'u', '\u016b': 'u', '\u016d': 'u', '\u016f': 'u', '\u0171': 'u', '\u0173': 'u', - '\u0174': 'W', '\u0175': 'w', - '\u0176': 'Y', '\u0177': 'y', '\u0178': 'Y', - '\u0179': 'Z', '\u017b': 'Z', '\u017d': 'Z', - '\u017a': 'z', '\u017c': 'z', '\u017e': 'z', - '\u0132': 'IJ', '\u0133': 'ij', - '\u0152': 'Oe', '\u0153': 'oe', - '\u0149': "'n", '\u017f': 's' - }; - - /** Used to map characters to HTML entities. */ - var htmlEscapes = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' - }; - - /** Used to map HTML entities to characters. */ - var htmlUnescapes = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - ''': "'" - }; - - /** Used to escape characters for inclusion in compiled string literals. */ - var stringEscapes = { - '\\': '\\', - "'": "'", - '\n': 'n', - '\r': 'r', - '\u2028': 'u2028', - '\u2029': 'u2029' - }; - - /** Built-in method references without a dependency on `root`. */ - var freeParseFloat = parseFloat, - freeParseInt = parseInt; - - /** Detect free variable `global` from Node.js. */ - var freeGlobal = typeof global == 'object' && global && global.Object === Object && global; - - /** Detect free variable `self`. */ - var freeSelf = typeof self == 'object' && self && self.Object === Object && self; - - /** Used as a reference to the global object. */ - var root = freeGlobal || freeSelf || Function('return this')(); - - /** Detect free variable `exports`. */ - var freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports; - - /** Detect free variable `module`. */ - var freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module; - - /** Detect the popular CommonJS extension `module.exports`. */ - var moduleExports = freeModule && freeModule.exports === freeExports; - - /** Detect free variable `process` from Node.js. */ - var freeProcess = moduleExports && freeGlobal.process; - - /** Used to access faster Node.js helpers. */ - var nodeUtil = (function() { - try { - // Use `util.types` for Node.js 10+. - var types = freeModule && freeModule.require && freeModule.require('util').types; - - if (types) { - return types; - } - - // Legacy `process.binding('util')` for Node.js < 10. - return freeProcess && freeProcess.binding && freeProcess.binding('util'); - } catch (e) {} - }()); - - /* Node.js helper references. */ - var nodeIsArrayBuffer = nodeUtil && nodeUtil.isArrayBuffer, - nodeIsDate = nodeUtil && nodeUtil.isDate, - nodeIsMap = nodeUtil && nodeUtil.isMap, - nodeIsRegExp = nodeUtil && nodeUtil.isRegExp, - nodeIsSet = nodeUtil && nodeUtil.isSet, - nodeIsTypedArray = nodeUtil && nodeUtil.isTypedArray; - - /*--------------------------------------------------------------------------*/ - - /** - * A faster alternative to `Function#apply`, this function invokes `func` - * with the `this` binding of `thisArg` and the arguments of `args`. - * - * @private - * @param {Function} func The function to invoke. - * @param {*} thisArg The `this` binding of `func`. - * @param {Array} args The arguments to invoke `func` with. - * @returns {*} Returns the result of `func`. - */ - function apply(func, thisArg, args) { - switch (args.length) { - case 0: return func.call(thisArg); - case 1: return func.call(thisArg, args[0]); - case 2: return func.call(thisArg, args[0], args[1]); - case 3: return func.call(thisArg, args[0], args[1], args[2]); - } - return func.apply(thisArg, args); - } - - /** - * A specialized version of `baseAggregator` for arrays. - * - * @private - * @param {Array} [array] The array to iterate over. - * @param {Function} setter The function to set `accumulator` values. - * @param {Function} iteratee The iteratee to transform keys. - * @param {Object} accumulator The initial aggregated object. - * @returns {Function} Returns `accumulator`. - */ - function arrayAggregator(array, setter, iteratee, accumulator) { - var index = -1, - length = array == null ? 0 : array.length; - - while (++index < length) { - var value = array[index]; - setter(accumulator, value, iteratee(value), array); - } - return accumulator; - } - - /** - * A specialized version of `_.forEach` for arrays without support for - * iteratee shorthands. - * - * @private - * @param {Array} [array] The array to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Array} Returns `array`. - */ - function arrayEach(array, iteratee) { - var index = -1, - length = array == null ? 0 : array.length; - - while (++index < length) { - if (iteratee(array[index], index, array) === false) { - break; - } - } - return array; - } - - /** - * A specialized version of `_.forEachRight` for arrays without support for - * iteratee shorthands. - * - * @private - * @param {Array} [array] The array to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Array} Returns `array`. - */ - function arrayEachRight(array, iteratee) { - var length = array == null ? 0 : array.length; - - while (length--) { - if (iteratee(array[length], length, array) === false) { - break; - } - } - return array; - } - - /** - * A specialized version of `_.every` for arrays without support for - * iteratee shorthands. - * - * @private - * @param {Array} [array] The array to iterate over. - * @param {Function} predicate The function invoked per iteration. - * @returns {boolean} Returns `true` if all elements pass the predicate check, - * else `false`. - */ - function arrayEvery(array, predicate) { - var index = -1, - length = array == null ? 0 : array.length; - - while (++index < length) { - if (!predicate(array[index], index, array)) { - return false; - } - } - return true; - } - - /** - * A specialized version of `_.filter` for arrays without support for - * iteratee shorthands. - * - * @private - * @param {Array} [array] The array to iterate over. - * @param {Function} predicate The function invoked per iteration. - * @returns {Array} Returns the new filtered array. - */ - function arrayFilter(array, predicate) { - var index = -1, - length = array == null ? 0 : array.length, - resIndex = 0, - result = []; - - while (++index < length) { - var value = array[index]; - if (predicate(value, index, array)) { - result[resIndex++] = value; - } - } - return result; - } - - /** - * A specialized version of `_.includes` for arrays without support for - * specifying an index to search from. - * - * @private - * @param {Array} [array] The array to inspect. - * @param {*} target The value to search for. - * @returns {boolean} Returns `true` if `target` is found, else `false`. - */ - function arrayIncludes(array, value) { - var length = array == null ? 0 : array.length; - return !!length && baseIndexOf(array, value, 0) > -1; - } - - /** - * This function is like `arrayIncludes` except that it accepts a comparator. - * - * @private - * @param {Array} [array] The array to inspect. - * @param {*} target The value to search for. - * @param {Function} comparator The comparator invoked per element. - * @returns {boolean} Returns `true` if `target` is found, else `false`. - */ - function arrayIncludesWith(array, value, comparator) { - var index = -1, - length = array == null ? 0 : array.length; - - while (++index < length) { - if (comparator(value, array[index])) { - return true; - } - } - return false; - } - - /** - * A specialized version of `_.map` for arrays without support for iteratee - * shorthands. - * - * @private - * @param {Array} [array] The array to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Array} Returns the new mapped array. - */ - function arrayMap(array, iteratee) { - var index = -1, - length = array == null ? 0 : array.length, - result = Array(length); - - while (++index < length) { - result[index] = iteratee(array[index], index, array); - } - return result; - } - - /** - * Appends the elements of `values` to `array`. - * - * @private - * @param {Array} array The array to modify. - * @param {Array} values The values to append. - * @returns {Array} Returns `array`. - */ - function arrayPush(array, values) { - var index = -1, - length = values.length, - offset = array.length; - - while (++index < length) { - array[offset + index] = values[index]; - } - return array; - } - - /** - * A specialized version of `_.reduce` for arrays without support for - * iteratee shorthands. - * - * @private - * @param {Array} [array] The array to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @param {*} [accumulator] The initial value. - * @param {boolean} [initAccum] Specify using the first element of `array` as - * the initial value. - * @returns {*} Returns the accumulated value. - */ - function arrayReduce(array, iteratee, accumulator, initAccum) { - var index = -1, - length = array == null ? 0 : array.length; - - if (initAccum && length) { - accumulator = array[++index]; - } - while (++index < length) { - accumulator = iteratee(accumulator, array[index], index, array); - } - return accumulator; - } - - /** - * A specialized version of `_.reduceRight` for arrays without support for - * iteratee shorthands. - * - * @private - * @param {Array} [array] The array to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @param {*} [accumulator] The initial value. - * @param {boolean} [initAccum] Specify using the last element of `array` as - * the initial value. - * @returns {*} Returns the accumulated value. - */ - function arrayReduceRight(array, iteratee, accumulator, initAccum) { - var length = array == null ? 0 : array.length; - if (initAccum && length) { - accumulator = array[--length]; - } - while (length--) { - accumulator = iteratee(accumulator, array[length], length, array); - } - return accumulator; - } - - /** - * A specialized version of `_.some` for arrays without support for iteratee - * shorthands. - * - * @private - * @param {Array} [array] The array to iterate over. - * @param {Function} predicate The function invoked per iteration. - * @returns {boolean} Returns `true` if any element passes the predicate check, - * else `false`. - */ - function arraySome(array, predicate) { - var index = -1, - length = array == null ? 0 : array.length; - - while (++index < length) { - if (predicate(array[index], index, array)) { - return true; - } - } - return false; - } - - /** - * Gets the size of an ASCII `string`. - * - * @private - * @param {string} string The string inspect. - * @returns {number} Returns the string size. - */ - var asciiSize = baseProperty('length'); - - /** - * Converts an ASCII `string` to an array. - * - * @private - * @param {string} string The string to convert. - * @returns {Array} Returns the converted array. - */ - function asciiToArray(string) { - return string.split(''); - } - - /** - * Splits an ASCII `string` into an array of its words. - * - * @private - * @param {string} The string to inspect. - * @returns {Array} Returns the words of `string`. - */ - function asciiWords(string) { - return string.match(reAsciiWord) || []; - } - - /** - * The base implementation of methods like `_.findKey` and `_.findLastKey`, - * without support for iteratee shorthands, which iterates over `collection` - * using `eachFunc`. - * - * @private - * @param {Array|Object} collection The collection to inspect. - * @param {Function} predicate The function invoked per iteration. - * @param {Function} eachFunc The function to iterate over `collection`. - * @returns {*} Returns the found element or its key, else `undefined`. - */ - function baseFindKey(collection, predicate, eachFunc) { - var result; - eachFunc(collection, function(value, key, collection) { - if (predicate(value, key, collection)) { - result = key; - return false; - } - }); - return result; - } - - /** - * The base implementation of `_.findIndex` and `_.findLastIndex` without - * support for iteratee shorthands. - * - * @private - * @param {Array} array The array to inspect. - * @param {Function} predicate The function invoked per iteration. - * @param {number} fromIndex The index to search from. - * @param {boolean} [fromRight] Specify iterating from right to left. - * @returns {number} Returns the index of the matched value, else `-1`. - */ - function baseFindIndex(array, predicate, fromIndex, fromRight) { - var length = array.length, - index = fromIndex + (fromRight ? 1 : -1); - - while ((fromRight ? index-- : ++index < length)) { - if (predicate(array[index], index, array)) { - return index; - } - } - return -1; - } - - /** - * The base implementation of `_.indexOf` without `fromIndex` bounds checks. - * - * @private - * @param {Array} array The array to inspect. - * @param {*} value The value to search for. - * @param {number} fromIndex The index to search from. - * @returns {number} Returns the index of the matched value, else `-1`. - */ - function baseIndexOf(array, value, fromIndex) { - return value === value - ? strictIndexOf(array, value, fromIndex) - : baseFindIndex(array, baseIsNaN, fromIndex); - } - - /** - * This function is like `baseIndexOf` except that it accepts a comparator. - * - * @private - * @param {Array} array The array to inspect. - * @param {*} value The value to search for. - * @param {number} fromIndex The index to search from. - * @param {Function} comparator The comparator invoked per element. - * @returns {number} Returns the index of the matched value, else `-1`. - */ - function baseIndexOfWith(array, value, fromIndex, comparator) { - var index = fromIndex - 1, - length = array.length; - - while (++index < length) { - if (comparator(array[index], value)) { - return index; - } - } - return -1; - } - - /** - * The base implementation of `_.isNaN` without support for number objects. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`. - */ - function baseIsNaN(value) { - return value !== value; - } - - /** - * The base implementation of `_.mean` and `_.meanBy` without support for - * iteratee shorthands. - * - * @private - * @param {Array} array The array to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {number} Returns the mean. - */ - function baseMean(array, iteratee) { - var length = array == null ? 0 : array.length; - return length ? (baseSum(array, iteratee) / length) : NAN; - } - - /** - * The base implementation of `_.property` without support for deep paths. - * - * @private - * @param {string} key The key of the property to get. - * @returns {Function} Returns the new accessor function. - */ - function baseProperty(key) { - return function(object) { - return object == null ? undefined : object[key]; - }; - } - - /** - * The base implementation of `_.propertyOf` without support for deep paths. - * - * @private - * @param {Object} object The object to query. - * @returns {Function} Returns the new accessor function. - */ - function basePropertyOf(object) { - return function(key) { - return object == null ? undefined : object[key]; - }; - } - - /** - * The base implementation of `_.reduce` and `_.reduceRight`, without support - * for iteratee shorthands, which iterates over `collection` using `eachFunc`. - * - * @private - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @param {*} accumulator The initial value. - * @param {boolean} initAccum Specify using the first or last element of - * `collection` as the initial value. - * @param {Function} eachFunc The function to iterate over `collection`. - * @returns {*} Returns the accumulated value. - */ - function baseReduce(collection, iteratee, accumulator, initAccum, eachFunc) { - eachFunc(collection, function(value, index, collection) { - accumulator = initAccum - ? (initAccum = false, value) - : iteratee(accumulator, value, index, collection); - }); - return accumulator; - } - - /** - * The base implementation of `_.sortBy` which uses `comparer` to define the - * sort order of `array` and replaces criteria objects with their corresponding - * values. - * - * @private - * @param {Array} array The array to sort. - * @param {Function} comparer The function to define sort order. - * @returns {Array} Returns `array`. - */ - function baseSortBy(array, comparer) { - var length = array.length; - - array.sort(comparer); - while (length--) { - array[length] = array[length].value; - } - return array; - } - - /** - * The base implementation of `_.sum` and `_.sumBy` without support for - * iteratee shorthands. - * - * @private - * @param {Array} array The array to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {number} Returns the sum. - */ - function baseSum(array, iteratee) { - var result, - index = -1, - length = array.length; - - while (++index < length) { - var current = iteratee(array[index]); - if (current !== undefined) { - result = result === undefined ? current : (result + current); - } - } - return result; - } - - /** - * The base implementation of `_.times` without support for iteratee shorthands - * or max array length checks. - * - * @private - * @param {number} n The number of times to invoke `iteratee`. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Array} Returns the array of results. - */ - function baseTimes(n, iteratee) { - var index = -1, - result = Array(n); - - while (++index < n) { - result[index] = iteratee(index); - } - return result; - } - - /** - * The base implementation of `_.toPairs` and `_.toPairsIn` which creates an array - * of key-value pairs for `object` corresponding to the property names of `props`. - * - * @private - * @param {Object} object The object to query. - * @param {Array} props The property names to get values for. - * @returns {Object} Returns the key-value pairs. - */ - function baseToPairs(object, props) { - return arrayMap(props, function(key) { - return [key, object[key]]; - }); - } - - /** - * The base implementation of `_.unary` without support for storing metadata. - * - * @private - * @param {Function} func The function to cap arguments for. - * @returns {Function} Returns the new capped function. - */ - function baseUnary(func) { - return function(value) { - return func(value); - }; - } - - /** - * The base implementation of `_.values` and `_.valuesIn` which creates an - * array of `object` property values corresponding to the property names - * of `props`. - * - * @private - * @param {Object} object The object to query. - * @param {Array} props The property names to get values for. - * @returns {Object} Returns the array of property values. - */ - function baseValues(object, props) { - return arrayMap(props, function(key) { - return object[key]; - }); - } - - /** - * Checks if a `cache` value for `key` exists. - * - * @private - * @param {Object} cache The cache to query. - * @param {string} key The key of the entry to check. - * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. - */ - function cacheHas(cache, key) { - return cache.has(key); - } - - /** - * Used by `_.trim` and `_.trimStart` to get the index of the first string symbol - * that is not found in the character symbols. - * - * @private - * @param {Array} strSymbols The string symbols to inspect. - * @param {Array} chrSymbols The character symbols to find. - * @returns {number} Returns the index of the first unmatched string symbol. - */ - function charsStartIndex(strSymbols, chrSymbols) { - var index = -1, - length = strSymbols.length; - - while (++index < length && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {} - return index; - } - - /** - * Used by `_.trim` and `_.trimEnd` to get the index of the last string symbol - * that is not found in the character symbols. - * - * @private - * @param {Array} strSymbols The string symbols to inspect. - * @param {Array} chrSymbols The character symbols to find. - * @returns {number} Returns the index of the last unmatched string symbol. - */ - function charsEndIndex(strSymbols, chrSymbols) { - var index = strSymbols.length; - - while (index-- && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {} - return index; - } - - /** - * Gets the number of `placeholder` occurrences in `array`. - * - * @private - * @param {Array} array The array to inspect. - * @param {*} placeholder The placeholder to search for. - * @returns {number} Returns the placeholder count. - */ - function countHolders(array, placeholder) { - var length = array.length, - result = 0; - - while (length--) { - if (array[length] === placeholder) { - ++result; - } - } - return result; - } - - /** - * Used by `_.deburr` to convert Latin-1 Supplement and Latin Extended-A - * letters to basic Latin letters. - * - * @private - * @param {string} letter The matched letter to deburr. - * @returns {string} Returns the deburred letter. - */ - var deburrLetter = basePropertyOf(deburredLetters); - - /** - * Used by `_.escape` to convert characters to HTML entities. - * - * @private - * @param {string} chr The matched character to escape. - * @returns {string} Returns the escaped character. - */ - var escapeHtmlChar = basePropertyOf(htmlEscapes); - - /** - * Used by `_.template` to escape characters for inclusion in compiled string literals. - * - * @private - * @param {string} chr The matched character to escape. - * @returns {string} Returns the escaped character. - */ - function escapeStringChar(chr) { - return '\\' + stringEscapes[chr]; - } - - /** - * Gets the value at `key` of `object`. - * - * @private - * @param {Object} [object] The object to query. - * @param {string} key The key of the property to get. - * @returns {*} Returns the property value. - */ - function getValue(object, key) { - return object == null ? undefined : object[key]; - } - - /** - * Checks if `string` contains Unicode symbols. - * - * @private - * @param {string} string The string to inspect. - * @returns {boolean} Returns `true` if a symbol is found, else `false`. - */ - function hasUnicode(string) { - return reHasUnicode.test(string); - } - - /** - * Checks if `string` contains a word composed of Unicode symbols. - * - * @private - * @param {string} string The string to inspect. - * @returns {boolean} Returns `true` if a word is found, else `false`. - */ - function hasUnicodeWord(string) { - return reHasUnicodeWord.test(string); - } - - /** - * Converts `iterator` to an array. - * - * @private - * @param {Object} iterator The iterator to convert. - * @returns {Array} Returns the converted array. - */ - function iteratorToArray(iterator) { - var data, - result = []; - - while (!(data = iterator.next()).done) { - result.push(data.value); - } - return result; - } - - /** - * Converts `map` to its key-value pairs. - * - * @private - * @param {Object} map The map to convert. - * @returns {Array} Returns the key-value pairs. - */ - function mapToArray(map) { - var index = -1, - result = Array(map.size); - - map.forEach(function(value, key) { - result[++index] = [key, value]; - }); - return result; - } - - /** - * Creates a unary function that invokes `func` with its argument transformed. - * - * @private - * @param {Function} func The function to wrap. - * @param {Function} transform The argument transform. - * @returns {Function} Returns the new function. - */ - function overArg(func, transform) { - return function(arg) { - return func(transform(arg)); - }; - } - - /** - * Replaces all `placeholder` elements in `array` with an internal placeholder - * and returns an array of their indexes. - * - * @private - * @param {Array} array The array to modify. - * @param {*} placeholder The placeholder to replace. - * @returns {Array} Returns the new array of placeholder indexes. - */ - function replaceHolders(array, placeholder) { - var index = -1, - length = array.length, - resIndex = 0, - result = []; - - while (++index < length) { - var value = array[index]; - if (value === placeholder || value === PLACEHOLDER) { - array[index] = PLACEHOLDER; - result[resIndex++] = index; - } - } - return result; - } - - /** - * Gets the value at `key`, unless `key` is "__proto__". - * - * @private - * @param {Object} object The object to query. - * @param {string} key The key of the property to get. - * @returns {*} Returns the property value. - */ - function safeGet(object, key) { - return key == '__proto__' - ? undefined - : object[key]; - } - - /** - * Converts `set` to an array of its values. - * - * @private - * @param {Object} set The set to convert. - * @returns {Array} Returns the values. - */ - function setToArray(set) { - var index = -1, - result = Array(set.size); - - set.forEach(function(value) { - result[++index] = value; - }); - return result; - } - - /** - * Converts `set` to its value-value pairs. - * - * @private - * @param {Object} set The set to convert. - * @returns {Array} Returns the value-value pairs. - */ - function setToPairs(set) { - var index = -1, - result = Array(set.size); - - set.forEach(function(value) { - result[++index] = [value, value]; - }); - return result; - } - - /** - * A specialized version of `_.indexOf` which performs strict equality - * comparisons of values, i.e. `===`. - * - * @private - * @param {Array} array The array to inspect. - * @param {*} value The value to search for. - * @param {number} fromIndex The index to search from. - * @returns {number} Returns the index of the matched value, else `-1`. - */ - function strictIndexOf(array, value, fromIndex) { - var index = fromIndex - 1, - length = array.length; - - while (++index < length) { - if (array[index] === value) { - return index; - } - } - return -1; - } - - /** - * A specialized version of `_.lastIndexOf` which performs strict equality - * comparisons of values, i.e. `===`. - * - * @private - * @param {Array} array The array to inspect. - * @param {*} value The value to search for. - * @param {number} fromIndex The index to search from. - * @returns {number} Returns the index of the matched value, else `-1`. - */ - function strictLastIndexOf(array, value, fromIndex) { - var index = fromIndex + 1; - while (index--) { - if (array[index] === value) { - return index; - } - } - return index; - } - - /** - * Gets the number of symbols in `string`. - * - * @private - * @param {string} string The string to inspect. - * @returns {number} Returns the string size. - */ - function stringSize(string) { - return hasUnicode(string) - ? unicodeSize(string) - : asciiSize(string); - } - - /** - * Converts `string` to an array. - * - * @private - * @param {string} string The string to convert. - * @returns {Array} Returns the converted array. - */ - function stringToArray(string) { - return hasUnicode(string) - ? unicodeToArray(string) - : asciiToArray(string); - } - - /** - * Used by `_.unescape` to convert HTML entities to characters. - * - * @private - * @param {string} chr The matched character to unescape. - * @returns {string} Returns the unescaped character. - */ - var unescapeHtmlChar = basePropertyOf(htmlUnescapes); - - /** - * Gets the size of a Unicode `string`. - * - * @private - * @param {string} string The string inspect. - * @returns {number} Returns the string size. - */ - function unicodeSize(string) { - var result = reUnicode.lastIndex = 0; - while (reUnicode.test(string)) { - ++result; - } - return result; - } - - /** - * Converts a Unicode `string` to an array. - * - * @private - * @param {string} string The string to convert. - * @returns {Array} Returns the converted array. - */ - function unicodeToArray(string) { - return string.match(reUnicode) || []; - } - - /** - * Splits a Unicode `string` into an array of its words. - * - * @private - * @param {string} The string to inspect. - * @returns {Array} Returns the words of `string`. - */ - function unicodeWords(string) { - return string.match(reUnicodeWord) || []; - } - - /*--------------------------------------------------------------------------*/ - - /** - * Create a new pristine `lodash` function using the `context` object. - * - * @static - * @memberOf _ - * @since 1.1.0 - * @category Util - * @param {Object} [context=root] The context object. - * @returns {Function} Returns a new `lodash` function. - * @example - * - * _.mixin({ 'foo': _.constant('foo') }); - * - * var lodash = _.runInContext(); - * lodash.mixin({ 'bar': lodash.constant('bar') }); - * - * _.isFunction(_.foo); - * // => true - * _.isFunction(_.bar); - * // => false - * - * lodash.isFunction(lodash.foo); - * // => false - * lodash.isFunction(lodash.bar); - * // => true - * - * // Create a suped-up `defer` in Node.js. - * var defer = _.runInContext({ 'setTimeout': setImmediate }).defer; - */ - var runInContext = (function runInContext(context) { - context = context == null ? root : _.defaults(root.Object(), context, _.pick(root, contextProps)); - - /** Built-in constructor references. */ - var Array = context.Array, - Date = context.Date, - Error = context.Error, - Function = context.Function, - Math = context.Math, - Object = context.Object, - RegExp = context.RegExp, - String = context.String, - TypeError = context.TypeError; - - /** Used for built-in method references. */ - var arrayProto = Array.prototype, - funcProto = Function.prototype, - objectProto = Object.prototype; - - /** Used to detect overreaching core-js shims. */ - var coreJsData = context['__core-js_shared__']; - - /** Used to resolve the decompiled source of functions. */ - var funcToString = funcProto.toString; - - /** Used to check objects for own properties. */ - var hasOwnProperty = objectProto.hasOwnProperty; - - /** Used to generate unique IDs. */ - var idCounter = 0; - - /** Used to detect methods masquerading as native. */ - var maskSrcKey = (function() { - var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || ''); - return uid ? ('Symbol(src)_1.' + uid) : ''; - }()); - - /** - * Used to resolve the - * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) - * of values. - */ - var nativeObjectToString = objectProto.toString; - - /** Used to infer the `Object` constructor. */ - var objectCtorString = funcToString.call(Object); - - /** Used to restore the original `_` reference in `_.noConflict`. */ - var oldDash = root._; - - /** Used to detect if a method is native. */ - var reIsNative = RegExp('^' + - funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&') - .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$' - ); - - /** Built-in value references. */ - var Buffer = moduleExports ? context.Buffer : undefined, - Symbol = context.Symbol, - Uint8Array = context.Uint8Array, - allocUnsafe = Buffer ? Buffer.allocUnsafe : undefined, - getPrototype = overArg(Object.getPrototypeOf, Object), - objectCreate = Object.create, - propertyIsEnumerable = objectProto.propertyIsEnumerable, - splice = arrayProto.splice, - spreadableSymbol = Symbol ? Symbol.isConcatSpreadable : undefined, - symIterator = Symbol ? Symbol.iterator : undefined, - symToStringTag = Symbol ? Symbol.toStringTag : undefined; - - var defineProperty = (function() { - try { - var func = getNative(Object, 'defineProperty'); - func({}, '', {}); - return func; - } catch (e) {} - }()); - - /** Mocked built-ins. */ - var ctxClearTimeout = context.clearTimeout !== root.clearTimeout && context.clearTimeout, - ctxNow = Date && Date.now !== root.Date.now && Date.now, - ctxSetTimeout = context.setTimeout !== root.setTimeout && context.setTimeout; - - /* Built-in method references for those with the same name as other `lodash` methods. */ - var nativeCeil = Math.ceil, - nativeFloor = Math.floor, - nativeGetSymbols = Object.getOwnPropertySymbols, - nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined, - nativeIsFinite = context.isFinite, - nativeJoin = arrayProto.join, - nativeKeys = overArg(Object.keys, Object), - nativeMax = Math.max, - nativeMin = Math.min, - nativeNow = Date.now, - nativeParseInt = context.parseInt, - nativeRandom = Math.random, - nativeReverse = arrayProto.reverse; - - /* Built-in method references that are verified to be native. */ - var DataView = getNative(context, 'DataView'), - Map = getNative(context, 'Map'), - Promise = getNative(context, 'Promise'), - Set = getNative(context, 'Set'), - WeakMap = getNative(context, 'WeakMap'), - nativeCreate = getNative(Object, 'create'); - - /** Used to store function metadata. */ - var metaMap = WeakMap && new WeakMap; - - /** Used to lookup unminified function names. */ - var realNames = {}; - - /** Used to detect maps, sets, and weakmaps. */ - var dataViewCtorString = toSource(DataView), - mapCtorString = toSource(Map), - promiseCtorString = toSource(Promise), - setCtorString = toSource(Set), - weakMapCtorString = toSource(WeakMap); - - /** Used to convert symbols to primitives and strings. */ - var symbolProto = Symbol ? Symbol.prototype : undefined, - symbolValueOf = symbolProto ? symbolProto.valueOf : undefined, - symbolToString = symbolProto ? symbolProto.toString : undefined; - - /*------------------------------------------------------------------------*/ - - /** - * Creates a `lodash` object which wraps `value` to enable implicit method - * chain sequences. Methods that operate on and return arrays, collections, - * and functions can be chained together. Methods that retrieve a single value - * or may return a primitive value will automatically end the chain sequence - * and return the unwrapped value. Otherwise, the value must be unwrapped - * with `_#value`. - * - * Explicit chain sequences, which must be unwrapped with `_#value`, may be - * enabled using `_.chain`. - * - * The execution of chained methods is lazy, that is, it's deferred until - * `_#value` is implicitly or explicitly called. - * - * Lazy evaluation allows several methods to support shortcut fusion. - * Shortcut fusion is an optimization to merge iteratee calls; this avoids - * the creation of intermediate arrays and can greatly reduce the number of - * iteratee executions. Sections of a chain sequence qualify for shortcut - * fusion if the section is applied to an array and iteratees accept only - * one argument. The heuristic for whether a section qualifies for shortcut - * fusion is subject to change. - * - * Chaining is supported in custom builds as long as the `_#value` method is - * directly or indirectly included in the build. - * - * In addition to lodash methods, wrappers have `Array` and `String` methods. - * - * The wrapper `Array` methods are: - * `concat`, `join`, `pop`, `push`, `shift`, `sort`, `splice`, and `unshift` - * - * The wrapper `String` methods are: - * `replace` and `split` - * - * The wrapper methods that support shortcut fusion are: - * `at`, `compact`, `drop`, `dropRight`, `dropWhile`, `filter`, `find`, - * `findLast`, `head`, `initial`, `last`, `map`, `reject`, `reverse`, `slice`, - * `tail`, `take`, `takeRight`, `takeRightWhile`, `takeWhile`, and `toArray` - * - * The chainable wrapper methods are: - * `after`, `ary`, `assign`, `assignIn`, `assignInWith`, `assignWith`, `at`, - * `before`, `bind`, `bindAll`, `bindKey`, `castArray`, `chain`, `chunk`, - * `commit`, `compact`, `concat`, `conforms`, `constant`, `countBy`, `create`, - * `curry`, `debounce`, `defaults`, `defaultsDeep`, `defer`, `delay`, - * `difference`, `differenceBy`, `differenceWith`, `drop`, `dropRight`, - * `dropRightWhile`, `dropWhile`, `extend`, `extendWith`, `fill`, `filter`, - * `flatMap`, `flatMapDeep`, `flatMapDepth`, `flatten`, `flattenDeep`, - * `flattenDepth`, `flip`, `flow`, `flowRight`, `fromPairs`, `functions`, - * `functionsIn`, `groupBy`, `initial`, `intersection`, `intersectionBy`, - * `intersectionWith`, `invert`, `invertBy`, `invokeMap`, `iteratee`, `keyBy`, - * `keys`, `keysIn`, `map`, `mapKeys`, `mapValues`, `matches`, `matchesProperty`, - * `memoize`, `merge`, `mergeWith`, `method`, `methodOf`, `mixin`, `negate`, - * `nthArg`, `omit`, `omitBy`, `once`, `orderBy`, `over`, `overArgs`, - * `overEvery`, `overSome`, `partial`, `partialRight`, `partition`, `pick`, - * `pickBy`, `plant`, `property`, `propertyOf`, `pull`, `pullAll`, `pullAllBy`, - * `pullAllWith`, `pullAt`, `push`, `range`, `rangeRight`, `rearg`, `reject`, - * `remove`, `rest`, `reverse`, `sampleSize`, `set`, `setWith`, `shuffle`, - * `slice`, `sort`, `sortBy`, `splice`, `spread`, `tail`, `take`, `takeRight`, - * `takeRightWhile`, `takeWhile`, `tap`, `throttle`, `thru`, `toArray`, - * `toPairs`, `toPairsIn`, `toPath`, `toPlainObject`, `transform`, `unary`, - * `union`, `unionBy`, `unionWith`, `uniq`, `uniqBy`, `uniqWith`, `unset`, - * `unshift`, `unzip`, `unzipWith`, `update`, `updateWith`, `values`, - * `valuesIn`, `without`, `wrap`, `xor`, `xorBy`, `xorWith`, `zip`, - * `zipObject`, `zipObjectDeep`, and `zipWith` - * - * The wrapper methods that are **not** chainable by default are: - * `add`, `attempt`, `camelCase`, `capitalize`, `ceil`, `clamp`, `clone`, - * `cloneDeep`, `cloneDeepWith`, `cloneWith`, `conformsTo`, `deburr`, - * `defaultTo`, `divide`, `each`, `eachRight`, `endsWith`, `eq`, `escape`, - * `escapeRegExp`, `every`, `find`, `findIndex`, `findKey`, `findLast`, - * `findLastIndex`, `findLastKey`, `first`, `floor`, `forEach`, `forEachRight`, - * `forIn`, `forInRight`, `forOwn`, `forOwnRight`, `get`, `gt`, `gte`, `has`, - * `hasIn`, `head`, `identity`, `includes`, `indexOf`, `inRange`, `invoke`, - * `isArguments`, `isArray`, `isArrayBuffer`, `isArrayLike`, `isArrayLikeObject`, - * `isBoolean`, `isBuffer`, `isDate`, `isElement`, `isEmpty`, `isEqual`, - * `isEqualWith`, `isError`, `isFinite`, `isFunction`, `isInteger`, `isLength`, - * `isMap`, `isMatch`, `isMatchWith`, `isNaN`, `isNative`, `isNil`, `isNull`, - * `isNumber`, `isObject`, `isObjectLike`, `isPlainObject`, `isRegExp`, - * `isSafeInteger`, `isSet`, `isString`, `isUndefined`, `isTypedArray`, - * `isWeakMap`, `isWeakSet`, `join`, `kebabCase`, `last`, `lastIndexOf`, - * `lowerCase`, `lowerFirst`, `lt`, `lte`, `max`, `maxBy`, `mean`, `meanBy`, - * `min`, `minBy`, `multiply`, `noConflict`, `noop`, `now`, `nth`, `pad`, - * `padEnd`, `padStart`, `parseInt`, `pop`, `random`, `reduce`, `reduceRight`, - * `repeat`, `result`, `round`, `runInContext`, `sample`, `shift`, `size`, - * `snakeCase`, `some`, `sortedIndex`, `sortedIndexBy`, `sortedLastIndex`, - * `sortedLastIndexBy`, `startCase`, `startsWith`, `stubArray`, `stubFalse`, - * `stubObject`, `stubString`, `stubTrue`, `subtract`, `sum`, `sumBy`, - * `template`, `times`, `toFinite`, `toInteger`, `toJSON`, `toLength`, - * `toLower`, `toNumber`, `toSafeInteger`, `toString`, `toUpper`, `trim`, - * `trimEnd`, `trimStart`, `truncate`, `unescape`, `uniqueId`, `upperCase`, - * `upperFirst`, `value`, and `words` - * - * @name _ - * @constructor - * @category Seq - * @param {*} value The value to wrap in a `lodash` instance. - * @returns {Object} Returns the new `lodash` wrapper instance. - * @example - * - * function square(n) { - * return n * n; - * } - * - * var wrapped = _([1, 2, 3]); - * - * // Returns an unwrapped value. - * wrapped.reduce(_.add); - * // => 6 - * - * // Returns a wrapped value. - * var squares = wrapped.map(square); - * - * _.isArray(squares); - * // => false - * - * _.isArray(squares.value()); - * // => true - */ - function lodash(value) { - if (isObjectLike(value) && !isArray(value) && !(value instanceof LazyWrapper)) { - if (value instanceof LodashWrapper) { - return value; - } - if (hasOwnProperty.call(value, '__wrapped__')) { - return wrapperClone(value); - } - } - return new LodashWrapper(value); - } - - /** - * The base implementation of `_.create` without support for assigning - * properties to the created object. - * - * @private - * @param {Object} proto The object to inherit from. - * @returns {Object} Returns the new object. - */ - var baseCreate = (function() { - function object() {} - return function(proto) { - if (!isObject(proto)) { - return {}; - } - if (objectCreate) { - return objectCreate(proto); - } - object.prototype = proto; - var result = new object; - object.prototype = undefined; - return result; - }; - }()); - - /** - * The function whose prototype chain sequence wrappers inherit from. - * - * @private - */ - function baseLodash() { - // No operation performed. - } - - /** - * The base constructor for creating `lodash` wrapper objects. - * - * @private - * @param {*} value The value to wrap. - * @param {boolean} [chainAll] Enable explicit method chain sequences. - */ - function LodashWrapper(value, chainAll) { - this.__wrapped__ = value; - this.__actions__ = []; - this.__chain__ = !!chainAll; - this.__index__ = 0; - this.__values__ = undefined; - } - - /** - * By default, the template delimiters used by lodash are like those in - * embedded Ruby (ERB) as well as ES2015 template strings. Change the - * following template settings to use alternative delimiters. - * - * @static - * @memberOf _ - * @type {Object} - */ - lodash.templateSettings = { - - /** - * Used to detect `data` property values to be HTML-escaped. - * - * @memberOf _.templateSettings - * @type {RegExp} - */ - 'escape': reEscape, - - /** - * Used to detect code to be evaluated. - * - * @memberOf _.templateSettings - * @type {RegExp} - */ - 'evaluate': reEvaluate, - - /** - * Used to detect `data` property values to inject. - * - * @memberOf _.templateSettings - * @type {RegExp} - */ - 'interpolate': reInterpolate, - - /** - * Used to reference the data object in the template text. - * - * @memberOf _.templateSettings - * @type {string} - */ - 'variable': '', - - /** - * Used to import variables into the compiled template. - * - * @memberOf _.templateSettings - * @type {Object} - */ - 'imports': { - - /** - * A reference to the `lodash` function. - * - * @memberOf _.templateSettings.imports - * @type {Function} - */ - '_': lodash - } - }; - - // Ensure wrappers are instances of `baseLodash`. - lodash.prototype = baseLodash.prototype; - lodash.prototype.constructor = lodash; - - LodashWrapper.prototype = baseCreate(baseLodash.prototype); - LodashWrapper.prototype.constructor = LodashWrapper; - - /*------------------------------------------------------------------------*/ - - /** - * Creates a lazy wrapper object which wraps `value` to enable lazy evaluation. - * - * @private - * @constructor - * @param {*} value The value to wrap. - */ - function LazyWrapper(value) { - this.__wrapped__ = value; - this.__actions__ = []; - this.__dir__ = 1; - this.__filtered__ = false; - this.__iteratees__ = []; - this.__takeCount__ = MAX_ARRAY_LENGTH; - this.__views__ = []; - } - - /** - * Creates a clone of the lazy wrapper object. - * - * @private - * @name clone - * @memberOf LazyWrapper - * @returns {Object} Returns the cloned `LazyWrapper` object. - */ - function lazyClone() { - var result = new LazyWrapper(this.__wrapped__); - result.__actions__ = copyArray(this.__actions__); - result.__dir__ = this.__dir__; - result.__filtered__ = this.__filtered__; - result.__iteratees__ = copyArray(this.__iteratees__); - result.__takeCount__ = this.__takeCount__; - result.__views__ = copyArray(this.__views__); - return result; - } - - /** - * Reverses the direction of lazy iteration. - * - * @private - * @name reverse - * @memberOf LazyWrapper - * @returns {Object} Returns the new reversed `LazyWrapper` object. - */ - function lazyReverse() { - if (this.__filtered__) { - var result = new LazyWrapper(this); - result.__dir__ = -1; - result.__filtered__ = true; - } else { - result = this.clone(); - result.__dir__ *= -1; - } - return result; - } - - /** - * Extracts the unwrapped value from its lazy wrapper. - * - * @private - * @name value - * @memberOf LazyWrapper - * @returns {*} Returns the unwrapped value. - */ - function lazyValue() { - var array = this.__wrapped__.value(), - dir = this.__dir__, - isArr = isArray(array), - isRight = dir < 0, - arrLength = isArr ? array.length : 0, - view = getView(0, arrLength, this.__views__), - start = view.start, - end = view.end, - length = end - start, - index = isRight ? end : (start - 1), - iteratees = this.__iteratees__, - iterLength = iteratees.length, - resIndex = 0, - takeCount = nativeMin(length, this.__takeCount__); - - if (!isArr || (!isRight && arrLength == length && takeCount == length)) { - return baseWrapperValue(array, this.__actions__); - } - var result = []; - - outer: - while (length-- && resIndex < takeCount) { - index += dir; - - var iterIndex = -1, - value = array[index]; - - while (++iterIndex < iterLength) { - var data = iteratees[iterIndex], - iteratee = data.iteratee, - type = data.type, - computed = iteratee(value); - - if (type == LAZY_MAP_FLAG) { - value = computed; - } else if (!computed) { - if (type == LAZY_FILTER_FLAG) { - continue outer; - } else { - break outer; - } - } - } - result[resIndex++] = value; - } - return result; - } - - // Ensure `LazyWrapper` is an instance of `baseLodash`. - LazyWrapper.prototype = baseCreate(baseLodash.prototype); - LazyWrapper.prototype.constructor = LazyWrapper; - - /*------------------------------------------------------------------------*/ - - /** - * Creates a hash object. - * - * @private - * @constructor - * @param {Array} [entries] The key-value pairs to cache. - */ - function Hash(entries) { - var index = -1, - length = entries == null ? 0 : entries.length; - - this.clear(); - while (++index < length) { - var entry = entries[index]; - this.set(entry[0], entry[1]); - } - } - - /** - * Removes all key-value entries from the hash. - * - * @private - * @name clear - * @memberOf Hash - */ - function hashClear() { - this.__data__ = nativeCreate ? nativeCreate(null) : {}; - this.size = 0; - } - - /** - * Removes `key` and its value from the hash. - * - * @private - * @name delete - * @memberOf Hash - * @param {Object} hash The hash to modify. - * @param {string} key The key of the value to remove. - * @returns {boolean} Returns `true` if the entry was removed, else `false`. - */ - function hashDelete(key) { - var result = this.has(key) && delete this.__data__[key]; - this.size -= result ? 1 : 0; - return result; - } - - /** - * Gets the hash value for `key`. - * - * @private - * @name get - * @memberOf Hash - * @param {string} key The key of the value to get. - * @returns {*} Returns the entry value. - */ - function hashGet(key) { - var data = this.__data__; - if (nativeCreate) { - var result = data[key]; - return result === HASH_UNDEFINED ? undefined : result; - } - return hasOwnProperty.call(data, key) ? data[key] : undefined; - } - - /** - * Checks if a hash value for `key` exists. - * - * @private - * @name has - * @memberOf Hash - * @param {string} key The key of the entry to check. - * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. - */ - function hashHas(key) { - var data = this.__data__; - return nativeCreate ? (data[key] !== undefined) : hasOwnProperty.call(data, key); - } - - /** - * Sets the hash `key` to `value`. - * - * @private - * @name set - * @memberOf Hash - * @param {string} key The key of the value to set. - * @param {*} value The value to set. - * @returns {Object} Returns the hash instance. - */ - function hashSet(key, value) { - var data = this.__data__; - this.size += this.has(key) ? 0 : 1; - data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value; - return this; - } - - // Add methods to `Hash`. - Hash.prototype.clear = hashClear; - Hash.prototype['delete'] = hashDelete; - Hash.prototype.get = hashGet; - Hash.prototype.has = hashHas; - Hash.prototype.set = hashSet; - - /*------------------------------------------------------------------------*/ - - /** - * Creates an list cache object. - * - * @private - * @constructor - * @param {Array} [entries] The key-value pairs to cache. - */ - function ListCache(entries) { - var index = -1, - length = entries == null ? 0 : entries.length; - - this.clear(); - while (++index < length) { - var entry = entries[index]; - this.set(entry[0], entry[1]); - } - } - - /** - * Removes all key-value entries from the list cache. - * - * @private - * @name clear - * @memberOf ListCache - */ - function listCacheClear() { - this.__data__ = []; - this.size = 0; - } - - /** - * Removes `key` and its value from the list cache. - * - * @private - * @name delete - * @memberOf ListCache - * @param {string} key The key of the value to remove. - * @returns {boolean} Returns `true` if the entry was removed, else `false`. - */ - function listCacheDelete(key) { - var data = this.__data__, - index = assocIndexOf(data, key); - - if (index < 0) { - return false; - } - var lastIndex = data.length - 1; - if (index == lastIndex) { - data.pop(); - } else { - splice.call(data, index, 1); - } - --this.size; - return true; - } - - /** - * Gets the list cache value for `key`. - * - * @private - * @name get - * @memberOf ListCache - * @param {string} key The key of the value to get. - * @returns {*} Returns the entry value. - */ - function listCacheGet(key) { - var data = this.__data__, - index = assocIndexOf(data, key); - - return index < 0 ? undefined : data[index][1]; - } - - /** - * Checks if a list cache value for `key` exists. - * - * @private - * @name has - * @memberOf ListCache - * @param {string} key The key of the entry to check. - * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. - */ - function listCacheHas(key) { - return assocIndexOf(this.__data__, key) > -1; - } - - /** - * Sets the list cache `key` to `value`. - * - * @private - * @name set - * @memberOf ListCache - * @param {string} key The key of the value to set. - * @param {*} value The value to set. - * @returns {Object} Returns the list cache instance. - */ - function listCacheSet(key, value) { - var data = this.__data__, - index = assocIndexOf(data, key); - - if (index < 0) { - ++this.size; - data.push([key, value]); - } else { - data[index][1] = value; - } - return this; - } - - // Add methods to `ListCache`. - ListCache.prototype.clear = listCacheClear; - ListCache.prototype['delete'] = listCacheDelete; - ListCache.prototype.get = listCacheGet; - ListCache.prototype.has = listCacheHas; - ListCache.prototype.set = listCacheSet; - - /*------------------------------------------------------------------------*/ - - /** - * Creates a map cache object to store key-value pairs. - * - * @private - * @constructor - * @param {Array} [entries] The key-value pairs to cache. - */ - function MapCache(entries) { - var index = -1, - length = entries == null ? 0 : entries.length; - - this.clear(); - while (++index < length) { - var entry = entries[index]; - this.set(entry[0], entry[1]); - } - } - - /** - * Removes all key-value entries from the map. - * - * @private - * @name clear - * @memberOf MapCache - */ - function mapCacheClear() { - this.size = 0; - this.__data__ = { - 'hash': new Hash, - 'map': new (Map || ListCache), - 'string': new Hash - }; - } - - /** - * Removes `key` and its value from the map. - * - * @private - * @name delete - * @memberOf MapCache - * @param {string} key The key of the value to remove. - * @returns {boolean} Returns `true` if the entry was removed, else `false`. - */ - function mapCacheDelete(key) { - var result = getMapData(this, key)['delete'](key); - this.size -= result ? 1 : 0; - return result; - } - - /** - * Gets the map value for `key`. - * - * @private - * @name get - * @memberOf MapCache - * @param {string} key The key of the value to get. - * @returns {*} Returns the entry value. - */ - function mapCacheGet(key) { - return getMapData(this, key).get(key); - } - - /** - * Checks if a map value for `key` exists. - * - * @private - * @name has - * @memberOf MapCache - * @param {string} key The key of the entry to check. - * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. - */ - function mapCacheHas(key) { - return getMapData(this, key).has(key); - } - - /** - * Sets the map `key` to `value`. - * - * @private - * @name set - * @memberOf MapCache - * @param {string} key The key of the value to set. - * @param {*} value The value to set. - * @returns {Object} Returns the map cache instance. - */ - function mapCacheSet(key, value) { - var data = getMapData(this, key), - size = data.size; - - data.set(key, value); - this.size += data.size == size ? 0 : 1; - return this; - } - - // Add methods to `MapCache`. - MapCache.prototype.clear = mapCacheClear; - MapCache.prototype['delete'] = mapCacheDelete; - MapCache.prototype.get = mapCacheGet; - MapCache.prototype.has = mapCacheHas; - MapCache.prototype.set = mapCacheSet; - - /*------------------------------------------------------------------------*/ - - /** - * - * Creates an array cache object to store unique values. - * - * @private - * @constructor - * @param {Array} [values] The values to cache. - */ - function SetCache(values) { - var index = -1, - length = values == null ? 0 : values.length; - - this.__data__ = new MapCache; - while (++index < length) { - this.add(values[index]); - } - } - - /** - * Adds `value` to the array cache. - * - * @private - * @name add - * @memberOf SetCache - * @alias push - * @param {*} value The value to cache. - * @returns {Object} Returns the cache instance. - */ - function setCacheAdd(value) { - this.__data__.set(value, HASH_UNDEFINED); - return this; - } - - /** - * Checks if `value` is in the array cache. - * - * @private - * @name has - * @memberOf SetCache - * @param {*} value The value to search for. - * @returns {number} Returns `true` if `value` is found, else `false`. - */ - function setCacheHas(value) { - return this.__data__.has(value); - } - - // Add methods to `SetCache`. - SetCache.prototype.add = SetCache.prototype.push = setCacheAdd; - SetCache.prototype.has = setCacheHas; - - /*------------------------------------------------------------------------*/ - - /** - * Creates a stack cache object to store key-value pairs. - * - * @private - * @constructor - * @param {Array} [entries] The key-value pairs to cache. - */ - function Stack(entries) { - var data = this.__data__ = new ListCache(entries); - this.size = data.size; - } - - /** - * Removes all key-value entries from the stack. - * - * @private - * @name clear - * @memberOf Stack - */ - function stackClear() { - this.__data__ = new ListCache; - this.size = 0; - } - - /** - * Removes `key` and its value from the stack. - * - * @private - * @name delete - * @memberOf Stack - * @param {string} key The key of the value to remove. - * @returns {boolean} Returns `true` if the entry was removed, else `false`. - */ - function stackDelete(key) { - var data = this.__data__, - result = data['delete'](key); - - this.size = data.size; - return result; - } - - /** - * Gets the stack value for `key`. - * - * @private - * @name get - * @memberOf Stack - * @param {string} key The key of the value to get. - * @returns {*} Returns the entry value. - */ - function stackGet(key) { - return this.__data__.get(key); - } - - /** - * Checks if a stack value for `key` exists. - * - * @private - * @name has - * @memberOf Stack - * @param {string} key The key of the entry to check. - * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. - */ - function stackHas(key) { - return this.__data__.has(key); - } - - /** - * Sets the stack `key` to `value`. - * - * @private - * @name set - * @memberOf Stack - * @param {string} key The key of the value to set. - * @param {*} value The value to set. - * @returns {Object} Returns the stack cache instance. - */ - function stackSet(key, value) { - var data = this.__data__; - if (data instanceof ListCache) { - var pairs = data.__data__; - if (!Map || (pairs.length < LARGE_ARRAY_SIZE - 1)) { - pairs.push([key, value]); - this.size = ++data.size; - return this; - } - data = this.__data__ = new MapCache(pairs); - } - data.set(key, value); - this.size = data.size; - return this; - } - - // Add methods to `Stack`. - Stack.prototype.clear = stackClear; - Stack.prototype['delete'] = stackDelete; - Stack.prototype.get = stackGet; - Stack.prototype.has = stackHas; - Stack.prototype.set = stackSet; - - /*------------------------------------------------------------------------*/ - - /** - * Creates an array of the enumerable property names of the array-like `value`. - * - * @private - * @param {*} value The value to query. - * @param {boolean} inherited Specify returning inherited property names. - * @returns {Array} Returns the array of property names. - */ - function arrayLikeKeys(value, inherited) { - var isArr = isArray(value), - isArg = !isArr && isArguments(value), - isBuff = !isArr && !isArg && isBuffer(value), - isType = !isArr && !isArg && !isBuff && isTypedArray(value), - skipIndexes = isArr || isArg || isBuff || isType, - result = skipIndexes ? baseTimes(value.length, String) : [], - length = result.length; - - for (var key in value) { - if ((inherited || hasOwnProperty.call(value, key)) && - !(skipIndexes && ( - // Safari 9 has enumerable `arguments.length` in strict mode. - key == 'length' || - // Node.js 0.10 has enumerable non-index properties on buffers. - (isBuff && (key == 'offset' || key == 'parent')) || - // PhantomJS 2 has enumerable non-index properties on typed arrays. - (isType && (key == 'buffer' || key == 'byteLength' || key == 'byteOffset')) || - // Skip index properties. - isIndex(key, length) - ))) { - result.push(key); - } - } - return result; - } - - /** - * A specialized version of `_.sample` for arrays. - * - * @private - * @param {Array} array The array to sample. - * @returns {*} Returns the random element. - */ - function arraySample(array) { - var length = array.length; - return length ? array[baseRandom(0, length - 1)] : undefined; - } - - /** - * A specialized version of `_.sampleSize` for arrays. - * - * @private - * @param {Array} array The array to sample. - * @param {number} n The number of elements to sample. - * @returns {Array} Returns the random elements. - */ - function arraySampleSize(array, n) { - return shuffleSelf(copyArray(array), baseClamp(n, 0, array.length)); - } - - /** - * A specialized version of `_.shuffle` for arrays. - * - * @private - * @param {Array} array The array to shuffle. - * @returns {Array} Returns the new shuffled array. - */ - function arrayShuffle(array) { - return shuffleSelf(copyArray(array)); - } - - /** - * This function is like `assignValue` except that it doesn't assign - * `undefined` values. - * - * @private - * @param {Object} object The object to modify. - * @param {string} key The key of the property to assign. - * @param {*} value The value to assign. - */ - function assignMergeValue(object, key, value) { - if ((value !== undefined && !eq(object[key], value)) || - (value === undefined && !(key in object))) { - baseAssignValue(object, key, value); - } - } - - /** - * Assigns `value` to `key` of `object` if the existing value is not equivalent - * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * for equality comparisons. - * - * @private - * @param {Object} object The object to modify. - * @param {string} key The key of the property to assign. - * @param {*} value The value to assign. - */ - function assignValue(object, key, value) { - var objValue = object[key]; - if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) || - (value === undefined && !(key in object))) { - baseAssignValue(object, key, value); - } - } - - /** - * Gets the index at which the `key` is found in `array` of key-value pairs. - * - * @private - * @param {Array} array The array to inspect. - * @param {*} key The key to search for. - * @returns {number} Returns the index of the matched value, else `-1`. - */ - function assocIndexOf(array, key) { - var length = array.length; - while (length--) { - if (eq(array[length][0], key)) { - return length; - } - } - return -1; - } - - /** - * Aggregates elements of `collection` on `accumulator` with keys transformed - * by `iteratee` and values set by `setter`. - * - * @private - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} setter The function to set `accumulator` values. - * @param {Function} iteratee The iteratee to transform keys. - * @param {Object} accumulator The initial aggregated object. - * @returns {Function} Returns `accumulator`. - */ - function baseAggregator(collection, setter, iteratee, accumulator) { - baseEach(collection, function(value, key, collection) { - setter(accumulator, value, iteratee(value), collection); - }); - return accumulator; - } - - /** - * The base implementation of `_.assign` without support for multiple sources - * or `customizer` functions. - * - * @private - * @param {Object} object The destination object. - * @param {Object} source The source object. - * @returns {Object} Returns `object`. - */ - function baseAssign(object, source) { - return object && copyObject(source, keys(source), object); - } - - /** - * The base implementation of `_.assignIn` without support for multiple sources - * or `customizer` functions. - * - * @private - * @param {Object} object The destination object. - * @param {Object} source The source object. - * @returns {Object} Returns `object`. - */ - function baseAssignIn(object, source) { - return object && copyObject(source, keysIn(source), object); - } - - /** - * The base implementation of `assignValue` and `assignMergeValue` without - * value checks. - * - * @private - * @param {Object} object The object to modify. - * @param {string} key The key of the property to assign. - * @param {*} value The value to assign. - */ - function baseAssignValue(object, key, value) { - if (key == '__proto__' && defineProperty) { - defineProperty(object, key, { - 'configurable': true, - 'enumerable': true, - 'value': value, - 'writable': true - }); - } else { - object[key] = value; - } - } - - /** - * The base implementation of `_.at` without support for individual paths. - * - * @private - * @param {Object} object The object to iterate over. - * @param {string[]} paths The property paths to pick. - * @returns {Array} Returns the picked elements. - */ - function baseAt(object, paths) { - var index = -1, - length = paths.length, - result = Array(length), - skip = object == null; - - while (++index < length) { - result[index] = skip ? undefined : get(object, paths[index]); - } - return result; - } - - /** - * The base implementation of `_.clamp` which doesn't coerce arguments. - * - * @private - * @param {number} number The number to clamp. - * @param {number} [lower] The lower bound. - * @param {number} upper The upper bound. - * @returns {number} Returns the clamped number. - */ - function baseClamp(number, lower, upper) { - if (number === number) { - if (upper !== undefined) { - number = number <= upper ? number : upper; - } - if (lower !== undefined) { - number = number >= lower ? number : lower; - } - } - return number; - } - - /** - * The base implementation of `_.clone` and `_.cloneDeep` which tracks - * traversed objects. - * - * @private - * @param {*} value The value to clone. - * @param {boolean} bitmask The bitmask flags. - * 1 - Deep clone - * 2 - Flatten inherited properties - * 4 - Clone symbols - * @param {Function} [customizer] The function to customize cloning. - * @param {string} [key] The key of `value`. - * @param {Object} [object] The parent object of `value`. - * @param {Object} [stack] Tracks traversed objects and their clone counterparts. - * @returns {*} Returns the cloned value. - */ - function baseClone(value, bitmask, customizer, key, object, stack) { - var result, - isDeep = bitmask & CLONE_DEEP_FLAG, - isFlat = bitmask & CLONE_FLAT_FLAG, - isFull = bitmask & CLONE_SYMBOLS_FLAG; - - if (customizer) { - result = object ? customizer(value, key, object, stack) : customizer(value); - } - if (result !== undefined) { - return result; - } - if (!isObject(value)) { - return value; - } - var isArr = isArray(value); - if (isArr) { - result = initCloneArray(value); - if (!isDeep) { - return copyArray(value, result); - } - } else { - var tag = getTag(value), - isFunc = tag == funcTag || tag == genTag; - - if (isBuffer(value)) { - return cloneBuffer(value, isDeep); - } - if (tag == objectTag || tag == argsTag || (isFunc && !object)) { - result = (isFlat || isFunc) ? {} : initCloneObject(value); - if (!isDeep) { - return isFlat - ? copySymbolsIn(value, baseAssignIn(result, value)) - : copySymbols(value, baseAssign(result, value)); - } - } else { - if (!cloneableTags[tag]) { - return object ? value : {}; - } - result = initCloneByTag(value, tag, isDeep); - } - } - // Check for circular references and return its corresponding clone. - stack || (stack = new Stack); - var stacked = stack.get(value); - if (stacked) { - return stacked; - } - stack.set(value, result); - - if (isSet(value)) { - value.forEach(function(subValue) { - result.add(baseClone(subValue, bitmask, customizer, subValue, value, stack)); - }); - - return result; - } - - if (isMap(value)) { - value.forEach(function(subValue, key) { - result.set(key, baseClone(subValue, bitmask, customizer, key, value, stack)); - }); - - return result; - } - - var keysFunc = isFull - ? (isFlat ? getAllKeysIn : getAllKeys) - : (isFlat ? keysIn : keys); - - var props = isArr ? undefined : keysFunc(value); - arrayEach(props || value, function(subValue, key) { - if (props) { - key = subValue; - subValue = value[key]; - } - // Recursively populate clone (susceptible to call stack limits). - assignValue(result, key, baseClone(subValue, bitmask, customizer, key, value, stack)); - }); - return result; - } - - /** - * The base implementation of `_.conforms` which doesn't clone `source`. - * - * @private - * @param {Object} source The object of property predicates to conform to. - * @returns {Function} Returns the new spec function. - */ - function baseConforms(source) { - var props = keys(source); - return function(object) { - return baseConformsTo(object, source, props); - }; - } - - /** - * The base implementation of `_.conformsTo` which accepts `props` to check. - * - * @private - * @param {Object} object The object to inspect. - * @param {Object} source The object of property predicates to conform to. - * @returns {boolean} Returns `true` if `object` conforms, else `false`. - */ - function baseConformsTo(object, source, props) { - var length = props.length; - if (object == null) { - return !length; - } - object = Object(object); - while (length--) { - var key = props[length], - predicate = source[key], - value = object[key]; - - if ((value === undefined && !(key in object)) || !predicate(value)) { - return false; - } - } - return true; - } - - /** - * The base implementation of `_.delay` and `_.defer` which accepts `args` - * to provide to `func`. - * - * @private - * @param {Function} func The function to delay. - * @param {number} wait The number of milliseconds to delay invocation. - * @param {Array} args The arguments to provide to `func`. - * @returns {number|Object} Returns the timer id or timeout object. - */ - function baseDelay(func, wait, args) { - if (typeof func != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - return setTimeout(function() { func.apply(undefined, args); }, wait); - } - - /** - * The base implementation of methods like `_.difference` without support - * for excluding multiple arrays or iteratee shorthands. - * - * @private - * @param {Array} array The array to inspect. - * @param {Array} values The values to exclude. - * @param {Function} [iteratee] The iteratee invoked per element. - * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns the new array of filtered values. - */ - function baseDifference(array, values, iteratee, comparator) { - var index = -1, - includes = arrayIncludes, - isCommon = true, - length = array.length, - result = [], - valuesLength = values.length; - - if (!length) { - return result; - } - if (iteratee) { - values = arrayMap(values, baseUnary(iteratee)); - } - if (comparator) { - includes = arrayIncludesWith; - isCommon = false; - } - else if (values.length >= LARGE_ARRAY_SIZE) { - includes = cacheHas; - isCommon = false; - values = new SetCache(values); - } - outer: - while (++index < length) { - var value = array[index], - computed = iteratee == null ? value : iteratee(value); - - value = (comparator || value !== 0) ? value : 0; - if (isCommon && computed === computed) { - var valuesIndex = valuesLength; - while (valuesIndex--) { - if (values[valuesIndex] === computed) { - continue outer; - } - } - result.push(value); - } - else if (!includes(values, computed, comparator)) { - result.push(value); - } - } - return result; - } - - /** - * The base implementation of `_.forEach` without support for iteratee shorthands. - * - * @private - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Array|Object} Returns `collection`. - */ - var baseEach = createBaseEach(baseForOwn); - - /** - * The base implementation of `_.forEachRight` without support for iteratee shorthands. - * - * @private - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Array|Object} Returns `collection`. - */ - var baseEachRight = createBaseEach(baseForOwnRight, true); - - /** - * The base implementation of `_.every` without support for iteratee shorthands. - * - * @private - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} predicate The function invoked per iteration. - * @returns {boolean} Returns `true` if all elements pass the predicate check, - * else `false` - */ - function baseEvery(collection, predicate) { - var result = true; - baseEach(collection, function(value, index, collection) { - result = !!predicate(value, index, collection); - return result; - }); - return result; - } - - /** - * The base implementation of methods like `_.max` and `_.min` which accepts a - * `comparator` to determine the extremum value. - * - * @private - * @param {Array} array The array to iterate over. - * @param {Function} iteratee The iteratee invoked per iteration. - * @param {Function} comparator The comparator used to compare values. - * @returns {*} Returns the extremum value. - */ - function baseExtremum(array, iteratee, comparator) { - var index = -1, - length = array.length; - - while (++index < length) { - var value = array[index], - current = iteratee(value); - - if (current != null && (computed === undefined - ? (current === current && !isSymbol(current)) - : comparator(current, computed) - )) { - var computed = current, - result = value; - } - } - return result; - } - - /** - * The base implementation of `_.fill` without an iteratee call guard. - * - * @private - * @param {Array} array The array to fill. - * @param {*} value The value to fill `array` with. - * @param {number} [start=0] The start position. - * @param {number} [end=array.length] The end position. - * @returns {Array} Returns `array`. - */ - function baseFill(array, value, start, end) { - var length = array.length; - - start = toInteger(start); - if (start < 0) { - start = -start > length ? 0 : (length + start); - } - end = (end === undefined || end > length) ? length : toInteger(end); - if (end < 0) { - end += length; - } - end = start > end ? 0 : toLength(end); - while (start < end) { - array[start++] = value; - } - return array; - } - - /** - * The base implementation of `_.filter` without support for iteratee shorthands. - * - * @private - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} predicate The function invoked per iteration. - * @returns {Array} Returns the new filtered array. - */ - function baseFilter(collection, predicate) { - var result = []; - baseEach(collection, function(value, index, collection) { - if (predicate(value, index, collection)) { - result.push(value); - } - }); - return result; - } - - /** - * The base implementation of `_.flatten` with support for restricting flattening. - * - * @private - * @param {Array} array The array to flatten. - * @param {number} depth The maximum recursion depth. - * @param {boolean} [predicate=isFlattenable] The function invoked per iteration. - * @param {boolean} [isStrict] Restrict to values that pass `predicate` checks. - * @param {Array} [result=[]] The initial result value. - * @returns {Array} Returns the new flattened array. - */ - function baseFlatten(array, depth, predicate, isStrict, result) { - var index = -1, - length = array.length; - - predicate || (predicate = isFlattenable); - result || (result = []); - - while (++index < length) { - var value = array[index]; - if (depth > 0 && predicate(value)) { - if (depth > 1) { - // Recursively flatten arrays (susceptible to call stack limits). - baseFlatten(value, depth - 1, predicate, isStrict, result); - } else { - arrayPush(result, value); - } - } else if (!isStrict) { - result[result.length] = value; - } - } - return result; - } - - /** - * The base implementation of `baseForOwn` which iterates over `object` - * properties returned by `keysFunc` and invokes `iteratee` for each property. - * Iteratee functions may exit iteration early by explicitly returning `false`. - * - * @private - * @param {Object} object The object to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @param {Function} keysFunc The function to get the keys of `object`. - * @returns {Object} Returns `object`. - */ - var baseFor = createBaseFor(); - - /** - * This function is like `baseFor` except that it iterates over properties - * in the opposite order. - * - * @private - * @param {Object} object The object to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @param {Function} keysFunc The function to get the keys of `object`. - * @returns {Object} Returns `object`. - */ - var baseForRight = createBaseFor(true); - - /** - * The base implementation of `_.forOwn` without support for iteratee shorthands. - * - * @private - * @param {Object} object The object to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Object} Returns `object`. - */ - function baseForOwn(object, iteratee) { - return object && baseFor(object, iteratee, keys); - } - - /** - * The base implementation of `_.forOwnRight` without support for iteratee shorthands. - * - * @private - * @param {Object} object The object to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Object} Returns `object`. - */ - function baseForOwnRight(object, iteratee) { - return object && baseForRight(object, iteratee, keys); - } - - /** - * The base implementation of `_.functions` which creates an array of - * `object` function property names filtered from `props`. - * - * @private - * @param {Object} object The object to inspect. - * @param {Array} props The property names to filter. - * @returns {Array} Returns the function names. - */ - function baseFunctions(object, props) { - return arrayFilter(props, function(key) { - return isFunction(object[key]); - }); - } - - /** - * The base implementation of `_.get` without support for default values. - * - * @private - * @param {Object} object The object to query. - * @param {Array|string} path The path of the property to get. - * @returns {*} Returns the resolved value. - */ - function baseGet(object, path) { - path = castPath(path, object); - - var index = 0, - length = path.length; - - while (object != null && index < length) { - object = object[toKey(path[index++])]; - } - return (index && index == length) ? object : undefined; - } - - /** - * The base implementation of `getAllKeys` and `getAllKeysIn` which uses - * `keysFunc` and `symbolsFunc` to get the enumerable property names and - * symbols of `object`. - * - * @private - * @param {Object} object The object to query. - * @param {Function} keysFunc The function to get the keys of `object`. - * @param {Function} symbolsFunc The function to get the symbols of `object`. - * @returns {Array} Returns the array of property names and symbols. - */ - function baseGetAllKeys(object, keysFunc, symbolsFunc) { - var result = keysFunc(object); - return isArray(object) ? result : arrayPush(result, symbolsFunc(object)); - } - - /** - * The base implementation of `getTag` without fallbacks for buggy environments. - * - * @private - * @param {*} value The value to query. - * @returns {string} Returns the `toStringTag`. - */ - function baseGetTag(value) { - if (value == null) { - return value === undefined ? undefinedTag : nullTag; - } - return (symToStringTag && symToStringTag in Object(value)) - ? getRawTag(value) - : objectToString(value); - } - - /** - * The base implementation of `_.gt` which doesn't coerce arguments. - * - * @private - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {boolean} Returns `true` if `value` is greater than `other`, - * else `false`. - */ - function baseGt(value, other) { - return value > other; - } - - /** - * The base implementation of `_.has` without support for deep paths. - * - * @private - * @param {Object} [object] The object to query. - * @param {Array|string} key The key to check. - * @returns {boolean} Returns `true` if `key` exists, else `false`. - */ - function baseHas(object, key) { - return object != null && hasOwnProperty.call(object, key); - } - - /** - * The base implementation of `_.hasIn` without support for deep paths. - * - * @private - * @param {Object} [object] The object to query. - * @param {Array|string} key The key to check. - * @returns {boolean} Returns `true` if `key` exists, else `false`. - */ - function baseHasIn(object, key) { - return object != null && key in Object(object); - } - - /** - * The base implementation of `_.inRange` which doesn't coerce arguments. - * - * @private - * @param {number} number The number to check. - * @param {number} start The start of the range. - * @param {number} end The end of the range. - * @returns {boolean} Returns `true` if `number` is in the range, else `false`. - */ - function baseInRange(number, start, end) { - return number >= nativeMin(start, end) && number < nativeMax(start, end); - } - - /** - * The base implementation of methods like `_.intersection`, without support - * for iteratee shorthands, that accepts an array of arrays to inspect. - * - * @private - * @param {Array} arrays The arrays to inspect. - * @param {Function} [iteratee] The iteratee invoked per element. - * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns the new array of shared values. - */ - function baseIntersection(arrays, iteratee, comparator) { - var includes = comparator ? arrayIncludesWith : arrayIncludes, - length = arrays[0].length, - othLength = arrays.length, - othIndex = othLength, - caches = Array(othLength), - maxLength = Infinity, - result = []; - - while (othIndex--) { - var array = arrays[othIndex]; - if (othIndex && iteratee) { - array = arrayMap(array, baseUnary(iteratee)); - } - maxLength = nativeMin(array.length, maxLength); - caches[othIndex] = !comparator && (iteratee || (length >= 120 && array.length >= 120)) - ? new SetCache(othIndex && array) - : undefined; - } - array = arrays[0]; - - var index = -1, - seen = caches[0]; - - outer: - while (++index < length && result.length < maxLength) { - var value = array[index], - computed = iteratee ? iteratee(value) : value; - - value = (comparator || value !== 0) ? value : 0; - if (!(seen - ? cacheHas(seen, computed) - : includes(result, computed, comparator) - )) { - othIndex = othLength; - while (--othIndex) { - var cache = caches[othIndex]; - if (!(cache - ? cacheHas(cache, computed) - : includes(arrays[othIndex], computed, comparator)) - ) { - continue outer; - } - } - if (seen) { - seen.push(computed); - } - result.push(value); - } - } - return result; - } - - /** - * The base implementation of `_.invert` and `_.invertBy` which inverts - * `object` with values transformed by `iteratee` and set by `setter`. - * - * @private - * @param {Object} object The object to iterate over. - * @param {Function} setter The function to set `accumulator` values. - * @param {Function} iteratee The iteratee to transform values. - * @param {Object} accumulator The initial inverted object. - * @returns {Function} Returns `accumulator`. - */ - function baseInverter(object, setter, iteratee, accumulator) { - baseForOwn(object, function(value, key, object) { - setter(accumulator, iteratee(value), key, object); - }); - return accumulator; - } - - /** - * The base implementation of `_.invoke` without support for individual - * method arguments. - * - * @private - * @param {Object} object The object to query. - * @param {Array|string} path The path of the method to invoke. - * @param {Array} args The arguments to invoke the method with. - * @returns {*} Returns the result of the invoked method. - */ - function baseInvoke(object, path, args) { - path = castPath(path, object); - object = parent(object, path); - var func = object == null ? object : object[toKey(last(path))]; - return func == null ? undefined : apply(func, object, args); - } - - /** - * The base implementation of `_.isArguments`. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an `arguments` object, - */ - function baseIsArguments(value) { - return isObjectLike(value) && baseGetTag(value) == argsTag; - } - - /** - * The base implementation of `_.isArrayBuffer` without Node.js optimizations. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an array buffer, else `false`. - */ - function baseIsArrayBuffer(value) { - return isObjectLike(value) && baseGetTag(value) == arrayBufferTag; - } - - /** - * The base implementation of `_.isDate` without Node.js optimizations. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a date object, else `false`. - */ - function baseIsDate(value) { - return isObjectLike(value) && baseGetTag(value) == dateTag; - } - - /** - * The base implementation of `_.isEqual` which supports partial comparisons - * and tracks traversed objects. - * - * @private - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @param {boolean} bitmask The bitmask flags. - * 1 - Unordered comparison - * 2 - Partial comparison - * @param {Function} [customizer] The function to customize comparisons. - * @param {Object} [stack] Tracks traversed `value` and `other` objects. - * @returns {boolean} Returns `true` if the values are equivalent, else `false`. - */ - function baseIsEqual(value, other, bitmask, customizer, stack) { - if (value === other) { - return true; - } - if (value == null || other == null || (!isObjectLike(value) && !isObjectLike(other))) { - return value !== value && other !== other; - } - return baseIsEqualDeep(value, other, bitmask, customizer, baseIsEqual, stack); - } - - /** - * A specialized version of `baseIsEqual` for arrays and objects which performs - * deep comparisons and tracks traversed objects enabling objects with circular - * references to be compared. - * - * @private - * @param {Object} object The object to compare. - * @param {Object} other The other object to compare. - * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. - * @param {Function} customizer The function to customize comparisons. - * @param {Function} equalFunc The function to determine equivalents of values. - * @param {Object} [stack] Tracks traversed `object` and `other` objects. - * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. - */ - function baseIsEqualDeep(object, other, bitmask, customizer, equalFunc, stack) { - var objIsArr = isArray(object), - othIsArr = isArray(other), - objTag = objIsArr ? arrayTag : getTag(object), - othTag = othIsArr ? arrayTag : getTag(other); - - objTag = objTag == argsTag ? objectTag : objTag; - othTag = othTag == argsTag ? objectTag : othTag; - - var objIsObj = objTag == objectTag, - othIsObj = othTag == objectTag, - isSameTag = objTag == othTag; - - if (isSameTag && isBuffer(object)) { - if (!isBuffer(other)) { - return false; - } - objIsArr = true; - objIsObj = false; - } - if (isSameTag && !objIsObj) { - stack || (stack = new Stack); - return (objIsArr || isTypedArray(object)) - ? equalArrays(object, other, bitmask, customizer, equalFunc, stack) - : equalByTag(object, other, objTag, bitmask, customizer, equalFunc, stack); - } - if (!(bitmask & COMPARE_PARTIAL_FLAG)) { - var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'), - othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__'); - - if (objIsWrapped || othIsWrapped) { - var objUnwrapped = objIsWrapped ? object.value() : object, - othUnwrapped = othIsWrapped ? other.value() : other; - - stack || (stack = new Stack); - return equalFunc(objUnwrapped, othUnwrapped, bitmask, customizer, stack); - } - } - if (!isSameTag) { - return false; - } - stack || (stack = new Stack); - return equalObjects(object, other, bitmask, customizer, equalFunc, stack); - } - - /** - * The base implementation of `_.isMap` without Node.js optimizations. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a map, else `false`. - */ - function baseIsMap(value) { - return isObjectLike(value) && getTag(value) == mapTag; - } - - /** - * The base implementation of `_.isMatch` without support for iteratee shorthands. - * - * @private - * @param {Object} object The object to inspect. - * @param {Object} source The object of property values to match. - * @param {Array} matchData The property names, values, and compare flags to match. - * @param {Function} [customizer] The function to customize comparisons. - * @returns {boolean} Returns `true` if `object` is a match, else `false`. - */ - function baseIsMatch(object, source, matchData, customizer) { - var index = matchData.length, - length = index, - noCustomizer = !customizer; - - if (object == null) { - return !length; - } - object = Object(object); - while (index--) { - var data = matchData[index]; - if ((noCustomizer && data[2]) - ? data[1] !== object[data[0]] - : !(data[0] in object) - ) { - return false; - } - } - while (++index < length) { - data = matchData[index]; - var key = data[0], - objValue = object[key], - srcValue = data[1]; - - if (noCustomizer && data[2]) { - if (objValue === undefined && !(key in object)) { - return false; - } - } else { - var stack = new Stack; - if (customizer) { - var result = customizer(objValue, srcValue, key, object, source, stack); - } - if (!(result === undefined - ? baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG, customizer, stack) - : result - )) { - return false; - } - } - } - return true; - } - - /** - * The base implementation of `_.isNative` without bad shim checks. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a native function, - * else `false`. - */ - function baseIsNative(value) { - if (!isObject(value) || isMasked(value)) { - return false; - } - var pattern = isFunction(value) ? reIsNative : reIsHostCtor; - return pattern.test(toSource(value)); - } - - /** - * The base implementation of `_.isRegExp` without Node.js optimizations. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a regexp, else `false`. - */ - function baseIsRegExp(value) { - return isObjectLike(value) && baseGetTag(value) == regexpTag; - } - - /** - * The base implementation of `_.isSet` without Node.js optimizations. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a set, else `false`. - */ - function baseIsSet(value) { - return isObjectLike(value) && getTag(value) == setTag; - } - - /** - * The base implementation of `_.isTypedArray` without Node.js optimizations. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a typed array, else `false`. - */ - function baseIsTypedArray(value) { - return isObjectLike(value) && - isLength(value.length) && !!typedArrayTags[baseGetTag(value)]; - } - - /** - * The base implementation of `_.iteratee`. - * - * @private - * @param {*} [value=_.identity] The value to convert to an iteratee. - * @returns {Function} Returns the iteratee. - */ - function baseIteratee(value) { - // Don't store the `typeof` result in a variable to avoid a JIT bug in Safari 9. - // See https://bugs.webkit.org/show_bug.cgi?id=156034 for more details. - if (typeof value == 'function') { - return value; - } - if (value == null) { - return identity; - } - if (typeof value == 'object') { - return isArray(value) - ? baseMatchesProperty(value[0], value[1]) - : baseMatches(value); - } - return property(value); - } - - /** - * The base implementation of `_.keys` which doesn't treat sparse arrays as dense. - * - * @private - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property names. - */ - function baseKeys(object) { - if (!isPrototype(object)) { - return nativeKeys(object); - } - var result = []; - for (var key in Object(object)) { - if (hasOwnProperty.call(object, key) && key != 'constructor') { - result.push(key); - } - } - return result; - } - - /** - * The base implementation of `_.keysIn` which doesn't treat sparse arrays as dense. - * - * @private - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property names. - */ - function baseKeysIn(object) { - if (!isObject(object)) { - return nativeKeysIn(object); - } - var isProto = isPrototype(object), - result = []; - - for (var key in object) { - if (!(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) { - result.push(key); - } - } - return result; - } - - /** - * The base implementation of `_.lt` which doesn't coerce arguments. - * - * @private - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {boolean} Returns `true` if `value` is less than `other`, - * else `false`. - */ - function baseLt(value, other) { - return value < other; - } - - /** - * The base implementation of `_.map` without support for iteratee shorthands. - * - * @private - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Array} Returns the new mapped array. - */ - function baseMap(collection, iteratee) { - var index = -1, - result = isArrayLike(collection) ? Array(collection.length) : []; - - baseEach(collection, function(value, key, collection) { - result[++index] = iteratee(value, key, collection); - }); - return result; - } - - /** - * The base implementation of `_.matches` which doesn't clone `source`. - * - * @private - * @param {Object} source The object of property values to match. - * @returns {Function} Returns the new spec function. - */ - function baseMatches(source) { - var matchData = getMatchData(source); - if (matchData.length == 1 && matchData[0][2]) { - return matchesStrictComparable(matchData[0][0], matchData[0][1]); - } - return function(object) { - return object === source || baseIsMatch(object, source, matchData); - }; - } - - /** - * The base implementation of `_.matchesProperty` which doesn't clone `srcValue`. - * - * @private - * @param {string} path The path of the property to get. - * @param {*} srcValue The value to match. - * @returns {Function} Returns the new spec function. - */ - function baseMatchesProperty(path, srcValue) { - if (isKey(path) && isStrictComparable(srcValue)) { - return matchesStrictComparable(toKey(path), srcValue); - } - return function(object) { - var objValue = get(object, path); - return (objValue === undefined && objValue === srcValue) - ? hasIn(object, path) - : baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG); - }; - } - - /** - * The base implementation of `_.merge` without support for multiple sources. - * - * @private - * @param {Object} object The destination object. - * @param {Object} source The source object. - * @param {number} srcIndex The index of `source`. - * @param {Function} [customizer] The function to customize merged values. - * @param {Object} [stack] Tracks traversed source values and their merged - * counterparts. - */ - function baseMerge(object, source, srcIndex, customizer, stack) { - if (object === source) { - return; - } - baseFor(source, function(srcValue, key) { - if (isObject(srcValue)) { - stack || (stack = new Stack); - baseMergeDeep(object, source, key, srcIndex, baseMerge, customizer, stack); - } - else { - var newValue = customizer - ? customizer(safeGet(object, key), srcValue, (key + ''), object, source, stack) - : undefined; - - if (newValue === undefined) { - newValue = srcValue; - } - assignMergeValue(object, key, newValue); - } - }, keysIn); - } - - /** - * A specialized version of `baseMerge` for arrays and objects which performs - * deep merges and tracks traversed objects enabling objects with circular - * references to be merged. - * - * @private - * @param {Object} object The destination object. - * @param {Object} source The source object. - * @param {string} key The key of the value to merge. - * @param {number} srcIndex The index of `source`. - * @param {Function} mergeFunc The function to merge values. - * @param {Function} [customizer] The function to customize assigned values. - * @param {Object} [stack] Tracks traversed source values and their merged - * counterparts. - */ - function baseMergeDeep(object, source, key, srcIndex, mergeFunc, customizer, stack) { - var objValue = safeGet(object, key), - srcValue = safeGet(source, key), - stacked = stack.get(srcValue); - - if (stacked) { - assignMergeValue(object, key, stacked); - return; - } - var newValue = customizer - ? customizer(objValue, srcValue, (key + ''), object, source, stack) - : undefined; - - var isCommon = newValue === undefined; - - if (isCommon) { - var isArr = isArray(srcValue), - isBuff = !isArr && isBuffer(srcValue), - isTyped = !isArr && !isBuff && isTypedArray(srcValue); - - newValue = srcValue; - if (isArr || isBuff || isTyped) { - if (isArray(objValue)) { - newValue = objValue; - } - else if (isArrayLikeObject(objValue)) { - newValue = copyArray(objValue); - } - else if (isBuff) { - isCommon = false; - newValue = cloneBuffer(srcValue, true); - } - else if (isTyped) { - isCommon = false; - newValue = cloneTypedArray(srcValue, true); - } - else { - newValue = []; - } - } - else if (isPlainObject(srcValue) || isArguments(srcValue)) { - newValue = objValue; - if (isArguments(objValue)) { - newValue = toPlainObject(objValue); - } - else if (!isObject(objValue) || (srcIndex && isFunction(objValue))) { - newValue = initCloneObject(srcValue); - } - } - else { - isCommon = false; - } - } - if (isCommon) { - // Recursively merge objects and arrays (susceptible to call stack limits). - stack.set(srcValue, newValue); - mergeFunc(newValue, srcValue, srcIndex, customizer, stack); - stack['delete'](srcValue); - } - assignMergeValue(object, key, newValue); - } - - /** - * The base implementation of `_.nth` which doesn't coerce arguments. - * - * @private - * @param {Array} array The array to query. - * @param {number} n The index of the element to return. - * @returns {*} Returns the nth element of `array`. - */ - function baseNth(array, n) { - var length = array.length; - if (!length) { - return; - } - n += n < 0 ? length : 0; - return isIndex(n, length) ? array[n] : undefined; - } - - /** - * The base implementation of `_.orderBy` without param guards. - * - * @private - * @param {Array|Object} collection The collection to iterate over. - * @param {Function[]|Object[]|string[]} iteratees The iteratees to sort by. - * @param {string[]} orders The sort orders of `iteratees`. - * @returns {Array} Returns the new sorted array. - */ - function baseOrderBy(collection, iteratees, orders) { - var index = -1; - iteratees = arrayMap(iteratees.length ? iteratees : [identity], baseUnary(getIteratee())); - - var result = baseMap(collection, function(value, key, collection) { - var criteria = arrayMap(iteratees, function(iteratee) { - return iteratee(value); - }); - return { 'criteria': criteria, 'index': ++index, 'value': value }; - }); - - return baseSortBy(result, function(object, other) { - return compareMultiple(object, other, orders); - }); - } - - /** - * The base implementation of `_.pick` without support for individual - * property identifiers. - * - * @private - * @param {Object} object The source object. - * @param {string[]} paths The property paths to pick. - * @returns {Object} Returns the new object. - */ - function basePick(object, paths) { - return basePickBy(object, paths, function(value, path) { - return hasIn(object, path); - }); - } - - /** - * The base implementation of `_.pickBy` without support for iteratee shorthands. - * - * @private - * @param {Object} object The source object. - * @param {string[]} paths The property paths to pick. - * @param {Function} predicate The function invoked per property. - * @returns {Object} Returns the new object. - */ - function basePickBy(object, paths, predicate) { - var index = -1, - length = paths.length, - result = {}; - - while (++index < length) { - var path = paths[index], - value = baseGet(object, path); - - if (predicate(value, path)) { - baseSet(result, castPath(path, object), value); - } - } - return result; - } - - /** - * A specialized version of `baseProperty` which supports deep paths. - * - * @private - * @param {Array|string} path The path of the property to get. - * @returns {Function} Returns the new accessor function. - */ - function basePropertyDeep(path) { - return function(object) { - return baseGet(object, path); - }; - } - - /** - * The base implementation of `_.pullAllBy` without support for iteratee - * shorthands. - * - * @private - * @param {Array} array The array to modify. - * @param {Array} values The values to remove. - * @param {Function} [iteratee] The iteratee invoked per element. - * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns `array`. - */ - function basePullAll(array, values, iteratee, comparator) { - var indexOf = comparator ? baseIndexOfWith : baseIndexOf, - index = -1, - length = values.length, - seen = array; - - if (array === values) { - values = copyArray(values); - } - if (iteratee) { - seen = arrayMap(array, baseUnary(iteratee)); - } - while (++index < length) { - var fromIndex = 0, - value = values[index], - computed = iteratee ? iteratee(value) : value; - - while ((fromIndex = indexOf(seen, computed, fromIndex, comparator)) > -1) { - if (seen !== array) { - splice.call(seen, fromIndex, 1); - } - splice.call(array, fromIndex, 1); - } - } - return array; - } - - /** - * The base implementation of `_.pullAt` without support for individual - * indexes or capturing the removed elements. - * - * @private - * @param {Array} array The array to modify. - * @param {number[]} indexes The indexes of elements to remove. - * @returns {Array} Returns `array`. - */ - function basePullAt(array, indexes) { - var length = array ? indexes.length : 0, - lastIndex = length - 1; - - while (length--) { - var index = indexes[length]; - if (length == lastIndex || index !== previous) { - var previous = index; - if (isIndex(index)) { - splice.call(array, index, 1); - } else { - baseUnset(array, index); - } - } - } - return array; - } - - /** - * The base implementation of `_.random` without support for returning - * floating-point numbers. - * - * @private - * @param {number} lower The lower bound. - * @param {number} upper The upper bound. - * @returns {number} Returns the random number. - */ - function baseRandom(lower, upper) { - return lower + nativeFloor(nativeRandom() * (upper - lower + 1)); - } - - /** - * The base implementation of `_.range` and `_.rangeRight` which doesn't - * coerce arguments. - * - * @private - * @param {number} start The start of the range. - * @param {number} end The end of the range. - * @param {number} step The value to increment or decrement by. - * @param {boolean} [fromRight] Specify iterating from right to left. - * @returns {Array} Returns the range of numbers. - */ - function baseRange(start, end, step, fromRight) { - var index = -1, - length = nativeMax(nativeCeil((end - start) / (step || 1)), 0), - result = Array(length); - - while (length--) { - result[fromRight ? length : ++index] = start; - start += step; - } - return result; - } - - /** - * The base implementation of `_.repeat` which doesn't coerce arguments. - * - * @private - * @param {string} string The string to repeat. - * @param {number} n The number of times to repeat the string. - * @returns {string} Returns the repeated string. - */ - function baseRepeat(string, n) { - var result = ''; - if (!string || n < 1 || n > MAX_SAFE_INTEGER) { - return result; - } - // Leverage the exponentiation by squaring algorithm for a faster repeat. - // See https://en.wikipedia.org/wiki/Exponentiation_by_squaring for more details. - do { - if (n % 2) { - result += string; - } - n = nativeFloor(n / 2); - if (n) { - string += string; - } - } while (n); - - return result; - } - - /** - * The base implementation of `_.rest` which doesn't validate or coerce arguments. - * - * @private - * @param {Function} func The function to apply a rest parameter to. - * @param {number} [start=func.length-1] The start position of the rest parameter. - * @returns {Function} Returns the new function. - */ - function baseRest(func, start) { - return setToString(overRest(func, start, identity), func + ''); - } - - /** - * The base implementation of `_.sample`. - * - * @private - * @param {Array|Object} collection The collection to sample. - * @returns {*} Returns the random element. - */ - function baseSample(collection) { - return arraySample(values(collection)); - } - - /** - * The base implementation of `_.sampleSize` without param guards. - * - * @private - * @param {Array|Object} collection The collection to sample. - * @param {number} n The number of elements to sample. - * @returns {Array} Returns the random elements. - */ - function baseSampleSize(collection, n) { - var array = values(collection); - return shuffleSelf(array, baseClamp(n, 0, array.length)); - } - - /** - * The base implementation of `_.set`. - * - * @private - * @param {Object} object The object to modify. - * @param {Array|string} path The path of the property to set. - * @param {*} value The value to set. - * @param {Function} [customizer] The function to customize path creation. - * @returns {Object} Returns `object`. - */ - function baseSet(object, path, value, customizer) { - if (!isObject(object)) { - return object; - } - path = castPath(path, object); - - var index = -1, - length = path.length, - lastIndex = length - 1, - nested = object; - - while (nested != null && ++index < length) { - var key = toKey(path[index]), - newValue = value; - - if (index != lastIndex) { - var objValue = nested[key]; - newValue = customizer ? customizer(objValue, key, nested) : undefined; - if (newValue === undefined) { - newValue = isObject(objValue) - ? objValue - : (isIndex(path[index + 1]) ? [] : {}); - } - } - assignValue(nested, key, newValue); - nested = nested[key]; - } - return object; - } - - /** - * The base implementation of `setData` without support for hot loop shorting. - * - * @private - * @param {Function} func The function to associate metadata with. - * @param {*} data The metadata. - * @returns {Function} Returns `func`. - */ - var baseSetData = !metaMap ? identity : function(func, data) { - metaMap.set(func, data); - return func; - }; - - /** - * The base implementation of `setToString` without support for hot loop shorting. - * - * @private - * @param {Function} func The function to modify. - * @param {Function} string The `toString` result. - * @returns {Function} Returns `func`. - */ - var baseSetToString = !defineProperty ? identity : function(func, string) { - return defineProperty(func, 'toString', { - 'configurable': true, - 'enumerable': false, - 'value': constant(string), - 'writable': true - }); - }; - - /** - * The base implementation of `_.shuffle`. - * - * @private - * @param {Array|Object} collection The collection to shuffle. - * @returns {Array} Returns the new shuffled array. - */ - function baseShuffle(collection) { - return shuffleSelf(values(collection)); - } - - /** - * The base implementation of `_.slice` without an iteratee call guard. - * - * @private - * @param {Array} array The array to slice. - * @param {number} [start=0] The start position. - * @param {number} [end=array.length] The end position. - * @returns {Array} Returns the slice of `array`. - */ - function baseSlice(array, start, end) { - var index = -1, - length = array.length; - - if (start < 0) { - start = -start > length ? 0 : (length + start); - } - end = end > length ? length : end; - if (end < 0) { - end += length; - } - length = start > end ? 0 : ((end - start) >>> 0); - start >>>= 0; - - var result = Array(length); - while (++index < length) { - result[index] = array[index + start]; - } - return result; - } - - /** - * The base implementation of `_.some` without support for iteratee shorthands. - * - * @private - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} predicate The function invoked per iteration. - * @returns {boolean} Returns `true` if any element passes the predicate check, - * else `false`. - */ - function baseSome(collection, predicate) { - var result; - - baseEach(collection, function(value, index, collection) { - result = predicate(value, index, collection); - return !result; - }); - return !!result; - } - - /** - * The base implementation of `_.sortedIndex` and `_.sortedLastIndex` which - * performs a binary search of `array` to determine the index at which `value` - * should be inserted into `array` in order to maintain its sort order. - * - * @private - * @param {Array} array The sorted array to inspect. - * @param {*} value The value to evaluate. - * @param {boolean} [retHighest] Specify returning the highest qualified index. - * @returns {number} Returns the index at which `value` should be inserted - * into `array`. - */ - function baseSortedIndex(array, value, retHighest) { - var low = 0, - high = array == null ? low : array.length; - - if (typeof value == 'number' && value === value && high <= HALF_MAX_ARRAY_LENGTH) { - while (low < high) { - var mid = (low + high) >>> 1, - computed = array[mid]; - - if (computed !== null && !isSymbol(computed) && - (retHighest ? (computed <= value) : (computed < value))) { - low = mid + 1; - } else { - high = mid; - } - } - return high; - } - return baseSortedIndexBy(array, value, identity, retHighest); - } - - /** - * The base implementation of `_.sortedIndexBy` and `_.sortedLastIndexBy` - * which invokes `iteratee` for `value` and each element of `array` to compute - * their sort ranking. The iteratee is invoked with one argument; (value). - * - * @private - * @param {Array} array The sorted array to inspect. - * @param {*} value The value to evaluate. - * @param {Function} iteratee The iteratee invoked per element. - * @param {boolean} [retHighest] Specify returning the highest qualified index. - * @returns {number} Returns the index at which `value` should be inserted - * into `array`. - */ - function baseSortedIndexBy(array, value, iteratee, retHighest) { - value = iteratee(value); - - var low = 0, - high = array == null ? 0 : array.length, - valIsNaN = value !== value, - valIsNull = value === null, - valIsSymbol = isSymbol(value), - valIsUndefined = value === undefined; - - while (low < high) { - var mid = nativeFloor((low + high) / 2), - computed = iteratee(array[mid]), - othIsDefined = computed !== undefined, - othIsNull = computed === null, - othIsReflexive = computed === computed, - othIsSymbol = isSymbol(computed); - - if (valIsNaN) { - var setLow = retHighest || othIsReflexive; - } else if (valIsUndefined) { - setLow = othIsReflexive && (retHighest || othIsDefined); - } else if (valIsNull) { - setLow = othIsReflexive && othIsDefined && (retHighest || !othIsNull); - } else if (valIsSymbol) { - setLow = othIsReflexive && othIsDefined && !othIsNull && (retHighest || !othIsSymbol); - } else if (othIsNull || othIsSymbol) { - setLow = false; - } else { - setLow = retHighest ? (computed <= value) : (computed < value); - } - if (setLow) { - low = mid + 1; - } else { - high = mid; - } - } - return nativeMin(high, MAX_ARRAY_INDEX); - } - - /** - * The base implementation of `_.sortedUniq` and `_.sortedUniqBy` without - * support for iteratee shorthands. - * - * @private - * @param {Array} array The array to inspect. - * @param {Function} [iteratee] The iteratee invoked per element. - * @returns {Array} Returns the new duplicate free array. - */ - function baseSortedUniq(array, iteratee) { - var index = -1, - length = array.length, - resIndex = 0, - result = []; - - while (++index < length) { - var value = array[index], - computed = iteratee ? iteratee(value) : value; - - if (!index || !eq(computed, seen)) { - var seen = computed; - result[resIndex++] = value === 0 ? 0 : value; - } - } - return result; - } - - /** - * The base implementation of `_.toNumber` which doesn't ensure correct - * conversions of binary, hexadecimal, or octal string values. - * - * @private - * @param {*} value The value to process. - * @returns {number} Returns the number. - */ - function baseToNumber(value) { - if (typeof value == 'number') { - return value; - } - if (isSymbol(value)) { - return NAN; - } - return +value; - } - - /** - * The base implementation of `_.toString` which doesn't convert nullish - * values to empty strings. - * - * @private - * @param {*} value The value to process. - * @returns {string} Returns the string. - */ - function baseToString(value) { - // Exit early for strings to avoid a performance hit in some environments. - if (typeof value == 'string') { - return value; - } - if (isArray(value)) { - // Recursively convert values (susceptible to call stack limits). - return arrayMap(value, baseToString) + ''; - } - if (isSymbol(value)) { - return symbolToString ? symbolToString.call(value) : ''; - } - var result = (value + ''); - return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result; - } - - /** - * The base implementation of `_.uniqBy` without support for iteratee shorthands. - * - * @private - * @param {Array} array The array to inspect. - * @param {Function} [iteratee] The iteratee invoked per element. - * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns the new duplicate free array. - */ - function baseUniq(array, iteratee, comparator) { - var index = -1, - includes = arrayIncludes, - length = array.length, - isCommon = true, - result = [], - seen = result; - - if (comparator) { - isCommon = false; - includes = arrayIncludesWith; - } - else if (length >= LARGE_ARRAY_SIZE) { - var set = iteratee ? null : createSet(array); - if (set) { - return setToArray(set); - } - isCommon = false; - includes = cacheHas; - seen = new SetCache; - } - else { - seen = iteratee ? [] : result; - } - outer: - while (++index < length) { - var value = array[index], - computed = iteratee ? iteratee(value) : value; - - value = (comparator || value !== 0) ? value : 0; - if (isCommon && computed === computed) { - var seenIndex = seen.length; - while (seenIndex--) { - if (seen[seenIndex] === computed) { - continue outer; - } - } - if (iteratee) { - seen.push(computed); - } - result.push(value); - } - else if (!includes(seen, computed, comparator)) { - if (seen !== result) { - seen.push(computed); - } - result.push(value); - } - } - return result; - } - - /** - * The base implementation of `_.unset`. - * - * @private - * @param {Object} object The object to modify. - * @param {Array|string} path The property path to unset. - * @returns {boolean} Returns `true` if the property is deleted, else `false`. - */ - function baseUnset(object, path) { - path = castPath(path, object); - object = parent(object, path); - return object == null || delete object[toKey(last(path))]; - } - - /** - * The base implementation of `_.update`. - * - * @private - * @param {Object} object The object to modify. - * @param {Array|string} path The path of the property to update. - * @param {Function} updater The function to produce the updated value. - * @param {Function} [customizer] The function to customize path creation. - * @returns {Object} Returns `object`. - */ - function baseUpdate(object, path, updater, customizer) { - return baseSet(object, path, updater(baseGet(object, path)), customizer); - } - - /** - * The base implementation of methods like `_.dropWhile` and `_.takeWhile` - * without support for iteratee shorthands. - * - * @private - * @param {Array} array The array to query. - * @param {Function} predicate The function invoked per iteration. - * @param {boolean} [isDrop] Specify dropping elements instead of taking them. - * @param {boolean} [fromRight] Specify iterating from right to left. - * @returns {Array} Returns the slice of `array`. - */ - function baseWhile(array, predicate, isDrop, fromRight) { - var length = array.length, - index = fromRight ? length : -1; - - while ((fromRight ? index-- : ++index < length) && - predicate(array[index], index, array)) {} - - return isDrop - ? baseSlice(array, (fromRight ? 0 : index), (fromRight ? index + 1 : length)) - : baseSlice(array, (fromRight ? index + 1 : 0), (fromRight ? length : index)); - } - - /** - * The base implementation of `wrapperValue` which returns the result of - * performing a sequence of actions on the unwrapped `value`, where each - * successive action is supplied the return value of the previous. - * - * @private - * @param {*} value The unwrapped value. - * @param {Array} actions Actions to perform to resolve the unwrapped value. - * @returns {*} Returns the resolved value. - */ - function baseWrapperValue(value, actions) { - var result = value; - if (result instanceof LazyWrapper) { - result = result.value(); - } - return arrayReduce(actions, function(result, action) { - return action.func.apply(action.thisArg, arrayPush([result], action.args)); - }, result); - } - - /** - * The base implementation of methods like `_.xor`, without support for - * iteratee shorthands, that accepts an array of arrays to inspect. - * - * @private - * @param {Array} arrays The arrays to inspect. - * @param {Function} [iteratee] The iteratee invoked per element. - * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns the new array of values. - */ - function baseXor(arrays, iteratee, comparator) { - var length = arrays.length; - if (length < 2) { - return length ? baseUniq(arrays[0]) : []; - } - var index = -1, - result = Array(length); - - while (++index < length) { - var array = arrays[index], - othIndex = -1; - - while (++othIndex < length) { - if (othIndex != index) { - result[index] = baseDifference(result[index] || array, arrays[othIndex], iteratee, comparator); - } - } - } - return baseUniq(baseFlatten(result, 1), iteratee, comparator); - } - - /** - * This base implementation of `_.zipObject` which assigns values using `assignFunc`. - * - * @private - * @param {Array} props The property identifiers. - * @param {Array} values The property values. - * @param {Function} assignFunc The function to assign values. - * @returns {Object} Returns the new object. - */ - function baseZipObject(props, values, assignFunc) { - var index = -1, - length = props.length, - valsLength = values.length, - result = {}; - - while (++index < length) { - var value = index < valsLength ? values[index] : undefined; - assignFunc(result, props[index], value); - } - return result; - } - - /** - * Casts `value` to an empty array if it's not an array like object. - * - * @private - * @param {*} value The value to inspect. - * @returns {Array|Object} Returns the cast array-like object. - */ - function castArrayLikeObject(value) { - return isArrayLikeObject(value) ? value : []; - } - - /** - * Casts `value` to `identity` if it's not a function. - * - * @private - * @param {*} value The value to inspect. - * @returns {Function} Returns cast function. - */ - function castFunction(value) { - return typeof value == 'function' ? value : identity; - } - - /** - * Casts `value` to a path array if it's not one. - * - * @private - * @param {*} value The value to inspect. - * @param {Object} [object] The object to query keys on. - * @returns {Array} Returns the cast property path array. - */ - function castPath(value, object) { - if (isArray(value)) { - return value; - } - return isKey(value, object) ? [value] : stringToPath(toString(value)); - } - - /** - * A `baseRest` alias which can be replaced with `identity` by module - * replacement plugins. - * - * @private - * @type {Function} - * @param {Function} func The function to apply a rest parameter to. - * @returns {Function} Returns the new function. - */ - var castRest = baseRest; - - /** - * Casts `array` to a slice if it's needed. - * - * @private - * @param {Array} array The array to inspect. - * @param {number} start The start position. - * @param {number} [end=array.length] The end position. - * @returns {Array} Returns the cast slice. - */ - function castSlice(array, start, end) { - var length = array.length; - end = end === undefined ? length : end; - return (!start && end >= length) ? array : baseSlice(array, start, end); - } - - /** - * A simple wrapper around the global [`clearTimeout`](https://mdn.io/clearTimeout). - * - * @private - * @param {number|Object} id The timer id or timeout object of the timer to clear. - */ - var clearTimeout = ctxClearTimeout || function(id) { - return root.clearTimeout(id); - }; - - /** - * Creates a clone of `buffer`. - * - * @private - * @param {Buffer} buffer The buffer to clone. - * @param {boolean} [isDeep] Specify a deep clone. - * @returns {Buffer} Returns the cloned buffer. - */ - function cloneBuffer(buffer, isDeep) { - if (isDeep) { - return buffer.slice(); - } - var length = buffer.length, - result = allocUnsafe ? allocUnsafe(length) : new buffer.constructor(length); - - buffer.copy(result); - return result; - } - - /** - * Creates a clone of `arrayBuffer`. - * - * @private - * @param {ArrayBuffer} arrayBuffer The array buffer to clone. - * @returns {ArrayBuffer} Returns the cloned array buffer. - */ - function cloneArrayBuffer(arrayBuffer) { - var result = new arrayBuffer.constructor(arrayBuffer.byteLength); - new Uint8Array(result).set(new Uint8Array(arrayBuffer)); - return result; - } - - /** - * Creates a clone of `dataView`. - * - * @private - * @param {Object} dataView The data view to clone. - * @param {boolean} [isDeep] Specify a deep clone. - * @returns {Object} Returns the cloned data view. - */ - function cloneDataView(dataView, isDeep) { - var buffer = isDeep ? cloneArrayBuffer(dataView.buffer) : dataView.buffer; - return new dataView.constructor(buffer, dataView.byteOffset, dataView.byteLength); - } - - /** - * Creates a clone of `regexp`. - * - * @private - * @param {Object} regexp The regexp to clone. - * @returns {Object} Returns the cloned regexp. - */ - function cloneRegExp(regexp) { - var result = new regexp.constructor(regexp.source, reFlags.exec(regexp)); - result.lastIndex = regexp.lastIndex; - return result; - } - - /** - * Creates a clone of the `symbol` object. - * - * @private - * @param {Object} symbol The symbol object to clone. - * @returns {Object} Returns the cloned symbol object. - */ - function cloneSymbol(symbol) { - return symbolValueOf ? Object(symbolValueOf.call(symbol)) : {}; - } - - /** - * Creates a clone of `typedArray`. - * - * @private - * @param {Object} typedArray The typed array to clone. - * @param {boolean} [isDeep] Specify a deep clone. - * @returns {Object} Returns the cloned typed array. - */ - function cloneTypedArray(typedArray, isDeep) { - var buffer = isDeep ? cloneArrayBuffer(typedArray.buffer) : typedArray.buffer; - return new typedArray.constructor(buffer, typedArray.byteOffset, typedArray.length); - } - - /** - * Compares values to sort them in ascending order. - * - * @private - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {number} Returns the sort order indicator for `value`. - */ - function compareAscending(value, other) { - if (value !== other) { - var valIsDefined = value !== undefined, - valIsNull = value === null, - valIsReflexive = value === value, - valIsSymbol = isSymbol(value); - - var othIsDefined = other !== undefined, - othIsNull = other === null, - othIsReflexive = other === other, - othIsSymbol = isSymbol(other); - - if ((!othIsNull && !othIsSymbol && !valIsSymbol && value > other) || - (valIsSymbol && othIsDefined && othIsReflexive && !othIsNull && !othIsSymbol) || - (valIsNull && othIsDefined && othIsReflexive) || - (!valIsDefined && othIsReflexive) || - !valIsReflexive) { - return 1; - } - if ((!valIsNull && !valIsSymbol && !othIsSymbol && value < other) || - (othIsSymbol && valIsDefined && valIsReflexive && !valIsNull && !valIsSymbol) || - (othIsNull && valIsDefined && valIsReflexive) || - (!othIsDefined && valIsReflexive) || - !othIsReflexive) { - return -1; - } - } - return 0; - } - - /** - * Used by `_.orderBy` to compare multiple properties of a value to another - * and stable sort them. - * - * If `orders` is unspecified, all values are sorted in ascending order. Otherwise, - * specify an order of "desc" for descending or "asc" for ascending sort order - * of corresponding values. - * - * @private - * @param {Object} object The object to compare. - * @param {Object} other The other object to compare. - * @param {boolean[]|string[]} orders The order to sort by for each property. - * @returns {number} Returns the sort order indicator for `object`. - */ - function compareMultiple(object, other, orders) { - var index = -1, - objCriteria = object.criteria, - othCriteria = other.criteria, - length = objCriteria.length, - ordersLength = orders.length; - - while (++index < length) { - var result = compareAscending(objCriteria[index], othCriteria[index]); - if (result) { - if (index >= ordersLength) { - return result; - } - var order = orders[index]; - return result * (order == 'desc' ? -1 : 1); - } - } - // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications - // that causes it, under certain circumstances, to provide the same value for - // `object` and `other`. See https://github.com/jashkenas/underscore/pull/1247 - // for more details. - // - // This also ensures a stable sort in V8 and other engines. - // See https://bugs.chromium.org/p/v8/issues/detail?id=90 for more details. - return object.index - other.index; - } - - /** - * Creates an array that is the composition of partially applied arguments, - * placeholders, and provided arguments into a single array of arguments. - * - * @private - * @param {Array} args The provided arguments. - * @param {Array} partials The arguments to prepend to those provided. - * @param {Array} holders The `partials` placeholder indexes. - * @params {boolean} [isCurried] Specify composing for a curried function. - * @returns {Array} Returns the new array of composed arguments. - */ - function composeArgs(args, partials, holders, isCurried) { - var argsIndex = -1, - argsLength = args.length, - holdersLength = holders.length, - leftIndex = -1, - leftLength = partials.length, - rangeLength = nativeMax(argsLength - holdersLength, 0), - result = Array(leftLength + rangeLength), - isUncurried = !isCurried; - - while (++leftIndex < leftLength) { - result[leftIndex] = partials[leftIndex]; - } - while (++argsIndex < holdersLength) { - if (isUncurried || argsIndex < argsLength) { - result[holders[argsIndex]] = args[argsIndex]; - } - } - while (rangeLength--) { - result[leftIndex++] = args[argsIndex++]; - } - return result; - } - - /** - * This function is like `composeArgs` except that the arguments composition - * is tailored for `_.partialRight`. - * - * @private - * @param {Array} args The provided arguments. - * @param {Array} partials The arguments to append to those provided. - * @param {Array} holders The `partials` placeholder indexes. - * @params {boolean} [isCurried] Specify composing for a curried function. - * @returns {Array} Returns the new array of composed arguments. - */ - function composeArgsRight(args, partials, holders, isCurried) { - var argsIndex = -1, - argsLength = args.length, - holdersIndex = -1, - holdersLength = holders.length, - rightIndex = -1, - rightLength = partials.length, - rangeLength = nativeMax(argsLength - holdersLength, 0), - result = Array(rangeLength + rightLength), - isUncurried = !isCurried; - - while (++argsIndex < rangeLength) { - result[argsIndex] = args[argsIndex]; - } - var offset = argsIndex; - while (++rightIndex < rightLength) { - result[offset + rightIndex] = partials[rightIndex]; - } - while (++holdersIndex < holdersLength) { - if (isUncurried || argsIndex < argsLength) { - result[offset + holders[holdersIndex]] = args[argsIndex++]; - } - } - return result; - } - - /** - * Copies the values of `source` to `array`. - * - * @private - * @param {Array} source The array to copy values from. - * @param {Array} [array=[]] The array to copy values to. - * @returns {Array} Returns `array`. - */ - function copyArray(source, array) { - var index = -1, - length = source.length; - - array || (array = Array(length)); - while (++index < length) { - array[index] = source[index]; - } - return array; - } - - /** - * Copies properties of `source` to `object`. - * - * @private - * @param {Object} source The object to copy properties from. - * @param {Array} props The property identifiers to copy. - * @param {Object} [object={}] The object to copy properties to. - * @param {Function} [customizer] The function to customize copied values. - * @returns {Object} Returns `object`. - */ - function copyObject(source, props, object, customizer) { - var isNew = !object; - object || (object = {}); - - var index = -1, - length = props.length; - - while (++index < length) { - var key = props[index]; - - var newValue = customizer - ? customizer(object[key], source[key], key, object, source) - : undefined; - - if (newValue === undefined) { - newValue = source[key]; - } - if (isNew) { - baseAssignValue(object, key, newValue); - } else { - assignValue(object, key, newValue); - } - } - return object; - } - - /** - * Copies own symbols of `source` to `object`. - * - * @private - * @param {Object} source The object to copy symbols from. - * @param {Object} [object={}] The object to copy symbols to. - * @returns {Object} Returns `object`. - */ - function copySymbols(source, object) { - return copyObject(source, getSymbols(source), object); - } - - /** - * Copies own and inherited symbols of `source` to `object`. - * - * @private - * @param {Object} source The object to copy symbols from. - * @param {Object} [object={}] The object to copy symbols to. - * @returns {Object} Returns `object`. - */ - function copySymbolsIn(source, object) { - return copyObject(source, getSymbolsIn(source), object); - } - - /** - * Creates a function like `_.groupBy`. - * - * @private - * @param {Function} setter The function to set accumulator values. - * @param {Function} [initializer] The accumulator object initializer. - * @returns {Function} Returns the new aggregator function. - */ - function createAggregator(setter, initializer) { - return function(collection, iteratee) { - var func = isArray(collection) ? arrayAggregator : baseAggregator, - accumulator = initializer ? initializer() : {}; - - return func(collection, setter, getIteratee(iteratee, 2), accumulator); - }; - } - - /** - * Creates a function like `_.assign`. - * - * @private - * @param {Function} assigner The function to assign values. - * @returns {Function} Returns the new assigner function. - */ - function createAssigner(assigner) { - return baseRest(function(object, sources) { - var index = -1, - length = sources.length, - customizer = length > 1 ? sources[length - 1] : undefined, - guard = length > 2 ? sources[2] : undefined; - - customizer = (assigner.length > 3 && typeof customizer == 'function') - ? (length--, customizer) - : undefined; - - if (guard && isIterateeCall(sources[0], sources[1], guard)) { - customizer = length < 3 ? undefined : customizer; - length = 1; - } - object = Object(object); - while (++index < length) { - var source = sources[index]; - if (source) { - assigner(object, source, index, customizer); - } - } - return object; - }); - } - - /** - * Creates a `baseEach` or `baseEachRight` function. - * - * @private - * @param {Function} eachFunc The function to iterate over a collection. - * @param {boolean} [fromRight] Specify iterating from right to left. - * @returns {Function} Returns the new base function. - */ - function createBaseEach(eachFunc, fromRight) { - return function(collection, iteratee) { - if (collection == null) { - return collection; - } - if (!isArrayLike(collection)) { - return eachFunc(collection, iteratee); - } - var length = collection.length, - index = fromRight ? length : -1, - iterable = Object(collection); - - while ((fromRight ? index-- : ++index < length)) { - if (iteratee(iterable[index], index, iterable) === false) { - break; - } - } - return collection; - }; - } - - /** - * Creates a base function for methods like `_.forIn` and `_.forOwn`. - * - * @private - * @param {boolean} [fromRight] Specify iterating from right to left. - * @returns {Function} Returns the new base function. - */ - function createBaseFor(fromRight) { - return function(object, iteratee, keysFunc) { - var index = -1, - iterable = Object(object), - props = keysFunc(object), - length = props.length; - - while (length--) { - var key = props[fromRight ? length : ++index]; - if (iteratee(iterable[key], key, iterable) === false) { - break; - } - } - return object; - }; - } - - /** - * Creates a function that wraps `func` to invoke it with the optional `this` - * binding of `thisArg`. - * - * @private - * @param {Function} func The function to wrap. - * @param {number} bitmask The bitmask flags. See `createWrap` for more details. - * @param {*} [thisArg] The `this` binding of `func`. - * @returns {Function} Returns the new wrapped function. - */ - function createBind(func, bitmask, thisArg) { - var isBind = bitmask & WRAP_BIND_FLAG, - Ctor = createCtor(func); - - function wrapper() { - var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func; - return fn.apply(isBind ? thisArg : this, arguments); - } - return wrapper; - } - - /** - * Creates a function like `_.lowerFirst`. - * - * @private - * @param {string} methodName The name of the `String` case method to use. - * @returns {Function} Returns the new case function. - */ - function createCaseFirst(methodName) { - return function(string) { - string = toString(string); - - var strSymbols = hasUnicode(string) - ? stringToArray(string) - : undefined; - - var chr = strSymbols - ? strSymbols[0] - : string.charAt(0); - - var trailing = strSymbols - ? castSlice(strSymbols, 1).join('') - : string.slice(1); - - return chr[methodName]() + trailing; - }; - } - - /** - * Creates a function like `_.camelCase`. - * - * @private - * @param {Function} callback The function to combine each word. - * @returns {Function} Returns the new compounder function. - */ - function createCompounder(callback) { - return function(string) { - return arrayReduce(words(deburr(string).replace(reApos, '')), callback, ''); - }; - } - - /** - * Creates a function that produces an instance of `Ctor` regardless of - * whether it was invoked as part of a `new` expression or by `call` or `apply`. - * - * @private - * @param {Function} Ctor The constructor to wrap. - * @returns {Function} Returns the new wrapped function. - */ - function createCtor(Ctor) { - return function() { - // Use a `switch` statement to work with class constructors. See - // http://ecma-international.org/ecma-262/7.0/#sec-ecmascript-function-objects-call-thisargument-argumentslist - // for more details. - var args = arguments; - switch (args.length) { - case 0: return new Ctor; - case 1: return new Ctor(args[0]); - case 2: return new Ctor(args[0], args[1]); - case 3: return new Ctor(args[0], args[1], args[2]); - case 4: return new Ctor(args[0], args[1], args[2], args[3]); - case 5: return new Ctor(args[0], args[1], args[2], args[3], args[4]); - case 6: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5]); - case 7: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6]); - } - var thisBinding = baseCreate(Ctor.prototype), - result = Ctor.apply(thisBinding, args); - - // Mimic the constructor's `return` behavior. - // See https://es5.github.io/#x13.2.2 for more details. - return isObject(result) ? result : thisBinding; - }; - } - - /** - * Creates a function that wraps `func` to enable currying. - * - * @private - * @param {Function} func The function to wrap. - * @param {number} bitmask The bitmask flags. See `createWrap` for more details. - * @param {number} arity The arity of `func`. - * @returns {Function} Returns the new wrapped function. - */ - function createCurry(func, bitmask, arity) { - var Ctor = createCtor(func); - - function wrapper() { - var length = arguments.length, - args = Array(length), - index = length, - placeholder = getHolder(wrapper); - - while (index--) { - args[index] = arguments[index]; - } - var holders = (length < 3 && args[0] !== placeholder && args[length - 1] !== placeholder) - ? [] - : replaceHolders(args, placeholder); - - length -= holders.length; - if (length < arity) { - return createRecurry( - func, bitmask, createHybrid, wrapper.placeholder, undefined, - args, holders, undefined, undefined, arity - length); - } - var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func; - return apply(fn, this, args); - } - return wrapper; - } - - /** - * Creates a `_.find` or `_.findLast` function. - * - * @private - * @param {Function} findIndexFunc The function to find the collection index. - * @returns {Function} Returns the new find function. - */ - function createFind(findIndexFunc) { - return function(collection, predicate, fromIndex) { - var iterable = Object(collection); - if (!isArrayLike(collection)) { - var iteratee = getIteratee(predicate, 3); - collection = keys(collection); - predicate = function(key) { return iteratee(iterable[key], key, iterable); }; - } - var index = findIndexFunc(collection, predicate, fromIndex); - return index > -1 ? iterable[iteratee ? collection[index] : index] : undefined; - }; - } - - /** - * Creates a `_.flow` or `_.flowRight` function. - * - * @private - * @param {boolean} [fromRight] Specify iterating from right to left. - * @returns {Function} Returns the new flow function. - */ - function createFlow(fromRight) { - return flatRest(function(funcs) { - var length = funcs.length, - index = length, - prereq = LodashWrapper.prototype.thru; - - if (fromRight) { - funcs.reverse(); - } - while (index--) { - var func = funcs[index]; - if (typeof func != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - if (prereq && !wrapper && getFuncName(func) == 'wrapper') { - var wrapper = new LodashWrapper([], true); - } - } - index = wrapper ? index : length; - while (++index < length) { - func = funcs[index]; - - var funcName = getFuncName(func), - data = funcName == 'wrapper' ? getData(func) : undefined; - - if (data && isLaziable(data[0]) && - data[1] == (WRAP_ARY_FLAG | WRAP_CURRY_FLAG | WRAP_PARTIAL_FLAG | WRAP_REARG_FLAG) && - !data[4].length && data[9] == 1 - ) { - wrapper = wrapper[getFuncName(data[0])].apply(wrapper, data[3]); - } else { - wrapper = (func.length == 1 && isLaziable(func)) - ? wrapper[funcName]() - : wrapper.thru(func); - } - } - return function() { - var args = arguments, - value = args[0]; - - if (wrapper && args.length == 1 && isArray(value)) { - return wrapper.plant(value).value(); - } - var index = 0, - result = length ? funcs[index].apply(this, args) : value; - - while (++index < length) { - result = funcs[index].call(this, result); - } - return result; - }; - }); - } - - /** - * Creates a function that wraps `func` to invoke it with optional `this` - * binding of `thisArg`, partial application, and currying. - * - * @private - * @param {Function|string} func The function or method name to wrap. - * @param {number} bitmask The bitmask flags. See `createWrap` for more details. - * @param {*} [thisArg] The `this` binding of `func`. - * @param {Array} [partials] The arguments to prepend to those provided to - * the new function. - * @param {Array} [holders] The `partials` placeholder indexes. - * @param {Array} [partialsRight] The arguments to append to those provided - * to the new function. - * @param {Array} [holdersRight] The `partialsRight` placeholder indexes. - * @param {Array} [argPos] The argument positions of the new function. - * @param {number} [ary] The arity cap of `func`. - * @param {number} [arity] The arity of `func`. - * @returns {Function} Returns the new wrapped function. - */ - function createHybrid(func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity) { - var isAry = bitmask & WRAP_ARY_FLAG, - isBind = bitmask & WRAP_BIND_FLAG, - isBindKey = bitmask & WRAP_BIND_KEY_FLAG, - isCurried = bitmask & (WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG), - isFlip = bitmask & WRAP_FLIP_FLAG, - Ctor = isBindKey ? undefined : createCtor(func); - - function wrapper() { - var length = arguments.length, - args = Array(length), - index = length; - - while (index--) { - args[index] = arguments[index]; - } - if (isCurried) { - var placeholder = getHolder(wrapper), - holdersCount = countHolders(args, placeholder); - } - if (partials) { - args = composeArgs(args, partials, holders, isCurried); - } - if (partialsRight) { - args = composeArgsRight(args, partialsRight, holdersRight, isCurried); - } - length -= holdersCount; - if (isCurried && length < arity) { - var newHolders = replaceHolders(args, placeholder); - return createRecurry( - func, bitmask, createHybrid, wrapper.placeholder, thisArg, - args, newHolders, argPos, ary, arity - length - ); - } - var thisBinding = isBind ? thisArg : this, - fn = isBindKey ? thisBinding[func] : func; - - length = args.length; - if (argPos) { - args = reorder(args, argPos); - } else if (isFlip && length > 1) { - args.reverse(); - } - if (isAry && ary < length) { - args.length = ary; - } - if (this && this !== root && this instanceof wrapper) { - fn = Ctor || createCtor(fn); - } - return fn.apply(thisBinding, args); - } - return wrapper; - } - - /** - * Creates a function like `_.invertBy`. - * - * @private - * @param {Function} setter The function to set accumulator values. - * @param {Function} toIteratee The function to resolve iteratees. - * @returns {Function} Returns the new inverter function. - */ - function createInverter(setter, toIteratee) { - return function(object, iteratee) { - return baseInverter(object, setter, toIteratee(iteratee), {}); - }; - } - - /** - * Creates a function that performs a mathematical operation on two values. - * - * @private - * @param {Function} operator The function to perform the operation. - * @param {number} [defaultValue] The value used for `undefined` arguments. - * @returns {Function} Returns the new mathematical operation function. - */ - function createMathOperation(operator, defaultValue) { - return function(value, other) { - var result; - if (value === undefined && other === undefined) { - return defaultValue; - } - if (value !== undefined) { - result = value; - } - if (other !== undefined) { - if (result === undefined) { - return other; - } - if (typeof value == 'string' || typeof other == 'string') { - value = baseToString(value); - other = baseToString(other); - } else { - value = baseToNumber(value); - other = baseToNumber(other); - } - result = operator(value, other); - } - return result; - }; - } - - /** - * Creates a function like `_.over`. - * - * @private - * @param {Function} arrayFunc The function to iterate over iteratees. - * @returns {Function} Returns the new over function. - */ - function createOver(arrayFunc) { - return flatRest(function(iteratees) { - iteratees = arrayMap(iteratees, baseUnary(getIteratee())); - return baseRest(function(args) { - var thisArg = this; - return arrayFunc(iteratees, function(iteratee) { - return apply(iteratee, thisArg, args); - }); - }); - }); - } - - /** - * Creates the padding for `string` based on `length`. The `chars` string - * is truncated if the number of characters exceeds `length`. - * - * @private - * @param {number} length The padding length. - * @param {string} [chars=' '] The string used as padding. - * @returns {string} Returns the padding for `string`. - */ - function createPadding(length, chars) { - chars = chars === undefined ? ' ' : baseToString(chars); - - var charsLength = chars.length; - if (charsLength < 2) { - return charsLength ? baseRepeat(chars, length) : chars; - } - var result = baseRepeat(chars, nativeCeil(length / stringSize(chars))); - return hasUnicode(chars) - ? castSlice(stringToArray(result), 0, length).join('') - : result.slice(0, length); - } - - /** - * Creates a function that wraps `func` to invoke it with the `this` binding - * of `thisArg` and `partials` prepended to the arguments it receives. - * - * @private - * @param {Function} func The function to wrap. - * @param {number} bitmask The bitmask flags. See `createWrap` for more details. - * @param {*} thisArg The `this` binding of `func`. - * @param {Array} partials The arguments to prepend to those provided to - * the new function. - * @returns {Function} Returns the new wrapped function. - */ - function createPartial(func, bitmask, thisArg, partials) { - var isBind = bitmask & WRAP_BIND_FLAG, - Ctor = createCtor(func); - - function wrapper() { - var argsIndex = -1, - argsLength = arguments.length, - leftIndex = -1, - leftLength = partials.length, - args = Array(leftLength + argsLength), - fn = (this && this !== root && this instanceof wrapper) ? Ctor : func; - - while (++leftIndex < leftLength) { - args[leftIndex] = partials[leftIndex]; - } - while (argsLength--) { - args[leftIndex++] = arguments[++argsIndex]; - } - return apply(fn, isBind ? thisArg : this, args); - } - return wrapper; - } - - /** - * Creates a `_.range` or `_.rangeRight` function. - * - * @private - * @param {boolean} [fromRight] Specify iterating from right to left. - * @returns {Function} Returns the new range function. - */ - function createRange(fromRight) { - return function(start, end, step) { - if (step && typeof step != 'number' && isIterateeCall(start, end, step)) { - end = step = undefined; - } - // Ensure the sign of `-0` is preserved. - start = toFinite(start); - if (end === undefined) { - end = start; - start = 0; - } else { - end = toFinite(end); - } - step = step === undefined ? (start < end ? 1 : -1) : toFinite(step); - return baseRange(start, end, step, fromRight); - }; - } - - /** - * Creates a function that performs a relational operation on two values. - * - * @private - * @param {Function} operator The function to perform the operation. - * @returns {Function} Returns the new relational operation function. - */ - function createRelationalOperation(operator) { - return function(value, other) { - if (!(typeof value == 'string' && typeof other == 'string')) { - value = toNumber(value); - other = toNumber(other); - } - return operator(value, other); - }; - } - - /** - * Creates a function that wraps `func` to continue currying. - * - * @private - * @param {Function} func The function to wrap. - * @param {number} bitmask The bitmask flags. See `createWrap` for more details. - * @param {Function} wrapFunc The function to create the `func` wrapper. - * @param {*} placeholder The placeholder value. - * @param {*} [thisArg] The `this` binding of `func`. - * @param {Array} [partials] The arguments to prepend to those provided to - * the new function. - * @param {Array} [holders] The `partials` placeholder indexes. - * @param {Array} [argPos] The argument positions of the new function. - * @param {number} [ary] The arity cap of `func`. - * @param {number} [arity] The arity of `func`. - * @returns {Function} Returns the new wrapped function. - */ - function createRecurry(func, bitmask, wrapFunc, placeholder, thisArg, partials, holders, argPos, ary, arity) { - var isCurry = bitmask & WRAP_CURRY_FLAG, - newHolders = isCurry ? holders : undefined, - newHoldersRight = isCurry ? undefined : holders, - newPartials = isCurry ? partials : undefined, - newPartialsRight = isCurry ? undefined : partials; - - bitmask |= (isCurry ? WRAP_PARTIAL_FLAG : WRAP_PARTIAL_RIGHT_FLAG); - bitmask &= ~(isCurry ? WRAP_PARTIAL_RIGHT_FLAG : WRAP_PARTIAL_FLAG); - - if (!(bitmask & WRAP_CURRY_BOUND_FLAG)) { - bitmask &= ~(WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG); - } - var newData = [ - func, bitmask, thisArg, newPartials, newHolders, newPartialsRight, - newHoldersRight, argPos, ary, arity - ]; - - var result = wrapFunc.apply(undefined, newData); - if (isLaziable(func)) { - setData(result, newData); - } - result.placeholder = placeholder; - return setWrapToString(result, func, bitmask); - } - - /** - * Creates a function like `_.round`. - * - * @private - * @param {string} methodName The name of the `Math` method to use when rounding. - * @returns {Function} Returns the new round function. - */ - function createRound(methodName) { - var func = Math[methodName]; - return function(number, precision) { - number = toNumber(number); - precision = precision == null ? 0 : nativeMin(toInteger(precision), 292); - if (precision) { - // Shift with exponential notation to avoid floating-point issues. - // See [MDN](https://mdn.io/round#Examples) for more details. - var pair = (toString(number) + 'e').split('e'), - value = func(pair[0] + 'e' + (+pair[1] + precision)); - - pair = (toString(value) + 'e').split('e'); - return +(pair[0] + 'e' + (+pair[1] - precision)); - } - return func(number); - }; - } - - /** - * Creates a set object of `values`. - * - * @private - * @param {Array} values The values to add to the set. - * @returns {Object} Returns the new set. - */ - var createSet = !(Set && (1 / setToArray(new Set([,-0]))[1]) == INFINITY) ? noop : function(values) { - return new Set(values); - }; - - /** - * Creates a `_.toPairs` or `_.toPairsIn` function. - * - * @private - * @param {Function} keysFunc The function to get the keys of a given object. - * @returns {Function} Returns the new pairs function. - */ - function createToPairs(keysFunc) { - return function(object) { - var tag = getTag(object); - if (tag == mapTag) { - return mapToArray(object); - } - if (tag == setTag) { - return setToPairs(object); - } - return baseToPairs(object, keysFunc(object)); - }; - } - - /** - * Creates a function that either curries or invokes `func` with optional - * `this` binding and partially applied arguments. - * - * @private - * @param {Function|string} func The function or method name to wrap. - * @param {number} bitmask The bitmask flags. - * 1 - `_.bind` - * 2 - `_.bindKey` - * 4 - `_.curry` or `_.curryRight` of a bound function - * 8 - `_.curry` - * 16 - `_.curryRight` - * 32 - `_.partial` - * 64 - `_.partialRight` - * 128 - `_.rearg` - * 256 - `_.ary` - * 512 - `_.flip` - * @param {*} [thisArg] The `this` binding of `func`. - * @param {Array} [partials] The arguments to be partially applied. - * @param {Array} [holders] The `partials` placeholder indexes. - * @param {Array} [argPos] The argument positions of the new function. - * @param {number} [ary] The arity cap of `func`. - * @param {number} [arity] The arity of `func`. - * @returns {Function} Returns the new wrapped function. - */ - function createWrap(func, bitmask, thisArg, partials, holders, argPos, ary, arity) { - var isBindKey = bitmask & WRAP_BIND_KEY_FLAG; - if (!isBindKey && typeof func != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - var length = partials ? partials.length : 0; - if (!length) { - bitmask &= ~(WRAP_PARTIAL_FLAG | WRAP_PARTIAL_RIGHT_FLAG); - partials = holders = undefined; - } - ary = ary === undefined ? ary : nativeMax(toInteger(ary), 0); - arity = arity === undefined ? arity : toInteger(arity); - length -= holders ? holders.length : 0; - - if (bitmask & WRAP_PARTIAL_RIGHT_FLAG) { - var partialsRight = partials, - holdersRight = holders; - - partials = holders = undefined; - } - var data = isBindKey ? undefined : getData(func); - - var newData = [ - func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, - argPos, ary, arity - ]; - - if (data) { - mergeData(newData, data); - } - func = newData[0]; - bitmask = newData[1]; - thisArg = newData[2]; - partials = newData[3]; - holders = newData[4]; - arity = newData[9] = newData[9] === undefined - ? (isBindKey ? 0 : func.length) - : nativeMax(newData[9] - length, 0); - - if (!arity && bitmask & (WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG)) { - bitmask &= ~(WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG); - } - if (!bitmask || bitmask == WRAP_BIND_FLAG) { - var result = createBind(func, bitmask, thisArg); - } else if (bitmask == WRAP_CURRY_FLAG || bitmask == WRAP_CURRY_RIGHT_FLAG) { - result = createCurry(func, bitmask, arity); - } else if ((bitmask == WRAP_PARTIAL_FLAG || bitmask == (WRAP_BIND_FLAG | WRAP_PARTIAL_FLAG)) && !holders.length) { - result = createPartial(func, bitmask, thisArg, partials); - } else { - result = createHybrid.apply(undefined, newData); - } - var setter = data ? baseSetData : setData; - return setWrapToString(setter(result, newData), func, bitmask); - } - - /** - * Used by `_.defaults` to customize its `_.assignIn` use to assign properties - * of source objects to the destination object for all destination properties - * that resolve to `undefined`. - * - * @private - * @param {*} objValue The destination value. - * @param {*} srcValue The source value. - * @param {string} key The key of the property to assign. - * @param {Object} object The parent object of `objValue`. - * @returns {*} Returns the value to assign. - */ - function customDefaultsAssignIn(objValue, srcValue, key, object) { - if (objValue === undefined || - (eq(objValue, objectProto[key]) && !hasOwnProperty.call(object, key))) { - return srcValue; - } - return objValue; - } - - /** - * Used by `_.defaultsDeep` to customize its `_.merge` use to merge source - * objects into destination objects that are passed thru. - * - * @private - * @param {*} objValue The destination value. - * @param {*} srcValue The source value. - * @param {string} key The key of the property to merge. - * @param {Object} object The parent object of `objValue`. - * @param {Object} source The parent object of `srcValue`. - * @param {Object} [stack] Tracks traversed source values and their merged - * counterparts. - * @returns {*} Returns the value to assign. - */ - function customDefaultsMerge(objValue, srcValue, key, object, source, stack) { - if (isObject(objValue) && isObject(srcValue)) { - // Recursively merge objects and arrays (susceptible to call stack limits). - stack.set(srcValue, objValue); - baseMerge(objValue, srcValue, undefined, customDefaultsMerge, stack); - stack['delete'](srcValue); - } - return objValue; - } - - /** - * Used by `_.omit` to customize its `_.cloneDeep` use to only clone plain - * objects. - * - * @private - * @param {*} value The value to inspect. - * @param {string} key The key of the property to inspect. - * @returns {*} Returns the uncloned value or `undefined` to defer cloning to `_.cloneDeep`. - */ - function customOmitClone(value) { - return isPlainObject(value) ? undefined : value; - } - - /** - * A specialized version of `baseIsEqualDeep` for arrays with support for - * partial deep comparisons. - * - * @private - * @param {Array} array The array to compare. - * @param {Array} other The other array to compare. - * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. - * @param {Function} customizer The function to customize comparisons. - * @param {Function} equalFunc The function to determine equivalents of values. - * @param {Object} stack Tracks traversed `array` and `other` objects. - * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`. - */ - function equalArrays(array, other, bitmask, customizer, equalFunc, stack) { - var isPartial = bitmask & COMPARE_PARTIAL_FLAG, - arrLength = array.length, - othLength = other.length; - - if (arrLength != othLength && !(isPartial && othLength > arrLength)) { - return false; - } - // Assume cyclic values are equal. - var stacked = stack.get(array); - if (stacked && stack.get(other)) { - return stacked == other; - } - var index = -1, - result = true, - seen = (bitmask & COMPARE_UNORDERED_FLAG) ? new SetCache : undefined; - - stack.set(array, other); - stack.set(other, array); - - // Ignore non-index properties. - while (++index < arrLength) { - var arrValue = array[index], - othValue = other[index]; - - if (customizer) { - var compared = isPartial - ? customizer(othValue, arrValue, index, other, array, stack) - : customizer(arrValue, othValue, index, array, other, stack); - } - if (compared !== undefined) { - if (compared) { - continue; - } - result = false; - break; - } - // Recursively compare arrays (susceptible to call stack limits). - if (seen) { - if (!arraySome(other, function(othValue, othIndex) { - if (!cacheHas(seen, othIndex) && - (arrValue === othValue || equalFunc(arrValue, othValue, bitmask, customizer, stack))) { - return seen.push(othIndex); - } - })) { - result = false; - break; - } - } else if (!( - arrValue === othValue || - equalFunc(arrValue, othValue, bitmask, customizer, stack) - )) { - result = false; - break; - } - } - stack['delete'](array); - stack['delete'](other); - return result; - } - - /** - * A specialized version of `baseIsEqualDeep` for comparing objects of - * the same `toStringTag`. - * - * **Note:** This function only supports comparing values with tags of - * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`. - * - * @private - * @param {Object} object The object to compare. - * @param {Object} other The other object to compare. - * @param {string} tag The `toStringTag` of the objects to compare. - * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. - * @param {Function} customizer The function to customize comparisons. - * @param {Function} equalFunc The function to determine equivalents of values. - * @param {Object} stack Tracks traversed `object` and `other` objects. - * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. - */ - function equalByTag(object, other, tag, bitmask, customizer, equalFunc, stack) { - switch (tag) { - case dataViewTag: - if ((object.byteLength != other.byteLength) || - (object.byteOffset != other.byteOffset)) { - return false; - } - object = object.buffer; - other = other.buffer; - - case arrayBufferTag: - if ((object.byteLength != other.byteLength) || - !equalFunc(new Uint8Array(object), new Uint8Array(other))) { - return false; - } - return true; - - case boolTag: - case dateTag: - case numberTag: - // Coerce booleans to `1` or `0` and dates to milliseconds. - // Invalid dates are coerced to `NaN`. - return eq(+object, +other); - - case errorTag: - return object.name == other.name && object.message == other.message; - - case regexpTag: - case stringTag: - // Coerce regexes to strings and treat strings, primitives and objects, - // as equal. See http://www.ecma-international.org/ecma-262/7.0/#sec-regexp.prototype.tostring - // for more details. - return object == (other + ''); - - case mapTag: - var convert = mapToArray; - - case setTag: - var isPartial = bitmask & COMPARE_PARTIAL_FLAG; - convert || (convert = setToArray); - - if (object.size != other.size && !isPartial) { - return false; - } - // Assume cyclic values are equal. - var stacked = stack.get(object); - if (stacked) { - return stacked == other; - } - bitmask |= COMPARE_UNORDERED_FLAG; - - // Recursively compare objects (susceptible to call stack limits). - stack.set(object, other); - var result = equalArrays(convert(object), convert(other), bitmask, customizer, equalFunc, stack); - stack['delete'](object); - return result; - - case symbolTag: - if (symbolValueOf) { - return symbolValueOf.call(object) == symbolValueOf.call(other); - } - } - return false; - } - - /** - * A specialized version of `baseIsEqualDeep` for objects with support for - * partial deep comparisons. - * - * @private - * @param {Object} object The object to compare. - * @param {Object} other The other object to compare. - * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. - * @param {Function} customizer The function to customize comparisons. - * @param {Function} equalFunc The function to determine equivalents of values. - * @param {Object} stack Tracks traversed `object` and `other` objects. - * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. - */ - function equalObjects(object, other, bitmask, customizer, equalFunc, stack) { - var isPartial = bitmask & COMPARE_PARTIAL_FLAG, - objProps = getAllKeys(object), - objLength = objProps.length, - othProps = getAllKeys(other), - othLength = othProps.length; - - if (objLength != othLength && !isPartial) { - return false; - } - var index = objLength; - while (index--) { - var key = objProps[index]; - if (!(isPartial ? key in other : hasOwnProperty.call(other, key))) { - return false; - } - } - // Assume cyclic values are equal. - var stacked = stack.get(object); - if (stacked && stack.get(other)) { - return stacked == other; - } - var result = true; - stack.set(object, other); - stack.set(other, object); - - var skipCtor = isPartial; - while (++index < objLength) { - key = objProps[index]; - var objValue = object[key], - othValue = other[key]; - - if (customizer) { - var compared = isPartial - ? customizer(othValue, objValue, key, other, object, stack) - : customizer(objValue, othValue, key, object, other, stack); - } - // Recursively compare objects (susceptible to call stack limits). - if (!(compared === undefined - ? (objValue === othValue || equalFunc(objValue, othValue, bitmask, customizer, stack)) - : compared - )) { - result = false; - break; - } - skipCtor || (skipCtor = key == 'constructor'); - } - if (result && !skipCtor) { - var objCtor = object.constructor, - othCtor = other.constructor; - - // Non `Object` object instances with different constructors are not equal. - if (objCtor != othCtor && - ('constructor' in object && 'constructor' in other) && - !(typeof objCtor == 'function' && objCtor instanceof objCtor && - typeof othCtor == 'function' && othCtor instanceof othCtor)) { - result = false; - } - } - stack['delete'](object); - stack['delete'](other); - return result; - } - - /** - * A specialized version of `baseRest` which flattens the rest array. - * - * @private - * @param {Function} func The function to apply a rest parameter to. - * @returns {Function} Returns the new function. - */ - function flatRest(func) { - return setToString(overRest(func, undefined, flatten), func + ''); - } - - /** - * Creates an array of own enumerable property names and symbols of `object`. - * - * @private - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property names and symbols. - */ - function getAllKeys(object) { - return baseGetAllKeys(object, keys, getSymbols); - } - - /** - * Creates an array of own and inherited enumerable property names and - * symbols of `object`. - * - * @private - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property names and symbols. - */ - function getAllKeysIn(object) { - return baseGetAllKeys(object, keysIn, getSymbolsIn); - } - - /** - * Gets metadata for `func`. - * - * @private - * @param {Function} func The function to query. - * @returns {*} Returns the metadata for `func`. - */ - var getData = !metaMap ? noop : function(func) { - return metaMap.get(func); - }; - - /** - * Gets the name of `func`. - * - * @private - * @param {Function} func The function to query. - * @returns {string} Returns the function name. - */ - function getFuncName(func) { - var result = (func.name + ''), - array = realNames[result], - length = hasOwnProperty.call(realNames, result) ? array.length : 0; - - while (length--) { - var data = array[length], - otherFunc = data.func; - if (otherFunc == null || otherFunc == func) { - return data.name; - } - } - return result; - } - - /** - * Gets the argument placeholder value for `func`. - * - * @private - * @param {Function} func The function to inspect. - * @returns {*} Returns the placeholder value. - */ - function getHolder(func) { - var object = hasOwnProperty.call(lodash, 'placeholder') ? lodash : func; - return object.placeholder; - } - - /** - * Gets the appropriate "iteratee" function. If `_.iteratee` is customized, - * this function returns the custom method, otherwise it returns `baseIteratee`. - * If arguments are provided, the chosen function is invoked with them and - * its result is returned. - * - * @private - * @param {*} [value] The value to convert to an iteratee. - * @param {number} [arity] The arity of the created iteratee. - * @returns {Function} Returns the chosen function or its result. - */ - function getIteratee() { - var result = lodash.iteratee || iteratee; - result = result === iteratee ? baseIteratee : result; - return arguments.length ? result(arguments[0], arguments[1]) : result; - } - - /** - * Gets the data for `map`. - * - * @private - * @param {Object} map The map to query. - * @param {string} key The reference key. - * @returns {*} Returns the map data. - */ - function getMapData(map, key) { - var data = map.__data__; - return isKeyable(key) - ? data[typeof key == 'string' ? 'string' : 'hash'] - : data.map; - } - - /** - * Gets the property names, values, and compare flags of `object`. - * - * @private - * @param {Object} object The object to query. - * @returns {Array} Returns the match data of `object`. - */ - function getMatchData(object) { - var result = keys(object), - length = result.length; - - while (length--) { - var key = result[length], - value = object[key]; - - result[length] = [key, value, isStrictComparable(value)]; - } - return result; - } - - /** - * Gets the native function at `key` of `object`. - * - * @private - * @param {Object} object The object to query. - * @param {string} key The key of the method to get. - * @returns {*} Returns the function if it's native, else `undefined`. - */ - function getNative(object, key) { - var value = getValue(object, key); - return baseIsNative(value) ? value : undefined; - } - - /** - * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values. - * - * @private - * @param {*} value The value to query. - * @returns {string} Returns the raw `toStringTag`. - */ - function getRawTag(value) { - var isOwn = hasOwnProperty.call(value, symToStringTag), - tag = value[symToStringTag]; - - try { - value[symToStringTag] = undefined; - var unmasked = true; - } catch (e) {} - - var result = nativeObjectToString.call(value); - if (unmasked) { - if (isOwn) { - value[symToStringTag] = tag; - } else { - delete value[symToStringTag]; - } - } - return result; - } - - /** - * Creates an array of the own enumerable symbols of `object`. - * - * @private - * @param {Object} object The object to query. - * @returns {Array} Returns the array of symbols. - */ - var getSymbols = !nativeGetSymbols ? stubArray : function(object) { - if (object == null) { - return []; - } - object = Object(object); - return arrayFilter(nativeGetSymbols(object), function(symbol) { - return propertyIsEnumerable.call(object, symbol); - }); - }; - - /** - * Creates an array of the own and inherited enumerable symbols of `object`. - * - * @private - * @param {Object} object The object to query. - * @returns {Array} Returns the array of symbols. - */ - var getSymbolsIn = !nativeGetSymbols ? stubArray : function(object) { - var result = []; - while (object) { - arrayPush(result, getSymbols(object)); - object = getPrototype(object); - } - return result; - }; - - /** - * Gets the `toStringTag` of `value`. - * - * @private - * @param {*} value The value to query. - * @returns {string} Returns the `toStringTag`. - */ - var getTag = baseGetTag; - - // Fallback for data views, maps, sets, and weak maps in IE 11 and promises in Node.js < 6. - if ((DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag) || - (Map && getTag(new Map) != mapTag) || - (Promise && getTag(Promise.resolve()) != promiseTag) || - (Set && getTag(new Set) != setTag) || - (WeakMap && getTag(new WeakMap) != weakMapTag)) { - getTag = function(value) { - var result = baseGetTag(value), - Ctor = result == objectTag ? value.constructor : undefined, - ctorString = Ctor ? toSource(Ctor) : ''; - - if (ctorString) { - switch (ctorString) { - case dataViewCtorString: return dataViewTag; - case mapCtorString: return mapTag; - case promiseCtorString: return promiseTag; - case setCtorString: return setTag; - case weakMapCtorString: return weakMapTag; - } - } - return result; - }; - } - - /** - * Gets the view, applying any `transforms` to the `start` and `end` positions. - * - * @private - * @param {number} start The start of the view. - * @param {number} end The end of the view. - * @param {Array} transforms The transformations to apply to the view. - * @returns {Object} Returns an object containing the `start` and `end` - * positions of the view. - */ - function getView(start, end, transforms) { - var index = -1, - length = transforms.length; - - while (++index < length) { - var data = transforms[index], - size = data.size; - - switch (data.type) { - case 'drop': start += size; break; - case 'dropRight': end -= size; break; - case 'take': end = nativeMin(end, start + size); break; - case 'takeRight': start = nativeMax(start, end - size); break; - } - } - return { 'start': start, 'end': end }; - } - - /** - * Extracts wrapper details from the `source` body comment. - * - * @private - * @param {string} source The source to inspect. - * @returns {Array} Returns the wrapper details. - */ - function getWrapDetails(source) { - var match = source.match(reWrapDetails); - return match ? match[1].split(reSplitDetails) : []; - } - - /** - * Checks if `path` exists on `object`. - * - * @private - * @param {Object} object The object to query. - * @param {Array|string} path The path to check. - * @param {Function} hasFunc The function to check properties. - * @returns {boolean} Returns `true` if `path` exists, else `false`. - */ - function hasPath(object, path, hasFunc) { - path = castPath(path, object); - - var index = -1, - length = path.length, - result = false; - - while (++index < length) { - var key = toKey(path[index]); - if (!(result = object != null && hasFunc(object, key))) { - break; - } - object = object[key]; - } - if (result || ++index != length) { - return result; - } - length = object == null ? 0 : object.length; - return !!length && isLength(length) && isIndex(key, length) && - (isArray(object) || isArguments(object)); - } - - /** - * Initializes an array clone. - * - * @private - * @param {Array} array The array to clone. - * @returns {Array} Returns the initialized clone. - */ - function initCloneArray(array) { - var length = array.length, - result = new array.constructor(length); - - // Add properties assigned by `RegExp#exec`. - if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) { - result.index = array.index; - result.input = array.input; - } - return result; - } - - /** - * Initializes an object clone. - * - * @private - * @param {Object} object The object to clone. - * @returns {Object} Returns the initialized clone. - */ - function initCloneObject(object) { - return (typeof object.constructor == 'function' && !isPrototype(object)) - ? baseCreate(getPrototype(object)) - : {}; - } - - /** - * Initializes an object clone based on its `toStringTag`. - * - * **Note:** This function only supports cloning values with tags of - * `Boolean`, `Date`, `Error`, `Map`, `Number`, `RegExp`, `Set`, or `String`. - * - * @private - * @param {Object} object The object to clone. - * @param {string} tag The `toStringTag` of the object to clone. - * @param {boolean} [isDeep] Specify a deep clone. - * @returns {Object} Returns the initialized clone. - */ - function initCloneByTag(object, tag, isDeep) { - var Ctor = object.constructor; - switch (tag) { - case arrayBufferTag: - return cloneArrayBuffer(object); - - case boolTag: - case dateTag: - return new Ctor(+object); - - case dataViewTag: - return cloneDataView(object, isDeep); - - case float32Tag: case float64Tag: - case int8Tag: case int16Tag: case int32Tag: - case uint8Tag: case uint8ClampedTag: case uint16Tag: case uint32Tag: - return cloneTypedArray(object, isDeep); - - case mapTag: - return new Ctor; - - case numberTag: - case stringTag: - return new Ctor(object); - - case regexpTag: - return cloneRegExp(object); - - case setTag: - return new Ctor; - - case symbolTag: - return cloneSymbol(object); - } - } - - /** - * Inserts wrapper `details` in a comment at the top of the `source` body. - * - * @private - * @param {string} source The source to modify. - * @returns {Array} details The details to insert. - * @returns {string} Returns the modified source. - */ - function insertWrapDetails(source, details) { - var length = details.length; - if (!length) { - return source; - } - var lastIndex = length - 1; - details[lastIndex] = (length > 1 ? '& ' : '') + details[lastIndex]; - details = details.join(length > 2 ? ', ' : ' '); - return source.replace(reWrapComment, '{\n/* [wrapped with ' + details + '] */\n'); - } - - /** - * Checks if `value` is a flattenable `arguments` object or array. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is flattenable, else `false`. - */ - function isFlattenable(value) { - return isArray(value) || isArguments(value) || - !!(spreadableSymbol && value && value[spreadableSymbol]); - } - - /** - * Checks if `value` is a valid array-like index. - * - * @private - * @param {*} value The value to check. - * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index. - * @returns {boolean} Returns `true` if `value` is a valid index, else `false`. - */ - function isIndex(value, length) { - var type = typeof value; - length = length == null ? MAX_SAFE_INTEGER : length; - - return !!length && - (type == 'number' || - (type != 'symbol' && reIsUint.test(value))) && - (value > -1 && value % 1 == 0 && value < length); - } - - /** - * Checks if the given arguments are from an iteratee call. - * - * @private - * @param {*} value The potential iteratee value argument. - * @param {*} index The potential iteratee index or key argument. - * @param {*} object The potential iteratee object argument. - * @returns {boolean} Returns `true` if the arguments are from an iteratee call, - * else `false`. - */ - function isIterateeCall(value, index, object) { - if (!isObject(object)) { - return false; - } - var type = typeof index; - if (type == 'number' - ? (isArrayLike(object) && isIndex(index, object.length)) - : (type == 'string' && index in object) - ) { - return eq(object[index], value); - } - return false; - } - - /** - * Checks if `value` is a property name and not a property path. - * - * @private - * @param {*} value The value to check. - * @param {Object} [object] The object to query keys on. - * @returns {boolean} Returns `true` if `value` is a property name, else `false`. - */ - function isKey(value, object) { - if (isArray(value)) { - return false; - } - var type = typeof value; - if (type == 'number' || type == 'symbol' || type == 'boolean' || - value == null || isSymbol(value)) { - return true; - } - return reIsPlainProp.test(value) || !reIsDeepProp.test(value) || - (object != null && value in Object(object)); - } - - /** - * Checks if `value` is suitable for use as unique object key. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is suitable, else `false`. - */ - function isKeyable(value) { - var type = typeof value; - return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean') - ? (value !== '__proto__') - : (value === null); - } - - /** - * Checks if `func` has a lazy counterpart. - * - * @private - * @param {Function} func The function to check. - * @returns {boolean} Returns `true` if `func` has a lazy counterpart, - * else `false`. - */ - function isLaziable(func) { - var funcName = getFuncName(func), - other = lodash[funcName]; - - if (typeof other != 'function' || !(funcName in LazyWrapper.prototype)) { - return false; - } - if (func === other) { - return true; - } - var data = getData(other); - return !!data && func === data[0]; - } - - /** - * Checks if `func` has its source masked. - * - * @private - * @param {Function} func The function to check. - * @returns {boolean} Returns `true` if `func` is masked, else `false`. - */ - function isMasked(func) { - return !!maskSrcKey && (maskSrcKey in func); - } - - /** - * Checks if `func` is capable of being masked. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `func` is maskable, else `false`. - */ - var isMaskable = coreJsData ? isFunction : stubFalse; - - /** - * Checks if `value` is likely a prototype object. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a prototype, else `false`. - */ - function isPrototype(value) { - var Ctor = value && value.constructor, - proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto; - - return value === proto; - } - - /** - * Checks if `value` is suitable for strict equality comparisons, i.e. `===`. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` if suitable for strict - * equality comparisons, else `false`. - */ - function isStrictComparable(value) { - return value === value && !isObject(value); - } - - /** - * A specialized version of `matchesProperty` for source values suitable - * for strict equality comparisons, i.e. `===`. - * - * @private - * @param {string} key The key of the property to get. - * @param {*} srcValue The value to match. - * @returns {Function} Returns the new spec function. - */ - function matchesStrictComparable(key, srcValue) { - return function(object) { - if (object == null) { - return false; - } - return object[key] === srcValue && - (srcValue !== undefined || (key in Object(object))); - }; - } - - /** - * A specialized version of `_.memoize` which clears the memoized function's - * cache when it exceeds `MAX_MEMOIZE_SIZE`. - * - * @private - * @param {Function} func The function to have its output memoized. - * @returns {Function} Returns the new memoized function. - */ - function memoizeCapped(func) { - var result = memoize(func, function(key) { - if (cache.size === MAX_MEMOIZE_SIZE) { - cache.clear(); - } - return key; - }); - - var cache = result.cache; - return result; - } - - /** - * Merges the function metadata of `source` into `data`. - * - * Merging metadata reduces the number of wrappers used to invoke a function. - * This is possible because methods like `_.bind`, `_.curry`, and `_.partial` - * may be applied regardless of execution order. Methods like `_.ary` and - * `_.rearg` modify function arguments, making the order in which they are - * executed important, preventing the merging of metadata. However, we make - * an exception for a safe combined case where curried functions have `_.ary` - * and or `_.rearg` applied. - * - * @private - * @param {Array} data The destination metadata. - * @param {Array} source The source metadata. - * @returns {Array} Returns `data`. - */ - function mergeData(data, source) { - var bitmask = data[1], - srcBitmask = source[1], - newBitmask = bitmask | srcBitmask, - isCommon = newBitmask < (WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG | WRAP_ARY_FLAG); - - var isCombo = - ((srcBitmask == WRAP_ARY_FLAG) && (bitmask == WRAP_CURRY_FLAG)) || - ((srcBitmask == WRAP_ARY_FLAG) && (bitmask == WRAP_REARG_FLAG) && (data[7].length <= source[8])) || - ((srcBitmask == (WRAP_ARY_FLAG | WRAP_REARG_FLAG)) && (source[7].length <= source[8]) && (bitmask == WRAP_CURRY_FLAG)); - - // Exit early if metadata can't be merged. - if (!(isCommon || isCombo)) { - return data; - } - // Use source `thisArg` if available. - if (srcBitmask & WRAP_BIND_FLAG) { - data[2] = source[2]; - // Set when currying a bound function. - newBitmask |= bitmask & WRAP_BIND_FLAG ? 0 : WRAP_CURRY_BOUND_FLAG; - } - // Compose partial arguments. - var value = source[3]; - if (value) { - var partials = data[3]; - data[3] = partials ? composeArgs(partials, value, source[4]) : value; - data[4] = partials ? replaceHolders(data[3], PLACEHOLDER) : source[4]; - } - // Compose partial right arguments. - value = source[5]; - if (value) { - partials = data[5]; - data[5] = partials ? composeArgsRight(partials, value, source[6]) : value; - data[6] = partials ? replaceHolders(data[5], PLACEHOLDER) : source[6]; - } - // Use source `argPos` if available. - value = source[7]; - if (value) { - data[7] = value; - } - // Use source `ary` if it's smaller. - if (srcBitmask & WRAP_ARY_FLAG) { - data[8] = data[8] == null ? source[8] : nativeMin(data[8], source[8]); - } - // Use source `arity` if one is not provided. - if (data[9] == null) { - data[9] = source[9]; - } - // Use source `func` and merge bitmasks. - data[0] = source[0]; - data[1] = newBitmask; - - return data; - } - - /** - * This function is like - * [`Object.keys`](http://ecma-international.org/ecma-262/7.0/#sec-object.keys) - * except that it includes inherited enumerable properties. - * - * @private - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property names. - */ - function nativeKeysIn(object) { - var result = []; - if (object != null) { - for (var key in Object(object)) { - result.push(key); - } - } - return result; - } - - /** - * Converts `value` to a string using `Object.prototype.toString`. - * - * @private - * @param {*} value The value to convert. - * @returns {string} Returns the converted string. - */ - function objectToString(value) { - return nativeObjectToString.call(value); - } - - /** - * A specialized version of `baseRest` which transforms the rest array. - * - * @private - * @param {Function} func The function to apply a rest parameter to. - * @param {number} [start=func.length-1] The start position of the rest parameter. - * @param {Function} transform The rest array transform. - * @returns {Function} Returns the new function. - */ - function overRest(func, start, transform) { - start = nativeMax(start === undefined ? (func.length - 1) : start, 0); - return function() { - var args = arguments, - index = -1, - length = nativeMax(args.length - start, 0), - array = Array(length); - - while (++index < length) { - array[index] = args[start + index]; - } - index = -1; - var otherArgs = Array(start + 1); - while (++index < start) { - otherArgs[index] = args[index]; - } - otherArgs[start] = transform(array); - return apply(func, this, otherArgs); - }; - } - - /** - * Gets the parent value at `path` of `object`. - * - * @private - * @param {Object} object The object to query. - * @param {Array} path The path to get the parent value of. - * @returns {*} Returns the parent value. - */ - function parent(object, path) { - return path.length < 2 ? object : baseGet(object, baseSlice(path, 0, -1)); - } - - /** - * Reorder `array` according to the specified indexes where the element at - * the first index is assigned as the first element, the element at - * the second index is assigned as the second element, and so on. - * - * @private - * @param {Array} array The array to reorder. - * @param {Array} indexes The arranged array indexes. - * @returns {Array} Returns `array`. - */ - function reorder(array, indexes) { - var arrLength = array.length, - length = nativeMin(indexes.length, arrLength), - oldArray = copyArray(array); - - while (length--) { - var index = indexes[length]; - array[length] = isIndex(index, arrLength) ? oldArray[index] : undefined; - } - return array; - } - - /** - * Sets metadata for `func`. - * - * **Note:** If this function becomes hot, i.e. is invoked a lot in a short - * period of time, it will trip its breaker and transition to an identity - * function to avoid garbage collection pauses in V8. See - * [V8 issue 2070](https://bugs.chromium.org/p/v8/issues/detail?id=2070) - * for more details. - * - * @private - * @param {Function} func The function to associate metadata with. - * @param {*} data The metadata. - * @returns {Function} Returns `func`. - */ - var setData = shortOut(baseSetData); - - /** - * A simple wrapper around the global [`setTimeout`](https://mdn.io/setTimeout). - * - * @private - * @param {Function} func The function to delay. - * @param {number} wait The number of milliseconds to delay invocation. - * @returns {number|Object} Returns the timer id or timeout object. - */ - var setTimeout = ctxSetTimeout || function(func, wait) { - return root.setTimeout(func, wait); - }; - - /** - * Sets the `toString` method of `func` to return `string`. - * - * @private - * @param {Function} func The function to modify. - * @param {Function} string The `toString` result. - * @returns {Function} Returns `func`. - */ - var setToString = shortOut(baseSetToString); - - /** - * Sets the `toString` method of `wrapper` to mimic the source of `reference` - * with wrapper details in a comment at the top of the source body. - * - * @private - * @param {Function} wrapper The function to modify. - * @param {Function} reference The reference function. - * @param {number} bitmask The bitmask flags. See `createWrap` for more details. - * @returns {Function} Returns `wrapper`. - */ - function setWrapToString(wrapper, reference, bitmask) { - var source = (reference + ''); - return setToString(wrapper, insertWrapDetails(source, updateWrapDetails(getWrapDetails(source), bitmask))); - } - - /** - * Creates a function that'll short out and invoke `identity` instead - * of `func` when it's called `HOT_COUNT` or more times in `HOT_SPAN` - * milliseconds. - * - * @private - * @param {Function} func The function to restrict. - * @returns {Function} Returns the new shortable function. - */ - function shortOut(func) { - var count = 0, - lastCalled = 0; - - return function() { - var stamp = nativeNow(), - remaining = HOT_SPAN - (stamp - lastCalled); - - lastCalled = stamp; - if (remaining > 0) { - if (++count >= HOT_COUNT) { - return arguments[0]; - } - } else { - count = 0; - } - return func.apply(undefined, arguments); - }; - } - - /** - * A specialized version of `_.shuffle` which mutates and sets the size of `array`. - * - * @private - * @param {Array} array The array to shuffle. - * @param {number} [size=array.length] The size of `array`. - * @returns {Array} Returns `array`. - */ - function shuffleSelf(array, size) { - var index = -1, - length = array.length, - lastIndex = length - 1; - - size = size === undefined ? length : size; - while (++index < size) { - var rand = baseRandom(index, lastIndex), - value = array[rand]; - - array[rand] = array[index]; - array[index] = value; - } - array.length = size; - return array; - } - - /** - * Converts `string` to a property path array. - * - * @private - * @param {string} string The string to convert. - * @returns {Array} Returns the property path array. - */ - var stringToPath = memoizeCapped(function(string) { - var result = []; - if (string.charCodeAt(0) === 46 /* . */) { - result.push(''); - } - string.replace(rePropName, function(match, number, quote, subString) { - result.push(quote ? subString.replace(reEscapeChar, '$1') : (number || match)); - }); - return result; - }); - - /** - * Converts `value` to a string key if it's not a string or symbol. - * - * @private - * @param {*} value The value to inspect. - * @returns {string|symbol} Returns the key. - */ - function toKey(value) { - if (typeof value == 'string' || isSymbol(value)) { - return value; - } - var result = (value + ''); - return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result; - } - - /** - * Converts `func` to its source code. - * - * @private - * @param {Function} func The function to convert. - * @returns {string} Returns the source code. - */ - function toSource(func) { - if (func != null) { - try { - return funcToString.call(func); - } catch (e) {} - try { - return (func + ''); - } catch (e) {} - } - return ''; - } - - /** - * Updates wrapper `details` based on `bitmask` flags. - * - * @private - * @returns {Array} details The details to modify. - * @param {number} bitmask The bitmask flags. See `createWrap` for more details. - * @returns {Array} Returns `details`. - */ - function updateWrapDetails(details, bitmask) { - arrayEach(wrapFlags, function(pair) { - var value = '_.' + pair[0]; - if ((bitmask & pair[1]) && !arrayIncludes(details, value)) { - details.push(value); - } - }); - return details.sort(); - } - - /** - * Creates a clone of `wrapper`. - * - * @private - * @param {Object} wrapper The wrapper to clone. - * @returns {Object} Returns the cloned wrapper. - */ - function wrapperClone(wrapper) { - if (wrapper instanceof LazyWrapper) { - return wrapper.clone(); - } - var result = new LodashWrapper(wrapper.__wrapped__, wrapper.__chain__); - result.__actions__ = copyArray(wrapper.__actions__); - result.__index__ = wrapper.__index__; - result.__values__ = wrapper.__values__; - return result; - } - - /*------------------------------------------------------------------------*/ - - /** - * Creates an array of elements split into groups the length of `size`. - * If `array` can't be split evenly, the final chunk will be the remaining - * elements. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Array - * @param {Array} array The array to process. - * @param {number} [size=1] The length of each chunk - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {Array} Returns the new array of chunks. - * @example - * - * _.chunk(['a', 'b', 'c', 'd'], 2); - * // => [['a', 'b'], ['c', 'd']] - * - * _.chunk(['a', 'b', 'c', 'd'], 3); - * // => [['a', 'b', 'c'], ['d']] - */ - function chunk(array, size, guard) { - if ((guard ? isIterateeCall(array, size, guard) : size === undefined)) { - size = 1; - } else { - size = nativeMax(toInteger(size), 0); - } - var length = array == null ? 0 : array.length; - if (!length || size < 1) { - return []; - } - var index = 0, - resIndex = 0, - result = Array(nativeCeil(length / size)); - - while (index < length) { - result[resIndex++] = baseSlice(array, index, (index += size)); - } - return result; - } - - /** - * Creates an array with all falsey values removed. The values `false`, `null`, - * `0`, `""`, `undefined`, and `NaN` are falsey. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {Array} array The array to compact. - * @returns {Array} Returns the new array of filtered values. - * @example - * - * _.compact([0, 1, false, 2, '', 3]); - * // => [1, 2, 3] - */ - function compact(array) { - var index = -1, - length = array == null ? 0 : array.length, - resIndex = 0, - result = []; - - while (++index < length) { - var value = array[index]; - if (value) { - result[resIndex++] = value; - } - } - return result; - } - - /** - * Creates a new array concatenating `array` with any additional arrays - * and/or values. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to concatenate. - * @param {...*} [values] The values to concatenate. - * @returns {Array} Returns the new concatenated array. - * @example - * - * var array = [1]; - * var other = _.concat(array, 2, [3], [[4]]); - * - * console.log(other); - * // => [1, 2, 3, [4]] - * - * console.log(array); - * // => [1] - */ - function concat() { - var length = arguments.length; - if (!length) { - return []; - } - var args = Array(length - 1), - array = arguments[0], - index = length; - - while (index--) { - args[index - 1] = arguments[index]; - } - return arrayPush(isArray(array) ? copyArray(array) : [array], baseFlatten(args, 1)); - } - - /** - * Creates an array of `array` values not included in the other given arrays - * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * for equality comparisons. The order and references of result values are - * determined by the first array. - * - * **Note:** Unlike `_.pullAll`, this method returns a new array. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {...Array} [values] The values to exclude. - * @returns {Array} Returns the new array of filtered values. - * @see _.without, _.xor - * @example - * - * _.difference([2, 1], [2, 3]); - * // => [1] - */ - var difference = baseRest(function(array, values) { - return isArrayLikeObject(array) - ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true)) - : []; - }); - - /** - * This method is like `_.difference` except that it accepts `iteratee` which - * is invoked for each element of `array` and `values` to generate the criterion - * by which they're compared. The order and references of result values are - * determined by the first array. The iteratee is invoked with one argument: - * (value). - * - * **Note:** Unlike `_.pullAllBy`, this method returns a new array. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {...Array} [values] The values to exclude. - * @param {Function} [iteratee=_.identity] The iteratee invoked per element. - * @returns {Array} Returns the new array of filtered values. - * @example - * - * _.differenceBy([2.1, 1.2], [2.3, 3.4], Math.floor); - * // => [1.2] - * - * // The `_.property` iteratee shorthand. - * _.differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x'); - * // => [{ 'x': 2 }] - */ - var differenceBy = baseRest(function(array, values) { - var iteratee = last(values); - if (isArrayLikeObject(iteratee)) { - iteratee = undefined; - } - return isArrayLikeObject(array) - ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), getIteratee(iteratee, 2)) - : []; - }); - - /** - * This method is like `_.difference` except that it accepts `comparator` - * which is invoked to compare elements of `array` to `values`. The order and - * references of result values are determined by the first array. The comparator - * is invoked with two arguments: (arrVal, othVal). - * - * **Note:** Unlike `_.pullAllWith`, this method returns a new array. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {...Array} [values] The values to exclude. - * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns the new array of filtered values. - * @example - * - * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]; - * - * _.differenceWith(objects, [{ 'x': 1, 'y': 2 }], _.isEqual); - * // => [{ 'x': 2, 'y': 1 }] - */ - var differenceWith = baseRest(function(array, values) { - var comparator = last(values); - if (isArrayLikeObject(comparator)) { - comparator = undefined; - } - return isArrayLikeObject(array) - ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), undefined, comparator) - : []; - }); - - /** - * Creates a slice of `array` with `n` elements dropped from the beginning. - * - * @static - * @memberOf _ - * @since 0.5.0 - * @category Array - * @param {Array} array The array to query. - * @param {number} [n=1] The number of elements to drop. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {Array} Returns the slice of `array`. - * @example - * - * _.drop([1, 2, 3]); - * // => [2, 3] - * - * _.drop([1, 2, 3], 2); - * // => [3] - * - * _.drop([1, 2, 3], 5); - * // => [] - * - * _.drop([1, 2, 3], 0); - * // => [1, 2, 3] - */ - function drop(array, n, guard) { - var length = array == null ? 0 : array.length; - if (!length) { - return []; - } - n = (guard || n === undefined) ? 1 : toInteger(n); - return baseSlice(array, n < 0 ? 0 : n, length); - } - - /** - * Creates a slice of `array` with `n` elements dropped from the end. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Array - * @param {Array} array The array to query. - * @param {number} [n=1] The number of elements to drop. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {Array} Returns the slice of `array`. - * @example - * - * _.dropRight([1, 2, 3]); - * // => [1, 2] - * - * _.dropRight([1, 2, 3], 2); - * // => [1] - * - * _.dropRight([1, 2, 3], 5); - * // => [] - * - * _.dropRight([1, 2, 3], 0); - * // => [1, 2, 3] - */ - function dropRight(array, n, guard) { - var length = array == null ? 0 : array.length; - if (!length) { - return []; - } - n = (guard || n === undefined) ? 1 : toInteger(n); - n = length - n; - return baseSlice(array, 0, n < 0 ? 0 : n); - } - - /** - * Creates a slice of `array` excluding elements dropped from the end. - * Elements are dropped until `predicate` returns falsey. The predicate is - * invoked with three arguments: (value, index, array). - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Array - * @param {Array} array The array to query. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @returns {Array} Returns the slice of `array`. - * @example - * - * var users = [ - * { 'user': 'barney', 'active': true }, - * { 'user': 'fred', 'active': false }, - * { 'user': 'pebbles', 'active': false } - * ]; - * - * _.dropRightWhile(users, function(o) { return !o.active; }); - * // => objects for ['barney'] - * - * // The `_.matches` iteratee shorthand. - * _.dropRightWhile(users, { 'user': 'pebbles', 'active': false }); - * // => objects for ['barney', 'fred'] - * - * // The `_.matchesProperty` iteratee shorthand. - * _.dropRightWhile(users, ['active', false]); - * // => objects for ['barney'] - * - * // The `_.property` iteratee shorthand. - * _.dropRightWhile(users, 'active'); - * // => objects for ['barney', 'fred', 'pebbles'] - */ - function dropRightWhile(array, predicate) { - return (array && array.length) - ? baseWhile(array, getIteratee(predicate, 3), true, true) - : []; - } - - /** - * Creates a slice of `array` excluding elements dropped from the beginning. - * Elements are dropped until `predicate` returns falsey. The predicate is - * invoked with three arguments: (value, index, array). - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Array - * @param {Array} array The array to query. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @returns {Array} Returns the slice of `array`. - * @example - * - * var users = [ - * { 'user': 'barney', 'active': false }, - * { 'user': 'fred', 'active': false }, - * { 'user': 'pebbles', 'active': true } - * ]; - * - * _.dropWhile(users, function(o) { return !o.active; }); - * // => objects for ['pebbles'] - * - * // The `_.matches` iteratee shorthand. - * _.dropWhile(users, { 'user': 'barney', 'active': false }); - * // => objects for ['fred', 'pebbles'] - * - * // The `_.matchesProperty` iteratee shorthand. - * _.dropWhile(users, ['active', false]); - * // => objects for ['pebbles'] - * - * // The `_.property` iteratee shorthand. - * _.dropWhile(users, 'active'); - * // => objects for ['barney', 'fred', 'pebbles'] - */ - function dropWhile(array, predicate) { - return (array && array.length) - ? baseWhile(array, getIteratee(predicate, 3), true) - : []; - } - - /** - * Fills elements of `array` with `value` from `start` up to, but not - * including, `end`. - * - * **Note:** This method mutates `array`. - * - * @static - * @memberOf _ - * @since 3.2.0 - * @category Array - * @param {Array} array The array to fill. - * @param {*} value The value to fill `array` with. - * @param {number} [start=0] The start position. - * @param {number} [end=array.length] The end position. - * @returns {Array} Returns `array`. - * @example - * - * var array = [1, 2, 3]; - * - * _.fill(array, 'a'); - * console.log(array); - * // => ['a', 'a', 'a'] - * - * _.fill(Array(3), 2); - * // => [2, 2, 2] - * - * _.fill([4, 6, 8, 10], '*', 1, 3); - * // => [4, '*', '*', 10] - */ - function fill(array, value, start, end) { - var length = array == null ? 0 : array.length; - if (!length) { - return []; - } - if (start && typeof start != 'number' && isIterateeCall(array, value, start)) { - start = 0; - end = length; - } - return baseFill(array, value, start, end); - } - - /** - * This method is like `_.find` except that it returns the index of the first - * element `predicate` returns truthy for instead of the element itself. - * - * @static - * @memberOf _ - * @since 1.1.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @param {number} [fromIndex=0] The index to search from. - * @returns {number} Returns the index of the found element, else `-1`. - * @example - * - * var users = [ - * { 'user': 'barney', 'active': false }, - * { 'user': 'fred', 'active': false }, - * { 'user': 'pebbles', 'active': true } - * ]; - * - * _.findIndex(users, function(o) { return o.user == 'barney'; }); - * // => 0 - * - * // The `_.matches` iteratee shorthand. - * _.findIndex(users, { 'user': 'fred', 'active': false }); - * // => 1 - * - * // The `_.matchesProperty` iteratee shorthand. - * _.findIndex(users, ['active', false]); - * // => 0 - * - * // The `_.property` iteratee shorthand. - * _.findIndex(users, 'active'); - * // => 2 - */ - function findIndex(array, predicate, fromIndex) { - var length = array == null ? 0 : array.length; - if (!length) { - return -1; - } - var index = fromIndex == null ? 0 : toInteger(fromIndex); - if (index < 0) { - index = nativeMax(length + index, 0); - } - return baseFindIndex(array, getIteratee(predicate, 3), index); - } - - /** - * This method is like `_.findIndex` except that it iterates over elements - * of `collection` from right to left. - * - * @static - * @memberOf _ - * @since 2.0.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @param {number} [fromIndex=array.length-1] The index to search from. - * @returns {number} Returns the index of the found element, else `-1`. - * @example - * - * var users = [ - * { 'user': 'barney', 'active': true }, - * { 'user': 'fred', 'active': false }, - * { 'user': 'pebbles', 'active': false } - * ]; - * - * _.findLastIndex(users, function(o) { return o.user == 'pebbles'; }); - * // => 2 - * - * // The `_.matches` iteratee shorthand. - * _.findLastIndex(users, { 'user': 'barney', 'active': true }); - * // => 0 - * - * // The `_.matchesProperty` iteratee shorthand. - * _.findLastIndex(users, ['active', false]); - * // => 2 - * - * // The `_.property` iteratee shorthand. - * _.findLastIndex(users, 'active'); - * // => 0 - */ - function findLastIndex(array, predicate, fromIndex) { - var length = array == null ? 0 : array.length; - if (!length) { - return -1; - } - var index = length - 1; - if (fromIndex !== undefined) { - index = toInteger(fromIndex); - index = fromIndex < 0 - ? nativeMax(length + index, 0) - : nativeMin(index, length - 1); - } - return baseFindIndex(array, getIteratee(predicate, 3), index, true); - } - - /** - * Flattens `array` a single level deep. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {Array} array The array to flatten. - * @returns {Array} Returns the new flattened array. - * @example - * - * _.flatten([1, [2, [3, [4]], 5]]); - * // => [1, 2, [3, [4]], 5] - */ - function flatten(array) { - var length = array == null ? 0 : array.length; - return length ? baseFlatten(array, 1) : []; - } - - /** - * Recursively flattens `array`. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Array - * @param {Array} array The array to flatten. - * @returns {Array} Returns the new flattened array. - * @example - * - * _.flattenDeep([1, [2, [3, [4]], 5]]); - * // => [1, 2, 3, 4, 5] - */ - function flattenDeep(array) { - var length = array == null ? 0 : array.length; - return length ? baseFlatten(array, INFINITY) : []; - } - - /** - * Recursively flatten `array` up to `depth` times. - * - * @static - * @memberOf _ - * @since 4.4.0 - * @category Array - * @param {Array} array The array to flatten. - * @param {number} [depth=1] The maximum recursion depth. - * @returns {Array} Returns the new flattened array. - * @example - * - * var array = [1, [2, [3, [4]], 5]]; - * - * _.flattenDepth(array, 1); - * // => [1, 2, [3, [4]], 5] - * - * _.flattenDepth(array, 2); - * // => [1, 2, 3, [4], 5] - */ - function flattenDepth(array, depth) { - var length = array == null ? 0 : array.length; - if (!length) { - return []; - } - depth = depth === undefined ? 1 : toInteger(depth); - return baseFlatten(array, depth); - } - - /** - * The inverse of `_.toPairs`; this method returns an object composed - * from key-value `pairs`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} pairs The key-value pairs. - * @returns {Object} Returns the new object. - * @example - * - * _.fromPairs([['a', 1], ['b', 2]]); - * // => { 'a': 1, 'b': 2 } - */ - function fromPairs(pairs) { - var index = -1, - length = pairs == null ? 0 : pairs.length, - result = {}; - - while (++index < length) { - var pair = pairs[index]; - result[pair[0]] = pair[1]; - } - return result; - } - - /** - * Gets the first element of `array`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @alias first - * @category Array - * @param {Array} array The array to query. - * @returns {*} Returns the first element of `array`. - * @example - * - * _.head([1, 2, 3]); - * // => 1 - * - * _.head([]); - * // => undefined - */ - function head(array) { - return (array && array.length) ? array[0] : undefined; - } - - /** - * Gets the index at which the first occurrence of `value` is found in `array` - * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * for equality comparisons. If `fromIndex` is negative, it's used as the - * offset from the end of `array`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {*} value The value to search for. - * @param {number} [fromIndex=0] The index to search from. - * @returns {number} Returns the index of the matched value, else `-1`. - * @example - * - * _.indexOf([1, 2, 1, 2], 2); - * // => 1 - * - * // Search from the `fromIndex`. - * _.indexOf([1, 2, 1, 2], 2, 2); - * // => 3 - */ - function indexOf(array, value, fromIndex) { - var length = array == null ? 0 : array.length; - if (!length) { - return -1; - } - var index = fromIndex == null ? 0 : toInteger(fromIndex); - if (index < 0) { - index = nativeMax(length + index, 0); - } - return baseIndexOf(array, value, index); - } - - /** - * Gets all but the last element of `array`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {Array} array The array to query. - * @returns {Array} Returns the slice of `array`. - * @example - * - * _.initial([1, 2, 3]); - * // => [1, 2] - */ - function initial(array) { - var length = array == null ? 0 : array.length; - return length ? baseSlice(array, 0, -1) : []; - } - - /** - * Creates an array of unique values that are included in all given arrays - * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * for equality comparisons. The order and references of result values are - * determined by the first array. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {...Array} [arrays] The arrays to inspect. - * @returns {Array} Returns the new array of intersecting values. - * @example - * - * _.intersection([2, 1], [2, 3]); - * // => [2] - */ - var intersection = baseRest(function(arrays) { - var mapped = arrayMap(arrays, castArrayLikeObject); - return (mapped.length && mapped[0] === arrays[0]) - ? baseIntersection(mapped) - : []; - }); - - /** - * This method is like `_.intersection` except that it accepts `iteratee` - * which is invoked for each element of each `arrays` to generate the criterion - * by which they're compared. The order and references of result values are - * determined by the first array. The iteratee is invoked with one argument: - * (value). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {...Array} [arrays] The arrays to inspect. - * @param {Function} [iteratee=_.identity] The iteratee invoked per element. - * @returns {Array} Returns the new array of intersecting values. - * @example - * - * _.intersectionBy([2.1, 1.2], [2.3, 3.4], Math.floor); - * // => [2.1] - * - * // The `_.property` iteratee shorthand. - * _.intersectionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x'); - * // => [{ 'x': 1 }] - */ - var intersectionBy = baseRest(function(arrays) { - var iteratee = last(arrays), - mapped = arrayMap(arrays, castArrayLikeObject); - - if (iteratee === last(mapped)) { - iteratee = undefined; - } else { - mapped.pop(); - } - return (mapped.length && mapped[0] === arrays[0]) - ? baseIntersection(mapped, getIteratee(iteratee, 2)) - : []; - }); - - /** - * This method is like `_.intersection` except that it accepts `comparator` - * which is invoked to compare elements of `arrays`. The order and references - * of result values are determined by the first array. The comparator is - * invoked with two arguments: (arrVal, othVal). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {...Array} [arrays] The arrays to inspect. - * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns the new array of intersecting values. - * @example - * - * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]; - * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }]; - * - * _.intersectionWith(objects, others, _.isEqual); - * // => [{ 'x': 1, 'y': 2 }] - */ - var intersectionWith = baseRest(function(arrays) { - var comparator = last(arrays), - mapped = arrayMap(arrays, castArrayLikeObject); - - comparator = typeof comparator == 'function' ? comparator : undefined; - if (comparator) { - mapped.pop(); - } - return (mapped.length && mapped[0] === arrays[0]) - ? baseIntersection(mapped, undefined, comparator) - : []; - }); - - /** - * Converts all elements in `array` into a string separated by `separator`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to convert. - * @param {string} [separator=','] The element separator. - * @returns {string} Returns the joined string. - * @example - * - * _.join(['a', 'b', 'c'], '~'); - * // => 'a~b~c' - */ - function join(array, separator) { - return array == null ? '' : nativeJoin.call(array, separator); - } - - /** - * Gets the last element of `array`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {Array} array The array to query. - * @returns {*} Returns the last element of `array`. - * @example - * - * _.last([1, 2, 3]); - * // => 3 - */ - function last(array) { - var length = array == null ? 0 : array.length; - return length ? array[length - 1] : undefined; - } - - /** - * This method is like `_.indexOf` except that it iterates over elements of - * `array` from right to left. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {*} value The value to search for. - * @param {number} [fromIndex=array.length-1] The index to search from. - * @returns {number} Returns the index of the matched value, else `-1`. - * @example - * - * _.lastIndexOf([1, 2, 1, 2], 2); - * // => 3 - * - * // Search from the `fromIndex`. - * _.lastIndexOf([1, 2, 1, 2], 2, 2); - * // => 1 - */ - function lastIndexOf(array, value, fromIndex) { - var length = array == null ? 0 : array.length; - if (!length) { - return -1; - } - var index = length; - if (fromIndex !== undefined) { - index = toInteger(fromIndex); - index = index < 0 ? nativeMax(length + index, 0) : nativeMin(index, length - 1); - } - return value === value - ? strictLastIndexOf(array, value, index) - : baseFindIndex(array, baseIsNaN, index, true); - } - - /** - * Gets the element at index `n` of `array`. If `n` is negative, the nth - * element from the end is returned. - * - * @static - * @memberOf _ - * @since 4.11.0 - * @category Array - * @param {Array} array The array to query. - * @param {number} [n=0] The index of the element to return. - * @returns {*} Returns the nth element of `array`. - * @example - * - * var array = ['a', 'b', 'c', 'd']; - * - * _.nth(array, 1); - * // => 'b' - * - * _.nth(array, -2); - * // => 'c'; - */ - function nth(array, n) { - return (array && array.length) ? baseNth(array, toInteger(n)) : undefined; - } - - /** - * Removes all given values from `array` using - * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * for equality comparisons. - * - * **Note:** Unlike `_.without`, this method mutates `array`. Use `_.remove` - * to remove elements from an array by predicate. - * - * @static - * @memberOf _ - * @since 2.0.0 - * @category Array - * @param {Array} array The array to modify. - * @param {...*} [values] The values to remove. - * @returns {Array} Returns `array`. - * @example - * - * var array = ['a', 'b', 'c', 'a', 'b', 'c']; - * - * _.pull(array, 'a', 'c'); - * console.log(array); - * // => ['b', 'b'] - */ - var pull = baseRest(pullAll); - - /** - * This method is like `_.pull` except that it accepts an array of values to remove. - * - * **Note:** Unlike `_.difference`, this method mutates `array`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to modify. - * @param {Array} values The values to remove. - * @returns {Array} Returns `array`. - * @example - * - * var array = ['a', 'b', 'c', 'a', 'b', 'c']; - * - * _.pullAll(array, ['a', 'c']); - * console.log(array); - * // => ['b', 'b'] - */ - function pullAll(array, values) { - return (array && array.length && values && values.length) - ? basePullAll(array, values) - : array; - } - - /** - * This method is like `_.pullAll` except that it accepts `iteratee` which is - * invoked for each element of `array` and `values` to generate the criterion - * by which they're compared. The iteratee is invoked with one argument: (value). - * - * **Note:** Unlike `_.differenceBy`, this method mutates `array`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to modify. - * @param {Array} values The values to remove. - * @param {Function} [iteratee=_.identity] The iteratee invoked per element. - * @returns {Array} Returns `array`. - * @example - * - * var array = [{ 'x': 1 }, { 'x': 2 }, { 'x': 3 }, { 'x': 1 }]; - * - * _.pullAllBy(array, [{ 'x': 1 }, { 'x': 3 }], 'x'); - * console.log(array); - * // => [{ 'x': 2 }] - */ - function pullAllBy(array, values, iteratee) { - return (array && array.length && values && values.length) - ? basePullAll(array, values, getIteratee(iteratee, 2)) - : array; - } - - /** - * This method is like `_.pullAll` except that it accepts `comparator` which - * is invoked to compare elements of `array` to `values`. The comparator is - * invoked with two arguments: (arrVal, othVal). - * - * **Note:** Unlike `_.differenceWith`, this method mutates `array`. - * - * @static - * @memberOf _ - * @since 4.6.0 - * @category Array - * @param {Array} array The array to modify. - * @param {Array} values The values to remove. - * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns `array`. - * @example - * - * var array = [{ 'x': 1, 'y': 2 }, { 'x': 3, 'y': 4 }, { 'x': 5, 'y': 6 }]; - * - * _.pullAllWith(array, [{ 'x': 3, 'y': 4 }], _.isEqual); - * console.log(array); - * // => [{ 'x': 1, 'y': 2 }, { 'x': 5, 'y': 6 }] - */ - function pullAllWith(array, values, comparator) { - return (array && array.length && values && values.length) - ? basePullAll(array, values, undefined, comparator) - : array; - } - - /** - * Removes elements from `array` corresponding to `indexes` and returns an - * array of removed elements. - * - * **Note:** Unlike `_.at`, this method mutates `array`. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Array - * @param {Array} array The array to modify. - * @param {...(number|number[])} [indexes] The indexes of elements to remove. - * @returns {Array} Returns the new array of removed elements. - * @example - * - * var array = ['a', 'b', 'c', 'd']; - * var pulled = _.pullAt(array, [1, 3]); - * - * console.log(array); - * // => ['a', 'c'] - * - * console.log(pulled); - * // => ['b', 'd'] - */ - var pullAt = flatRest(function(array, indexes) { - var length = array == null ? 0 : array.length, - result = baseAt(array, indexes); - - basePullAt(array, arrayMap(indexes, function(index) { - return isIndex(index, length) ? +index : index; - }).sort(compareAscending)); - - return result; - }); - - /** - * Removes all elements from `array` that `predicate` returns truthy for - * and returns an array of the removed elements. The predicate is invoked - * with three arguments: (value, index, array). - * - * **Note:** Unlike `_.filter`, this method mutates `array`. Use `_.pull` - * to pull elements from an array by value. - * - * @static - * @memberOf _ - * @since 2.0.0 - * @category Array - * @param {Array} array The array to modify. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @returns {Array} Returns the new array of removed elements. - * @example - * - * var array = [1, 2, 3, 4]; - * var evens = _.remove(array, function(n) { - * return n % 2 == 0; - * }); - * - * console.log(array); - * // => [1, 3] - * - * console.log(evens); - * // => [2, 4] - */ - function remove(array, predicate) { - var result = []; - if (!(array && array.length)) { - return result; - } - var index = -1, - indexes = [], - length = array.length; - - predicate = getIteratee(predicate, 3); - while (++index < length) { - var value = array[index]; - if (predicate(value, index, array)) { - result.push(value); - indexes.push(index); - } - } - basePullAt(array, indexes); - return result; - } - - /** - * Reverses `array` so that the first element becomes the last, the second - * element becomes the second to last, and so on. - * - * **Note:** This method mutates `array` and is based on - * [`Array#reverse`](https://mdn.io/Array/reverse). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to modify. - * @returns {Array} Returns `array`. - * @example - * - * var array = [1, 2, 3]; - * - * _.reverse(array); - * // => [3, 2, 1] - * - * console.log(array); - * // => [3, 2, 1] - */ - function reverse(array) { - return array == null ? array : nativeReverse.call(array); - } - - /** - * Creates a slice of `array` from `start` up to, but not including, `end`. - * - * **Note:** This method is used instead of - * [`Array#slice`](https://mdn.io/Array/slice) to ensure dense arrays are - * returned. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Array - * @param {Array} array The array to slice. - * @param {number} [start=0] The start position. - * @param {number} [end=array.length] The end position. - * @returns {Array} Returns the slice of `array`. - */ - function slice(array, start, end) { - var length = array == null ? 0 : array.length; - if (!length) { - return []; - } - if (end && typeof end != 'number' && isIterateeCall(array, start, end)) { - start = 0; - end = length; - } - else { - start = start == null ? 0 : toInteger(start); - end = end === undefined ? length : toInteger(end); - } - return baseSlice(array, start, end); - } - - /** - * Uses a binary search to determine the lowest index at which `value` - * should be inserted into `array` in order to maintain its sort order. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {Array} array The sorted array to inspect. - * @param {*} value The value to evaluate. - * @returns {number} Returns the index at which `value` should be inserted - * into `array`. - * @example - * - * _.sortedIndex([30, 50], 40); - * // => 1 - */ - function sortedIndex(array, value) { - return baseSortedIndex(array, value); - } - - /** - * This method is like `_.sortedIndex` except that it accepts `iteratee` - * which is invoked for `value` and each element of `array` to compute their - * sort ranking. The iteratee is invoked with one argument: (value). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The sorted array to inspect. - * @param {*} value The value to evaluate. - * @param {Function} [iteratee=_.identity] The iteratee invoked per element. - * @returns {number} Returns the index at which `value` should be inserted - * into `array`. - * @example - * - * var objects = [{ 'x': 4 }, { 'x': 5 }]; - * - * _.sortedIndexBy(objects, { 'x': 4 }, function(o) { return o.x; }); - * // => 0 - * - * // The `_.property` iteratee shorthand. - * _.sortedIndexBy(objects, { 'x': 4 }, 'x'); - * // => 0 - */ - function sortedIndexBy(array, value, iteratee) { - return baseSortedIndexBy(array, value, getIteratee(iteratee, 2)); - } - - /** - * This method is like `_.indexOf` except that it performs a binary - * search on a sorted `array`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {*} value The value to search for. - * @returns {number} Returns the index of the matched value, else `-1`. - * @example - * - * _.sortedIndexOf([4, 5, 5, 5, 6], 5); - * // => 1 - */ - function sortedIndexOf(array, value) { - var length = array == null ? 0 : array.length; - if (length) { - var index = baseSortedIndex(array, value); - if (index < length && eq(array[index], value)) { - return index; - } - } - return -1; - } - - /** - * This method is like `_.sortedIndex` except that it returns the highest - * index at which `value` should be inserted into `array` in order to - * maintain its sort order. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Array - * @param {Array} array The sorted array to inspect. - * @param {*} value The value to evaluate. - * @returns {number} Returns the index at which `value` should be inserted - * into `array`. - * @example - * - * _.sortedLastIndex([4, 5, 5, 5, 6], 5); - * // => 4 - */ - function sortedLastIndex(array, value) { - return baseSortedIndex(array, value, true); - } - - /** - * This method is like `_.sortedLastIndex` except that it accepts `iteratee` - * which is invoked for `value` and each element of `array` to compute their - * sort ranking. The iteratee is invoked with one argument: (value). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The sorted array to inspect. - * @param {*} value The value to evaluate. - * @param {Function} [iteratee=_.identity] The iteratee invoked per element. - * @returns {number} Returns the index at which `value` should be inserted - * into `array`. - * @example - * - * var objects = [{ 'x': 4 }, { 'x': 5 }]; - * - * _.sortedLastIndexBy(objects, { 'x': 4 }, function(o) { return o.x; }); - * // => 1 - * - * // The `_.property` iteratee shorthand. - * _.sortedLastIndexBy(objects, { 'x': 4 }, 'x'); - * // => 1 - */ - function sortedLastIndexBy(array, value, iteratee) { - return baseSortedIndexBy(array, value, getIteratee(iteratee, 2), true); - } - - /** - * This method is like `_.lastIndexOf` except that it performs a binary - * search on a sorted `array`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {*} value The value to search for. - * @returns {number} Returns the index of the matched value, else `-1`. - * @example - * - * _.sortedLastIndexOf([4, 5, 5, 5, 6], 5); - * // => 3 - */ - function sortedLastIndexOf(array, value) { - var length = array == null ? 0 : array.length; - if (length) { - var index = baseSortedIndex(array, value, true) - 1; - if (eq(array[index], value)) { - return index; - } - } - return -1; - } - - /** - * This method is like `_.uniq` except that it's designed and optimized - * for sorted arrays. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to inspect. - * @returns {Array} Returns the new duplicate free array. - * @example - * - * _.sortedUniq([1, 1, 2]); - * // => [1, 2] - */ - function sortedUniq(array) { - return (array && array.length) - ? baseSortedUniq(array) - : []; - } - - /** - * This method is like `_.uniqBy` except that it's designed and optimized - * for sorted arrays. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {Function} [iteratee] The iteratee invoked per element. - * @returns {Array} Returns the new duplicate free array. - * @example - * - * _.sortedUniqBy([1.1, 1.2, 2.3, 2.4], Math.floor); - * // => [1.1, 2.3] - */ - function sortedUniqBy(array, iteratee) { - return (array && array.length) - ? baseSortedUniq(array, getIteratee(iteratee, 2)) - : []; - } - - /** - * Gets all but the first element of `array`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to query. - * @returns {Array} Returns the slice of `array`. - * @example - * - * _.tail([1, 2, 3]); - * // => [2, 3] - */ - function tail(array) { - var length = array == null ? 0 : array.length; - return length ? baseSlice(array, 1, length) : []; - } - - /** - * Creates a slice of `array` with `n` elements taken from the beginning. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {Array} array The array to query. - * @param {number} [n=1] The number of elements to take. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {Array} Returns the slice of `array`. - * @example - * - * _.take([1, 2, 3]); - * // => [1] - * - * _.take([1, 2, 3], 2); - * // => [1, 2] - * - * _.take([1, 2, 3], 5); - * // => [1, 2, 3] - * - * _.take([1, 2, 3], 0); - * // => [] - */ - function take(array, n, guard) { - if (!(array && array.length)) { - return []; - } - n = (guard || n === undefined) ? 1 : toInteger(n); - return baseSlice(array, 0, n < 0 ? 0 : n); - } - - /** - * Creates a slice of `array` with `n` elements taken from the end. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Array - * @param {Array} array The array to query. - * @param {number} [n=1] The number of elements to take. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {Array} Returns the slice of `array`. - * @example - * - * _.takeRight([1, 2, 3]); - * // => [3] - * - * _.takeRight([1, 2, 3], 2); - * // => [2, 3] - * - * _.takeRight([1, 2, 3], 5); - * // => [1, 2, 3] - * - * _.takeRight([1, 2, 3], 0); - * // => [] - */ - function takeRight(array, n, guard) { - var length = array == null ? 0 : array.length; - if (!length) { - return []; - } - n = (guard || n === undefined) ? 1 : toInteger(n); - n = length - n; - return baseSlice(array, n < 0 ? 0 : n, length); - } - - /** - * Creates a slice of `array` with elements taken from the end. Elements are - * taken until `predicate` returns falsey. The predicate is invoked with - * three arguments: (value, index, array). - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Array - * @param {Array} array The array to query. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @returns {Array} Returns the slice of `array`. - * @example - * - * var users = [ - * { 'user': 'barney', 'active': true }, - * { 'user': 'fred', 'active': false }, - * { 'user': 'pebbles', 'active': false } - * ]; - * - * _.takeRightWhile(users, function(o) { return !o.active; }); - * // => objects for ['fred', 'pebbles'] - * - * // The `_.matches` iteratee shorthand. - * _.takeRightWhile(users, { 'user': 'pebbles', 'active': false }); - * // => objects for ['pebbles'] - * - * // The `_.matchesProperty` iteratee shorthand. - * _.takeRightWhile(users, ['active', false]); - * // => objects for ['fred', 'pebbles'] - * - * // The `_.property` iteratee shorthand. - * _.takeRightWhile(users, 'active'); - * // => [] - */ - function takeRightWhile(array, predicate) { - return (array && array.length) - ? baseWhile(array, getIteratee(predicate, 3), false, true) - : []; - } - - /** - * Creates a slice of `array` with elements taken from the beginning. Elements - * are taken until `predicate` returns falsey. The predicate is invoked with - * three arguments: (value, index, array). - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Array - * @param {Array} array The array to query. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @returns {Array} Returns the slice of `array`. - * @example - * - * var users = [ - * { 'user': 'barney', 'active': false }, - * { 'user': 'fred', 'active': false }, - * { 'user': 'pebbles', 'active': true } - * ]; - * - * _.takeWhile(users, function(o) { return !o.active; }); - * // => objects for ['barney', 'fred'] - * - * // The `_.matches` iteratee shorthand. - * _.takeWhile(users, { 'user': 'barney', 'active': false }); - * // => objects for ['barney'] - * - * // The `_.matchesProperty` iteratee shorthand. - * _.takeWhile(users, ['active', false]); - * // => objects for ['barney', 'fred'] - * - * // The `_.property` iteratee shorthand. - * _.takeWhile(users, 'active'); - * // => [] - */ - function takeWhile(array, predicate) { - return (array && array.length) - ? baseWhile(array, getIteratee(predicate, 3)) - : []; - } - - /** - * Creates an array of unique values, in order, from all given arrays using - * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * for equality comparisons. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {...Array} [arrays] The arrays to inspect. - * @returns {Array} Returns the new array of combined values. - * @example - * - * _.union([2], [1, 2]); - * // => [2, 1] - */ - var union = baseRest(function(arrays) { - return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true)); - }); - - /** - * This method is like `_.union` except that it accepts `iteratee` which is - * invoked for each element of each `arrays` to generate the criterion by - * which uniqueness is computed. Result values are chosen from the first - * array in which the value occurs. The iteratee is invoked with one argument: - * (value). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {...Array} [arrays] The arrays to inspect. - * @param {Function} [iteratee=_.identity] The iteratee invoked per element. - * @returns {Array} Returns the new array of combined values. - * @example - * - * _.unionBy([2.1], [1.2, 2.3], Math.floor); - * // => [2.1, 1.2] - * - * // The `_.property` iteratee shorthand. - * _.unionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x'); - * // => [{ 'x': 1 }, { 'x': 2 }] - */ - var unionBy = baseRest(function(arrays) { - var iteratee = last(arrays); - if (isArrayLikeObject(iteratee)) { - iteratee = undefined; - } - return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true), getIteratee(iteratee, 2)); - }); - - /** - * This method is like `_.union` except that it accepts `comparator` which - * is invoked to compare elements of `arrays`. Result values are chosen from - * the first array in which the value occurs. The comparator is invoked - * with two arguments: (arrVal, othVal). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {...Array} [arrays] The arrays to inspect. - * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns the new array of combined values. - * @example - * - * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]; - * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }]; - * - * _.unionWith(objects, others, _.isEqual); - * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }] - */ - var unionWith = baseRest(function(arrays) { - var comparator = last(arrays); - comparator = typeof comparator == 'function' ? comparator : undefined; - return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true), undefined, comparator); - }); - - /** - * Creates a duplicate-free version of an array, using - * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * for equality comparisons, in which only the first occurrence of each element - * is kept. The order of result values is determined by the order they occur - * in the array. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {Array} array The array to inspect. - * @returns {Array} Returns the new duplicate free array. - * @example - * - * _.uniq([2, 1, 2]); - * // => [2, 1] - */ - function uniq(array) { - return (array && array.length) ? baseUniq(array) : []; - } - - /** - * This method is like `_.uniq` except that it accepts `iteratee` which is - * invoked for each element in `array` to generate the criterion by which - * uniqueness is computed. The order of result values is determined by the - * order they occur in the array. The iteratee is invoked with one argument: - * (value). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {Function} [iteratee=_.identity] The iteratee invoked per element. - * @returns {Array} Returns the new duplicate free array. - * @example - * - * _.uniqBy([2.1, 1.2, 2.3], Math.floor); - * // => [2.1, 1.2] - * - * // The `_.property` iteratee shorthand. - * _.uniqBy([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x'); - * // => [{ 'x': 1 }, { 'x': 2 }] - */ - function uniqBy(array, iteratee) { - return (array && array.length) ? baseUniq(array, getIteratee(iteratee, 2)) : []; - } - - /** - * This method is like `_.uniq` except that it accepts `comparator` which - * is invoked to compare elements of `array`. The order of result values is - * determined by the order they occur in the array.The comparator is invoked - * with two arguments: (arrVal, othVal). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns the new duplicate free array. - * @example - * - * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 2 }]; - * - * _.uniqWith(objects, _.isEqual); - * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }] - */ - function uniqWith(array, comparator) { - comparator = typeof comparator == 'function' ? comparator : undefined; - return (array && array.length) ? baseUniq(array, undefined, comparator) : []; - } - - /** - * This method is like `_.zip` except that it accepts an array of grouped - * elements and creates an array regrouping the elements to their pre-zip - * configuration. - * - * @static - * @memberOf _ - * @since 1.2.0 - * @category Array - * @param {Array} array The array of grouped elements to process. - * @returns {Array} Returns the new array of regrouped elements. - * @example - * - * var zipped = _.zip(['a', 'b'], [1, 2], [true, false]); - * // => [['a', 1, true], ['b', 2, false]] - * - * _.unzip(zipped); - * // => [['a', 'b'], [1, 2], [true, false]] - */ - function unzip(array) { - if (!(array && array.length)) { - return []; - } - var length = 0; - array = arrayFilter(array, function(group) { - if (isArrayLikeObject(group)) { - length = nativeMax(group.length, length); - return true; - } - }); - return baseTimes(length, function(index) { - return arrayMap(array, baseProperty(index)); - }); - } - - /** - * This method is like `_.unzip` except that it accepts `iteratee` to specify - * how regrouped values should be combined. The iteratee is invoked with the - * elements of each group: (...group). - * - * @static - * @memberOf _ - * @since 3.8.0 - * @category Array - * @param {Array} array The array of grouped elements to process. - * @param {Function} [iteratee=_.identity] The function to combine - * regrouped values. - * @returns {Array} Returns the new array of regrouped elements. - * @example - * - * var zipped = _.zip([1, 2], [10, 20], [100, 200]); - * // => [[1, 10, 100], [2, 20, 200]] - * - * _.unzipWith(zipped, _.add); - * // => [3, 30, 300] - */ - function unzipWith(array, iteratee) { - if (!(array && array.length)) { - return []; - } - var result = unzip(array); - if (iteratee == null) { - return result; - } - return arrayMap(result, function(group) { - return apply(iteratee, undefined, group); - }); - } - - /** - * Creates an array excluding all given values using - * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * for equality comparisons. - * - * **Note:** Unlike `_.pull`, this method returns a new array. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {Array} array The array to inspect. - * @param {...*} [values] The values to exclude. - * @returns {Array} Returns the new array of filtered values. - * @see _.difference, _.xor - * @example - * - * _.without([2, 1, 2, 3], 1, 2); - * // => [3] - */ - var without = baseRest(function(array, values) { - return isArrayLikeObject(array) - ? baseDifference(array, values) - : []; - }); - - /** - * Creates an array of unique values that is the - * [symmetric difference](https://en.wikipedia.org/wiki/Symmetric_difference) - * of the given arrays. The order of result values is determined by the order - * they occur in the arrays. - * - * @static - * @memberOf _ - * @since 2.4.0 - * @category Array - * @param {...Array} [arrays] The arrays to inspect. - * @returns {Array} Returns the new array of filtered values. - * @see _.difference, _.without - * @example - * - * _.xor([2, 1], [2, 3]); - * // => [1, 3] - */ - var xor = baseRest(function(arrays) { - return baseXor(arrayFilter(arrays, isArrayLikeObject)); - }); - - /** - * This method is like `_.xor` except that it accepts `iteratee` which is - * invoked for each element of each `arrays` to generate the criterion by - * which by which they're compared. The order of result values is determined - * by the order they occur in the arrays. The iteratee is invoked with one - * argument: (value). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {...Array} [arrays] The arrays to inspect. - * @param {Function} [iteratee=_.identity] The iteratee invoked per element. - * @returns {Array} Returns the new array of filtered values. - * @example - * - * _.xorBy([2.1, 1.2], [2.3, 3.4], Math.floor); - * // => [1.2, 3.4] - * - * // The `_.property` iteratee shorthand. - * _.xorBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x'); - * // => [{ 'x': 2 }] - */ - var xorBy = baseRest(function(arrays) { - var iteratee = last(arrays); - if (isArrayLikeObject(iteratee)) { - iteratee = undefined; - } - return baseXor(arrayFilter(arrays, isArrayLikeObject), getIteratee(iteratee, 2)); - }); - - /** - * This method is like `_.xor` except that it accepts `comparator` which is - * invoked to compare elements of `arrays`. The order of result values is - * determined by the order they occur in the arrays. The comparator is invoked - * with two arguments: (arrVal, othVal). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Array - * @param {...Array} [arrays] The arrays to inspect. - * @param {Function} [comparator] The comparator invoked per element. - * @returns {Array} Returns the new array of filtered values. - * @example - * - * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]; - * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }]; - * - * _.xorWith(objects, others, _.isEqual); - * // => [{ 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }] - */ - var xorWith = baseRest(function(arrays) { - var comparator = last(arrays); - comparator = typeof comparator == 'function' ? comparator : undefined; - return baseXor(arrayFilter(arrays, isArrayLikeObject), undefined, comparator); - }); - - /** - * Creates an array of grouped elements, the first of which contains the - * first elements of the given arrays, the second of which contains the - * second elements of the given arrays, and so on. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Array - * @param {...Array} [arrays] The arrays to process. - * @returns {Array} Returns the new array of grouped elements. - * @example - * - * _.zip(['a', 'b'], [1, 2], [true, false]); - * // => [['a', 1, true], ['b', 2, false]] - */ - var zip = baseRest(unzip); - - /** - * This method is like `_.fromPairs` except that it accepts two arrays, - * one of property identifiers and one of corresponding values. - * - * @static - * @memberOf _ - * @since 0.4.0 - * @category Array - * @param {Array} [props=[]] The property identifiers. - * @param {Array} [values=[]] The property values. - * @returns {Object} Returns the new object. - * @example - * - * _.zipObject(['a', 'b'], [1, 2]); - * // => { 'a': 1, 'b': 2 } - */ - function zipObject(props, values) { - return baseZipObject(props || [], values || [], assignValue); - } - - /** - * This method is like `_.zipObject` except that it supports property paths. - * - * @static - * @memberOf _ - * @since 4.1.0 - * @category Array - * @param {Array} [props=[]] The property identifiers. - * @param {Array} [values=[]] The property values. - * @returns {Object} Returns the new object. - * @example - * - * _.zipObjectDeep(['a.b[0].c', 'a.b[1].d'], [1, 2]); - * // => { 'a': { 'b': [{ 'c': 1 }, { 'd': 2 }] } } - */ - function zipObjectDeep(props, values) { - return baseZipObject(props || [], values || [], baseSet); - } - - /** - * This method is like `_.zip` except that it accepts `iteratee` to specify - * how grouped values should be combined. The iteratee is invoked with the - * elements of each group: (...group). - * - * @static - * @memberOf _ - * @since 3.8.0 - * @category Array - * @param {...Array} [arrays] The arrays to process. - * @param {Function} [iteratee=_.identity] The function to combine - * grouped values. - * @returns {Array} Returns the new array of grouped elements. - * @example - * - * _.zipWith([1, 2], [10, 20], [100, 200], function(a, b, c) { - * return a + b + c; - * }); - * // => [111, 222] - */ - var zipWith = baseRest(function(arrays) { - var length = arrays.length, - iteratee = length > 1 ? arrays[length - 1] : undefined; - - iteratee = typeof iteratee == 'function' ? (arrays.pop(), iteratee) : undefined; - return unzipWith(arrays, iteratee); - }); - - /*------------------------------------------------------------------------*/ - - /** - * Creates a `lodash` wrapper instance that wraps `value` with explicit method - * chain sequences enabled. The result of such sequences must be unwrapped - * with `_#value`. - * - * @static - * @memberOf _ - * @since 1.3.0 - * @category Seq - * @param {*} value The value to wrap. - * @returns {Object} Returns the new `lodash` wrapper instance. - * @example - * - * var users = [ - * { 'user': 'barney', 'age': 36 }, - * { 'user': 'fred', 'age': 40 }, - * { 'user': 'pebbles', 'age': 1 } - * ]; - * - * var youngest = _ - * .chain(users) - * .sortBy('age') - * .map(function(o) { - * return o.user + ' is ' + o.age; - * }) - * .head() - * .value(); - * // => 'pebbles is 1' - */ - function chain(value) { - var result = lodash(value); - result.__chain__ = true; - return result; - } - - /** - * This method invokes `interceptor` and returns `value`. The interceptor - * is invoked with one argument; (value). The purpose of this method is to - * "tap into" a method chain sequence in order to modify intermediate results. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Seq - * @param {*} value The value to provide to `interceptor`. - * @param {Function} interceptor The function to invoke. - * @returns {*} Returns `value`. - * @example - * - * _([1, 2, 3]) - * .tap(function(array) { - * // Mutate input array. - * array.pop(); - * }) - * .reverse() - * .value(); - * // => [2, 1] - */ - function tap(value, interceptor) { - interceptor(value); - return value; - } - - /** - * This method is like `_.tap` except that it returns the result of `interceptor`. - * The purpose of this method is to "pass thru" values replacing intermediate - * results in a method chain sequence. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Seq - * @param {*} value The value to provide to `interceptor`. - * @param {Function} interceptor The function to invoke. - * @returns {*} Returns the result of `interceptor`. - * @example - * - * _(' abc ') - * .chain() - * .trim() - * .thru(function(value) { - * return [value]; - * }) - * .value(); - * // => ['abc'] - */ - function thru(value, interceptor) { - return interceptor(value); - } - - /** - * This method is the wrapper version of `_.at`. - * - * @name at - * @memberOf _ - * @since 1.0.0 - * @category Seq - * @param {...(string|string[])} [paths] The property paths to pick. - * @returns {Object} Returns the new `lodash` wrapper instance. - * @example - * - * var object = { 'a': [{ 'b': { 'c': 3 } }, 4] }; - * - * _(object).at(['a[0].b.c', 'a[1]']).value(); - * // => [3, 4] - */ - var wrapperAt = flatRest(function(paths) { - var length = paths.length, - start = length ? paths[0] : 0, - value = this.__wrapped__, - interceptor = function(object) { return baseAt(object, paths); }; - - if (length > 1 || this.__actions__.length || - !(value instanceof LazyWrapper) || !isIndex(start)) { - return this.thru(interceptor); - } - value = value.slice(start, +start + (length ? 1 : 0)); - value.__actions__.push({ - 'func': thru, - 'args': [interceptor], - 'thisArg': undefined - }); - return new LodashWrapper(value, this.__chain__).thru(function(array) { - if (length && !array.length) { - array.push(undefined); - } - return array; - }); - }); - - /** - * Creates a `lodash` wrapper instance with explicit method chain sequences enabled. - * - * @name chain - * @memberOf _ - * @since 0.1.0 - * @category Seq - * @returns {Object} Returns the new `lodash` wrapper instance. - * @example - * - * var users = [ - * { 'user': 'barney', 'age': 36 }, - * { 'user': 'fred', 'age': 40 } - * ]; - * - * // A sequence without explicit chaining. - * _(users).head(); - * // => { 'user': 'barney', 'age': 36 } - * - * // A sequence with explicit chaining. - * _(users) - * .chain() - * .head() - * .pick('user') - * .value(); - * // => { 'user': 'barney' } - */ - function wrapperChain() { - return chain(this); - } - - /** - * Executes the chain sequence and returns the wrapped result. - * - * @name commit - * @memberOf _ - * @since 3.2.0 - * @category Seq - * @returns {Object} Returns the new `lodash` wrapper instance. - * @example - * - * var array = [1, 2]; - * var wrapped = _(array).push(3); - * - * console.log(array); - * // => [1, 2] - * - * wrapped = wrapped.commit(); - * console.log(array); - * // => [1, 2, 3] - * - * wrapped.last(); - * // => 3 - * - * console.log(array); - * // => [1, 2, 3] - */ - function wrapperCommit() { - return new LodashWrapper(this.value(), this.__chain__); - } - - /** - * Gets the next value on a wrapped object following the - * [iterator protocol](https://mdn.io/iteration_protocols#iterator). - * - * @name next - * @memberOf _ - * @since 4.0.0 - * @category Seq - * @returns {Object} Returns the next iterator value. - * @example - * - * var wrapped = _([1, 2]); - * - * wrapped.next(); - * // => { 'done': false, 'value': 1 } - * - * wrapped.next(); - * // => { 'done': false, 'value': 2 } - * - * wrapped.next(); - * // => { 'done': true, 'value': undefined } - */ - function wrapperNext() { - if (this.__values__ === undefined) { - this.__values__ = toArray(this.value()); - } - var done = this.__index__ >= this.__values__.length, - value = done ? undefined : this.__values__[this.__index__++]; - - return { 'done': done, 'value': value }; - } - - /** - * Enables the wrapper to be iterable. - * - * @name Symbol.iterator - * @memberOf _ - * @since 4.0.0 - * @category Seq - * @returns {Object} Returns the wrapper object. - * @example - * - * var wrapped = _([1, 2]); - * - * wrapped[Symbol.iterator]() === wrapped; - * // => true - * - * Array.from(wrapped); - * // => [1, 2] - */ - function wrapperToIterator() { - return this; - } - - /** - * Creates a clone of the chain sequence planting `value` as the wrapped value. - * - * @name plant - * @memberOf _ - * @since 3.2.0 - * @category Seq - * @param {*} value The value to plant. - * @returns {Object} Returns the new `lodash` wrapper instance. - * @example - * - * function square(n) { - * return n * n; - * } - * - * var wrapped = _([1, 2]).map(square); - * var other = wrapped.plant([3, 4]); - * - * other.value(); - * // => [9, 16] - * - * wrapped.value(); - * // => [1, 4] - */ - function wrapperPlant(value) { - var result, - parent = this; - - while (parent instanceof baseLodash) { - var clone = wrapperClone(parent); - clone.__index__ = 0; - clone.__values__ = undefined; - if (result) { - previous.__wrapped__ = clone; - } else { - result = clone; - } - var previous = clone; - parent = parent.__wrapped__; - } - previous.__wrapped__ = value; - return result; - } - - /** - * This method is the wrapper version of `_.reverse`. - * - * **Note:** This method mutates the wrapped array. - * - * @name reverse - * @memberOf _ - * @since 0.1.0 - * @category Seq - * @returns {Object} Returns the new `lodash` wrapper instance. - * @example - * - * var array = [1, 2, 3]; - * - * _(array).reverse().value() - * // => [3, 2, 1] - * - * console.log(array); - * // => [3, 2, 1] - */ - function wrapperReverse() { - var value = this.__wrapped__; - if (value instanceof LazyWrapper) { - var wrapped = value; - if (this.__actions__.length) { - wrapped = new LazyWrapper(this); - } - wrapped = wrapped.reverse(); - wrapped.__actions__.push({ - 'func': thru, - 'args': [reverse], - 'thisArg': undefined - }); - return new LodashWrapper(wrapped, this.__chain__); - } - return this.thru(reverse); - } - - /** - * Executes the chain sequence to resolve the unwrapped value. - * - * @name value - * @memberOf _ - * @since 0.1.0 - * @alias toJSON, valueOf - * @category Seq - * @returns {*} Returns the resolved unwrapped value. - * @example - * - * _([1, 2, 3]).value(); - * // => [1, 2, 3] - */ - function wrapperValue() { - return baseWrapperValue(this.__wrapped__, this.__actions__); - } - - /*------------------------------------------------------------------------*/ - - /** - * Creates an object composed of keys generated from the results of running - * each element of `collection` thru `iteratee`. The corresponding value of - * each key is the number of times the key was returned by `iteratee`. The - * iteratee is invoked with one argument: (value). - * - * @static - * @memberOf _ - * @since 0.5.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The iteratee to transform keys. - * @returns {Object} Returns the composed aggregate object. - * @example - * - * _.countBy([6.1, 4.2, 6.3], Math.floor); - * // => { '4': 1, '6': 2 } - * - * // The `_.property` iteratee shorthand. - * _.countBy(['one', 'two', 'three'], 'length'); - * // => { '3': 2, '5': 1 } - */ - var countBy = createAggregator(function(result, value, key) { - if (hasOwnProperty.call(result, key)) { - ++result[key]; - } else { - baseAssignValue(result, key, 1); - } - }); - - /** - * Checks if `predicate` returns truthy for **all** elements of `collection`. - * Iteration is stopped once `predicate` returns falsey. The predicate is - * invoked with three arguments: (value, index|key, collection). - * - * **Note:** This method returns `true` for - * [empty collections](https://en.wikipedia.org/wiki/Empty_set) because - * [everything is true](https://en.wikipedia.org/wiki/Vacuous_truth) of - * elements of empty collections. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {boolean} Returns `true` if all elements pass the predicate check, - * else `false`. - * @example - * - * _.every([true, 1, null, 'yes'], Boolean); - * // => false - * - * var users = [ - * { 'user': 'barney', 'age': 36, 'active': false }, - * { 'user': 'fred', 'age': 40, 'active': false } - * ]; - * - * // The `_.matches` iteratee shorthand. - * _.every(users, { 'user': 'barney', 'active': false }); - * // => false - * - * // The `_.matchesProperty` iteratee shorthand. - * _.every(users, ['active', false]); - * // => true - * - * // The `_.property` iteratee shorthand. - * _.every(users, 'active'); - * // => false - */ - function every(collection, predicate, guard) { - var func = isArray(collection) ? arrayEvery : baseEvery; - if (guard && isIterateeCall(collection, predicate, guard)) { - predicate = undefined; - } - return func(collection, getIteratee(predicate, 3)); - } - - /** - * Iterates over elements of `collection`, returning an array of all elements - * `predicate` returns truthy for. The predicate is invoked with three - * arguments: (value, index|key, collection). - * - * **Note:** Unlike `_.remove`, this method returns a new array. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @returns {Array} Returns the new filtered array. - * @see _.reject - * @example - * - * var users = [ - * { 'user': 'barney', 'age': 36, 'active': true }, - * { 'user': 'fred', 'age': 40, 'active': false } - * ]; - * - * _.filter(users, function(o) { return !o.active; }); - * // => objects for ['fred'] - * - * // The `_.matches` iteratee shorthand. - * _.filter(users, { 'age': 36, 'active': true }); - * // => objects for ['barney'] - * - * // The `_.matchesProperty` iteratee shorthand. - * _.filter(users, ['active', false]); - * // => objects for ['fred'] - * - * // The `_.property` iteratee shorthand. - * _.filter(users, 'active'); - * // => objects for ['barney'] - */ - function filter(collection, predicate) { - var func = isArray(collection) ? arrayFilter : baseFilter; - return func(collection, getIteratee(predicate, 3)); - } - - /** - * Iterates over elements of `collection`, returning the first element - * `predicate` returns truthy for. The predicate is invoked with three - * arguments: (value, index|key, collection). - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object} collection The collection to inspect. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @param {number} [fromIndex=0] The index to search from. - * @returns {*} Returns the matched element, else `undefined`. - * @example - * - * var users = [ - * { 'user': 'barney', 'age': 36, 'active': true }, - * { 'user': 'fred', 'age': 40, 'active': false }, - * { 'user': 'pebbles', 'age': 1, 'active': true } - * ]; - * - * _.find(users, function(o) { return o.age < 40; }); - * // => object for 'barney' - * - * // The `_.matches` iteratee shorthand. - * _.find(users, { 'age': 1, 'active': true }); - * // => object for 'pebbles' - * - * // The `_.matchesProperty` iteratee shorthand. - * _.find(users, ['active', false]); - * // => object for 'fred' - * - * // The `_.property` iteratee shorthand. - * _.find(users, 'active'); - * // => object for 'barney' - */ - var find = createFind(findIndex); - - /** - * This method is like `_.find` except that it iterates over elements of - * `collection` from right to left. - * - * @static - * @memberOf _ - * @since 2.0.0 - * @category Collection - * @param {Array|Object} collection The collection to inspect. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @param {number} [fromIndex=collection.length-1] The index to search from. - * @returns {*} Returns the matched element, else `undefined`. - * @example - * - * _.findLast([1, 2, 3, 4], function(n) { - * return n % 2 == 1; - * }); - * // => 3 - */ - var findLast = createFind(findLastIndex); - - /** - * Creates a flattened array of values by running each element in `collection` - * thru `iteratee` and flattening the mapped results. The iteratee is invoked - * with three arguments: (value, index|key, collection). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Array} Returns the new flattened array. - * @example - * - * function duplicate(n) { - * return [n, n]; - * } - * - * _.flatMap([1, 2], duplicate); - * // => [1, 1, 2, 2] - */ - function flatMap(collection, iteratee) { - return baseFlatten(map(collection, iteratee), 1); - } - - /** - * This method is like `_.flatMap` except that it recursively flattens the - * mapped results. - * - * @static - * @memberOf _ - * @since 4.7.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Array} Returns the new flattened array. - * @example - * - * function duplicate(n) { - * return [[[n, n]]]; - * } - * - * _.flatMapDeep([1, 2], duplicate); - * // => [1, 1, 2, 2] - */ - function flatMapDeep(collection, iteratee) { - return baseFlatten(map(collection, iteratee), INFINITY); - } - - /** - * This method is like `_.flatMap` except that it recursively flattens the - * mapped results up to `depth` times. - * - * @static - * @memberOf _ - * @since 4.7.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @param {number} [depth=1] The maximum recursion depth. - * @returns {Array} Returns the new flattened array. - * @example - * - * function duplicate(n) { - * return [[[n, n]]]; - * } - * - * _.flatMapDepth([1, 2], duplicate, 2); - * // => [[1, 1], [2, 2]] - */ - function flatMapDepth(collection, iteratee, depth) { - depth = depth === undefined ? 1 : toInteger(depth); - return baseFlatten(map(collection, iteratee), depth); - } - - /** - * Iterates over elements of `collection` and invokes `iteratee` for each element. - * The iteratee is invoked with three arguments: (value, index|key, collection). - * Iteratee functions may exit iteration early by explicitly returning `false`. - * - * **Note:** As with other "Collections" methods, objects with a "length" - * property are iterated like arrays. To avoid this behavior use `_.forIn` - * or `_.forOwn` for object iteration. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @alias each - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Array|Object} Returns `collection`. - * @see _.forEachRight - * @example - * - * _.forEach([1, 2], function(value) { - * console.log(value); - * }); - * // => Logs `1` then `2`. - * - * _.forEach({ 'a': 1, 'b': 2 }, function(value, key) { - * console.log(key); - * }); - * // => Logs 'a' then 'b' (iteration order is not guaranteed). - */ - function forEach(collection, iteratee) { - var func = isArray(collection) ? arrayEach : baseEach; - return func(collection, getIteratee(iteratee, 3)); - } - - /** - * This method is like `_.forEach` except that it iterates over elements of - * `collection` from right to left. - * - * @static - * @memberOf _ - * @since 2.0.0 - * @alias eachRight - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Array|Object} Returns `collection`. - * @see _.forEach - * @example - * - * _.forEachRight([1, 2], function(value) { - * console.log(value); - * }); - * // => Logs `2` then `1`. - */ - function forEachRight(collection, iteratee) { - var func = isArray(collection) ? arrayEachRight : baseEachRight; - return func(collection, getIteratee(iteratee, 3)); - } - - /** - * Creates an object composed of keys generated from the results of running - * each element of `collection` thru `iteratee`. The order of grouped values - * is determined by the order they occur in `collection`. The corresponding - * value of each key is an array of elements responsible for generating the - * key. The iteratee is invoked with one argument: (value). - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The iteratee to transform keys. - * @returns {Object} Returns the composed aggregate object. - * @example - * - * _.groupBy([6.1, 4.2, 6.3], Math.floor); - * // => { '4': [4.2], '6': [6.1, 6.3] } - * - * // The `_.property` iteratee shorthand. - * _.groupBy(['one', 'two', 'three'], 'length'); - * // => { '3': ['one', 'two'], '5': ['three'] } - */ - var groupBy = createAggregator(function(result, value, key) { - if (hasOwnProperty.call(result, key)) { - result[key].push(value); - } else { - baseAssignValue(result, key, [value]); - } - }); - - /** - * Checks if `value` is in `collection`. If `collection` is a string, it's - * checked for a substring of `value`, otherwise - * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * is used for equality comparisons. If `fromIndex` is negative, it's used as - * the offset from the end of `collection`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object|string} collection The collection to inspect. - * @param {*} value The value to search for. - * @param {number} [fromIndex=0] The index to search from. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.reduce`. - * @returns {boolean} Returns `true` if `value` is found, else `false`. - * @example - * - * _.includes([1, 2, 3], 1); - * // => true - * - * _.includes([1, 2, 3], 1, 2); - * // => false - * - * _.includes({ 'a': 1, 'b': 2 }, 1); - * // => true - * - * _.includes('abcd', 'bc'); - * // => true - */ - function includes(collection, value, fromIndex, guard) { - collection = isArrayLike(collection) ? collection : values(collection); - fromIndex = (fromIndex && !guard) ? toInteger(fromIndex) : 0; - - var length = collection.length; - if (fromIndex < 0) { - fromIndex = nativeMax(length + fromIndex, 0); - } - return isString(collection) - ? (fromIndex <= length && collection.indexOf(value, fromIndex) > -1) - : (!!length && baseIndexOf(collection, value, fromIndex) > -1); - } - - /** - * Invokes the method at `path` of each element in `collection`, returning - * an array of the results of each invoked method. Any additional arguments - * are provided to each invoked method. If `path` is a function, it's invoked - * for, and `this` bound to, each element in `collection`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Array|Function|string} path The path of the method to invoke or - * the function invoked per iteration. - * @param {...*} [args] The arguments to invoke each method with. - * @returns {Array} Returns the array of results. - * @example - * - * _.invokeMap([[5, 1, 7], [3, 2, 1]], 'sort'); - * // => [[1, 5, 7], [1, 2, 3]] - * - * _.invokeMap([123, 456], String.prototype.split, ''); - * // => [['1', '2', '3'], ['4', '5', '6']] - */ - var invokeMap = baseRest(function(collection, path, args) { - var index = -1, - isFunc = typeof path == 'function', - result = isArrayLike(collection) ? Array(collection.length) : []; - - baseEach(collection, function(value) { - result[++index] = isFunc ? apply(path, value, args) : baseInvoke(value, path, args); - }); - return result; - }); - - /** - * Creates an object composed of keys generated from the results of running - * each element of `collection` thru `iteratee`. The corresponding value of - * each key is the last element responsible for generating the key. The - * iteratee is invoked with one argument: (value). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The iteratee to transform keys. - * @returns {Object} Returns the composed aggregate object. - * @example - * - * var array = [ - * { 'dir': 'left', 'code': 97 }, - * { 'dir': 'right', 'code': 100 } - * ]; - * - * _.keyBy(array, function(o) { - * return String.fromCharCode(o.code); - * }); - * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } } - * - * _.keyBy(array, 'dir'); - * // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } } - */ - var keyBy = createAggregator(function(result, value, key) { - baseAssignValue(result, key, value); - }); - - /** - * Creates an array of values by running each element in `collection` thru - * `iteratee`. The iteratee is invoked with three arguments: - * (value, index|key, collection). - * - * Many lodash methods are guarded to work as iteratees for methods like - * `_.every`, `_.filter`, `_.map`, `_.mapValues`, `_.reject`, and `_.some`. - * - * The guarded methods are: - * `ary`, `chunk`, `curry`, `curryRight`, `drop`, `dropRight`, `every`, - * `fill`, `invert`, `parseInt`, `random`, `range`, `rangeRight`, `repeat`, - * `sampleSize`, `slice`, `some`, `sortBy`, `split`, `take`, `takeRight`, - * `template`, `trim`, `trimEnd`, `trimStart`, and `words` - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Array} Returns the new mapped array. - * @example - * - * function square(n) { - * return n * n; - * } - * - * _.map([4, 8], square); - * // => [16, 64] - * - * _.map({ 'a': 4, 'b': 8 }, square); - * // => [16, 64] (iteration order is not guaranteed) - * - * var users = [ - * { 'user': 'barney' }, - * { 'user': 'fred' } - * ]; - * - * // The `_.property` iteratee shorthand. - * _.map(users, 'user'); - * // => ['barney', 'fred'] - */ - function map(collection, iteratee) { - var func = isArray(collection) ? arrayMap : baseMap; - return func(collection, getIteratee(iteratee, 3)); - } - - /** - * This method is like `_.sortBy` except that it allows specifying the sort - * orders of the iteratees to sort by. If `orders` is unspecified, all values - * are sorted in ascending order. Otherwise, specify an order of "desc" for - * descending or "asc" for ascending sort order of corresponding values. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Array[]|Function[]|Object[]|string[]} [iteratees=[_.identity]] - * The iteratees to sort by. - * @param {string[]} [orders] The sort orders of `iteratees`. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.reduce`. - * @returns {Array} Returns the new sorted array. - * @example - * - * var users = [ - * { 'user': 'fred', 'age': 48 }, - * { 'user': 'barney', 'age': 34 }, - * { 'user': 'fred', 'age': 40 }, - * { 'user': 'barney', 'age': 36 } - * ]; - * - * // Sort by `user` in ascending order and by `age` in descending order. - * _.orderBy(users, ['user', 'age'], ['asc', 'desc']); - * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]] - */ - function orderBy(collection, iteratees, orders, guard) { - if (collection == null) { - return []; - } - if (!isArray(iteratees)) { - iteratees = iteratees == null ? [] : [iteratees]; - } - orders = guard ? undefined : orders; - if (!isArray(orders)) { - orders = orders == null ? [] : [orders]; - } - return baseOrderBy(collection, iteratees, orders); - } - - /** - * Creates an array of elements split into two groups, the first of which - * contains elements `predicate` returns truthy for, the second of which - * contains elements `predicate` returns falsey for. The predicate is - * invoked with one argument: (value). - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @returns {Array} Returns the array of grouped elements. - * @example - * - * var users = [ - * { 'user': 'barney', 'age': 36, 'active': false }, - * { 'user': 'fred', 'age': 40, 'active': true }, - * { 'user': 'pebbles', 'age': 1, 'active': false } - * ]; - * - * _.partition(users, function(o) { return o.active; }); - * // => objects for [['fred'], ['barney', 'pebbles']] - * - * // The `_.matches` iteratee shorthand. - * _.partition(users, { 'age': 1, 'active': false }); - * // => objects for [['pebbles'], ['barney', 'fred']] - * - * // The `_.matchesProperty` iteratee shorthand. - * _.partition(users, ['active', false]); - * // => objects for [['barney', 'pebbles'], ['fred']] - * - * // The `_.property` iteratee shorthand. - * _.partition(users, 'active'); - * // => objects for [['fred'], ['barney', 'pebbles']] - */ - var partition = createAggregator(function(result, value, key) { - result[key ? 0 : 1].push(value); - }, function() { return [[], []]; }); - - /** - * Reduces `collection` to a value which is the accumulated result of running - * each element in `collection` thru `iteratee`, where each successive - * invocation is supplied the return value of the previous. If `accumulator` - * is not given, the first element of `collection` is used as the initial - * value. The iteratee is invoked with four arguments: - * (accumulator, value, index|key, collection). - * - * Many lodash methods are guarded to work as iteratees for methods like - * `_.reduce`, `_.reduceRight`, and `_.transform`. - * - * The guarded methods are: - * `assign`, `defaults`, `defaultsDeep`, `includes`, `merge`, `orderBy`, - * and `sortBy` - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @param {*} [accumulator] The initial value. - * @returns {*} Returns the accumulated value. - * @see _.reduceRight - * @example - * - * _.reduce([1, 2], function(sum, n) { - * return sum + n; - * }, 0); - * // => 3 - * - * _.reduce({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) { - * (result[value] || (result[value] = [])).push(key); - * return result; - * }, {}); - * // => { '1': ['a', 'c'], '2': ['b'] } (iteration order is not guaranteed) - */ - function reduce(collection, iteratee, accumulator) { - var func = isArray(collection) ? arrayReduce : baseReduce, - initAccum = arguments.length < 3; - - return func(collection, getIteratee(iteratee, 4), accumulator, initAccum, baseEach); - } - - /** - * This method is like `_.reduce` except that it iterates over elements of - * `collection` from right to left. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @param {*} [accumulator] The initial value. - * @returns {*} Returns the accumulated value. - * @see _.reduce - * @example - * - * var array = [[0, 1], [2, 3], [4, 5]]; - * - * _.reduceRight(array, function(flattened, other) { - * return flattened.concat(other); - * }, []); - * // => [4, 5, 2, 3, 0, 1] - */ - function reduceRight(collection, iteratee, accumulator) { - var func = isArray(collection) ? arrayReduceRight : baseReduce, - initAccum = arguments.length < 3; - - return func(collection, getIteratee(iteratee, 4), accumulator, initAccum, baseEachRight); - } - - /** - * The opposite of `_.filter`; this method returns the elements of `collection` - * that `predicate` does **not** return truthy for. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @returns {Array} Returns the new filtered array. - * @see _.filter - * @example - * - * var users = [ - * { 'user': 'barney', 'age': 36, 'active': false }, - * { 'user': 'fred', 'age': 40, 'active': true } - * ]; - * - * _.reject(users, function(o) { return !o.active; }); - * // => objects for ['fred'] - * - * // The `_.matches` iteratee shorthand. - * _.reject(users, { 'age': 40, 'active': true }); - * // => objects for ['barney'] - * - * // The `_.matchesProperty` iteratee shorthand. - * _.reject(users, ['active', false]); - * // => objects for ['fred'] - * - * // The `_.property` iteratee shorthand. - * _.reject(users, 'active'); - * // => objects for ['barney'] - */ - function reject(collection, predicate) { - var func = isArray(collection) ? arrayFilter : baseFilter; - return func(collection, negate(getIteratee(predicate, 3))); - } - - /** - * Gets a random element from `collection`. - * - * @static - * @memberOf _ - * @since 2.0.0 - * @category Collection - * @param {Array|Object} collection The collection to sample. - * @returns {*} Returns the random element. - * @example - * - * _.sample([1, 2, 3, 4]); - * // => 2 - */ - function sample(collection) { - var func = isArray(collection) ? arraySample : baseSample; - return func(collection); - } - - /** - * Gets `n` random elements at unique keys from `collection` up to the - * size of `collection`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Collection - * @param {Array|Object} collection The collection to sample. - * @param {number} [n=1] The number of elements to sample. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {Array} Returns the random elements. - * @example - * - * _.sampleSize([1, 2, 3], 2); - * // => [3, 1] - * - * _.sampleSize([1, 2, 3], 4); - * // => [2, 3, 1] - */ - function sampleSize(collection, n, guard) { - if ((guard ? isIterateeCall(collection, n, guard) : n === undefined)) { - n = 1; - } else { - n = toInteger(n); - } - var func = isArray(collection) ? arraySampleSize : baseSampleSize; - return func(collection, n); - } - - /** - * Creates an array of shuffled values, using a version of the - * [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher-Yates_shuffle). - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object} collection The collection to shuffle. - * @returns {Array} Returns the new shuffled array. - * @example - * - * _.shuffle([1, 2, 3, 4]); - * // => [4, 1, 3, 2] - */ - function shuffle(collection) { - var func = isArray(collection) ? arrayShuffle : baseShuffle; - return func(collection); - } - - /** - * Gets the size of `collection` by returning its length for array-like - * values or the number of own enumerable string keyed properties for objects. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object|string} collection The collection to inspect. - * @returns {number} Returns the collection size. - * @example - * - * _.size([1, 2, 3]); - * // => 3 - * - * _.size({ 'a': 1, 'b': 2 }); - * // => 2 - * - * _.size('pebbles'); - * // => 7 - */ - function size(collection) { - if (collection == null) { - return 0; - } - if (isArrayLike(collection)) { - return isString(collection) ? stringSize(collection) : collection.length; - } - var tag = getTag(collection); - if (tag == mapTag || tag == setTag) { - return collection.size; - } - return baseKeys(collection).length; - } - - /** - * Checks if `predicate` returns truthy for **any** element of `collection`. - * Iteration is stopped once `predicate` returns truthy. The predicate is - * invoked with three arguments: (value, index|key, collection). - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {boolean} Returns `true` if any element passes the predicate check, - * else `false`. - * @example - * - * _.some([null, 0, 'yes', false], Boolean); - * // => true - * - * var users = [ - * { 'user': 'barney', 'active': true }, - * { 'user': 'fred', 'active': false } - * ]; - * - * // The `_.matches` iteratee shorthand. - * _.some(users, { 'user': 'barney', 'active': false }); - * // => false - * - * // The `_.matchesProperty` iteratee shorthand. - * _.some(users, ['active', false]); - * // => true - * - * // The `_.property` iteratee shorthand. - * _.some(users, 'active'); - * // => true - */ - function some(collection, predicate, guard) { - var func = isArray(collection) ? arraySome : baseSome; - if (guard && isIterateeCall(collection, predicate, guard)) { - predicate = undefined; - } - return func(collection, getIteratee(predicate, 3)); - } - - /** - * Creates an array of elements, sorted in ascending order by the results of - * running each element in a collection thru each iteratee. This method - * performs a stable sort, that is, it preserves the original sort order of - * equal elements. The iteratees are invoked with one argument: (value). - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Collection - * @param {Array|Object} collection The collection to iterate over. - * @param {...(Function|Function[])} [iteratees=[_.identity]] - * The iteratees to sort by. - * @returns {Array} Returns the new sorted array. - * @example - * - * var users = [ - * { 'user': 'fred', 'age': 48 }, - * { 'user': 'barney', 'age': 36 }, - * { 'user': 'fred', 'age': 40 }, - * { 'user': 'barney', 'age': 34 } - * ]; - * - * _.sortBy(users, [function(o) { return o.user; }]); - * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]] - * - * _.sortBy(users, ['user', 'age']); - * // => objects for [['barney', 34], ['barney', 36], ['fred', 40], ['fred', 48]] - */ - var sortBy = baseRest(function(collection, iteratees) { - if (collection == null) { - return []; - } - var length = iteratees.length; - if (length > 1 && isIterateeCall(collection, iteratees[0], iteratees[1])) { - iteratees = []; - } else if (length > 2 && isIterateeCall(iteratees[0], iteratees[1], iteratees[2])) { - iteratees = [iteratees[0]]; - } - return baseOrderBy(collection, baseFlatten(iteratees, 1), []); - }); - - /*------------------------------------------------------------------------*/ - - /** - * Gets the timestamp of the number of milliseconds that have elapsed since - * the Unix epoch (1 January 1970 00:00:00 UTC). - * - * @static - * @memberOf _ - * @since 2.4.0 - * @category Date - * @returns {number} Returns the timestamp. - * @example - * - * _.defer(function(stamp) { - * console.log(_.now() - stamp); - * }, _.now()); - * // => Logs the number of milliseconds it took for the deferred invocation. - */ - var now = ctxNow || function() { - return root.Date.now(); - }; - - /*------------------------------------------------------------------------*/ - - /** - * The opposite of `_.before`; this method creates a function that invokes - * `func` once it's called `n` or more times. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Function - * @param {number} n The number of calls before `func` is invoked. - * @param {Function} func The function to restrict. - * @returns {Function} Returns the new restricted function. - * @example - * - * var saves = ['profile', 'settings']; - * - * var done = _.after(saves.length, function() { - * console.log('done saving!'); - * }); - * - * _.forEach(saves, function(type) { - * asyncSave({ 'type': type, 'complete': done }); - * }); - * // => Logs 'done saving!' after the two async saves have completed. - */ - function after(n, func) { - if (typeof func != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - n = toInteger(n); - return function() { - if (--n < 1) { - return func.apply(this, arguments); - } - }; - } - - /** - * Creates a function that invokes `func`, with up to `n` arguments, - * ignoring any additional arguments. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Function - * @param {Function} func The function to cap arguments for. - * @param {number} [n=func.length] The arity cap. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {Function} Returns the new capped function. - * @example - * - * _.map(['6', '8', '10'], _.ary(parseInt, 1)); - * // => [6, 8, 10] - */ - function ary(func, n, guard) { - n = guard ? undefined : n; - n = (func && n == null) ? func.length : n; - return createWrap(func, WRAP_ARY_FLAG, undefined, undefined, undefined, undefined, n); - } - - /** - * Creates a function that invokes `func`, with the `this` binding and arguments - * of the created function, while it's called less than `n` times. Subsequent - * calls to the created function return the result of the last `func` invocation. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Function - * @param {number} n The number of calls at which `func` is no longer invoked. - * @param {Function} func The function to restrict. - * @returns {Function} Returns the new restricted function. - * @example - * - * jQuery(element).on('click', _.before(5, addContactToList)); - * // => Allows adding up to 4 contacts to the list. - */ - function before(n, func) { - var result; - if (typeof func != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - n = toInteger(n); - return function() { - if (--n > 0) { - result = func.apply(this, arguments); - } - if (n <= 1) { - func = undefined; - } - return result; - }; - } - - /** - * Creates a function that invokes `func` with the `this` binding of `thisArg` - * and `partials` prepended to the arguments it receives. - * - * The `_.bind.placeholder` value, which defaults to `_` in monolithic builds, - * may be used as a placeholder for partially applied arguments. - * - * **Note:** Unlike native `Function#bind`, this method doesn't set the "length" - * property of bound functions. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Function - * @param {Function} func The function to bind. - * @param {*} thisArg The `this` binding of `func`. - * @param {...*} [partials] The arguments to be partially applied. - * @returns {Function} Returns the new bound function. - * @example - * - * function greet(greeting, punctuation) { - * return greeting + ' ' + this.user + punctuation; - * } - * - * var object = { 'user': 'fred' }; - * - * var bound = _.bind(greet, object, 'hi'); - * bound('!'); - * // => 'hi fred!' - * - * // Bound with placeholders. - * var bound = _.bind(greet, object, _, '!'); - * bound('hi'); - * // => 'hi fred!' - */ - var bind = baseRest(function(func, thisArg, partials) { - var bitmask = WRAP_BIND_FLAG; - if (partials.length) { - var holders = replaceHolders(partials, getHolder(bind)); - bitmask |= WRAP_PARTIAL_FLAG; - } - return createWrap(func, bitmask, thisArg, partials, holders); - }); - - /** - * Creates a function that invokes the method at `object[key]` with `partials` - * prepended to the arguments it receives. - * - * This method differs from `_.bind` by allowing bound functions to reference - * methods that may be redefined or don't yet exist. See - * [Peter Michaux's article](http://peter.michaux.ca/articles/lazy-function-definition-pattern) - * for more details. - * - * The `_.bindKey.placeholder` value, which defaults to `_` in monolithic - * builds, may be used as a placeholder for partially applied arguments. - * - * @static - * @memberOf _ - * @since 0.10.0 - * @category Function - * @param {Object} object The object to invoke the method on. - * @param {string} key The key of the method. - * @param {...*} [partials] The arguments to be partially applied. - * @returns {Function} Returns the new bound function. - * @example - * - * var object = { - * 'user': 'fred', - * 'greet': function(greeting, punctuation) { - * return greeting + ' ' + this.user + punctuation; - * } - * }; - * - * var bound = _.bindKey(object, 'greet', 'hi'); - * bound('!'); - * // => 'hi fred!' - * - * object.greet = function(greeting, punctuation) { - * return greeting + 'ya ' + this.user + punctuation; - * }; - * - * bound('!'); - * // => 'hiya fred!' - * - * // Bound with placeholders. - * var bound = _.bindKey(object, 'greet', _, '!'); - * bound('hi'); - * // => 'hiya fred!' - */ - var bindKey = baseRest(function(object, key, partials) { - var bitmask = WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG; - if (partials.length) { - var holders = replaceHolders(partials, getHolder(bindKey)); - bitmask |= WRAP_PARTIAL_FLAG; - } - return createWrap(key, bitmask, object, partials, holders); - }); - - /** - * Creates a function that accepts arguments of `func` and either invokes - * `func` returning its result, if at least `arity` number of arguments have - * been provided, or returns a function that accepts the remaining `func` - * arguments, and so on. The arity of `func` may be specified if `func.length` - * is not sufficient. - * - * The `_.curry.placeholder` value, which defaults to `_` in monolithic builds, - * may be used as a placeholder for provided arguments. - * - * **Note:** This method doesn't set the "length" property of curried functions. - * - * @static - * @memberOf _ - * @since 2.0.0 - * @category Function - * @param {Function} func The function to curry. - * @param {number} [arity=func.length] The arity of `func`. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {Function} Returns the new curried function. - * @example - * - * var abc = function(a, b, c) { - * return [a, b, c]; - * }; - * - * var curried = _.curry(abc); - * - * curried(1)(2)(3); - * // => [1, 2, 3] - * - * curried(1, 2)(3); - * // => [1, 2, 3] - * - * curried(1, 2, 3); - * // => [1, 2, 3] - * - * // Curried with placeholders. - * curried(1)(_, 3)(2); - * // => [1, 2, 3] - */ - function curry(func, arity, guard) { - arity = guard ? undefined : arity; - var result = createWrap(func, WRAP_CURRY_FLAG, undefined, undefined, undefined, undefined, undefined, arity); - result.placeholder = curry.placeholder; - return result; - } - - /** - * This method is like `_.curry` except that arguments are applied to `func` - * in the manner of `_.partialRight` instead of `_.partial`. - * - * The `_.curryRight.placeholder` value, which defaults to `_` in monolithic - * builds, may be used as a placeholder for provided arguments. - * - * **Note:** This method doesn't set the "length" property of curried functions. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Function - * @param {Function} func The function to curry. - * @param {number} [arity=func.length] The arity of `func`. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {Function} Returns the new curried function. - * @example - * - * var abc = function(a, b, c) { - * return [a, b, c]; - * }; - * - * var curried = _.curryRight(abc); - * - * curried(3)(2)(1); - * // => [1, 2, 3] - * - * curried(2, 3)(1); - * // => [1, 2, 3] - * - * curried(1, 2, 3); - * // => [1, 2, 3] - * - * // Curried with placeholders. - * curried(3)(1, _)(2); - * // => [1, 2, 3] - */ - function curryRight(func, arity, guard) { - arity = guard ? undefined : arity; - var result = createWrap(func, WRAP_CURRY_RIGHT_FLAG, undefined, undefined, undefined, undefined, undefined, arity); - result.placeholder = curryRight.placeholder; - return result; - } - - /** - * Creates a debounced function that delays invoking `func` until after `wait` - * milliseconds have elapsed since the last time the debounced function was - * invoked. The debounced function comes with a `cancel` method to cancel - * delayed `func` invocations and a `flush` method to immediately invoke them. - * Provide `options` to indicate whether `func` should be invoked on the - * leading and/or trailing edge of the `wait` timeout. The `func` is invoked - * with the last arguments provided to the debounced function. Subsequent - * calls to the debounced function return the result of the last `func` - * invocation. - * - * **Note:** If `leading` and `trailing` options are `true`, `func` is - * invoked on the trailing edge of the timeout only if the debounced function - * is invoked more than once during the `wait` timeout. - * - * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred - * until to the next tick, similar to `setTimeout` with a timeout of `0`. - * - * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/) - * for details over the differences between `_.debounce` and `_.throttle`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Function - * @param {Function} func The function to debounce. - * @param {number} [wait=0] The number of milliseconds to delay. - * @param {Object} [options={}] The options object. - * @param {boolean} [options.leading=false] - * Specify invoking on the leading edge of the timeout. - * @param {number} [options.maxWait] - * The maximum time `func` is allowed to be delayed before it's invoked. - * @param {boolean} [options.trailing=true] - * Specify invoking on the trailing edge of the timeout. - * @returns {Function} Returns the new debounced function. - * @example - * - * // Avoid costly calculations while the window size is in flux. - * jQuery(window).on('resize', _.debounce(calculateLayout, 150)); - * - * // Invoke `sendMail` when clicked, debouncing subsequent calls. - * jQuery(element).on('click', _.debounce(sendMail, 300, { - * 'leading': true, - * 'trailing': false - * })); - * - * // Ensure `batchLog` is invoked once after 1 second of debounced calls. - * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 }); - * var source = new EventSource('/stream'); - * jQuery(source).on('message', debounced); - * - * // Cancel the trailing debounced invocation. - * jQuery(window).on('popstate', debounced.cancel); - */ - function debounce(func, wait, options) { - var lastArgs, - lastThis, - maxWait, - result, - timerId, - lastCallTime, - lastInvokeTime = 0, - leading = false, - maxing = false, - trailing = true; - - if (typeof func != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - wait = toNumber(wait) || 0; - if (isObject(options)) { - leading = !!options.leading; - maxing = 'maxWait' in options; - maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait; - trailing = 'trailing' in options ? !!options.trailing : trailing; - } - - function invokeFunc(time) { - var args = lastArgs, - thisArg = lastThis; - - lastArgs = lastThis = undefined; - lastInvokeTime = time; - result = func.apply(thisArg, args); - return result; - } - - function leadingEdge(time) { - // Reset any `maxWait` timer. - lastInvokeTime = time; - // Start the timer for the trailing edge. - timerId = setTimeout(timerExpired, wait); - // Invoke the leading edge. - return leading ? invokeFunc(time) : result; - } - - function remainingWait(time) { - var timeSinceLastCall = time - lastCallTime, - timeSinceLastInvoke = time - lastInvokeTime, - timeWaiting = wait - timeSinceLastCall; - - return maxing - ? nativeMin(timeWaiting, maxWait - timeSinceLastInvoke) - : timeWaiting; - } - - function shouldInvoke(time) { - var timeSinceLastCall = time - lastCallTime, - timeSinceLastInvoke = time - lastInvokeTime; - - // Either this is the first call, activity has stopped and we're at the - // trailing edge, the system time has gone backwards and we're treating - // it as the trailing edge, or we've hit the `maxWait` limit. - return (lastCallTime === undefined || (timeSinceLastCall >= wait) || - (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait)); - } - - function timerExpired() { - var time = now(); - if (shouldInvoke(time)) { - return trailingEdge(time); - } - // Restart the timer. - timerId = setTimeout(timerExpired, remainingWait(time)); - } - - function trailingEdge(time) { - timerId = undefined; - - // Only invoke if we have `lastArgs` which means `func` has been - // debounced at least once. - if (trailing && lastArgs) { - return invokeFunc(time); - } - lastArgs = lastThis = undefined; - return result; - } - - function cancel() { - if (timerId !== undefined) { - clearTimeout(timerId); - } - lastInvokeTime = 0; - lastArgs = lastCallTime = lastThis = timerId = undefined; - } - - function flush() { - return timerId === undefined ? result : trailingEdge(now()); - } - - function debounced() { - var time = now(), - isInvoking = shouldInvoke(time); - - lastArgs = arguments; - lastThis = this; - lastCallTime = time; - - if (isInvoking) { - if (timerId === undefined) { - return leadingEdge(lastCallTime); - } - if (maxing) { - // Handle invocations in a tight loop. - timerId = setTimeout(timerExpired, wait); - return invokeFunc(lastCallTime); - } - } - if (timerId === undefined) { - timerId = setTimeout(timerExpired, wait); - } - return result; - } - debounced.cancel = cancel; - debounced.flush = flush; - return debounced; - } - - /** - * Defers invoking the `func` until the current call stack has cleared. Any - * additional arguments are provided to `func` when it's invoked. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Function - * @param {Function} func The function to defer. - * @param {...*} [args] The arguments to invoke `func` with. - * @returns {number} Returns the timer id. - * @example - * - * _.defer(function(text) { - * console.log(text); - * }, 'deferred'); - * // => Logs 'deferred' after one millisecond. - */ - var defer = baseRest(function(func, args) { - return baseDelay(func, 1, args); - }); - - /** - * Invokes `func` after `wait` milliseconds. Any additional arguments are - * provided to `func` when it's invoked. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Function - * @param {Function} func The function to delay. - * @param {number} wait The number of milliseconds to delay invocation. - * @param {...*} [args] The arguments to invoke `func` with. - * @returns {number} Returns the timer id. - * @example - * - * _.delay(function(text) { - * console.log(text); - * }, 1000, 'later'); - * // => Logs 'later' after one second. - */ - var delay = baseRest(function(func, wait, args) { - return baseDelay(func, toNumber(wait) || 0, args); - }); - - /** - * Creates a function that invokes `func` with arguments reversed. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Function - * @param {Function} func The function to flip arguments for. - * @returns {Function} Returns the new flipped function. - * @example - * - * var flipped = _.flip(function() { - * return _.toArray(arguments); - * }); - * - * flipped('a', 'b', 'c', 'd'); - * // => ['d', 'c', 'b', 'a'] - */ - function flip(func) { - return createWrap(func, WRAP_FLIP_FLAG); - } - - /** - * Creates a function that memoizes the result of `func`. If `resolver` is - * provided, it determines the cache key for storing the result based on the - * arguments provided to the memoized function. By default, the first argument - * provided to the memoized function is used as the map cache key. The `func` - * is invoked with the `this` binding of the memoized function. - * - * **Note:** The cache is exposed as the `cache` property on the memoized - * function. Its creation may be customized by replacing the `_.memoize.Cache` - * constructor with one whose instances implement the - * [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object) - * method interface of `clear`, `delete`, `get`, `has`, and `set`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Function - * @param {Function} func The function to have its output memoized. - * @param {Function} [resolver] The function to resolve the cache key. - * @returns {Function} Returns the new memoized function. - * @example - * - * var object = { 'a': 1, 'b': 2 }; - * var other = { 'c': 3, 'd': 4 }; - * - * var values = _.memoize(_.values); - * values(object); - * // => [1, 2] - * - * values(other); - * // => [3, 4] - * - * object.a = 2; - * values(object); - * // => [1, 2] - * - * // Modify the result cache. - * values.cache.set(object, ['a', 'b']); - * values(object); - * // => ['a', 'b'] - * - * // Replace `_.memoize.Cache`. - * _.memoize.Cache = WeakMap; - */ - function memoize(func, resolver) { - if (typeof func != 'function' || (resolver != null && typeof resolver != 'function')) { - throw new TypeError(FUNC_ERROR_TEXT); - } - var memoized = function() { - var args = arguments, - key = resolver ? resolver.apply(this, args) : args[0], - cache = memoized.cache; - - if (cache.has(key)) { - return cache.get(key); - } - var result = func.apply(this, args); - memoized.cache = cache.set(key, result) || cache; - return result; - }; - memoized.cache = new (memoize.Cache || MapCache); - return memoized; - } - - // Expose `MapCache`. - memoize.Cache = MapCache; - - /** - * Creates a function that negates the result of the predicate `func`. The - * `func` predicate is invoked with the `this` binding and arguments of the - * created function. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Function - * @param {Function} predicate The predicate to negate. - * @returns {Function} Returns the new negated function. - * @example - * - * function isEven(n) { - * return n % 2 == 0; - * } - * - * _.filter([1, 2, 3, 4, 5, 6], _.negate(isEven)); - * // => [1, 3, 5] - */ - function negate(predicate) { - if (typeof predicate != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - return function() { - var args = arguments; - switch (args.length) { - case 0: return !predicate.call(this); - case 1: return !predicate.call(this, args[0]); - case 2: return !predicate.call(this, args[0], args[1]); - case 3: return !predicate.call(this, args[0], args[1], args[2]); - } - return !predicate.apply(this, args); - }; - } - - /** - * Creates a function that is restricted to invoking `func` once. Repeat calls - * to the function return the value of the first invocation. The `func` is - * invoked with the `this` binding and arguments of the created function. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Function - * @param {Function} func The function to restrict. - * @returns {Function} Returns the new restricted function. - * @example - * - * var initialize = _.once(createApplication); - * initialize(); - * initialize(); - * // => `createApplication` is invoked once - */ - function once(func) { - return before(2, func); - } - - /** - * Creates a function that invokes `func` with its arguments transformed. - * - * @static - * @since 4.0.0 - * @memberOf _ - * @category Function - * @param {Function} func The function to wrap. - * @param {...(Function|Function[])} [transforms=[_.identity]] - * The argument transforms. - * @returns {Function} Returns the new function. - * @example - * - * function doubled(n) { - * return n * 2; - * } - * - * function square(n) { - * return n * n; - * } - * - * var func = _.overArgs(function(x, y) { - * return [x, y]; - * }, [square, doubled]); - * - * func(9, 3); - * // => [81, 6] - * - * func(10, 5); - * // => [100, 10] - */ - var overArgs = castRest(function(func, transforms) { - transforms = (transforms.length == 1 && isArray(transforms[0])) - ? arrayMap(transforms[0], baseUnary(getIteratee())) - : arrayMap(baseFlatten(transforms, 1), baseUnary(getIteratee())); - - var funcsLength = transforms.length; - return baseRest(function(args) { - var index = -1, - length = nativeMin(args.length, funcsLength); - - while (++index < length) { - args[index] = transforms[index].call(this, args[index]); - } - return apply(func, this, args); - }); - }); - - /** - * Creates a function that invokes `func` with `partials` prepended to the - * arguments it receives. This method is like `_.bind` except it does **not** - * alter the `this` binding. - * - * The `_.partial.placeholder` value, which defaults to `_` in monolithic - * builds, may be used as a placeholder for partially applied arguments. - * - * **Note:** This method doesn't set the "length" property of partially - * applied functions. - * - * @static - * @memberOf _ - * @since 0.2.0 - * @category Function - * @param {Function} func The function to partially apply arguments to. - * @param {...*} [partials] The arguments to be partially applied. - * @returns {Function} Returns the new partially applied function. - * @example - * - * function greet(greeting, name) { - * return greeting + ' ' + name; - * } - * - * var sayHelloTo = _.partial(greet, 'hello'); - * sayHelloTo('fred'); - * // => 'hello fred' - * - * // Partially applied with placeholders. - * var greetFred = _.partial(greet, _, 'fred'); - * greetFred('hi'); - * // => 'hi fred' - */ - var partial = baseRest(function(func, partials) { - var holders = replaceHolders(partials, getHolder(partial)); - return createWrap(func, WRAP_PARTIAL_FLAG, undefined, partials, holders); - }); - - /** - * This method is like `_.partial` except that partially applied arguments - * are appended to the arguments it receives. - * - * The `_.partialRight.placeholder` value, which defaults to `_` in monolithic - * builds, may be used as a placeholder for partially applied arguments. - * - * **Note:** This method doesn't set the "length" property of partially - * applied functions. - * - * @static - * @memberOf _ - * @since 1.0.0 - * @category Function - * @param {Function} func The function to partially apply arguments to. - * @param {...*} [partials] The arguments to be partially applied. - * @returns {Function} Returns the new partially applied function. - * @example - * - * function greet(greeting, name) { - * return greeting + ' ' + name; - * } - * - * var greetFred = _.partialRight(greet, 'fred'); - * greetFred('hi'); - * // => 'hi fred' - * - * // Partially applied with placeholders. - * var sayHelloTo = _.partialRight(greet, 'hello', _); - * sayHelloTo('fred'); - * // => 'hello fred' - */ - var partialRight = baseRest(function(func, partials) { - var holders = replaceHolders(partials, getHolder(partialRight)); - return createWrap(func, WRAP_PARTIAL_RIGHT_FLAG, undefined, partials, holders); - }); - - /** - * Creates a function that invokes `func` with arguments arranged according - * to the specified `indexes` where the argument value at the first index is - * provided as the first argument, the argument value at the second index is - * provided as the second argument, and so on. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Function - * @param {Function} func The function to rearrange arguments for. - * @param {...(number|number[])} indexes The arranged argument indexes. - * @returns {Function} Returns the new function. - * @example - * - * var rearged = _.rearg(function(a, b, c) { - * return [a, b, c]; - * }, [2, 0, 1]); - * - * rearged('b', 'c', 'a') - * // => ['a', 'b', 'c'] - */ - var rearg = flatRest(function(func, indexes) { - return createWrap(func, WRAP_REARG_FLAG, undefined, undefined, undefined, indexes); - }); - - /** - * Creates a function that invokes `func` with the `this` binding of the - * created function and arguments from `start` and beyond provided as - * an array. - * - * **Note:** This method is based on the - * [rest parameter](https://mdn.io/rest_parameters). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Function - * @param {Function} func The function to apply a rest parameter to. - * @param {number} [start=func.length-1] The start position of the rest parameter. - * @returns {Function} Returns the new function. - * @example - * - * var say = _.rest(function(what, names) { - * return what + ' ' + _.initial(names).join(', ') + - * (_.size(names) > 1 ? ', & ' : '') + _.last(names); - * }); - * - * say('hello', 'fred', 'barney', 'pebbles'); - * // => 'hello fred, barney, & pebbles' - */ - function rest(func, start) { - if (typeof func != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - start = start === undefined ? start : toInteger(start); - return baseRest(func, start); - } - - /** - * Creates a function that invokes `func` with the `this` binding of the - * create function and an array of arguments much like - * [`Function#apply`](http://www.ecma-international.org/ecma-262/7.0/#sec-function.prototype.apply). - * - * **Note:** This method is based on the - * [spread operator](https://mdn.io/spread_operator). - * - * @static - * @memberOf _ - * @since 3.2.0 - * @category Function - * @param {Function} func The function to spread arguments over. - * @param {number} [start=0] The start position of the spread. - * @returns {Function} Returns the new function. - * @example - * - * var say = _.spread(function(who, what) { - * return who + ' says ' + what; - * }); - * - * say(['fred', 'hello']); - * // => 'fred says hello' - * - * var numbers = Promise.all([ - * Promise.resolve(40), - * Promise.resolve(36) - * ]); - * - * numbers.then(_.spread(function(x, y) { - * return x + y; - * })); - * // => a Promise of 76 - */ - function spread(func, start) { - if (typeof func != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - start = start == null ? 0 : nativeMax(toInteger(start), 0); - return baseRest(function(args) { - var array = args[start], - otherArgs = castSlice(args, 0, start); - - if (array) { - arrayPush(otherArgs, array); - } - return apply(func, this, otherArgs); - }); - } - - /** - * Creates a throttled function that only invokes `func` at most once per - * every `wait` milliseconds. The throttled function comes with a `cancel` - * method to cancel delayed `func` invocations and a `flush` method to - * immediately invoke them. Provide `options` to indicate whether `func` - * should be invoked on the leading and/or trailing edge of the `wait` - * timeout. The `func` is invoked with the last arguments provided to the - * throttled function. Subsequent calls to the throttled function return the - * result of the last `func` invocation. - * - * **Note:** If `leading` and `trailing` options are `true`, `func` is - * invoked on the trailing edge of the timeout only if the throttled function - * is invoked more than once during the `wait` timeout. - * - * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred - * until to the next tick, similar to `setTimeout` with a timeout of `0`. - * - * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/) - * for details over the differences between `_.throttle` and `_.debounce`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Function - * @param {Function} func The function to throttle. - * @param {number} [wait=0] The number of milliseconds to throttle invocations to. - * @param {Object} [options={}] The options object. - * @param {boolean} [options.leading=true] - * Specify invoking on the leading edge of the timeout. - * @param {boolean} [options.trailing=true] - * Specify invoking on the trailing edge of the timeout. - * @returns {Function} Returns the new throttled function. - * @example - * - * // Avoid excessively updating the position while scrolling. - * jQuery(window).on('scroll', _.throttle(updatePosition, 100)); - * - * // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes. - * var throttled = _.throttle(renewToken, 300000, { 'trailing': false }); - * jQuery(element).on('click', throttled); - * - * // Cancel the trailing throttled invocation. - * jQuery(window).on('popstate', throttled.cancel); - */ - function throttle(func, wait, options) { - var leading = true, - trailing = true; - - if (typeof func != 'function') { - throw new TypeError(FUNC_ERROR_TEXT); - } - if (isObject(options)) { - leading = 'leading' in options ? !!options.leading : leading; - trailing = 'trailing' in options ? !!options.trailing : trailing; - } - return debounce(func, wait, { - 'leading': leading, - 'maxWait': wait, - 'trailing': trailing - }); - } - - /** - * Creates a function that accepts up to one argument, ignoring any - * additional arguments. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Function - * @param {Function} func The function to cap arguments for. - * @returns {Function} Returns the new capped function. - * @example - * - * _.map(['6', '8', '10'], _.unary(parseInt)); - * // => [6, 8, 10] - */ - function unary(func) { - return ary(func, 1); - } - - /** - * Creates a function that provides `value` to `wrapper` as its first - * argument. Any additional arguments provided to the function are appended - * to those provided to the `wrapper`. The wrapper is invoked with the `this` - * binding of the created function. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Function - * @param {*} value The value to wrap. - * @param {Function} [wrapper=identity] The wrapper function. - * @returns {Function} Returns the new function. - * @example - * - * var p = _.wrap(_.escape, function(func, text) { - * return '

' + func(text) + '

'; - * }); - * - * p('fred, barney, & pebbles'); - * // => '

fred, barney, & pebbles

' - */ - function wrap(value, wrapper) { - return partial(castFunction(wrapper), value); - } - - /*------------------------------------------------------------------------*/ - - /** - * Casts `value` as an array if it's not one. - * - * @static - * @memberOf _ - * @since 4.4.0 - * @category Lang - * @param {*} value The value to inspect. - * @returns {Array} Returns the cast array. - * @example - * - * _.castArray(1); - * // => [1] - * - * _.castArray({ 'a': 1 }); - * // => [{ 'a': 1 }] - * - * _.castArray('abc'); - * // => ['abc'] - * - * _.castArray(null); - * // => [null] - * - * _.castArray(undefined); - * // => [undefined] - * - * _.castArray(); - * // => [] - * - * var array = [1, 2, 3]; - * console.log(_.castArray(array) === array); - * // => true - */ - function castArray() { - if (!arguments.length) { - return []; - } - var value = arguments[0]; - return isArray(value) ? value : [value]; - } - - /** - * Creates a shallow clone of `value`. - * - * **Note:** This method is loosely based on the - * [structured clone algorithm](https://mdn.io/Structured_clone_algorithm) - * and supports cloning arrays, array buffers, booleans, date objects, maps, - * numbers, `Object` objects, regexes, sets, strings, symbols, and typed - * arrays. The own enumerable properties of `arguments` objects are cloned - * as plain objects. An empty object is returned for uncloneable values such - * as error objects, functions, DOM nodes, and WeakMaps. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to clone. - * @returns {*} Returns the cloned value. - * @see _.cloneDeep - * @example - * - * var objects = [{ 'a': 1 }, { 'b': 2 }]; - * - * var shallow = _.clone(objects); - * console.log(shallow[0] === objects[0]); - * // => true - */ - function clone(value) { - return baseClone(value, CLONE_SYMBOLS_FLAG); - } - - /** - * This method is like `_.clone` except that it accepts `customizer` which - * is invoked to produce the cloned value. If `customizer` returns `undefined`, - * cloning is handled by the method instead. The `customizer` is invoked with - * up to four arguments; (value [, index|key, object, stack]). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to clone. - * @param {Function} [customizer] The function to customize cloning. - * @returns {*} Returns the cloned value. - * @see _.cloneDeepWith - * @example - * - * function customizer(value) { - * if (_.isElement(value)) { - * return value.cloneNode(false); - * } - * } - * - * var el = _.cloneWith(document.body, customizer); - * - * console.log(el === document.body); - * // => false - * console.log(el.nodeName); - * // => 'BODY' - * console.log(el.childNodes.length); - * // => 0 - */ - function cloneWith(value, customizer) { - customizer = typeof customizer == 'function' ? customizer : undefined; - return baseClone(value, CLONE_SYMBOLS_FLAG, customizer); - } - - /** - * This method is like `_.clone` except that it recursively clones `value`. - * - * @static - * @memberOf _ - * @since 1.0.0 - * @category Lang - * @param {*} value The value to recursively clone. - * @returns {*} Returns the deep cloned value. - * @see _.clone - * @example - * - * var objects = [{ 'a': 1 }, { 'b': 2 }]; - * - * var deep = _.cloneDeep(objects); - * console.log(deep[0] === objects[0]); - * // => false - */ - function cloneDeep(value) { - return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG); - } - - /** - * This method is like `_.cloneWith` except that it recursively clones `value`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to recursively clone. - * @param {Function} [customizer] The function to customize cloning. - * @returns {*} Returns the deep cloned value. - * @see _.cloneWith - * @example - * - * function customizer(value) { - * if (_.isElement(value)) { - * return value.cloneNode(true); - * } - * } - * - * var el = _.cloneDeepWith(document.body, customizer); - * - * console.log(el === document.body); - * // => false - * console.log(el.nodeName); - * // => 'BODY' - * console.log(el.childNodes.length); - * // => 20 - */ - function cloneDeepWith(value, customizer) { - customizer = typeof customizer == 'function' ? customizer : undefined; - return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG, customizer); - } - - /** - * Checks if `object` conforms to `source` by invoking the predicate - * properties of `source` with the corresponding property values of `object`. - * - * **Note:** This method is equivalent to `_.conforms` when `source` is - * partially applied. - * - * @static - * @memberOf _ - * @since 4.14.0 - * @category Lang - * @param {Object} object The object to inspect. - * @param {Object} source The object of property predicates to conform to. - * @returns {boolean} Returns `true` if `object` conforms, else `false`. - * @example - * - * var object = { 'a': 1, 'b': 2 }; - * - * _.conformsTo(object, { 'b': function(n) { return n > 1; } }); - * // => true - * - * _.conformsTo(object, { 'b': function(n) { return n > 2; } }); - * // => false - */ - function conformsTo(object, source) { - return source == null || baseConformsTo(object, source, keys(source)); - } - - /** - * Performs a - * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * comparison between two values to determine if they are equivalent. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {boolean} Returns `true` if the values are equivalent, else `false`. - * @example - * - * var object = { 'a': 1 }; - * var other = { 'a': 1 }; - * - * _.eq(object, object); - * // => true - * - * _.eq(object, other); - * // => false - * - * _.eq('a', 'a'); - * // => true - * - * _.eq('a', Object('a')); - * // => false - * - * _.eq(NaN, NaN); - * // => true - */ - function eq(value, other) { - return value === other || (value !== value && other !== other); - } - - /** - * Checks if `value` is greater than `other`. - * - * @static - * @memberOf _ - * @since 3.9.0 - * @category Lang - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {boolean} Returns `true` if `value` is greater than `other`, - * else `false`. - * @see _.lt - * @example - * - * _.gt(3, 1); - * // => true - * - * _.gt(3, 3); - * // => false - * - * _.gt(1, 3); - * // => false - */ - var gt = createRelationalOperation(baseGt); - - /** - * Checks if `value` is greater than or equal to `other`. - * - * @static - * @memberOf _ - * @since 3.9.0 - * @category Lang - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {boolean} Returns `true` if `value` is greater than or equal to - * `other`, else `false`. - * @see _.lte - * @example - * - * _.gte(3, 1); - * // => true - * - * _.gte(3, 3); - * // => true - * - * _.gte(1, 3); - * // => false - */ - var gte = createRelationalOperation(function(value, other) { - return value >= other; - }); - - /** - * Checks if `value` is likely an `arguments` object. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an `arguments` object, - * else `false`. - * @example - * - * _.isArguments(function() { return arguments; }()); - * // => true - * - * _.isArguments([1, 2, 3]); - * // => false - */ - var isArguments = baseIsArguments(function() { return arguments; }()) ? baseIsArguments : function(value) { - return isObjectLike(value) && hasOwnProperty.call(value, 'callee') && - !propertyIsEnumerable.call(value, 'callee'); - }; - - /** - * Checks if `value` is classified as an `Array` object. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an array, else `false`. - * @example - * - * _.isArray([1, 2, 3]); - * // => true - * - * _.isArray(document.body.children); - * // => false - * - * _.isArray('abc'); - * // => false - * - * _.isArray(_.noop); - * // => false - */ - var isArray = Array.isArray; - - /** - * Checks if `value` is classified as an `ArrayBuffer` object. - * - * @static - * @memberOf _ - * @since 4.3.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an array buffer, else `false`. - * @example - * - * _.isArrayBuffer(new ArrayBuffer(2)); - * // => true - * - * _.isArrayBuffer(new Array(2)); - * // => false - */ - var isArrayBuffer = nodeIsArrayBuffer ? baseUnary(nodeIsArrayBuffer) : baseIsArrayBuffer; - - /** - * Checks if `value` is array-like. A value is considered array-like if it's - * not a function and has a `value.length` that's an integer greater than or - * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is array-like, else `false`. - * @example - * - * _.isArrayLike([1, 2, 3]); - * // => true - * - * _.isArrayLike(document.body.children); - * // => true - * - * _.isArrayLike('abc'); - * // => true - * - * _.isArrayLike(_.noop); - * // => false - */ - function isArrayLike(value) { - return value != null && isLength(value.length) && !isFunction(value); - } - - /** - * This method is like `_.isArrayLike` except that it also checks if `value` - * is an object. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an array-like object, - * else `false`. - * @example - * - * _.isArrayLikeObject([1, 2, 3]); - * // => true - * - * _.isArrayLikeObject(document.body.children); - * // => true - * - * _.isArrayLikeObject('abc'); - * // => false - * - * _.isArrayLikeObject(_.noop); - * // => false - */ - function isArrayLikeObject(value) { - return isObjectLike(value) && isArrayLike(value); - } - - /** - * Checks if `value` is classified as a boolean primitive or object. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a boolean, else `false`. - * @example - * - * _.isBoolean(false); - * // => true - * - * _.isBoolean(null); - * // => false - */ - function isBoolean(value) { - return value === true || value === false || - (isObjectLike(value) && baseGetTag(value) == boolTag); - } - - /** - * Checks if `value` is a buffer. - * - * @static - * @memberOf _ - * @since 4.3.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a buffer, else `false`. - * @example - * - * _.isBuffer(new Buffer(2)); - * // => true - * - * _.isBuffer(new Uint8Array(2)); - * // => false - */ - var isBuffer = nativeIsBuffer || stubFalse; - - /** - * Checks if `value` is classified as a `Date` object. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a date object, else `false`. - * @example - * - * _.isDate(new Date); - * // => true - * - * _.isDate('Mon April 23 2012'); - * // => false - */ - var isDate = nodeIsDate ? baseUnary(nodeIsDate) : baseIsDate; - - /** - * Checks if `value` is likely a DOM element. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a DOM element, else `false`. - * @example - * - * _.isElement(document.body); - * // => true - * - * _.isElement(''); - * // => false - */ - function isElement(value) { - return isObjectLike(value) && value.nodeType === 1 && !isPlainObject(value); - } - - /** - * Checks if `value` is an empty object, collection, map, or set. - * - * Objects are considered empty if they have no own enumerable string keyed - * properties. - * - * Array-like values such as `arguments` objects, arrays, buffers, strings, or - * jQuery-like collections are considered empty if they have a `length` of `0`. - * Similarly, maps and sets are considered empty if they have a `size` of `0`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is empty, else `false`. - * @example - * - * _.isEmpty(null); - * // => true - * - * _.isEmpty(true); - * // => true - * - * _.isEmpty(1); - * // => true - * - * _.isEmpty([1, 2, 3]); - * // => false - * - * _.isEmpty({ 'a': 1 }); - * // => false - */ - function isEmpty(value) { - if (value == null) { - return true; - } - if (isArrayLike(value) && - (isArray(value) || typeof value == 'string' || typeof value.splice == 'function' || - isBuffer(value) || isTypedArray(value) || isArguments(value))) { - return !value.length; - } - var tag = getTag(value); - if (tag == mapTag || tag == setTag) { - return !value.size; - } - if (isPrototype(value)) { - return !baseKeys(value).length; - } - for (var key in value) { - if (hasOwnProperty.call(value, key)) { - return false; - } - } - return true; - } - - /** - * Performs a deep comparison between two values to determine if they are - * equivalent. - * - * **Note:** This method supports comparing arrays, array buffers, booleans, - * date objects, error objects, maps, numbers, `Object` objects, regexes, - * sets, strings, symbols, and typed arrays. `Object` objects are compared - * by their own, not inherited, enumerable properties. Functions and DOM - * nodes are compared by strict equality, i.e. `===`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {boolean} Returns `true` if the values are equivalent, else `false`. - * @example - * - * var object = { 'a': 1 }; - * var other = { 'a': 1 }; - * - * _.isEqual(object, other); - * // => true - * - * object === other; - * // => false - */ - function isEqual(value, other) { - return baseIsEqual(value, other); - } - - /** - * This method is like `_.isEqual` except that it accepts `customizer` which - * is invoked to compare values. If `customizer` returns `undefined`, comparisons - * are handled by the method instead. The `customizer` is invoked with up to - * six arguments: (objValue, othValue [, index|key, object, other, stack]). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @param {Function} [customizer] The function to customize comparisons. - * @returns {boolean} Returns `true` if the values are equivalent, else `false`. - * @example - * - * function isGreeting(value) { - * return /^h(?:i|ello)$/.test(value); - * } - * - * function customizer(objValue, othValue) { - * if (isGreeting(objValue) && isGreeting(othValue)) { - * return true; - * } - * } - * - * var array = ['hello', 'goodbye']; - * var other = ['hi', 'goodbye']; - * - * _.isEqualWith(array, other, customizer); - * // => true - */ - function isEqualWith(value, other, customizer) { - customizer = typeof customizer == 'function' ? customizer : undefined; - var result = customizer ? customizer(value, other) : undefined; - return result === undefined ? baseIsEqual(value, other, undefined, customizer) : !!result; - } - - /** - * Checks if `value` is an `Error`, `EvalError`, `RangeError`, `ReferenceError`, - * `SyntaxError`, `TypeError`, or `URIError` object. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an error object, else `false`. - * @example - * - * _.isError(new Error); - * // => true - * - * _.isError(Error); - * // => false - */ - function isError(value) { - if (!isObjectLike(value)) { - return false; - } - var tag = baseGetTag(value); - return tag == errorTag || tag == domExcTag || - (typeof value.message == 'string' && typeof value.name == 'string' && !isPlainObject(value)); - } - - /** - * Checks if `value` is a finite primitive number. - * - * **Note:** This method is based on - * [`Number.isFinite`](https://mdn.io/Number/isFinite). - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a finite number, else `false`. - * @example - * - * _.isFinite(3); - * // => true - * - * _.isFinite(Number.MIN_VALUE); - * // => true - * - * _.isFinite(Infinity); - * // => false - * - * _.isFinite('3'); - * // => false - */ - function isFinite(value) { - return typeof value == 'number' && nativeIsFinite(value); - } - - /** - * Checks if `value` is classified as a `Function` object. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a function, else `false`. - * @example - * - * _.isFunction(_); - * // => true - * - * _.isFunction(/abc/); - * // => false - */ - function isFunction(value) { - if (!isObject(value)) { - return false; - } - // The use of `Object#toString` avoids issues with the `typeof` operator - // in Safari 9 which returns 'object' for typed arrays and other constructors. - var tag = baseGetTag(value); - return tag == funcTag || tag == genTag || tag == asyncTag || tag == proxyTag; - } - - /** - * Checks if `value` is an integer. - * - * **Note:** This method is based on - * [`Number.isInteger`](https://mdn.io/Number/isInteger). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an integer, else `false`. - * @example - * - * _.isInteger(3); - * // => true - * - * _.isInteger(Number.MIN_VALUE); - * // => false - * - * _.isInteger(Infinity); - * // => false - * - * _.isInteger('3'); - * // => false - */ - function isInteger(value) { - return typeof value == 'number' && value == toInteger(value); - } - - /** - * Checks if `value` is a valid array-like length. - * - * **Note:** This method is loosely based on - * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. - * @example - * - * _.isLength(3); - * // => true - * - * _.isLength(Number.MIN_VALUE); - * // => false - * - * _.isLength(Infinity); - * // => false - * - * _.isLength('3'); - * // => false - */ - function isLength(value) { - return typeof value == 'number' && - value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; - } - - /** - * Checks if `value` is the - * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) - * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an object, else `false`. - * @example - * - * _.isObject({}); - * // => true - * - * _.isObject([1, 2, 3]); - * // => true - * - * _.isObject(_.noop); - * // => true - * - * _.isObject(null); - * // => false - */ - function isObject(value) { - var type = typeof value; - return value != null && (type == 'object' || type == 'function'); - } - - /** - * Checks if `value` is object-like. A value is object-like if it's not `null` - * and has a `typeof` result of "object". - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is object-like, else `false`. - * @example - * - * _.isObjectLike({}); - * // => true - * - * _.isObjectLike([1, 2, 3]); - * // => true - * - * _.isObjectLike(_.noop); - * // => false - * - * _.isObjectLike(null); - * // => false - */ - function isObjectLike(value) { - return value != null && typeof value == 'object'; - } - - /** - * Checks if `value` is classified as a `Map` object. - * - * @static - * @memberOf _ - * @since 4.3.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a map, else `false`. - * @example - * - * _.isMap(new Map); - * // => true - * - * _.isMap(new WeakMap); - * // => false - */ - var isMap = nodeIsMap ? baseUnary(nodeIsMap) : baseIsMap; - - /** - * Performs a partial deep comparison between `object` and `source` to - * determine if `object` contains equivalent property values. - * - * **Note:** This method is equivalent to `_.matches` when `source` is - * partially applied. - * - * Partial comparisons will match empty array and empty object `source` - * values against any array or object value, respectively. See `_.isEqual` - * for a list of supported value comparisons. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Lang - * @param {Object} object The object to inspect. - * @param {Object} source The object of property values to match. - * @returns {boolean} Returns `true` if `object` is a match, else `false`. - * @example - * - * var object = { 'a': 1, 'b': 2 }; - * - * _.isMatch(object, { 'b': 2 }); - * // => true - * - * _.isMatch(object, { 'b': 1 }); - * // => false - */ - function isMatch(object, source) { - return object === source || baseIsMatch(object, source, getMatchData(source)); - } - - /** - * This method is like `_.isMatch` except that it accepts `customizer` which - * is invoked to compare values. If `customizer` returns `undefined`, comparisons - * are handled by the method instead. The `customizer` is invoked with five - * arguments: (objValue, srcValue, index|key, object, source). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {Object} object The object to inspect. - * @param {Object} source The object of property values to match. - * @param {Function} [customizer] The function to customize comparisons. - * @returns {boolean} Returns `true` if `object` is a match, else `false`. - * @example - * - * function isGreeting(value) { - * return /^h(?:i|ello)$/.test(value); - * } - * - * function customizer(objValue, srcValue) { - * if (isGreeting(objValue) && isGreeting(srcValue)) { - * return true; - * } - * } - * - * var object = { 'greeting': 'hello' }; - * var source = { 'greeting': 'hi' }; - * - * _.isMatchWith(object, source, customizer); - * // => true - */ - function isMatchWith(object, source, customizer) { - customizer = typeof customizer == 'function' ? customizer : undefined; - return baseIsMatch(object, source, getMatchData(source), customizer); - } - - /** - * Checks if `value` is `NaN`. - * - * **Note:** This method is based on - * [`Number.isNaN`](https://mdn.io/Number/isNaN) and is not the same as - * global [`isNaN`](https://mdn.io/isNaN) which returns `true` for - * `undefined` and other non-number values. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`. - * @example - * - * _.isNaN(NaN); - * // => true - * - * _.isNaN(new Number(NaN)); - * // => true - * - * isNaN(undefined); - * // => true - * - * _.isNaN(undefined); - * // => false - */ - function isNaN(value) { - // An `NaN` primitive is the only value that is not equal to itself. - // Perform the `toStringTag` check first to avoid errors with some - // ActiveX objects in IE. - return isNumber(value) && value != +value; - } - - /** - * Checks if `value` is a pristine native function. - * - * **Note:** This method can't reliably detect native functions in the presence - * of the core-js package because core-js circumvents this kind of detection. - * Despite multiple requests, the core-js maintainer has made it clear: any - * attempt to fix the detection will be obstructed. As a result, we're left - * with little choice but to throw an error. Unfortunately, this also affects - * packages, like [babel-polyfill](https://www.npmjs.com/package/babel-polyfill), - * which rely on core-js. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a native function, - * else `false`. - * @example - * - * _.isNative(Array.prototype.push); - * // => true - * - * _.isNative(_); - * // => false - */ - function isNative(value) { - if (isMaskable(value)) { - throw new Error(CORE_ERROR_TEXT); - } - return baseIsNative(value); - } - - /** - * Checks if `value` is `null`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is `null`, else `false`. - * @example - * - * _.isNull(null); - * // => true - * - * _.isNull(void 0); - * // => false - */ - function isNull(value) { - return value === null; - } - - /** - * Checks if `value` is `null` or `undefined`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is nullish, else `false`. - * @example - * - * _.isNil(null); - * // => true - * - * _.isNil(void 0); - * // => true - * - * _.isNil(NaN); - * // => false - */ - function isNil(value) { - return value == null; - } - - /** - * Checks if `value` is classified as a `Number` primitive or object. - * - * **Note:** To exclude `Infinity`, `-Infinity`, and `NaN`, which are - * classified as numbers, use the `_.isFinite` method. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a number, else `false`. - * @example - * - * _.isNumber(3); - * // => true - * - * _.isNumber(Number.MIN_VALUE); - * // => true - * - * _.isNumber(Infinity); - * // => true - * - * _.isNumber('3'); - * // => false - */ - function isNumber(value) { - return typeof value == 'number' || - (isObjectLike(value) && baseGetTag(value) == numberTag); - } - - /** - * Checks if `value` is a plain object, that is, an object created by the - * `Object` constructor or one with a `[[Prototype]]` of `null`. - * - * @static - * @memberOf _ - * @since 0.8.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a plain object, else `false`. - * @example - * - * function Foo() { - * this.a = 1; - * } - * - * _.isPlainObject(new Foo); - * // => false - * - * _.isPlainObject([1, 2, 3]); - * // => false - * - * _.isPlainObject({ 'x': 0, 'y': 0 }); - * // => true - * - * _.isPlainObject(Object.create(null)); - * // => true - */ - function isPlainObject(value) { - if (!isObjectLike(value) || baseGetTag(value) != objectTag) { - return false; - } - var proto = getPrototype(value); - if (proto === null) { - return true; - } - var Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor; - return typeof Ctor == 'function' && Ctor instanceof Ctor && - funcToString.call(Ctor) == objectCtorString; - } - - /** - * Checks if `value` is classified as a `RegExp` object. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a regexp, else `false`. - * @example - * - * _.isRegExp(/abc/); - * // => true - * - * _.isRegExp('/abc/'); - * // => false - */ - var isRegExp = nodeIsRegExp ? baseUnary(nodeIsRegExp) : baseIsRegExp; - - /** - * Checks if `value` is a safe integer. An integer is safe if it's an IEEE-754 - * double precision number which isn't the result of a rounded unsafe integer. - * - * **Note:** This method is based on - * [`Number.isSafeInteger`](https://mdn.io/Number/isSafeInteger). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a safe integer, else `false`. - * @example - * - * _.isSafeInteger(3); - * // => true - * - * _.isSafeInteger(Number.MIN_VALUE); - * // => false - * - * _.isSafeInteger(Infinity); - * // => false - * - * _.isSafeInteger('3'); - * // => false - */ - function isSafeInteger(value) { - return isInteger(value) && value >= -MAX_SAFE_INTEGER && value <= MAX_SAFE_INTEGER; - } - - /** - * Checks if `value` is classified as a `Set` object. - * - * @static - * @memberOf _ - * @since 4.3.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a set, else `false`. - * @example - * - * _.isSet(new Set); - * // => true - * - * _.isSet(new WeakSet); - * // => false - */ - var isSet = nodeIsSet ? baseUnary(nodeIsSet) : baseIsSet; - - /** - * Checks if `value` is classified as a `String` primitive or object. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a string, else `false`. - * @example - * - * _.isString('abc'); - * // => true - * - * _.isString(1); - * // => false - */ - function isString(value) { - return typeof value == 'string' || - (!isArray(value) && isObjectLike(value) && baseGetTag(value) == stringTag); - } - - /** - * Checks if `value` is classified as a `Symbol` primitive or object. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a symbol, else `false`. - * @example - * - * _.isSymbol(Symbol.iterator); - * // => true - * - * _.isSymbol('abc'); - * // => false - */ - function isSymbol(value) { - return typeof value == 'symbol' || - (isObjectLike(value) && baseGetTag(value) == symbolTag); - } - - /** - * Checks if `value` is classified as a typed array. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a typed array, else `false`. - * @example - * - * _.isTypedArray(new Uint8Array); - * // => true - * - * _.isTypedArray([]); - * // => false - */ - var isTypedArray = nodeIsTypedArray ? baseUnary(nodeIsTypedArray) : baseIsTypedArray; - - /** - * Checks if `value` is `undefined`. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is `undefined`, else `false`. - * @example - * - * _.isUndefined(void 0); - * // => true - * - * _.isUndefined(null); - * // => false - */ - function isUndefined(value) { - return value === undefined; - } - - /** - * Checks if `value` is classified as a `WeakMap` object. - * - * @static - * @memberOf _ - * @since 4.3.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a weak map, else `false`. - * @example - * - * _.isWeakMap(new WeakMap); - * // => true - * - * _.isWeakMap(new Map); - * // => false - */ - function isWeakMap(value) { - return isObjectLike(value) && getTag(value) == weakMapTag; - } - - /** - * Checks if `value` is classified as a `WeakSet` object. - * - * @static - * @memberOf _ - * @since 4.3.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a weak set, else `false`. - * @example - * - * _.isWeakSet(new WeakSet); - * // => true - * - * _.isWeakSet(new Set); - * // => false - */ - function isWeakSet(value) { - return isObjectLike(value) && baseGetTag(value) == weakSetTag; - } - - /** - * Checks if `value` is less than `other`. - * - * @static - * @memberOf _ - * @since 3.9.0 - * @category Lang - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {boolean} Returns `true` if `value` is less than `other`, - * else `false`. - * @see _.gt - * @example - * - * _.lt(1, 3); - * // => true - * - * _.lt(3, 3); - * // => false - * - * _.lt(3, 1); - * // => false - */ - var lt = createRelationalOperation(baseLt); - - /** - * Checks if `value` is less than or equal to `other`. - * - * @static - * @memberOf _ - * @since 3.9.0 - * @category Lang - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {boolean} Returns `true` if `value` is less than or equal to - * `other`, else `false`. - * @see _.gte - * @example - * - * _.lte(1, 3); - * // => true - * - * _.lte(3, 3); - * // => true - * - * _.lte(3, 1); - * // => false - */ - var lte = createRelationalOperation(function(value, other) { - return value <= other; - }); - - /** - * Converts `value` to an array. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Lang - * @param {*} value The value to convert. - * @returns {Array} Returns the converted array. - * @example - * - * _.toArray({ 'a': 1, 'b': 2 }); - * // => [1, 2] - * - * _.toArray('abc'); - * // => ['a', 'b', 'c'] - * - * _.toArray(1); - * // => [] - * - * _.toArray(null); - * // => [] - */ - function toArray(value) { - if (!value) { - return []; - } - if (isArrayLike(value)) { - return isString(value) ? stringToArray(value) : copyArray(value); - } - if (symIterator && value[symIterator]) { - return iteratorToArray(value[symIterator]()); - } - var tag = getTag(value), - func = tag == mapTag ? mapToArray : (tag == setTag ? setToArray : values); - - return func(value); - } - - /** - * Converts `value` to a finite number. - * - * @static - * @memberOf _ - * @since 4.12.0 - * @category Lang - * @param {*} value The value to convert. - * @returns {number} Returns the converted number. - * @example - * - * _.toFinite(3.2); - * // => 3.2 - * - * _.toFinite(Number.MIN_VALUE); - * // => 5e-324 - * - * _.toFinite(Infinity); - * // => 1.7976931348623157e+308 - * - * _.toFinite('3.2'); - * // => 3.2 - */ - function toFinite(value) { - if (!value) { - return value === 0 ? value : 0; - } - value = toNumber(value); - if (value === INFINITY || value === -INFINITY) { - var sign = (value < 0 ? -1 : 1); - return sign * MAX_INTEGER; - } - return value === value ? value : 0; - } - - /** - * Converts `value` to an integer. - * - * **Note:** This method is loosely based on - * [`ToInteger`](http://www.ecma-international.org/ecma-262/7.0/#sec-tointeger). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to convert. - * @returns {number} Returns the converted integer. - * @example - * - * _.toInteger(3.2); - * // => 3 - * - * _.toInteger(Number.MIN_VALUE); - * // => 0 - * - * _.toInteger(Infinity); - * // => 1.7976931348623157e+308 - * - * _.toInteger('3.2'); - * // => 3 - */ - function toInteger(value) { - var result = toFinite(value), - remainder = result % 1; - - return result === result ? (remainder ? result - remainder : result) : 0; - } - - /** - * Converts `value` to an integer suitable for use as the length of an - * array-like object. - * - * **Note:** This method is based on - * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to convert. - * @returns {number} Returns the converted integer. - * @example - * - * _.toLength(3.2); - * // => 3 - * - * _.toLength(Number.MIN_VALUE); - * // => 0 - * - * _.toLength(Infinity); - * // => 4294967295 - * - * _.toLength('3.2'); - * // => 3 - */ - function toLength(value) { - return value ? baseClamp(toInteger(value), 0, MAX_ARRAY_LENGTH) : 0; - } - - /** - * Converts `value` to a number. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to process. - * @returns {number} Returns the number. - * @example - * - * _.toNumber(3.2); - * // => 3.2 - * - * _.toNumber(Number.MIN_VALUE); - * // => 5e-324 - * - * _.toNumber(Infinity); - * // => Infinity - * - * _.toNumber('3.2'); - * // => 3.2 - */ - function toNumber(value) { - if (typeof value == 'number') { - return value; - } - if (isSymbol(value)) { - return NAN; - } - if (isObject(value)) { - var other = typeof value.valueOf == 'function' ? value.valueOf() : value; - value = isObject(other) ? (other + '') : other; - } - if (typeof value != 'string') { - return value === 0 ? value : +value; - } - value = value.replace(reTrim, ''); - var isBinary = reIsBinary.test(value); - return (isBinary || reIsOctal.test(value)) - ? freeParseInt(value.slice(2), isBinary ? 2 : 8) - : (reIsBadHex.test(value) ? NAN : +value); - } - - /** - * Converts `value` to a plain object flattening inherited enumerable string - * keyed properties of `value` to own properties of the plain object. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Lang - * @param {*} value The value to convert. - * @returns {Object} Returns the converted plain object. - * @example - * - * function Foo() { - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.assign({ 'a': 1 }, new Foo); - * // => { 'a': 1, 'b': 2 } - * - * _.assign({ 'a': 1 }, _.toPlainObject(new Foo)); - * // => { 'a': 1, 'b': 2, 'c': 3 } - */ - function toPlainObject(value) { - return copyObject(value, keysIn(value)); - } - - /** - * Converts `value` to a safe integer. A safe integer can be compared and - * represented correctly. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to convert. - * @returns {number} Returns the converted integer. - * @example - * - * _.toSafeInteger(3.2); - * // => 3 - * - * _.toSafeInteger(Number.MIN_VALUE); - * // => 0 - * - * _.toSafeInteger(Infinity); - * // => 9007199254740991 - * - * _.toSafeInteger('3.2'); - * // => 3 - */ - function toSafeInteger(value) { - return value - ? baseClamp(toInteger(value), -MAX_SAFE_INTEGER, MAX_SAFE_INTEGER) - : (value === 0 ? value : 0); - } - - /** - * Converts `value` to a string. An empty string is returned for `null` - * and `undefined` values. The sign of `-0` is preserved. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to convert. - * @returns {string} Returns the converted string. - * @example - * - * _.toString(null); - * // => '' - * - * _.toString(-0); - * // => '-0' - * - * _.toString([1, 2, 3]); - * // => '1,2,3' - */ - function toString(value) { - return value == null ? '' : baseToString(value); - } - - /*------------------------------------------------------------------------*/ - - /** - * Assigns own enumerable string keyed properties of source objects to the - * destination object. Source objects are applied from left to right. - * Subsequent sources overwrite property assignments of previous sources. - * - * **Note:** This method mutates `object` and is loosely based on - * [`Object.assign`](https://mdn.io/Object/assign). - * - * @static - * @memberOf _ - * @since 0.10.0 - * @category Object - * @param {Object} object The destination object. - * @param {...Object} [sources] The source objects. - * @returns {Object} Returns `object`. - * @see _.assignIn - * @example - * - * function Foo() { - * this.a = 1; - * } - * - * function Bar() { - * this.c = 3; - * } - * - * Foo.prototype.b = 2; - * Bar.prototype.d = 4; - * - * _.assign({ 'a': 0 }, new Foo, new Bar); - * // => { 'a': 1, 'c': 3 } - */ - var assign = createAssigner(function(object, source) { - if (isPrototype(source) || isArrayLike(source)) { - copyObject(source, keys(source), object); - return; - } - for (var key in source) { - if (hasOwnProperty.call(source, key)) { - assignValue(object, key, source[key]); - } - } - }); - - /** - * This method is like `_.assign` except that it iterates over own and - * inherited source properties. - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @alias extend - * @category Object - * @param {Object} object The destination object. - * @param {...Object} [sources] The source objects. - * @returns {Object} Returns `object`. - * @see _.assign - * @example - * - * function Foo() { - * this.a = 1; - * } - * - * function Bar() { - * this.c = 3; - * } - * - * Foo.prototype.b = 2; - * Bar.prototype.d = 4; - * - * _.assignIn({ 'a': 0 }, new Foo, new Bar); - * // => { 'a': 1, 'b': 2, 'c': 3, 'd': 4 } - */ - var assignIn = createAssigner(function(object, source) { - copyObject(source, keysIn(source), object); - }); - - /** - * This method is like `_.assignIn` except that it accepts `customizer` - * which is invoked to produce the assigned values. If `customizer` returns - * `undefined`, assignment is handled by the method instead. The `customizer` - * is invoked with five arguments: (objValue, srcValue, key, object, source). - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @alias extendWith - * @category Object - * @param {Object} object The destination object. - * @param {...Object} sources The source objects. - * @param {Function} [customizer] The function to customize assigned values. - * @returns {Object} Returns `object`. - * @see _.assignWith - * @example - * - * function customizer(objValue, srcValue) { - * return _.isUndefined(objValue) ? srcValue : objValue; - * } - * - * var defaults = _.partialRight(_.assignInWith, customizer); - * - * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 }); - * // => { 'a': 1, 'b': 2 } - */ - var assignInWith = createAssigner(function(object, source, srcIndex, customizer) { - copyObject(source, keysIn(source), object, customizer); - }); - - /** - * This method is like `_.assign` except that it accepts `customizer` - * which is invoked to produce the assigned values. If `customizer` returns - * `undefined`, assignment is handled by the method instead. The `customizer` - * is invoked with five arguments: (objValue, srcValue, key, object, source). - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The destination object. - * @param {...Object} sources The source objects. - * @param {Function} [customizer] The function to customize assigned values. - * @returns {Object} Returns `object`. - * @see _.assignInWith - * @example - * - * function customizer(objValue, srcValue) { - * return _.isUndefined(objValue) ? srcValue : objValue; - * } - * - * var defaults = _.partialRight(_.assignWith, customizer); - * - * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 }); - * // => { 'a': 1, 'b': 2 } - */ - var assignWith = createAssigner(function(object, source, srcIndex, customizer) { - copyObject(source, keys(source), object, customizer); - }); - - /** - * Creates an array of values corresponding to `paths` of `object`. - * - * @static - * @memberOf _ - * @since 1.0.0 - * @category Object - * @param {Object} object The object to iterate over. - * @param {...(string|string[])} [paths] The property paths to pick. - * @returns {Array} Returns the picked values. - * @example - * - * var object = { 'a': [{ 'b': { 'c': 3 } }, 4] }; - * - * _.at(object, ['a[0].b.c', 'a[1]']); - * // => [3, 4] - */ - var at = flatRest(baseAt); - - /** - * Creates an object that inherits from the `prototype` object. If a - * `properties` object is given, its own enumerable string keyed properties - * are assigned to the created object. - * - * @static - * @memberOf _ - * @since 2.3.0 - * @category Object - * @param {Object} prototype The object to inherit from. - * @param {Object} [properties] The properties to assign to the object. - * @returns {Object} Returns the new object. - * @example - * - * function Shape() { - * this.x = 0; - * this.y = 0; - * } - * - * function Circle() { - * Shape.call(this); - * } - * - * Circle.prototype = _.create(Shape.prototype, { - * 'constructor': Circle - * }); - * - * var circle = new Circle; - * circle instanceof Circle; - * // => true - * - * circle instanceof Shape; - * // => true - */ - function create(prototype, properties) { - var result = baseCreate(prototype); - return properties == null ? result : baseAssign(result, properties); - } - - /** - * Assigns own and inherited enumerable string keyed properties of source - * objects to the destination object for all destination properties that - * resolve to `undefined`. Source objects are applied from left to right. - * Once a property is set, additional values of the same property are ignored. - * - * **Note:** This method mutates `object`. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Object - * @param {Object} object The destination object. - * @param {...Object} [sources] The source objects. - * @returns {Object} Returns `object`. - * @see _.defaultsDeep - * @example - * - * _.defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 }); - * // => { 'a': 1, 'b': 2 } - */ - var defaults = baseRest(function(object, sources) { - object = Object(object); - - var index = -1; - var length = sources.length; - var guard = length > 2 ? sources[2] : undefined; - - if (guard && isIterateeCall(sources[0], sources[1], guard)) { - length = 1; - } - - while (++index < length) { - var source = sources[index]; - var props = keysIn(source); - var propsIndex = -1; - var propsLength = props.length; - - while (++propsIndex < propsLength) { - var key = props[propsIndex]; - var value = object[key]; - - if (value === undefined || - (eq(value, objectProto[key]) && !hasOwnProperty.call(object, key))) { - object[key] = source[key]; - } - } - } - - return object; - }); - - /** - * This method is like `_.defaults` except that it recursively assigns - * default properties. - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 3.10.0 - * @category Object - * @param {Object} object The destination object. - * @param {...Object} [sources] The source objects. - * @returns {Object} Returns `object`. - * @see _.defaults - * @example - * - * _.defaultsDeep({ 'a': { 'b': 2 } }, { 'a': { 'b': 1, 'c': 3 } }); - * // => { 'a': { 'b': 2, 'c': 3 } } - */ - var defaultsDeep = baseRest(function(args) { - args.push(undefined, customDefaultsMerge); - return apply(mergeWith, undefined, args); - }); - - /** - * This method is like `_.find` except that it returns the key of the first - * element `predicate` returns truthy for instead of the element itself. - * - * @static - * @memberOf _ - * @since 1.1.0 - * @category Object - * @param {Object} object The object to inspect. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @returns {string|undefined} Returns the key of the matched element, - * else `undefined`. - * @example - * - * var users = { - * 'barney': { 'age': 36, 'active': true }, - * 'fred': { 'age': 40, 'active': false }, - * 'pebbles': { 'age': 1, 'active': true } - * }; - * - * _.findKey(users, function(o) { return o.age < 40; }); - * // => 'barney' (iteration order is not guaranteed) - * - * // The `_.matches` iteratee shorthand. - * _.findKey(users, { 'age': 1, 'active': true }); - * // => 'pebbles' - * - * // The `_.matchesProperty` iteratee shorthand. - * _.findKey(users, ['active', false]); - * // => 'fred' - * - * // The `_.property` iteratee shorthand. - * _.findKey(users, 'active'); - * // => 'barney' - */ - function findKey(object, predicate) { - return baseFindKey(object, getIteratee(predicate, 3), baseForOwn); - } - - /** - * This method is like `_.findKey` except that it iterates over elements of - * a collection in the opposite order. - * - * @static - * @memberOf _ - * @since 2.0.0 - * @category Object - * @param {Object} object The object to inspect. - * @param {Function} [predicate=_.identity] The function invoked per iteration. - * @returns {string|undefined} Returns the key of the matched element, - * else `undefined`. - * @example - * - * var users = { - * 'barney': { 'age': 36, 'active': true }, - * 'fred': { 'age': 40, 'active': false }, - * 'pebbles': { 'age': 1, 'active': true } - * }; - * - * _.findLastKey(users, function(o) { return o.age < 40; }); - * // => returns 'pebbles' assuming `_.findKey` returns 'barney' - * - * // The `_.matches` iteratee shorthand. - * _.findLastKey(users, { 'age': 36, 'active': true }); - * // => 'barney' - * - * // The `_.matchesProperty` iteratee shorthand. - * _.findLastKey(users, ['active', false]); - * // => 'fred' - * - * // The `_.property` iteratee shorthand. - * _.findLastKey(users, 'active'); - * // => 'pebbles' - */ - function findLastKey(object, predicate) { - return baseFindKey(object, getIteratee(predicate, 3), baseForOwnRight); - } - - /** - * Iterates over own and inherited enumerable string keyed properties of an - * object and invokes `iteratee` for each property. The iteratee is invoked - * with three arguments: (value, key, object). Iteratee functions may exit - * iteration early by explicitly returning `false`. - * - * @static - * @memberOf _ - * @since 0.3.0 - * @category Object - * @param {Object} object The object to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Object} Returns `object`. - * @see _.forInRight - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.forIn(new Foo, function(value, key) { - * console.log(key); - * }); - * // => Logs 'a', 'b', then 'c' (iteration order is not guaranteed). - */ - function forIn(object, iteratee) { - return object == null - ? object - : baseFor(object, getIteratee(iteratee, 3), keysIn); - } - - /** - * This method is like `_.forIn` except that it iterates over properties of - * `object` in the opposite order. - * - * @static - * @memberOf _ - * @since 2.0.0 - * @category Object - * @param {Object} object The object to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Object} Returns `object`. - * @see _.forIn - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.forInRight(new Foo, function(value, key) { - * console.log(key); - * }); - * // => Logs 'c', 'b', then 'a' assuming `_.forIn` logs 'a', 'b', then 'c'. - */ - function forInRight(object, iteratee) { - return object == null - ? object - : baseForRight(object, getIteratee(iteratee, 3), keysIn); - } - - /** - * Iterates over own enumerable string keyed properties of an object and - * invokes `iteratee` for each property. The iteratee is invoked with three - * arguments: (value, key, object). Iteratee functions may exit iteration - * early by explicitly returning `false`. - * - * @static - * @memberOf _ - * @since 0.3.0 - * @category Object - * @param {Object} object The object to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Object} Returns `object`. - * @see _.forOwnRight - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.forOwn(new Foo, function(value, key) { - * console.log(key); - * }); - * // => Logs 'a' then 'b' (iteration order is not guaranteed). - */ - function forOwn(object, iteratee) { - return object && baseForOwn(object, getIteratee(iteratee, 3)); - } - - /** - * This method is like `_.forOwn` except that it iterates over properties of - * `object` in the opposite order. - * - * @static - * @memberOf _ - * @since 2.0.0 - * @category Object - * @param {Object} object The object to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Object} Returns `object`. - * @see _.forOwn - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.forOwnRight(new Foo, function(value, key) { - * console.log(key); - * }); - * // => Logs 'b' then 'a' assuming `_.forOwn` logs 'a' then 'b'. - */ - function forOwnRight(object, iteratee) { - return object && baseForOwnRight(object, getIteratee(iteratee, 3)); - } - - /** - * Creates an array of function property names from own enumerable properties - * of `object`. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Object - * @param {Object} object The object to inspect. - * @returns {Array} Returns the function names. - * @see _.functionsIn - * @example - * - * function Foo() { - * this.a = _.constant('a'); - * this.b = _.constant('b'); - * } - * - * Foo.prototype.c = _.constant('c'); - * - * _.functions(new Foo); - * // => ['a', 'b'] - */ - function functions(object) { - return object == null ? [] : baseFunctions(object, keys(object)); - } - - /** - * Creates an array of function property names from own and inherited - * enumerable properties of `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The object to inspect. - * @returns {Array} Returns the function names. - * @see _.functions - * @example - * - * function Foo() { - * this.a = _.constant('a'); - * this.b = _.constant('b'); - * } - * - * Foo.prototype.c = _.constant('c'); - * - * _.functionsIn(new Foo); - * // => ['a', 'b', 'c'] - */ - function functionsIn(object) { - return object == null ? [] : baseFunctions(object, keysIn(object)); - } - - /** - * Gets the value at `path` of `object`. If the resolved value is - * `undefined`, the `defaultValue` is returned in its place. - * - * @static - * @memberOf _ - * @since 3.7.0 - * @category Object - * @param {Object} object The object to query. - * @param {Array|string} path The path of the property to get. - * @param {*} [defaultValue] The value returned for `undefined` resolved values. - * @returns {*} Returns the resolved value. - * @example - * - * var object = { 'a': [{ 'b': { 'c': 3 } }] }; - * - * _.get(object, 'a[0].b.c'); - * // => 3 - * - * _.get(object, ['a', '0', 'b', 'c']); - * // => 3 - * - * _.get(object, 'a.b.c', 'default'); - * // => 'default' - */ - function get(object, path, defaultValue) { - var result = object == null ? undefined : baseGet(object, path); - return result === undefined ? defaultValue : result; - } - - /** - * Checks if `path` is a direct property of `object`. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Object - * @param {Object} object The object to query. - * @param {Array|string} path The path to check. - * @returns {boolean} Returns `true` if `path` exists, else `false`. - * @example - * - * var object = { 'a': { 'b': 2 } }; - * var other = _.create({ 'a': _.create({ 'b': 2 }) }); - * - * _.has(object, 'a'); - * // => true - * - * _.has(object, 'a.b'); - * // => true - * - * _.has(object, ['a', 'b']); - * // => true - * - * _.has(other, 'a'); - * // => false - */ - function has(object, path) { - return object != null && hasPath(object, path, baseHas); - } - - /** - * Checks if `path` is a direct or inherited property of `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The object to query. - * @param {Array|string} path The path to check. - * @returns {boolean} Returns `true` if `path` exists, else `false`. - * @example - * - * var object = _.create({ 'a': _.create({ 'b': 2 }) }); - * - * _.hasIn(object, 'a'); - * // => true - * - * _.hasIn(object, 'a.b'); - * // => true - * - * _.hasIn(object, ['a', 'b']); - * // => true - * - * _.hasIn(object, 'b'); - * // => false - */ - function hasIn(object, path) { - return object != null && hasPath(object, path, baseHasIn); - } - - /** - * Creates an object composed of the inverted keys and values of `object`. - * If `object` contains duplicate values, subsequent values overwrite - * property assignments of previous values. - * - * @static - * @memberOf _ - * @since 0.7.0 - * @category Object - * @param {Object} object The object to invert. - * @returns {Object} Returns the new inverted object. - * @example - * - * var object = { 'a': 1, 'b': 2, 'c': 1 }; - * - * _.invert(object); - * // => { '1': 'c', '2': 'b' } - */ - var invert = createInverter(function(result, value, key) { - if (value != null && - typeof value.toString != 'function') { - value = nativeObjectToString.call(value); - } - - result[value] = key; - }, constant(identity)); - - /** - * This method is like `_.invert` except that the inverted object is generated - * from the results of running each element of `object` thru `iteratee`. The - * corresponding inverted value of each inverted key is an array of keys - * responsible for generating the inverted value. The iteratee is invoked - * with one argument: (value). - * - * @static - * @memberOf _ - * @since 4.1.0 - * @category Object - * @param {Object} object The object to invert. - * @param {Function} [iteratee=_.identity] The iteratee invoked per element. - * @returns {Object} Returns the new inverted object. - * @example - * - * var object = { 'a': 1, 'b': 2, 'c': 1 }; - * - * _.invertBy(object); - * // => { '1': ['a', 'c'], '2': ['b'] } - * - * _.invertBy(object, function(value) { - * return 'group' + value; - * }); - * // => { 'group1': ['a', 'c'], 'group2': ['b'] } - */ - var invertBy = createInverter(function(result, value, key) { - if (value != null && - typeof value.toString != 'function') { - value = nativeObjectToString.call(value); - } - - if (hasOwnProperty.call(result, value)) { - result[value].push(key); - } else { - result[value] = [key]; - } - }, getIteratee); - - /** - * Invokes the method at `path` of `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The object to query. - * @param {Array|string} path The path of the method to invoke. - * @param {...*} [args] The arguments to invoke the method with. - * @returns {*} Returns the result of the invoked method. - * @example - * - * var object = { 'a': [{ 'b': { 'c': [1, 2, 3, 4] } }] }; - * - * _.invoke(object, 'a[0].b.c.slice', 1, 3); - * // => [2, 3] - */ - var invoke = baseRest(baseInvoke); - - /** - * Creates an array of the own enumerable property names of `object`. - * - * **Note:** Non-object values are coerced to objects. See the - * [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys) - * for more details. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Object - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property names. - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.keys(new Foo); - * // => ['a', 'b'] (iteration order is not guaranteed) - * - * _.keys('hi'); - * // => ['0', '1'] - */ - function keys(object) { - return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object); - } - - /** - * Creates an array of the own and inherited enumerable property names of `object`. - * - * **Note:** Non-object values are coerced to objects. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Object - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property names. - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.keysIn(new Foo); - * // => ['a', 'b', 'c'] (iteration order is not guaranteed) - */ - function keysIn(object) { - return isArrayLike(object) ? arrayLikeKeys(object, true) : baseKeysIn(object); - } - - /** - * The opposite of `_.mapValues`; this method creates an object with the - * same values as `object` and keys generated by running each own enumerable - * string keyed property of `object` thru `iteratee`. The iteratee is invoked - * with three arguments: (value, key, object). - * - * @static - * @memberOf _ - * @since 3.8.0 - * @category Object - * @param {Object} object The object to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Object} Returns the new mapped object. - * @see _.mapValues - * @example - * - * _.mapKeys({ 'a': 1, 'b': 2 }, function(value, key) { - * return key + value; - * }); - * // => { 'a1': 1, 'b2': 2 } - */ - function mapKeys(object, iteratee) { - var result = {}; - iteratee = getIteratee(iteratee, 3); - - baseForOwn(object, function(value, key, object) { - baseAssignValue(result, iteratee(value, key, object), value); - }); - return result; - } - - /** - * Creates an object with the same keys as `object` and values generated - * by running each own enumerable string keyed property of `object` thru - * `iteratee`. The iteratee is invoked with three arguments: - * (value, key, object). - * - * @static - * @memberOf _ - * @since 2.4.0 - * @category Object - * @param {Object} object The object to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @returns {Object} Returns the new mapped object. - * @see _.mapKeys - * @example - * - * var users = { - * 'fred': { 'user': 'fred', 'age': 40 }, - * 'pebbles': { 'user': 'pebbles', 'age': 1 } - * }; - * - * _.mapValues(users, function(o) { return o.age; }); - * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed) - * - * // The `_.property` iteratee shorthand. - * _.mapValues(users, 'age'); - * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed) - */ - function mapValues(object, iteratee) { - var result = {}; - iteratee = getIteratee(iteratee, 3); - - baseForOwn(object, function(value, key, object) { - baseAssignValue(result, key, iteratee(value, key, object)); - }); - return result; - } - - /** - * This method is like `_.assign` except that it recursively merges own and - * inherited enumerable string keyed properties of source objects into the - * destination object. Source properties that resolve to `undefined` are - * skipped if a destination value exists. Array and plain object properties - * are merged recursively. Other objects and value types are overridden by - * assignment. Source objects are applied from left to right. Subsequent - * sources overwrite property assignments of previous sources. - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 0.5.0 - * @category Object - * @param {Object} object The destination object. - * @param {...Object} [sources] The source objects. - * @returns {Object} Returns `object`. - * @example - * - * var object = { - * 'a': [{ 'b': 2 }, { 'd': 4 }] - * }; - * - * var other = { - * 'a': [{ 'c': 3 }, { 'e': 5 }] - * }; - * - * _.merge(object, other); - * // => { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] } - */ - var merge = createAssigner(function(object, source, srcIndex) { - baseMerge(object, source, srcIndex); - }); - - /** - * This method is like `_.merge` except that it accepts `customizer` which - * is invoked to produce the merged values of the destination and source - * properties. If `customizer` returns `undefined`, merging is handled by the - * method instead. The `customizer` is invoked with six arguments: - * (objValue, srcValue, key, object, source, stack). - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The destination object. - * @param {...Object} sources The source objects. - * @param {Function} customizer The function to customize assigned values. - * @returns {Object} Returns `object`. - * @example - * - * function customizer(objValue, srcValue) { - * if (_.isArray(objValue)) { - * return objValue.concat(srcValue); - * } - * } - * - * var object = { 'a': [1], 'b': [2] }; - * var other = { 'a': [3], 'b': [4] }; - * - * _.mergeWith(object, other, customizer); - * // => { 'a': [1, 3], 'b': [2, 4] } - */ - var mergeWith = createAssigner(function(object, source, srcIndex, customizer) { - baseMerge(object, source, srcIndex, customizer); - }); - - /** - * The opposite of `_.pick`; this method creates an object composed of the - * own and inherited enumerable property paths of `object` that are not omitted. - * - * **Note:** This method is considerably slower than `_.pick`. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Object - * @param {Object} object The source object. - * @param {...(string|string[])} [paths] The property paths to omit. - * @returns {Object} Returns the new object. - * @example - * - * var object = { 'a': 1, 'b': '2', 'c': 3 }; - * - * _.omit(object, ['a', 'c']); - * // => { 'b': '2' } - */ - var omit = flatRest(function(object, paths) { - var result = {}; - if (object == null) { - return result; - } - var isDeep = false; - paths = arrayMap(paths, function(path) { - path = castPath(path, object); - isDeep || (isDeep = path.length > 1); - return path; - }); - copyObject(object, getAllKeysIn(object), result); - if (isDeep) { - result = baseClone(result, CLONE_DEEP_FLAG | CLONE_FLAT_FLAG | CLONE_SYMBOLS_FLAG, customOmitClone); - } - var length = paths.length; - while (length--) { - baseUnset(result, paths[length]); - } - return result; - }); - - /** - * The opposite of `_.pickBy`; this method creates an object composed of - * the own and inherited enumerable string keyed properties of `object` that - * `predicate` doesn't return truthy for. The predicate is invoked with two - * arguments: (value, key). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The source object. - * @param {Function} [predicate=_.identity] The function invoked per property. - * @returns {Object} Returns the new object. - * @example - * - * var object = { 'a': 1, 'b': '2', 'c': 3 }; - * - * _.omitBy(object, _.isNumber); - * // => { 'b': '2' } - */ - function omitBy(object, predicate) { - return pickBy(object, negate(getIteratee(predicate))); - } - - /** - * Creates an object composed of the picked `object` properties. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Object - * @param {Object} object The source object. - * @param {...(string|string[])} [paths] The property paths to pick. - * @returns {Object} Returns the new object. - * @example - * - * var object = { 'a': 1, 'b': '2', 'c': 3 }; - * - * _.pick(object, ['a', 'c']); - * // => { 'a': 1, 'c': 3 } - */ - var pick = flatRest(function(object, paths) { - return object == null ? {} : basePick(object, paths); - }); - - /** - * Creates an object composed of the `object` properties `predicate` returns - * truthy for. The predicate is invoked with two arguments: (value, key). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The source object. - * @param {Function} [predicate=_.identity] The function invoked per property. - * @returns {Object} Returns the new object. - * @example - * - * var object = { 'a': 1, 'b': '2', 'c': 3 }; - * - * _.pickBy(object, _.isNumber); - * // => { 'a': 1, 'c': 3 } - */ - function pickBy(object, predicate) { - if (object == null) { - return {}; - } - var props = arrayMap(getAllKeysIn(object), function(prop) { - return [prop]; - }); - predicate = getIteratee(predicate); - return basePickBy(object, props, function(value, path) { - return predicate(value, path[0]); - }); - } - - /** - * This method is like `_.get` except that if the resolved value is a - * function it's invoked with the `this` binding of its parent object and - * its result is returned. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Object - * @param {Object} object The object to query. - * @param {Array|string} path The path of the property to resolve. - * @param {*} [defaultValue] The value returned for `undefined` resolved values. - * @returns {*} Returns the resolved value. - * @example - * - * var object = { 'a': [{ 'b': { 'c1': 3, 'c2': _.constant(4) } }] }; - * - * _.result(object, 'a[0].b.c1'); - * // => 3 - * - * _.result(object, 'a[0].b.c2'); - * // => 4 - * - * _.result(object, 'a[0].b.c3', 'default'); - * // => 'default' - * - * _.result(object, 'a[0].b.c3', _.constant('default')); - * // => 'default' - */ - function result(object, path, defaultValue) { - path = castPath(path, object); - - var index = -1, - length = path.length; - - // Ensure the loop is entered when path is empty. - if (!length) { - length = 1; - object = undefined; - } - while (++index < length) { - var value = object == null ? undefined : object[toKey(path[index])]; - if (value === undefined) { - index = length; - value = defaultValue; - } - object = isFunction(value) ? value.call(object) : value; - } - return object; - } - - /** - * Sets the value at `path` of `object`. If a portion of `path` doesn't exist, - * it's created. Arrays are created for missing index properties while objects - * are created for all other missing properties. Use `_.setWith` to customize - * `path` creation. - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 3.7.0 - * @category Object - * @param {Object} object The object to modify. - * @param {Array|string} path The path of the property to set. - * @param {*} value The value to set. - * @returns {Object} Returns `object`. - * @example - * - * var object = { 'a': [{ 'b': { 'c': 3 } }] }; - * - * _.set(object, 'a[0].b.c', 4); - * console.log(object.a[0].b.c); - * // => 4 - * - * _.set(object, ['x', '0', 'y', 'z'], 5); - * console.log(object.x[0].y.z); - * // => 5 - */ - function set(object, path, value) { - return object == null ? object : baseSet(object, path, value); - } - - /** - * This method is like `_.set` except that it accepts `customizer` which is - * invoked to produce the objects of `path`. If `customizer` returns `undefined` - * path creation is handled by the method instead. The `customizer` is invoked - * with three arguments: (nsValue, key, nsObject). - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The object to modify. - * @param {Array|string} path The path of the property to set. - * @param {*} value The value to set. - * @param {Function} [customizer] The function to customize assigned values. - * @returns {Object} Returns `object`. - * @example - * - * var object = {}; - * - * _.setWith(object, '[0][1]', 'a', Object); - * // => { '0': { '1': 'a' } } - */ - function setWith(object, path, value, customizer) { - customizer = typeof customizer == 'function' ? customizer : undefined; - return object == null ? object : baseSet(object, path, value, customizer); - } - - /** - * Creates an array of own enumerable string keyed-value pairs for `object` - * which can be consumed by `_.fromPairs`. If `object` is a map or set, its - * entries are returned. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @alias entries - * @category Object - * @param {Object} object The object to query. - * @returns {Array} Returns the key-value pairs. - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.toPairs(new Foo); - * // => [['a', 1], ['b', 2]] (iteration order is not guaranteed) - */ - var toPairs = createToPairs(keys); - - /** - * Creates an array of own and inherited enumerable string keyed-value pairs - * for `object` which can be consumed by `_.fromPairs`. If `object` is a map - * or set, its entries are returned. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @alias entriesIn - * @category Object - * @param {Object} object The object to query. - * @returns {Array} Returns the key-value pairs. - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.toPairsIn(new Foo); - * // => [['a', 1], ['b', 2], ['c', 3]] (iteration order is not guaranteed) - */ - var toPairsIn = createToPairs(keysIn); - - /** - * An alternative to `_.reduce`; this method transforms `object` to a new - * `accumulator` object which is the result of running each of its own - * enumerable string keyed properties thru `iteratee`, with each invocation - * potentially mutating the `accumulator` object. If `accumulator` is not - * provided, a new object with the same `[[Prototype]]` will be used. The - * iteratee is invoked with four arguments: (accumulator, value, key, object). - * Iteratee functions may exit iteration early by explicitly returning `false`. - * - * @static - * @memberOf _ - * @since 1.3.0 - * @category Object - * @param {Object} object The object to iterate over. - * @param {Function} [iteratee=_.identity] The function invoked per iteration. - * @param {*} [accumulator] The custom accumulator value. - * @returns {*} Returns the accumulated value. - * @example - * - * _.transform([2, 3, 4], function(result, n) { - * result.push(n *= n); - * return n % 2 == 0; - * }, []); - * // => [4, 9] - * - * _.transform({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) { - * (result[value] || (result[value] = [])).push(key); - * }, {}); - * // => { '1': ['a', 'c'], '2': ['b'] } - */ - function transform(object, iteratee, accumulator) { - var isArr = isArray(object), - isArrLike = isArr || isBuffer(object) || isTypedArray(object); - - iteratee = getIteratee(iteratee, 4); - if (accumulator == null) { - var Ctor = object && object.constructor; - if (isArrLike) { - accumulator = isArr ? new Ctor : []; - } - else if (isObject(object)) { - accumulator = isFunction(Ctor) ? baseCreate(getPrototype(object)) : {}; - } - else { - accumulator = {}; - } - } - (isArrLike ? arrayEach : baseForOwn)(object, function(value, index, object) { - return iteratee(accumulator, value, index, object); - }); - return accumulator; - } - - /** - * Removes the property at `path` of `object`. - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Object - * @param {Object} object The object to modify. - * @param {Array|string} path The path of the property to unset. - * @returns {boolean} Returns `true` if the property is deleted, else `false`. - * @example - * - * var object = { 'a': [{ 'b': { 'c': 7 } }] }; - * _.unset(object, 'a[0].b.c'); - * // => true - * - * console.log(object); - * // => { 'a': [{ 'b': {} }] }; - * - * _.unset(object, ['a', '0', 'b', 'c']); - * // => true - * - * console.log(object); - * // => { 'a': [{ 'b': {} }] }; - */ - function unset(object, path) { - return object == null ? true : baseUnset(object, path); - } - - /** - * This method is like `_.set` except that accepts `updater` to produce the - * value to set. Use `_.updateWith` to customize `path` creation. The `updater` - * is invoked with one argument: (value). - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 4.6.0 - * @category Object - * @param {Object} object The object to modify. - * @param {Array|string} path The path of the property to set. - * @param {Function} updater The function to produce the updated value. - * @returns {Object} Returns `object`. - * @example - * - * var object = { 'a': [{ 'b': { 'c': 3 } }] }; - * - * _.update(object, 'a[0].b.c', function(n) { return n * n; }); - * console.log(object.a[0].b.c); - * // => 9 - * - * _.update(object, 'x[0].y.z', function(n) { return n ? n + 1 : 0; }); - * console.log(object.x[0].y.z); - * // => 0 - */ - function update(object, path, updater) { - return object == null ? object : baseUpdate(object, path, castFunction(updater)); - } - - /** - * This method is like `_.update` except that it accepts `customizer` which is - * invoked to produce the objects of `path`. If `customizer` returns `undefined` - * path creation is handled by the method instead. The `customizer` is invoked - * with three arguments: (nsValue, key, nsObject). - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 4.6.0 - * @category Object - * @param {Object} object The object to modify. - * @param {Array|string} path The path of the property to set. - * @param {Function} updater The function to produce the updated value. - * @param {Function} [customizer] The function to customize assigned values. - * @returns {Object} Returns `object`. - * @example - * - * var object = {}; - * - * _.updateWith(object, '[0][1]', _.constant('a'), Object); - * // => { '0': { '1': 'a' } } - */ - function updateWith(object, path, updater, customizer) { - customizer = typeof customizer == 'function' ? customizer : undefined; - return object == null ? object : baseUpdate(object, path, castFunction(updater), customizer); - } - - /** - * Creates an array of the own enumerable string keyed property values of `object`. - * - * **Note:** Non-object values are coerced to objects. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category Object - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property values. - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.values(new Foo); - * // => [1, 2] (iteration order is not guaranteed) - * - * _.values('hi'); - * // => ['h', 'i'] - */ - function values(object) { - return object == null ? [] : baseValues(object, keys(object)); - } - - /** - * Creates an array of the own and inherited enumerable string keyed property - * values of `object`. - * - * **Note:** Non-object values are coerced to objects. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category Object - * @param {Object} object The object to query. - * @returns {Array} Returns the array of property values. - * @example - * - * function Foo() { - * this.a = 1; - * this.b = 2; - * } - * - * Foo.prototype.c = 3; - * - * _.valuesIn(new Foo); - * // => [1, 2, 3] (iteration order is not guaranteed) - */ - function valuesIn(object) { - return object == null ? [] : baseValues(object, keysIn(object)); - } - - /*------------------------------------------------------------------------*/ - - /** - * Clamps `number` within the inclusive `lower` and `upper` bounds. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Number - * @param {number} number The number to clamp. - * @param {number} [lower] The lower bound. - * @param {number} upper The upper bound. - * @returns {number} Returns the clamped number. - * @example - * - * _.clamp(-10, -5, 5); - * // => -5 - * - * _.clamp(10, -5, 5); - * // => 5 - */ - function clamp(number, lower, upper) { - if (upper === undefined) { - upper = lower; - lower = undefined; - } - if (upper !== undefined) { - upper = toNumber(upper); - upper = upper === upper ? upper : 0; - } - if (lower !== undefined) { - lower = toNumber(lower); - lower = lower === lower ? lower : 0; - } - return baseClamp(toNumber(number), lower, upper); - } - - /** - * Checks if `n` is between `start` and up to, but not including, `end`. If - * `end` is not specified, it's set to `start` with `start` then set to `0`. - * If `start` is greater than `end` the params are swapped to support - * negative ranges. - * - * @static - * @memberOf _ - * @since 3.3.0 - * @category Number - * @param {number} number The number to check. - * @param {number} [start=0] The start of the range. - * @param {number} end The end of the range. - * @returns {boolean} Returns `true` if `number` is in the range, else `false`. - * @see _.range, _.rangeRight - * @example - * - * _.inRange(3, 2, 4); - * // => true - * - * _.inRange(4, 8); - * // => true - * - * _.inRange(4, 2); - * // => false - * - * _.inRange(2, 2); - * // => false - * - * _.inRange(1.2, 2); - * // => true - * - * _.inRange(5.2, 4); - * // => false - * - * _.inRange(-3, -2, -6); - * // => true - */ - function inRange(number, start, end) { - start = toFinite(start); - if (end === undefined) { - end = start; - start = 0; - } else { - end = toFinite(end); - } - number = toNumber(number); - return baseInRange(number, start, end); - } - - /** - * Produces a random number between the inclusive `lower` and `upper` bounds. - * If only one argument is provided a number between `0` and the given number - * is returned. If `floating` is `true`, or either `lower` or `upper` are - * floats, a floating-point number is returned instead of an integer. - * - * **Note:** JavaScript follows the IEEE-754 standard for resolving - * floating-point values which can produce unexpected results. - * - * @static - * @memberOf _ - * @since 0.7.0 - * @category Number - * @param {number} [lower=0] The lower bound. - * @param {number} [upper=1] The upper bound. - * @param {boolean} [floating] Specify returning a floating-point number. - * @returns {number} Returns the random number. - * @example - * - * _.random(0, 5); - * // => an integer between 0 and 5 - * - * _.random(5); - * // => also an integer between 0 and 5 - * - * _.random(5, true); - * // => a floating-point number between 0 and 5 - * - * _.random(1.2, 5.2); - * // => a floating-point number between 1.2 and 5.2 - */ - function random(lower, upper, floating) { - if (floating && typeof floating != 'boolean' && isIterateeCall(lower, upper, floating)) { - upper = floating = undefined; - } - if (floating === undefined) { - if (typeof upper == 'boolean') { - floating = upper; - upper = undefined; - } - else if (typeof lower == 'boolean') { - floating = lower; - lower = undefined; - } - } - if (lower === undefined && upper === undefined) { - lower = 0; - upper = 1; - } - else { - lower = toFinite(lower); - if (upper === undefined) { - upper = lower; - lower = 0; - } else { - upper = toFinite(upper); - } - } - if (lower > upper) { - var temp = lower; - lower = upper; - upper = temp; - } - if (floating || lower % 1 || upper % 1) { - var rand = nativeRandom(); - return nativeMin(lower + (rand * (upper - lower + freeParseFloat('1e-' + ((rand + '').length - 1)))), upper); - } - return baseRandom(lower, upper); - } - - /*------------------------------------------------------------------------*/ - - /** - * Converts `string` to [camel case](https://en.wikipedia.org/wiki/CamelCase). - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to convert. - * @returns {string} Returns the camel cased string. - * @example - * - * _.camelCase('Foo Bar'); - * // => 'fooBar' - * - * _.camelCase('--foo-bar--'); - * // => 'fooBar' - * - * _.camelCase('__FOO_BAR__'); - * // => 'fooBar' - */ - var camelCase = createCompounder(function(result, word, index) { - word = word.toLowerCase(); - return result + (index ? capitalize(word) : word); - }); - - /** - * Converts the first character of `string` to upper case and the remaining - * to lower case. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to capitalize. - * @returns {string} Returns the capitalized string. - * @example - * - * _.capitalize('FRED'); - * // => 'Fred' - */ - function capitalize(string) { - return upperFirst(toString(string).toLowerCase()); - } - - /** - * Deburrs `string` by converting - * [Latin-1 Supplement](https://en.wikipedia.org/wiki/Latin-1_Supplement_(Unicode_block)#Character_table) - * and [Latin Extended-A](https://en.wikipedia.org/wiki/Latin_Extended-A) - * letters to basic Latin letters and removing - * [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks). - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to deburr. - * @returns {string} Returns the deburred string. - * @example - * - * _.deburr('déjà vu'); - * // => 'deja vu' - */ - function deburr(string) { - string = toString(string); - return string && string.replace(reLatin, deburrLetter).replace(reComboMark, ''); - } - - /** - * Checks if `string` ends with the given target string. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to inspect. - * @param {string} [target] The string to search for. - * @param {number} [position=string.length] The position to search up to. - * @returns {boolean} Returns `true` if `string` ends with `target`, - * else `false`. - * @example - * - * _.endsWith('abc', 'c'); - * // => true - * - * _.endsWith('abc', 'b'); - * // => false - * - * _.endsWith('abc', 'b', 2); - * // => true - */ - function endsWith(string, target, position) { - string = toString(string); - target = baseToString(target); - - var length = string.length; - position = position === undefined - ? length - : baseClamp(toInteger(position), 0, length); - - var end = position; - position -= target.length; - return position >= 0 && string.slice(position, end) == target; - } - - /** - * Converts the characters "&", "<", ">", '"', and "'" in `string` to their - * corresponding HTML entities. - * - * **Note:** No other characters are escaped. To escape additional - * characters use a third-party library like [_he_](https://mths.be/he). - * - * Though the ">" character is escaped for symmetry, characters like - * ">" and "/" don't need escaping in HTML and have no special meaning - * unless they're part of a tag or unquoted attribute value. See - * [Mathias Bynens's article](https://mathiasbynens.be/notes/ambiguous-ampersands) - * (under "semi-related fun fact") for more details. - * - * When working with HTML you should always - * [quote attribute values](http://wonko.com/post/html-escaping) to reduce - * XSS vectors. - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category String - * @param {string} [string=''] The string to escape. - * @returns {string} Returns the escaped string. - * @example - * - * _.escape('fred, barney, & pebbles'); - * // => 'fred, barney, & pebbles' - */ - function escape(string) { - string = toString(string); - return (string && reHasUnescapedHtml.test(string)) - ? string.replace(reUnescapedHtml, escapeHtmlChar) - : string; - } - - /** - * Escapes the `RegExp` special characters "^", "$", "\", ".", "*", "+", - * "?", "(", ")", "[", "]", "{", "}", and "|" in `string`. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to escape. - * @returns {string} Returns the escaped string. - * @example - * - * _.escapeRegExp('[lodash](https://lodash.com/)'); - * // => '\[lodash\]\(https://lodash\.com/\)' - */ - function escapeRegExp(string) { - string = toString(string); - return (string && reHasRegExpChar.test(string)) - ? string.replace(reRegExpChar, '\\$&') - : string; - } - - /** - * Converts `string` to - * [kebab case](https://en.wikipedia.org/wiki/Letter_case#Special_case_styles). - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to convert. - * @returns {string} Returns the kebab cased string. - * @example - * - * _.kebabCase('Foo Bar'); - * // => 'foo-bar' - * - * _.kebabCase('fooBar'); - * // => 'foo-bar' - * - * _.kebabCase('__FOO_BAR__'); - * // => 'foo-bar' - */ - var kebabCase = createCompounder(function(result, word, index) { - return result + (index ? '-' : '') + word.toLowerCase(); - }); - - /** - * Converts `string`, as space separated words, to lower case. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category String - * @param {string} [string=''] The string to convert. - * @returns {string} Returns the lower cased string. - * @example - * - * _.lowerCase('--Foo-Bar--'); - * // => 'foo bar' - * - * _.lowerCase('fooBar'); - * // => 'foo bar' - * - * _.lowerCase('__FOO_BAR__'); - * // => 'foo bar' - */ - var lowerCase = createCompounder(function(result, word, index) { - return result + (index ? ' ' : '') + word.toLowerCase(); - }); - - /** - * Converts the first character of `string` to lower case. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category String - * @param {string} [string=''] The string to convert. - * @returns {string} Returns the converted string. - * @example - * - * _.lowerFirst('Fred'); - * // => 'fred' - * - * _.lowerFirst('FRED'); - * // => 'fRED' - */ - var lowerFirst = createCaseFirst('toLowerCase'); - - /** - * Pads `string` on the left and right sides if it's shorter than `length`. - * Padding characters are truncated if they can't be evenly divided by `length`. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to pad. - * @param {number} [length=0] The padding length. - * @param {string} [chars=' '] The string used as padding. - * @returns {string} Returns the padded string. - * @example - * - * _.pad('abc', 8); - * // => ' abc ' - * - * _.pad('abc', 8, '_-'); - * // => '_-abc_-_' - * - * _.pad('abc', 3); - * // => 'abc' - */ - function pad(string, length, chars) { - string = toString(string); - length = toInteger(length); - - var strLength = length ? stringSize(string) : 0; - if (!length || strLength >= length) { - return string; - } - var mid = (length - strLength) / 2; - return ( - createPadding(nativeFloor(mid), chars) + - string + - createPadding(nativeCeil(mid), chars) - ); - } - - /** - * Pads `string` on the right side if it's shorter than `length`. Padding - * characters are truncated if they exceed `length`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category String - * @param {string} [string=''] The string to pad. - * @param {number} [length=0] The padding length. - * @param {string} [chars=' '] The string used as padding. - * @returns {string} Returns the padded string. - * @example - * - * _.padEnd('abc', 6); - * // => 'abc ' - * - * _.padEnd('abc', 6, '_-'); - * // => 'abc_-_' - * - * _.padEnd('abc', 3); - * // => 'abc' - */ - function padEnd(string, length, chars) { - string = toString(string); - length = toInteger(length); - - var strLength = length ? stringSize(string) : 0; - return (length && strLength < length) - ? (string + createPadding(length - strLength, chars)) - : string; - } - - /** - * Pads `string` on the left side if it's shorter than `length`. Padding - * characters are truncated if they exceed `length`. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category String - * @param {string} [string=''] The string to pad. - * @param {number} [length=0] The padding length. - * @param {string} [chars=' '] The string used as padding. - * @returns {string} Returns the padded string. - * @example - * - * _.padStart('abc', 6); - * // => ' abc' - * - * _.padStart('abc', 6, '_-'); - * // => '_-_abc' - * - * _.padStart('abc', 3); - * // => 'abc' - */ - function padStart(string, length, chars) { - string = toString(string); - length = toInteger(length); - - var strLength = length ? stringSize(string) : 0; - return (length && strLength < length) - ? (createPadding(length - strLength, chars) + string) - : string; - } - - /** - * Converts `string` to an integer of the specified radix. If `radix` is - * `undefined` or `0`, a `radix` of `10` is used unless `value` is a - * hexadecimal, in which case a `radix` of `16` is used. - * - * **Note:** This method aligns with the - * [ES5 implementation](https://es5.github.io/#x15.1.2.2) of `parseInt`. - * - * @static - * @memberOf _ - * @since 1.1.0 - * @category String - * @param {string} string The string to convert. - * @param {number} [radix=10] The radix to interpret `value` by. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {number} Returns the converted integer. - * @example - * - * _.parseInt('08'); - * // => 8 - * - * _.map(['6', '08', '10'], _.parseInt); - * // => [6, 8, 10] - */ - function parseInt(string, radix, guard) { - if (guard || radix == null) { - radix = 0; - } else if (radix) { - radix = +radix; - } - return nativeParseInt(toString(string).replace(reTrimStart, ''), radix || 0); - } - - /** - * Repeats the given string `n` times. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to repeat. - * @param {number} [n=1] The number of times to repeat the string. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {string} Returns the repeated string. - * @example - * - * _.repeat('*', 3); - * // => '***' - * - * _.repeat('abc', 2); - * // => 'abcabc' - * - * _.repeat('abc', 0); - * // => '' - */ - function repeat(string, n, guard) { - if ((guard ? isIterateeCall(string, n, guard) : n === undefined)) { - n = 1; - } else { - n = toInteger(n); - } - return baseRepeat(toString(string), n); - } - - /** - * Replaces matches for `pattern` in `string` with `replacement`. - * - * **Note:** This method is based on - * [`String#replace`](https://mdn.io/String/replace). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category String - * @param {string} [string=''] The string to modify. - * @param {RegExp|string} pattern The pattern to replace. - * @param {Function|string} replacement The match replacement. - * @returns {string} Returns the modified string. - * @example - * - * _.replace('Hi Fred', 'Fred', 'Barney'); - * // => 'Hi Barney' - */ - function replace() { - var args = arguments, - string = toString(args[0]); - - return args.length < 3 ? string : string.replace(args[1], args[2]); - } - - /** - * Converts `string` to - * [snake case](https://en.wikipedia.org/wiki/Snake_case). - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to convert. - * @returns {string} Returns the snake cased string. - * @example - * - * _.snakeCase('Foo Bar'); - * // => 'foo_bar' - * - * _.snakeCase('fooBar'); - * // => 'foo_bar' - * - * _.snakeCase('--FOO-BAR--'); - * // => 'foo_bar' - */ - var snakeCase = createCompounder(function(result, word, index) { - return result + (index ? '_' : '') + word.toLowerCase(); - }); - - /** - * Splits `string` by `separator`. - * - * **Note:** This method is based on - * [`String#split`](https://mdn.io/String/split). - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category String - * @param {string} [string=''] The string to split. - * @param {RegExp|string} separator The separator pattern to split by. - * @param {number} [limit] The length to truncate results to. - * @returns {Array} Returns the string segments. - * @example - * - * _.split('a-b-c', '-', 2); - * // => ['a', 'b'] - */ - function split(string, separator, limit) { - if (limit && typeof limit != 'number' && isIterateeCall(string, separator, limit)) { - separator = limit = undefined; - } - limit = limit === undefined ? MAX_ARRAY_LENGTH : limit >>> 0; - if (!limit) { - return []; - } - string = toString(string); - if (string && ( - typeof separator == 'string' || - (separator != null && !isRegExp(separator)) - )) { - separator = baseToString(separator); - if (!separator && hasUnicode(string)) { - return castSlice(stringToArray(string), 0, limit); - } - } - return string.split(separator, limit); - } - - /** - * Converts `string` to - * [start case](https://en.wikipedia.org/wiki/Letter_case#Stylistic_or_specialised_usage). - * - * @static - * @memberOf _ - * @since 3.1.0 - * @category String - * @param {string} [string=''] The string to convert. - * @returns {string} Returns the start cased string. - * @example - * - * _.startCase('--foo-bar--'); - * // => 'Foo Bar' - * - * _.startCase('fooBar'); - * // => 'Foo Bar' - * - * _.startCase('__FOO_BAR__'); - * // => 'FOO BAR' - */ - var startCase = createCompounder(function(result, word, index) { - return result + (index ? ' ' : '') + upperFirst(word); - }); - - /** - * Checks if `string` starts with the given target string. - * - * @static - * @memberOf _ - * @since 3.0.0 - * @category String - * @param {string} [string=''] The string to inspect. - * @param {string} [target] The string to search for. - * @param {number} [position=0] The position to search from. - * @returns {boolean} Returns `true` if `string` starts with `target`, - * else `false`. - * @example - * - * _.startsWith('abc', 'a'); - * // => true - * - * _.startsWith('abc', 'b'); - * // => false - * - * _.startsWith('abc', 'b', 1); - * // => true - */ - function startsWith(string, target, position) { - string = toString(string); - position = position == null - ? 0 - : baseClamp(toInteger(position), 0, string.length); - - target = baseToString(target); - return string.slice(position, position + target.length) == target; - } - - /** - * Creates a compiled template function that can interpolate data properties - * in "interpolate" delimiters, HTML-escape interpolated data properties in - * "escape" delimiters, and execute JavaScript in "evaluate" delimiters. Data - * properties may be accessed as free variables in the template. If a setting - * object is given, it takes precedence over `_.templateSettings` values. - * - * **Note:** In the development build `_.template` utilizes - * [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl) - * for easier debugging. - * - * For more information on precompiling templates see - * [lodash's custom builds documentation](https://lodash.com/custom-builds). - * - * For more information on Chrome extension sandboxes see - * [Chrome's extensions documentation](https://developer.chrome.com/extensions/sandboxingEval). - * - * @static - * @since 0.1.0 - * @memberOf _ - * @category String - * @param {string} [string=''] The template string. - * @param {Object} [options={}] The options object. - * @param {RegExp} [options.escape=_.templateSettings.escape] - * The HTML "escape" delimiter. - * @param {RegExp} [options.evaluate=_.templateSettings.evaluate] - * The "evaluate" delimiter. - * @param {Object} [options.imports=_.templateSettings.imports] - * An object to import into the template as free variables. - * @param {RegExp} [options.interpolate=_.templateSettings.interpolate] - * The "interpolate" delimiter. - * @param {string} [options.sourceURL='lodash.templateSources[n]'] - * The sourceURL of the compiled template. - * @param {string} [options.variable='obj'] - * The data object variable name. - * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. - * @returns {Function} Returns the compiled template function. - * @example - * - * // Use the "interpolate" delimiter to create a compiled template. - * var compiled = _.template('hello <%= user %>!'); - * compiled({ 'user': 'fred' }); - * // => 'hello fred!' - * - * // Use the HTML "escape" delimiter to escape data property values. - * var compiled = _.template('<%- value %>'); - * compiled({ 'value': ' - - - - - - - diff --git a/integration/messaging/test/static/firebase-messaging-sw.js b/integration/messaging/test/static/firebase-messaging-sw.js deleted file mode 100644 index d19a00e44f0..00000000000 --- a/integration/messaging/test/static/firebase-messaging-sw.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @license - * Copyright 2017 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. - */ - -importScripts('./sw-base.js'); diff --git a/integration/messaging/test/static/helpers.js b/integration/messaging/test/static/helpers.js deleted file mode 100644 index 7712a636bea..00000000000 --- a/integration/messaging/test/static/helpers.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -async function addPayloadToDb(payload) { - const dbOpenReq = indexedDB.open(TEST_DB); - - dbOpenReq.onupgradeneeded = () => { - const db = dbOpenReq.result; - - // store creation is a synchronized call - console.log('creating object store...'); - db.createObjectStore(BACKGROUND_MESSAGES_OBJECT_STORE, { - keyPath: BACKGROUND_MESSAGES_OBJECT_STORE_PRIMARY_KEY - }); - }; - - dbOpenReq.onsuccess = () => { - const db = dbOpenReq.result; - - addPayloadToDbInternal(db, { - ...payload, - // ndx is required as the primary key of the store. It doesn't have any other testing purpose - ndx: BACKGROUND_MESSAGES_OBJECT_STORE_DEFAULT_NDX - }); - }; -} - -async function addPayloadToDbInternal(db, payload) { - // onsuccess might race with onupgradeneeded. Consequently causing "object stores was not found" - // error. Therefore, wait briefly for db.createObjectStore to complete - const delay = ms => new Promise(res => setTimeout(res, ms)); - await delay(/* milliseconds= */ 30000); - - tx = db.transaction(BACKGROUND_MESSAGES_OBJECT_STORE, 'readwrite'); - - console.log('adding message payload to db: ' + JSON.stringify(payload)); - addReq = tx.objectStore(BACKGROUND_MESSAGES_OBJECT_STORE).add(payload); -} diff --git a/integration/messaging/test/static/sw-base.js b/integration/messaging/test/static/sw-base.js deleted file mode 100644 index d7617bee105..00000000000 --- a/integration/messaging/test/static/sw-base.js +++ /dev/null @@ -1,37 +0,0 @@ -/** - * @license - * Copyright 2017 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. - */ - -importScripts('../constants.js'); -importScripts('../helpers.js'); - -// HEAD targets served through express -importScripts('/firebase-app.js'); -importScripts('/firebase-messaging.js'); - -firebase.initializeApp(FIREBASE_CONFIG); -const messaging = firebase.messaging(); - -messaging.setBackgroundMessageHandler(payload => { - console.log( - TAG + - 'a background message is received in the background handler hook: ' + - JSON.stringify(payload) + - '. Storing it into idb for tests to read...' - ); - - addPayloadToDb(payload); -}); diff --git a/integration/messaging/test/static/valid-manifest/index.html b/integration/messaging/test/static/valid-manifest/index.html deleted file mode 100644 index 3f482bab7af..00000000000 --- a/integration/messaging/test/static/valid-manifest/index.html +++ /dev/null @@ -1,34 +0,0 @@ - - - - - FCM Demo - - - - -

Valid Manifest

- - - - - - - - diff --git a/integration/messaging/test/static/valid-manifest/manifest.json b/integration/messaging/test/static/valid-manifest/manifest.json deleted file mode 100644 index c3181d8bc95..00000000000 --- a/integration/messaging/test/static/valid-manifest/manifest.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "gcm_sender_id": "750970317741" -} diff --git a/integration/messaging/test/static/valid-vapid-key-modern-sw/index.html b/integration/messaging/test/static/valid-vapid-key-modern-sw/index.html deleted file mode 100644 index 8d7d93c9f5a..00000000000 --- a/integration/messaging/test/static/valid-vapid-key-modern-sw/index.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - FCM Demo - - - -

Valid WITH VAPID Key - Modern SW

- - - - - - - - - diff --git a/integration/messaging/test/static/valid-vapid-key-modern-sw/sw.js b/integration/messaging/test/static/valid-vapid-key-modern-sw/sw.js deleted file mode 100644 index 459e83e948a..00000000000 --- a/integration/messaging/test/static/valid-vapid-key-modern-sw/sw.js +++ /dev/null @@ -1,37 +0,0 @@ -/** - * @license - * Copyright 2017 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. - */ - -importScripts('../constants.js'); -importScripts('../helpers.js'); - -// HEAD targets served through express -importScripts('/firebase-app.js'); -importScripts('/firebase-messaging.js'); - -firebase.initializeApp(FIREBASE_CONFIG); -const messaging = firebase.messaging(); - -messaging.onBackgroundMessage(payload => { - console.log( - TAG + - 'a background message is received in the onBackgroundMessage hook: ' + - JSON.stringify(payload) + - '. Storing it into idb for tests to read...' - ); - - addPayloadToDb(payload); -}); diff --git a/integration/messaging/test/static/valid-vapid-key/index.html b/integration/messaging/test/static/valid-vapid-key/index.html deleted file mode 100644 index b8d32ec7f00..00000000000 --- a/integration/messaging/test/static/valid-vapid-key/index.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - FCM Demo - - - -

Valid WITH VAPID Key

- - - - - - - - - diff --git a/integration/messaging/test/static/valid-vapid-key/sw.js b/integration/messaging/test/static/valid-vapid-key/sw.js deleted file mode 100644 index f18afe56bdd..00000000000 --- a/integration/messaging/test/static/valid-vapid-key/sw.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @license - * Copyright 2017 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. - */ - -importScripts('../sw-base.js'); diff --git a/integration/messaging/test/test-receive-background.js b/integration/messaging/test/test-receive-background.js deleted file mode 100644 index 258bff32051..00000000000 --- a/integration/messaging/test/test-receive-background.js +++ /dev/null @@ -1,157 +0,0 @@ -/** - * @license - * Copyright 2017 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. - */ - -const testServer = require('./utils/test-server'); -const sendMessage = require('./utils/sendMessage'); -const retrieveToken = require('./utils/retrieveToken'); -const seleniumAssistant = require('selenium-assistant'); -const getReceivedBackgroundMessages = require('./utils/getReceivedBackgroundMessages'); -const createPermittedWebDriver = require('./utils/createPermittedWebDriver'); -const checkSendResponse = require('./utils/checkSendResponse'); -const checkMessageReceived = require('./utils/checkMessageReceived'); -const openNewTab = require('./utils/openNewTab'); - -const TEST_DOMAINS = ['valid-vapid-key-modern-sw']; - -// 4 minutes. The fact that the flow includes making a request to the Send Service, -// storing/retrieving form indexedDb asynchronously makes these test units to have a execution time -// variance. Therefore, allowing these units to have a longer time to work is crucial. -const TIMEOUT_BACKGROUND_MESSAGE_TEST_UNIT_MILLISECONDS = 240000; - -// 1 minute. Wait for object store to be created and received message to be stored in idb. This -// waiting time MUST be longer than the wait time for adding to db in the sw. -const WAIT_TIME_BEFORE_RETRIEVING_BACKGROUND_MESSAGES_MILLISECONDS = 60000; - -const wait = ms => new Promise(res => setTimeout(res, ms)); - -// 50% of integration test run time is spent in testing receiving in background. Running these -// slower tests last so other tests can fail quickly if they do fail. -require('./test-token-delete'); -require('./test-token-update'); -require('./test-useValidManifest'); -require('./test-useDefaultServiceWorker'); -require('./test-receive-foreground'); - -describe('Firebase Messaging Integration Tests > Test Background Receive', function () { - this.retries(2); - let globalWebDriver; - - before(async function () { - await testServer.start(); - }); - - after(async function () { - await testServer.stop(); - }); - - // TODO: enable testing for firefox - seleniumAssistant.getLocalBrowsers().forEach(assistantBrowser => { - if (assistantBrowser.getId() !== 'chrome') { - return; - } - - TEST_DOMAINS.forEach(domain => { - describe(`Testing browser: ${assistantBrowser.getPrettyName()} : ${domain}`, function () { - before(async function () { - globalWebDriver = createPermittedWebDriver( - /* browser= */ assistantBrowser.getId() - ); - }); - - it('Background app can receive a {} empty message from sw', async function () { - this.timeout(TIMEOUT_BACKGROUND_MESSAGE_TEST_UNIT_MILLISECONDS); - - // Clearing the cache and db data by killing the previously instantiated driver. Note that - // ideally this call is placed inside the after/before hooks. However, Mocha forbids - // operations longer than 2s in hooks. Hence, this clearing call needs to be inside the - // test unit. - await seleniumAssistant.killWebDriver(globalWebDriver); - - globalWebDriver = createPermittedWebDriver( - /* browser= */ assistantBrowser.getId() - ); - - prepareBackgroundApp(globalWebDriver, domain); - - checkSendResponse( - await sendMessage({ - to: await retrieveToken(globalWebDriver) - }) - ); - - await wait( - WAIT_TIME_BEFORE_RETRIEVING_BACKGROUND_MESSAGES_MILLISECONDS - ); - - checkMessageReceived( - await getReceivedBackgroundMessages(globalWebDriver), - /* expectedNotificationPayload= */ null, - /* expectedDataPayload= */ null, - /* isLegacyPayload= */ false - ); - }); - - it('Background app can receive a {"data"} message frow sw', async function () { - this.timeout(TIMEOUT_BACKGROUND_MESSAGE_TEST_UNIT_MILLISECONDS); - - await seleniumAssistant.killWebDriver(globalWebDriver); - - globalWebDriver = createPermittedWebDriver( - /* browser= */ assistantBrowser.getId() - ); - - prepareBackgroundApp(globalWebDriver, domain); - - checkSendResponse( - await sendMessage({ - to: await retrieveToken(globalWebDriver), - data: getTestDataPayload() - }) - ); - - await wait( - WAIT_TIME_BEFORE_RETRIEVING_BACKGROUND_MESSAGES_MILLISECONDS - ); - - checkMessageReceived( - await getReceivedBackgroundMessages(globalWebDriver), - /* expectedNotificationPayload= */ null, - /* expectedDataPayload= */ getTestDataPayload() - ); - }); - }); - }); - }); -}); - -async function prepareBackgroundApp(globalWebDriver, domain) { - await globalWebDriver.get(`${testServer.serverAddress}/${domain}/`); - // TODO: remove the try/catch block once the underlying bug has been resolved. Shift window focus - // away from app window so that background messages can be received/processed - try { - await openNewTab(globalWebDriver); - } catch (err) { - // ChromeDriver seems to have an open bug which throws "JavascriptError: javascript error: - // circular reference". Nevertheless, a new tab can still be opened. Hence, just catch and - // continue here. - console.log('FCM (ignored on purpose): ' + err); - } -} - -function getTestDataPayload() { - return { hello: 'world' }; -} diff --git a/integration/messaging/test/test-receive-foreground.js b/integration/messaging/test/test-receive-foreground.js deleted file mode 100644 index 8c23a07fae6..00000000000 --- a/integration/messaging/test/test-receive-foreground.js +++ /dev/null @@ -1,177 +0,0 @@ -/** - * @license - * Copyright 2017 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. - */ - -const testServer = require('./utils/test-server'); -const sendMessage = require('./utils/sendMessage'); -const retrieveToken = require('./utils/retrieveToken'); -const seleniumAssistant = require('selenium-assistant'); -const checkSendResponse = require('./utils/checkSendResponse'); -const getReceivedForegroundMessages = require('./utils/getReceivedForegroundMessages'); -const checkMessageReceived = require('./utils/checkMessageReceived'); -const createPermittedWebDriver = require('./utils/createPermittedWebDriver'); - -// Only testing 'valid-vapid-key' because 'valid-vapid-key-modern-sw' has the same behavior -const TEST_DOMAINS = ['valid-vapid-key']; -const TIMEOUT_FOREGROUND_MESSAGE_TEST_UNIT_MILLISECONDS = 120000; - -// Getting and deleting token is the entry step of using FM SDK. Let it run first and fail quickly. -require('./test-token-delete'); - -describe('Firebase Messaging Integration Tests > Test Foreground Receive', function () { - this.retries(2); - let globalWebDriver; - - before(async function () { - await testServer.start(); - }); - - after(async function () { - await testServer.stop(); - }); - - // TODO: enable testing for firefox - seleniumAssistant.getLocalBrowsers().forEach(assistantBrowser => { - if (assistantBrowser.getId() !== 'chrome') { - return; - } - - TEST_DOMAINS.forEach(domain => { - describe(`Testing browser: ${assistantBrowser.getPrettyName()} : ${domain}`, function () { - before(async function () { - globalWebDriver = createPermittedWebDriver( - /* browser= */ assistantBrowser.getId() - ); - }); - }); - - it('Foreground app can receive a {} empty message in onMessage', async function () { - this.timeout(TIMEOUT_FOREGROUND_MESSAGE_TEST_UNIT_MILLISECONDS); - - await seleniumAssistant.killWebDriver(globalWebDriver); - - globalWebDriver = createPermittedWebDriver( - /* browser= */ assistantBrowser.getId() - ); - - await globalWebDriver.get(`${testServer.serverAddress}/${domain}/`); - - let token = await retrieveToken(globalWebDriver); - checkSendResponse( - await sendMessage({ - to: token - }) - ); - - await checkMessageReceived( - await getReceivedForegroundMessages(globalWebDriver), - /* expectedNotificationPayload= */ null, - /* expectedDataPayload= */ null - ); - }); - - it('Foreground app can receive a {"notification"} message in onMessage', async function () { - this.timeout(TIMEOUT_FOREGROUND_MESSAGE_TEST_UNIT_MILLISECONDS); - - await seleniumAssistant.killWebDriver(globalWebDriver); - - globalWebDriver = createPermittedWebDriver( - /* browser= */ assistantBrowser.getId() - ); - - await globalWebDriver.get(`${testServer.serverAddress}/${domain}/`); - - checkSendResponse( - await sendMessage({ - to: await retrieveToken(globalWebDriver), - notification: getTestNotificationPayload() - }) - ); - - await checkMessageReceived( - await getReceivedForegroundMessages(globalWebDriver), - /* expectedNotificationPayload= */ getTestNotificationPayload(), - /* expectedDataPayload= */ null - ); - }); - - it('Foreground app can receive a {"data"} message in onMessage', async function () { - this.timeout(TIMEOUT_FOREGROUND_MESSAGE_TEST_UNIT_MILLISECONDS); - - await seleniumAssistant.killWebDriver(globalWebDriver); - - globalWebDriver = createPermittedWebDriver( - /* browser= */ assistantBrowser.getId() - ); - - await globalWebDriver.get(`${testServer.serverAddress}/${domain}/`); - - checkSendResponse( - await sendMessage({ - to: await retrieveToken(globalWebDriver), - data: getTestDataPayload() - }) - ); - - await checkMessageReceived( - await getReceivedForegroundMessages(globalWebDriver), - /* expectedNotificationPayload= */ null, - /* expectedDataPayload= */ getTestDataPayload() - ); - }); - - it('Foreground app can receive a {"notification", "data"} message in onMessage', async function () { - this.timeout(TIMEOUT_FOREGROUND_MESSAGE_TEST_UNIT_MILLISECONDS); - - await seleniumAssistant.killWebDriver(globalWebDriver); - - globalWebDriver = createPermittedWebDriver( - /* browser= */ assistantBrowser.getId() - ); - - await globalWebDriver.get(`${testServer.serverAddress}/${domain}/`); - - checkSendResponse( - await sendMessage({ - to: await retrieveToken(globalWebDriver), - data: getTestDataPayload(), - notification: getTestNotificationPayload() - }) - ); - - await checkMessageReceived( - await getReceivedForegroundMessages(globalWebDriver), - /* expectedNotificationPayload= */ getTestNotificationPayload(), - /* expectedDataPayload= */ getTestDataPayload() - ); - }); - }); - }); -}); - -function getTestDataPayload() { - return { hello: 'world' }; -} - -function getTestNotificationPayload() { - return { - title: 'test title', - body: 'test body', - icon: '/test/icon.png', - click_action: '/', - tag: 'test-tag' - }; -} diff --git a/integration/messaging/test/test-token-delete.js b/integration/messaging/test/test-token-delete.js deleted file mode 100644 index d3f0fca908e..00000000000 --- a/integration/messaging/test/test-token-delete.js +++ /dev/null @@ -1,82 +0,0 @@ -/** - * @license - * Copyright 2017 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. - */ - -const expect = require('chai').expect; -const testServer = require('./utils/test-server'); -const deleteToken = require('./utils/deleteToken'); -const clearAppForTest = require('./utils/clearAppForTest'); -const retrieveToken = require('./utils/retrieveToken'); -const seleniumAssistant = require('selenium-assistant'); -const createPermittedWebDriver = require('./utils/createPermittedWebDriver'); - -const TEST_SUITE_TIMEOUT_MS = 20000; -const TEST_DOMAIN = 'valid-vapid-key'; - -describe('Firebase Messaging Integration Tests > get and delete token', function () { - this.timeout(TEST_SUITE_TIMEOUT_MS); - this.retries(2); - let globalWebDriver; - - before(async function () { - await testServer.start(); - }); - - after(async function () { - await testServer.stop(); - }); - - const availableBrowsers = seleniumAssistant.getLocalBrowsers(); - availableBrowsers.forEach(assistantBrowser => { - // TODO: enable testing for firefox - if (assistantBrowser.getId() !== 'chrome') { - return; - } - - describe(`Testing browser: ${assistantBrowser.getPrettyName()} : ${TEST_DOMAIN}`, function () { - before(async function () { - // Use one webDriver per browser instead of one per test to speed up test. - globalWebDriver = createPermittedWebDriver( - /* browser= */ assistantBrowser.getId() - ); - - await globalWebDriver.get( - `${testServer.serverAddress}/${TEST_DOMAIN}/` - ); - }); - - after(async function () { - await seleniumAssistant.killWebDriver(globalWebDriver); - }); - - afterEach(async function () { - await clearAppForTest(globalWebDriver); - }); - - it(`Test app can delete a valid token`, async function () { - const token = await retrieveToken(globalWebDriver); - expect(token).to.exist; - try { - await deleteToken(globalWebDriver, token); - console.log('Token deletion succeed.'); - } catch (e) { - console.log('Error trying to delete FCM token: ', e); - fail(); - } - }); - }); - }); -}); diff --git a/integration/messaging/test/test-token-update.js b/integration/messaging/test/test-token-update.js deleted file mode 100644 index d2fc33782c1..00000000000 --- a/integration/messaging/test/test-token-update.js +++ /dev/null @@ -1,88 +0,0 @@ -/** - * @license - * Copyright 2017 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. - */ - -const seleniumAssistant = require('selenium-assistant'); -const expect = require('chai').expect; -const testServer = require('./utils/test-server'); -const retrieveToken = require('./utils/retrieveToken'); -const clearAppForTest = require('./utils/clearAppForTest'); -const createPermittedWebDriver = require('./utils/createPermittedWebDriver'); -const timeForward = require('./utils/forwardTime'); -const triggerGetToken = require('./utils/triggerGetToken'); -const getErrors = require('./utils/getErrors'); - -const TEST_SUITE_TIMEOUT_MS = 70000; -const TEST_DOMAIN = 'valid-vapid-key'; - -// Getting and deleting token is the entry step of using FM SDK. Let it run first and fail quickly. -require('./test-token-delete'); - -describe('Firebase Messaging Integration Tests > update a token', function () { - this.timeout(TEST_SUITE_TIMEOUT_MS); - this.retries(2); - - let globalWebDriver; - - before(async function () { - await testServer.start(); - }); - - after(async function () { - await testServer.stop(); - }); - - const availableBrowsers = seleniumAssistant.getLocalBrowsers(); - // TODO: enable testing for edge and firefox if applicable - availableBrowsers.forEach(assistantBrowser => { - if (assistantBrowser.getId() !== 'chrome') { - return; - } - - describe(`Testing browser: ${assistantBrowser.getPrettyName()} : ${TEST_DOMAIN}`, function () { - before(async function () { - // Use one webDriver per browser instead of one per test to speed up test. - globalWebDriver = createPermittedWebDriver( - /* browser= */ assistantBrowser.getId() - ); - await globalWebDriver.get( - `${testServer.serverAddress}/${TEST_DOMAIN}/` - ); - }); - - after(async function () { - await seleniumAssistant.killWebDriver(globalWebDriver); - }); - - afterEach(async function () { - await clearAppForTest(globalWebDriver); - }); - - it(`should update a token`, async function () { - const token = await retrieveToken(globalWebDriver); - expect(token).to.exist; - - // roll the clock forward > 7days - await timeForward(globalWebDriver); - const updatedToken = await triggerGetToken(globalWebDriver); - const errors = await getErrors(globalWebDriver); - expect(errors).to.exist; - expect(errors.length).to.equal(0); - expect(updatedToken).to.exist; - }); - }); - }); -}); diff --git a/integration/messaging/test/test-useDefaultServiceWorker.js b/integration/messaging/test/test-useDefaultServiceWorker.js deleted file mode 100644 index 0e3a903042f..00000000000 --- a/integration/messaging/test/test-useDefaultServiceWorker.js +++ /dev/null @@ -1,64 +0,0 @@ -/** - * @license - * Copyright 2017 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. - */ - -const expect = require('chai').expect; -const testServer = require('./utils/test-server'); -const retrieveToken = require('./utils/retrieveToken'); -const seleniumAssistant = require('selenium-assistant'); -const createPermittedWebDriver = require('./utils/createPermittedWebDriver'); - -const TEST_DOMAIN = 'default-sw'; -const TEST_SUITE_TIMEOUT_MS = 70000; - -// Getting and deleting token is the entry step of using FM SDK. Let it run first and fail quickly. -require('./test-token-delete'); - -describe(`Firebase Messaging Integration Tests > Use 'firebase-messaging-sw.js' by default`, function () { - this.timeout(TEST_SUITE_TIMEOUT_MS); - - let globalWebDriver; - - before(async function () { - await testServer.start(); - }); - - after(async function () { - await testServer.stop(); - await seleniumAssistant.killWebDriver(globalWebDriver); - }); - - it(`should use default SW by default`, async function () { - globalWebDriver = createPermittedWebDriver('chrome'); - await globalWebDriver.get(`${testServer.serverAddress}/${TEST_DOMAIN}/`); - - // If we have a token, then we know the default SW worked. - const token = await retrieveToken(globalWebDriver); - expect(token).to.exist; - - const result = await globalWebDriver.executeAsyncScript(function (cb) { - navigator.serviceWorker - .getRegistrations() - .then(swReg => { - return ( - swReg[0].scope.indexOf('/firebase-cloud-messaging-push-scope') !== 0 - ); - }) - .then(cb, cb); - }); - expect(result).to.equal(true); - }); -}); diff --git a/integration/messaging/test/test-useValidManifest.js b/integration/messaging/test/test-useValidManifest.js deleted file mode 100644 index 4c566fc9eb4..00000000000 --- a/integration/messaging/test/test-useValidManifest.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * @license - * Copyright 2017 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. - */ - -const expect = require('chai').expect; -const testServer = require('./utils/test-server'); -const retrieveToken = require('./utils/retrieveToken'); -const seleniumAssistant = require('selenium-assistant'); -const createPermittedWebDriver = require('./utils/createPermittedWebDriver'); - -const TEST_DOMAIN = 'valid-manifest'; -const TEST_SUITE_TIMEOUT_MS = 70000; - -// Getting and deleting token is the entry step of using FM SDK. Let it run first and fail quickly. -require('./test-token-delete'); - -describe(`Firebase Messaging Integration Tests > Use 'use valid manifest`, function () { - this.timeout(TEST_SUITE_TIMEOUT_MS); - - let globalWebDriver; - - before(async function () { - await testServer.start(); - }); - - after(async function () { - await testServer.stop(); - await seleniumAssistant.killWebDriver(globalWebDriver); - }); - - it(`should allow valid manifest`, async function () { - globalWebDriver = createPermittedWebDriver('chrome'); - await globalWebDriver.get(`${testServer.serverAddress}/${TEST_DOMAIN}/`); - - // If we have a token, then we know the default SW + Manifest worked. - const token = await retrieveToken(globalWebDriver); - expect(token).to.exist; - }); -}); diff --git a/integration/messaging/test/utils/checkMessageReceived.js b/integration/messaging/test/utils/checkMessageReceived.js deleted file mode 100644 index c407ae6b9b8..00000000000 --- a/integration/messaging/test/utils/checkMessageReceived.js +++ /dev/null @@ -1,66 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ -const expect = require('chai').expect; - -const TEST_PROJECT_SENDER_ID = '750970317741'; -const DEFAULT_COLLAPSE_KEY_VALUE = 'do_not_collapse'; -const FIELD_FROM = 'from'; -const FIELD_COLLAPSE_KEY_LEGACY = 'collapse_key'; -const FIELD_COLLAPSE_KEY = 'collapseKey'; - -const FIELD_DATA = 'data'; -const FIELD_NOTIFICATION = 'notification'; - -module.exports = ( - receivedMessages, - expectedNotificationPayload, - expectedDataPayload -) => { - expect(receivedMessages).to.exist; - - const message = receivedMessages[0]; - - expect(message[FIELD_FROM]).to.equal(TEST_PROJECT_SENDER_ID); - const collapseKey = !!message[FIELD_COLLAPSE_KEY_LEGACY] - ? message[FIELD_COLLAPSE_KEY_LEGACY] - : message[FIELD_COLLAPSE_KEY]; - expect(collapseKey).to.equal(DEFAULT_COLLAPSE_KEY_VALUE); - - if (expectedNotificationPayload) { - expect(message[FIELD_NOTIFICATION]).to.deep.equal( - getTestNotificationPayload() - ); - } - - if (expectedDataPayload) { - expect(message[FIELD_DATA]).to.deep.equal(getTestDataPayload()); - } -}; - -function getTestNotificationPayload() { - return { - title: 'test title', - body: 'test body', - icon: '/test/icon.png', - click_action: '/', - tag: 'test-tag' - }; -} - -function getTestDataPayload() { - return { hello: 'world' }; -} diff --git a/integration/messaging/test/utils/checkSendResponse.js b/integration/messaging/test/utils/checkSendResponse.js deleted file mode 100644 index 5c43f78ef8a..00000000000 --- a/integration/messaging/test/utils/checkSendResponse.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ -const expect = require('chai').expect; - -module.exports = response => { - expect(response).to.exist; - expect(response.success).to.equal(1); -}; diff --git a/integration/messaging/test/utils/clearAppForTest.js b/integration/messaging/test/utils/clearAppForTest.js deleted file mode 100644 index d4e153dda9d..00000000000 --- a/integration/messaging/test/utils/clearAppForTest.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @license - * Copyright 2017 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. - */ - -module.exports = async webDriver => { - console.log('Clearing received messaged and errors from the test app.'); - - await webDriver.wait(() => { - return webDriver.executeScript(() => { - return !!window.__test; - }); - }); - - return webDriver.executeScript(() => { - return window.__test.clearInstanceForTest(); - }); -}; diff --git a/integration/messaging/test/utils/createPermittedWebDriver.js b/integration/messaging/test/utils/createPermittedWebDriver.js deleted file mode 100644 index 7adf117ae86..00000000000 --- a/integration/messaging/test/utils/createPermittedWebDriver.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * @license - * Copyright 2017 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. - */ - -const testServer = require('./test-server'); -const { Builder } = require('selenium-webdriver'); -const chrome = require('selenium-webdriver/chrome'); - -const CHROME_PREFERENCE_NOTIFICATION_ENABLED = 1; -const SERVER_ADDRESS_SEPARATOR = ',*'; - -module.exports = browser => { - const chromePreferences = { - profile: { - content_settings: { - exceptions: { - notifications: {} - } - } - } - }; - chromePreferences.profile.content_settings.exceptions.notifications[ - testServer.serverAddress + SERVER_ADDRESS_SEPARATOR - ] = { - setting: CHROME_PREFERENCE_NOTIFICATION_ENABLED - }; - - let chromeOptions = new chrome.Options().setUserPreferences( - chromePreferences - ); - - let driver = new Builder() - .forBrowser(browser) - .setChromeOptions(chromeOptions) - .build(); - return driver; -}; diff --git a/integration/messaging/test/utils/deleteToken.js b/integration/messaging/test/utils/deleteToken.js deleted file mode 100644 index 51a0f84b4a8..00000000000 --- a/integration/messaging/test/utils/deleteToken.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * @license - * Copyright 2017 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. - */ - -module.exports = async (webdriver, token) => { - console.log('Deleting token: ', token); - await webdriver.wait(() => { - return webdriver.executeScript(() => { - return !!window.__test; - }); - }); - - return webdriver.executeScript(token => { - return window.__test.triggerDeleteToken(token); - }); -}; diff --git a/integration/messaging/test/utils/forwardTime.js b/integration/messaging/test/utils/forwardTime.js deleted file mode 100644 index f97c25998b0..00000000000 --- a/integration/messaging/test/utils/forwardTime.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * @license - * Copyright 2017 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. - */ - -module.exports = async webdriver => { - console.log('Rolling time forward 8 days...'); - await webdriver.wait(() => { - return webdriver.executeScript(() => { - return !!window.__test; - }); - }); - - return webdriver.executeScript(() => { - return window.__test.triggerTimeForward(); - }); -}; diff --git a/integration/messaging/test/utils/getErrors.js b/integration/messaging/test/utils/getErrors.js deleted file mode 100644 index 85a72048f83..00000000000 --- a/integration/messaging/test/utils/getErrors.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @license - * Copyright 2017 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. - */ - -module.exports = async webdriver => { - console.log('Getting errors...'); - - await webdriver.wait(() => { - return webdriver.executeScript(() => { - return !!window.__test; - }); - }); - - return webdriver.executeScript(() => { - return window.__test.errors; - }); -}; diff --git a/integration/messaging/test/utils/getReceivedBackgroundMessages.js b/integration/messaging/test/utils/getReceivedBackgroundMessages.js deleted file mode 100644 index d6af2471545..00000000000 --- a/integration/messaging/test/utils/getReceivedBackgroundMessages.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * @license - * Copyright 2017 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. - */ - -const TEST_DB = 'FCM_INTEGRATION_TEST_DB'; -const BACKGROUND_MESSAGES_OBJECT_STORE = 'background_messages'; - -/** Getting received background messages are trickier than getting foreground messages from app. It - * requires idb object store creation with the service worker. Idb operations are fired as async - * events. This method needs to be called after the idb operations inside sw is done. In tests, - * consider adding a brief timeout before calling the method to give sw some time to work. - */ -module.exports = async webdriver => { - console.log('Getting received background messages from idb: '); - - await webdriver.executeScript(() => { - window.backgroundMessages = []; - - const dbOpenReq = indexedDB.open(TEST_DB); - - dbOpenReq.onsuccess = () => { - const db = dbOpenReq.result; - const tx = db.transaction(BACKGROUND_MESSAGES_OBJECT_STORE, 'readonly'); - - tx.objectStore(BACKGROUND_MESSAGES_OBJECT_STORE).getAll().onsuccess = - e => { - window.backgroundMessages = e.target.result; - }; - }; - }); - - await webdriver.wait(() => { - return webdriver.executeScript(() => { - return window.backgroundMessages.length > 0; - }); - }); - - console.log('Found background messages'); - return webdriver.executeScript(() => { - return window.backgroundMessages; - }); -}; diff --git a/integration/messaging/test/utils/getReceivedForegroundMessages.js b/integration/messaging/test/utils/getReceivedForegroundMessages.js deleted file mode 100644 index a1edbfee9c2..00000000000 --- a/integration/messaging/test/utils/getReceivedForegroundMessages.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @license - * Copyright 2017 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. - */ - -module.exports = async webdriver => { - console.log('Getting received foreground messages from test app: '); - - await webdriver.wait(() => { - return webdriver.executeScript(() => { - return window.__test.messages.length > 0; - }); - }); - - console.log('Found message.'); - return webdriver.executeScript(() => { - return window.__test.messages; - }); -}; diff --git a/integration/messaging/test/utils/openNewTab.js b/integration/messaging/test/utils/openNewTab.js deleted file mode 100644 index e141d0764b9..00000000000 --- a/integration/messaging/test/utils/openNewTab.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @license - * Copyright 2017 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. - */ - -module.exports = async webdriver => { - console.log('Opening a new tab in the browser...'); - - return webdriver.executeScript(() => { - return window.open(); - }); -}; diff --git a/integration/messaging/test/utils/retrieveToken.js b/integration/messaging/test/utils/retrieveToken.js deleted file mode 100644 index 7be388332c9..00000000000 --- a/integration/messaging/test/utils/retrieveToken.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @license - * Copyright 2017 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. - */ - -module.exports = async webdriver => { - console.log('retrieving token from test app'); - - await webdriver.wait(() => { - return webdriver.executeScript(() => { - return !!window.__test && !!window.__test.token; - }); - }); - - return webdriver.executeScript(() => { - return window.__test.token; - }); -}; diff --git a/integration/messaging/test/utils/sendMessage.js b/integration/messaging/test/utils/sendMessage.js deleted file mode 100644 index 1d2e95054eb..00000000000 --- a/integration/messaging/test/utils/sendMessage.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * @license - * Copyright 2017 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. - */ - -const undici = require('undici'); -const FCM_SEND_ENDPOINT = 'https://fcm.googleapis.com/fcm/send'; -// Rotatable fcm server key. It's generally a bad idea to expose server keys. The reason is to -// simplify testing process (no need to implement server side decryption of git secret). The -// justification is that a) this is a disposable test project b) the key itself is rotatable. -const FCM_KEY = - 'AAAArtlRq60:APA91bHFulW1dBpIPbArYXPbFtO9M_a9ZNXhnj9hGArfGK55g8fv5s5Qset6984xRIrqhZ_3IlKcG9LgSk3DiTdHMDIOkxboNJquNK1SChC7J0ULTvHPg7t0V6AjR1UEA21DXI22BM5N'; - -module.exports = async payload => { - console.log( - 'Requesting to send an FCM message with payload: ' + JSON.stringify(payload) - ); - - const response = await undici.fetch(FCM_SEND_ENDPOINT, { - method: 'POST', - body: JSON.stringify(payload), - headers: { - Authorization: 'key=' + FCM_KEY, - 'Content-Type': 'application/json' - } - }); - - // Note that FCM Send API responses are in HTML format - let res = await response.text(); - return JSON.parse(res); -}; diff --git a/integration/messaging/test/utils/test-server.js b/integration/messaging/test/utils/test-server.js deleted file mode 100644 index c178f0c72d4..00000000000 --- a/integration/messaging/test/utils/test-server.js +++ /dev/null @@ -1,74 +0,0 @@ -/** - * @license - * Copyright 2017 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. - */ - -const path = require('path'); -const express = require('express'); -const PORT_NUMBER = 3000; - -const FIREBASE_HEAD = express.static( - path.join( - /* firebase-js-sdk/integration/messaging */ process.env.PWD, - '../..', - '/packages/firebase' - ) -); - -const INTEGRATION_TEST_ASSETS = express.static( - path.join( - /* firebase-js-sdk/integration/messaging */ process.env.PWD, - 'test/static' - ) -); - -class MessagingTestServer { - constructor() { - this._app = express(); - this._app.use([INTEGRATION_TEST_ASSETS, FIREBASE_HEAD]); - this._server = null; - } - - get serverAddress() { - if (!this._server) { - return null; - } - - return `http://localhost:${PORT_NUMBER}`; - } - - async start() { - if (this._server) { - return; - } - - return new Promise((resolve, reject) => { - this._server = this._app.listen(PORT_NUMBER, () => { - resolve(); - }); - }); - } - - // Sometimes the server doesn't trigger the callback due to currently open sockets. So call close - // this._server - async stop() { - if (this._server) { - this._server.close(); - this._server = null; - } - } -} - -module.exports = new MessagingTestServer(); diff --git a/integration/messaging/test/utils/triggerGetToken.js b/integration/messaging/test/utils/triggerGetToken.js deleted file mode 100644 index 3668fd4a81d..00000000000 --- a/integration/messaging/test/utils/triggerGetToken.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @license - * Copyright 2017 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. - */ - -module.exports = async webdriver => { - console.log('Getting token...'); - - await webdriver.wait(() => { - return webdriver.executeScript(() => { - return !!window.__test; - }); - }); - - return webdriver.executeAsyncScript(cb => { - window.__test.triggerGetToken().then(token => { - cb(token); - }); - }); -}; diff --git a/patches/karma-webpack+5.0.0.patch b/patches/karma-webpack+5.0.0.patch deleted file mode 100644 index 3e3424a2912..00000000000 --- a/patches/karma-webpack+5.0.0.patch +++ /dev/null @@ -1,23 +0,0 @@ -diff --git a/node_modules/karma-webpack/lib/webpack/plugin.js b/node_modules/karma-webpack/lib/webpack/plugin.js -index 47b993c..3b75a9e 100644 ---- a/node_modules/karma-webpack/lib/webpack/plugin.js -+++ b/node_modules/karma-webpack/lib/webpack/plugin.js -@@ -1,4 +1,5 @@ - const fs = require('fs'); -+const path = require('path'); - - class KW_WebpackPlugin { - constructor(options) { -@@ -14,9 +15,10 @@ class KW_WebpackPlugin { - // read generated file content and store for karma preprocessor - this.controller.bundlesContent = {}; - stats.toJson().assets.forEach((webpackFileObj) => { -- const filePath = `${compiler.options.output.path}/${ -+ const filePath = path.resolve( -+ compiler.options.output.path, - webpackFileObj.name -- }`; -+ ); - this.controller.bundlesContent[webpackFileObj.name] = fs.readFileSync( - filePath, - 'utf-8' diff --git a/renovate.json b/renovate.json deleted file mode 100644 index c8b64a1df38..00000000000 --- a/renovate.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "extends": [ - "config:js-lib" - ], - "lockFileMaintenance": { - "enabled": true - }, - "packageRules": [ - { - "matchUpdateTypes": ["patch", "minor"], - "groupName": "all non-major dependencies", - "excludePackageNames": ["typescript"] - } - ], - "ignoreDeps": [ - "karma-sauce-launcher", - "protractor", - "long", - "rollup-plugin-copy-assets", - "whatwg-fetch", - "typedoc", - "@microsoft/tsdoc" - ], - "ignorePaths": [ - "auth/demo", - "auth/cordova/demo", - "auth-compat/demo" - ], - "assignees": [ - "@hsubox76" - ], - "schedule": "before 3am on Friday" -} diff --git a/repo-scripts/api-documenter/CHANGELOG.md b/repo-scripts/api-documenter/CHANGELOG.md deleted file mode 100644 index f860451d80c..00000000000 --- a/repo-scripts/api-documenter/CHANGELOG.md +++ /dev/null @@ -1,21 +0,0 @@ -# @firebase/api-documenter -## 0.4.0 -### Minor Changes - -- [#7864](https://github.com/firebase/firebase-js-sdk/pull/7864) Fix documentation links for function overloads and improve readability of function headings. -## 0.3.0 -### Minor Changes - -- [#6623](https://github.com/firebase/firebase-js-sdk/pull/6623) Add an option to sort functions by first param. (--sort-functions) -## 0.2.0 -### Minor Changes - -- [#6449](https://github.com/firebase/firebase-js-sdk/pull/6449) Updates to work with devsite changes. Added a required `--project` flag for generating markdown docs. -## 0.1.2 -### Patch Changes - -- [#4931](https://github.com/firebase/firebase-js-sdk/pull/4931) Support toc generation for Firebase devsite. -## 0.1.1 -### Patch Changes - -- [#4869](https://github.com/firebase/firebase-js-sdk/pull/4869) Generate API docs for namespace members diff --git a/repo-scripts/api-documenter/README.md b/repo-scripts/api-documenter/README.md deleted file mode 100644 index 302cdd25bd8..00000000000 --- a/repo-scripts/api-documenter/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# @firebase/api-documenter - -It is a fork of [API Documenter](https://github.com/microsoft/rushstack/tree/master/apps/api-documenter) -It reads the *.api.json data files produced by [API Extractor](https://api-extractor.com/), -and then generates files in [Markdown](https://en.wikipedia.org/wiki/Markdown) format suitable for displaying in Firebase Devsite. - -## Generate toc for Firebase devsite -`api-documenter-fire toc -i temp -p "/docs/reference/js/v9"` - -`-i` and `-p` (`--host-path`) are required parameters. -Use `-i` to specify the folder that contains api.json files. -Use `-p` to specify the g3 path that contains the reference docs. - -By default, the command will create `toc.yaml` in folder `/toc`. To change the output folder, use the flag `-o`. - -To generate toc for the Firebase JS SDK, also set the flag `-j` to create the top level `firebase` toc item. \ No newline at end of file diff --git a/repo-scripts/api-documenter/gulpfile.js b/repo-scripts/api-documenter/gulpfile.js deleted file mode 100644 index 44a9ffa12c3..00000000000 --- a/repo-scripts/api-documenter/gulpfile.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -const gulp = require('gulp'); - -gulp.task('copy-resources', function () { - return gulp.src('./src/schemas/*').pipe(gulp.dest('./dist/schemas')); -}); diff --git a/repo-scripts/api-documenter/package.json b/repo-scripts/api-documenter/package.json deleted file mode 100644 index 1e6b1a9ac21..00000000000 --- a/repo-scripts/api-documenter/package.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "@firebase/api-documenter", - "version": "0.4.0", - "description": "Read JSON files from api-extractor, generate documentation pages", - "repository": { - "directory": "repo-scripts/documenter", - "type": "git", - "url": "git+https://github.com/firebase/firebase-js-sdk.git" - }, - "license": "Apache-2.0", - "scripts": { - "build": "tsc && gulp copy-resources", - "test": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha src/**/*.test.ts --config ../../config/mocharc.node.js" - }, - "bin": { - "api-documenter-fire": "dist/start.js" - }, - "files": [ - "dist" - ], - "main": "dist/index.js", - "typings": "dist/rollup.d.ts", - "dependencies": { - "api-extractor-model-me": "0.1.1", - "@microsoft/tsdoc": "0.12.24", - "@rushstack/node-core-library": "3.59.7", - "@rushstack/ts-command-line": "4.19.1", - "colors": "~1.4.0", - "resolve": "~1.22.0", - "tslib": "^2.1.0", - "js-yaml": "4.1.0" - }, - "devDependencies": { - "@types/js-yaml": "4.0.9", - "@types/resolve": "1.20.6", - "mocha-chai-jest-snapshot": "1.1.3" - } -} diff --git a/repo-scripts/api-documenter/src/cli/ApiDocumenterCommandLine.ts b/repo-scripts/api-documenter/src/cli/ApiDocumenterCommandLine.ts deleted file mode 100644 index 4e98f53df13..00000000000 --- a/repo-scripts/api-documenter/src/cli/ApiDocumenterCommandLine.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -import { CommandLineParser } from '@rushstack/ts-command-line'; -import { MarkdownAction } from './MarkdownAction'; -import { TocAction } from './TocAction'; - -export class ApiDocumenterCommandLine extends CommandLineParser { - public constructor() { - super({ - toolFilename: 'api-documenter', - toolDescription: - 'Reads *.api.json files produced by api-extractor, ' + - ' and generates API documentation in various output formats.' - }); - this._populateActions(); - } - - protected onDefineParameters(): void { - // override - // No parameters - } - - private _populateActions(): void { - this.addAction(new MarkdownAction(this)); - this.addAction(new TocAction(this)); - } -} diff --git a/repo-scripts/api-documenter/src/cli/BaseAction.ts b/repo-scripts/api-documenter/src/cli/BaseAction.ts deleted file mode 100644 index 9d33c9c490d..00000000000 --- a/repo-scripts/api-documenter/src/cli/BaseAction.ts +++ /dev/null @@ -1,197 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -import * as path from 'path'; -import * as tsdoc from '@microsoft/tsdoc'; -import colors from 'colors'; - -import { - CommandLineAction, - CommandLineFlagParameter, - CommandLineStringParameter -} from '@rushstack/ts-command-line'; -import { FileSystem } from '@rushstack/node-core-library'; -import { - ApiModel, - ApiItem, - ApiItemContainerMixin, - ApiDocumentedItem, - IResolveDeclarationReferenceResult -} from 'api-extractor-model-me'; - -export interface IBuildApiModelResult { - apiModel: ApiModel; - inputFolder: string; - outputFolder: string; - addFileNameSuffix: boolean; - projectName?: string; -} - -export abstract class BaseAction extends CommandLineAction { - private _inputFolderParameter!: CommandLineStringParameter; - private _outputFolderParameter!: CommandLineStringParameter; - private _fileNameSuffixParameter!: CommandLineFlagParameter; - private _projectNameParameter!: CommandLineStringParameter; - - protected onDefineParameters(): void { - // override - this._inputFolderParameter = this.defineStringParameter({ - parameterLongName: '--input-folder', - parameterShortName: '-i', - argumentName: 'FOLDER1', - description: - `Specifies the input folder containing the *.api.json files to be processed.` + - ` If omitted, the default is "./input"` - }); - - this._outputFolderParameter = this.defineStringParameter({ - parameterLongName: '--output-folder', - parameterShortName: '-o', - argumentName: 'FOLDER2', - description: - `Specifies the output folder where the documentation will be written.` + - ` ANY EXISTING CONTENTS WILL BE DELETED!` + - ` If omitted, the default is "./${this.actionName}"` - }); - - this._fileNameSuffixParameter = this.defineFlagParameter({ - parameterLongName: '--name-suffix', - parameterShortName: '-s', - description: - `Add suffix to interface and class names in the file path.` + - `For example, packageA.myinterface_i.md for MyInterface interface, ` + - `Add packageA.myclass_c.md for MyClass class.` + - `This is to avoid name conflict in case packageA also has, for example, an entry point with the same name in lowercase.` + - `This option is specifically designed for the Admin SDK where such case occurs.` - }); - - this._projectNameParameter = this.defineStringParameter({ - parameterLongName: '--project', - argumentName: 'PROJECT', - description: - `Name of the project (js, admin, functions, etc.). This will be ` + - `used in the devsite header path to the _project.yaml file.` - }); - } - - protected buildApiModel(): IBuildApiModelResult { - const apiModel: ApiModel = new ApiModel(); - - const inputFolder: string = this._inputFolderParameter.value || './input'; - if (!FileSystem.exists(inputFolder)) { - throw new Error('The input folder does not exist: ' + inputFolder); - } - - const outputFolder: string = - this._outputFolderParameter.value || `./${this.actionName}`; - FileSystem.ensureFolder(outputFolder); - - const addFileNameSuffix: boolean = this._fileNameSuffixParameter.value; - - for (const filename of FileSystem.readFolder(inputFolder)) { - if (filename.match(/\.api\.json$/i)) { - console.log(`Reading ${filename}`); - const filenamePath: string = path.join(inputFolder, filename); - apiModel.loadPackage(filenamePath); - } - } - - this._applyInheritDoc(apiModel, apiModel); - - return { - apiModel, - inputFolder, - outputFolder, - addFileNameSuffix, - projectName: this._projectNameParameter.value - }; - } - - // TODO: This is a temporary workaround. The long term plan is for API Extractor's DocCommentEnhancer - // to apply all @inheritDoc tags before the .api.json file is written. - // See DocCommentEnhancer._applyInheritDoc() for more info. - private _applyInheritDoc(apiItem: ApiItem, apiModel: ApiModel): void { - if (apiItem instanceof ApiDocumentedItem) { - if (apiItem.tsdocComment) { - const inheritDocTag: tsdoc.DocInheritDocTag | undefined = - apiItem.tsdocComment.inheritDocTag; - - if (inheritDocTag && inheritDocTag.declarationReference) { - // Attempt to resolve the declaration reference - const result: IResolveDeclarationReferenceResult = - apiModel.resolveDeclarationReference( - inheritDocTag.declarationReference, - apiItem - ); - - if (result.errorMessage) { - console.log( - colors.yellow( - `Warning: Unresolved @inheritDoc tag for ${apiItem.displayName}: ` + - result.errorMessage - ) - ); - } else { - if ( - result.resolvedApiItem instanceof ApiDocumentedItem && - result.resolvedApiItem.tsdocComment && - result.resolvedApiItem !== apiItem - ) { - this._copyInheritedDocs( - apiItem.tsdocComment, - result.resolvedApiItem.tsdocComment - ); - } - } - } - } - } - - // Recurse members - if (ApiItemContainerMixin.isBaseClassOf(apiItem)) { - for (const member of apiItem.members) { - this._applyInheritDoc(member, apiModel); - } - } - } - - /** - * Copy the content from `sourceDocComment` to `targetDocComment`. - * This code is borrowed from DocCommentEnhancer as a temporary workaround. - */ - private _copyInheritedDocs( - targetDocComment: tsdoc.DocComment, - sourceDocComment: tsdoc.DocComment - ): void { - targetDocComment.summarySection = sourceDocComment.summarySection; - targetDocComment.remarksBlock = sourceDocComment.remarksBlock; - - targetDocComment.params.clear(); - for (const param of sourceDocComment.params) { - targetDocComment.params.add(param); - } - for (const typeParam of sourceDocComment.typeParams) { - targetDocComment.typeParams.add(typeParam); - } - targetDocComment.returnsBlock = sourceDocComment.returnsBlock; - - targetDocComment.inheritDocTag = undefined; - } -} diff --git a/repo-scripts/api-documenter/src/cli/MarkdownAction.ts b/repo-scripts/api-documenter/src/cli/MarkdownAction.ts deleted file mode 100644 index 903f41ebf49..00000000000 --- a/repo-scripts/api-documenter/src/cli/MarkdownAction.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -import { ApiDocumenterCommandLine } from './ApiDocumenterCommandLine'; -import { BaseAction } from './BaseAction'; -import { MarkdownDocumenter } from '../documenters/MarkdownDocumenter'; -import { CommandLineStringParameter } from '@rushstack/ts-command-line'; - -export class MarkdownAction extends BaseAction { - private _sortFunctions!: CommandLineStringParameter; - public constructor(parser: ApiDocumenterCommandLine) { - super({ - actionName: 'markdown', - summary: 'Generate documentation as Markdown files (*.md)', - documentation: - 'Generates API documentation as a collection of files in' + - ' Markdown format, suitable for example for publishing on a GitHub site.' - }); - } - - protected onDefineParameters(): void { - super.onDefineParameters(); - - this._sortFunctions = this.defineStringParameter({ - parameterLongName: '--sort-functions', - argumentName: 'PRIORITY_PARAMS', - description: - `Sorts functions tables and listings by first parameter. ` + - `Provide comma-separated strings for preferred params to be ` + - `ordered first. Alphabetical otherwise.` - }); - } - - protected async onExecute(): Promise { - // override - const { apiModel, outputFolder, addFileNameSuffix, projectName } = - this.buildApiModel(); - const sortFunctions: string = this._sortFunctions.value || ''; - - if (!projectName) { - throw new Error('No project name provided. Use --project.'); - } - - const markdownDocumenter: MarkdownDocumenter = new MarkdownDocumenter({ - apiModel, - documenterConfig: undefined, - outputFolder, - addFileNameSuffix, - projectName, - sortFunctions - }); - markdownDocumenter.generateFiles(); - } -} diff --git a/repo-scripts/api-documenter/src/cli/TocAction.ts b/repo-scripts/api-documenter/src/cli/TocAction.ts deleted file mode 100644 index 316be83a0d2..00000000000 --- a/repo-scripts/api-documenter/src/cli/TocAction.ts +++ /dev/null @@ -1,77 +0,0 @@ -/** - * @license - * Copyright 2021 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 { - CommandLineFlagParameter, - CommandLineStringParameter -} from '@rushstack/ts-command-line'; -import { ApiDocumenterCommandLine } from './ApiDocumenterCommandLine'; -import { BaseAction } from './BaseAction'; -import { generateToc } from '../toc'; - -export class TocAction extends BaseAction { - private _g3PathParameter!: CommandLineStringParameter; - private _jsSDKParameter!: CommandLineFlagParameter; - public constructor(parser: ApiDocumenterCommandLine) { - super({ - actionName: 'toc', - summary: 'Generate TOC(table of content) for Firebase devsite.', - documentation: 'Generate TOC(table of content) for Firebase devsite.' - }); - } - - protected onDefineParameters(): void { - super.onDefineParameters(); - - this._g3PathParameter = this.defineStringParameter({ - parameterLongName: '--host-path', - parameterShortName: '-p', - argumentName: 'HOSTPATH', - description: `Specifies the path where the reference docs resides (e.g. g3). - Used to generate paths in the toc` - }); - - this._jsSDKParameter = this.defineFlagParameter({ - parameterLongName: '--js-sdk', - parameterShortName: '-j', - description: - `Generating toc for the Firebase JS SDK.` + - `It will create an artificial top level toc item "firebase".` - }); - } - - protected async onExecute(): Promise { - // override - const { apiModel, outputFolder, addFileNameSuffix } = this.buildApiModel(); - const g3Path: string | undefined = this._g3PathParameter.value; - const jsSdk: boolean = this._jsSDKParameter.value; - - if (!g3Path) { - throw new Error( - '--g3-path is a required to generate toc, but it is not provided' - ); - } - - generateToc({ - apiModel, - outputFolder, - addFileNameSuffix, - g3Path, - jsSdk - }); - } -} diff --git a/repo-scripts/api-documenter/src/documenters/DocumenterConfig.ts b/repo-scripts/api-documenter/src/documenters/DocumenterConfig.ts deleted file mode 100644 index 9264905ab23..00000000000 --- a/repo-scripts/api-documenter/src/documenters/DocumenterConfig.ts +++ /dev/null @@ -1,84 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -import * as path from 'path'; -import { - JsonSchema, - JsonFile, - NewlineKind -} from '@rushstack/node-core-library'; -import { IConfigFile } from './IConfigFile'; - -/** - * Helper for loading the api-documenter.json file format. Later when the schema is more mature, - * this class will be used to represent the validated and normalized configuration, whereas `IConfigFile` - * represents the raw JSON file structure. - */ -export class DocumenterConfig { - public readonly configFilePath: string; - public readonly configFile: IConfigFile; - - /** - * Specifies what type of newlines API Documenter should use when writing output files. By default, the output files - * will be written with Windows-style newlines. - */ - public readonly newlineKind: NewlineKind; - - /** - * The JSON Schema for API Extractor config file (api-extractor.schema.json). - */ - public static readonly jsonSchema: JsonSchema = JsonSchema.fromFile( - path.join(__dirname, '..', 'schemas', 'api-documenter.schema.json') - ); - - /** - * The config file name "api-extractor.json". - */ - public static readonly FILENAME: string = 'api-documenter.json'; - - private constructor(filePath: string, configFile: IConfigFile) { - this.configFilePath = filePath; - this.configFile = configFile; - - switch (configFile.newlineKind) { - case 'lf': - this.newlineKind = NewlineKind.Lf; - break; - case 'os': - this.newlineKind = NewlineKind.OsDefault; - break; - default: - this.newlineKind = NewlineKind.CrLf; - break; - } - } - - /** - * Load and validate an api-documenter.json file. - */ - public static loadFile(configFilePath: string): DocumenterConfig { - const configFile: IConfigFile = JsonFile.loadAndValidate( - configFilePath, - DocumenterConfig.jsonSchema - ); - - return new DocumenterConfig(path.resolve(configFilePath), configFile); - } -} diff --git a/repo-scripts/api-documenter/src/documenters/IConfigFile.ts b/repo-scripts/api-documenter/src/documenters/IConfigFile.ts deleted file mode 100644 index 2a7bf8ab358..00000000000 --- a/repo-scripts/api-documenter/src/documenters/IConfigFile.ts +++ /dev/null @@ -1,112 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -/** - * TypeScript interface describing the config schema for toc.yml file format. - */ -export interface IConfigTableOfContents { - /** - * Optional category name that is recommended to be included along with - * one of the configs: {@link IConfigTableOfContents.categorizeByName} or - * {@link IConfigTableOfContents.categoryInlineTag}. - * Any items that are not matched according to the mentioned configuration options will be placed under this - * catchAll category. If none provided the items will not be included in the final toc.yml file. - */ - catchAllCategory?: string; - - /** - * Toggle either categorization of the API items should be made based on category name presence - * in the API item's name. Useful when there are API items without an inline tag to categorize them, - * but still need to place the items under categories. Note: this type of categorization might place some items - * under wrong categories if the names are similar but belong to different categories. - * In case that {@link IConfigTableOfContents.categoryInlineTag} is provided it will try categorize by - * using it and only if it didn't, it will attempt to categorize by name. - */ - categorizeByName?: boolean; - - /** - * Inline tag that will be used to categorize the API items. Will take precedence over the - * {@link IConfigTableOfContents.categorizeByName} flag in trying to place the API item according to the - * custom inline tag present in documentation of the source code. - */ - categoryInlineTag?: string; - - /** - * Array of node names that might have already items injected at the time of creating the - * {@link IConfigTableOfContents.tocConfig} tree structure but are still needed to be included as category - * nodes where API items will be pushed during the categorization algorithm. - */ - nonEmptyCategoryNodeNames?: string[]; -} - -/** - * Describes plugin packages to be loaded, and which features to enable. - */ -export interface IConfigPlugin { - /** - * Specifies the name of an API Documenter plugin package to be loaded. By convention, the NPM package name - * should have the prefix `doc-plugin-`. Its main entry point should export an object named - * `apiDocumenterPluginManifest` which implements the {@link IApiDocumenterPluginManifest} interface. - */ - packageName: string; - - /** - * A list of features to be enabled. The features are defined in {@link IApiDocumenterPluginManifest.features}. - * The `enabledFeatureNames` strings are matched with {@link IFeatureDefinition.featureName}. - */ - enabledFeatureNames: string[]; -} - -/** - * This interface represents the api-documenter.json file format. - */ -export interface IConfigFile { - /** - * Specifies the output target. - */ - outputTarget: 'docfx' | 'markdown'; - - /** - * Specifies what type of newlines API Documenter should use when writing output files. - * - * @remarks - * By default, the output files will be written with Windows-style newlines. - * To use POSIX-style newlines, specify "lf" instead. - * To use the OS's default newline kind, specify "os". - */ - newlineKind?: 'crlf' | 'lf' | 'os'; - - /** - * This enables an experimental feature that will be officially released with the next major version - * of API Documenter. It requires DocFX 2.46 or newer. It enables documentation for namespaces and - * adds them to the table of contents. This will also affect file layout as namespaced items will be nested - * under a directory for the namespace instead of just within the package. - * - * This setting currently only affects the 'docfx' output target. It is equivalent to the `--new-docfx-namespaces` - * command-line parameter. - */ - newDocfxNamespaces?: boolean; - - /** {@inheritDoc IConfigPlugin} */ - plugins?: IConfigPlugin[]; - - /** {@inheritDoc IConfigTableOfContents} */ - tableOfContents?: IConfigTableOfContents; -} diff --git a/repo-scripts/api-documenter/src/documenters/MarkdownDocumenter.ts b/repo-scripts/api-documenter/src/documenters/MarkdownDocumenter.ts deleted file mode 100644 index 1f24bffb46a..00000000000 --- a/repo-scripts/api-documenter/src/documenters/MarkdownDocumenter.ts +++ /dev/null @@ -1,1228 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -import { - FileSystem, - NewlineKind, - PackageName -} from '@rushstack/node-core-library'; -import { - DocSection, - TSDocConfiguration, - StringBuilder, - DocNode, - DocComment, - DocParagraph, - DocPlainText, - DocNodeContainer, - DocLinkTag, - DocFencedCode -} from '@microsoft/tsdoc'; -import { - ApiModel, - ApiItem, - ApiItemKind, - ApiReleaseTagMixin, - ReleaseTag, - ApiDocumentedItem, - ApiDeclaredItem, - ApiClass, - ApiPropertyItem, - ApiEnum, - ApiInterface, - ApiParameterListMixin, - ApiReturnTypeMixin, - Excerpt, - ExcerptTokenKind, - IResolveDeclarationReferenceResult, - ApiPackage, - ApiEntryPoint, - ApiNamespace -} from 'api-extractor-model-me'; - -import { CustomDocNodes } from '../nodes/CustomDocNodeKind'; -import { CustomMarkdownEmitter } from '../markdown/CustomMarkdownEmitter'; -import { PluginLoader } from '../plugin/PluginLoader'; -import { - IMarkdownDocumenterFeatureOnBeforeWritePageArgs, - MarkdownDocumenterFeatureContext -} from '../plugin/MarkdownDocumenterFeature'; -import { DocumenterConfig } from './DocumenterConfig'; -import { MarkdownDocumenterAccessor } from '../plugin/MarkdownDocumenterAccessor'; -import { - getLinkForApiItem, - getFilenameForApiItem, - createBetaWarning, - createRemarksSection, - createTitleCell, - createModifiersCell, - createDescriptionCell, - createEnumTables, - createThrowsSection, - createEntryPointTitleCell, - createExampleSection, - getHeadingAnchorForApiItem -} from './MarkdownDocumenterHelpers'; -import * as path from 'path'; -import { DocHeading } from '../nodes/DocHeading'; -import { DocNoteBox } from '../nodes/DocNoteBox'; -import { DocTable } from '../nodes/DocTable'; -import { DocTableRow } from '../nodes/DocTableRow'; -import { DocTableCell } from '../nodes/DocTableCell'; -import { DocEmphasisSpan } from '../nodes/DocEmphasisSpan'; -import { Utilities } from '../utils/Utilities'; - -export interface IMarkdownDocumenterOptions { - apiModel: ApiModel; - documenterConfig: DocumenterConfig | undefined; - outputFolder: string; - addFileNameSuffix: boolean; - projectName: string; - sortFunctions: string; -} - -/** - * Renders API documentation in the Markdown file format. - * For more info: https://en.wikipedia.org/wiki/Markdown - */ -export class MarkdownDocumenter { - private readonly _apiModel: ApiModel; - private readonly _documenterConfig: DocumenterConfig | undefined; - private readonly _tsdocConfiguration: TSDocConfiguration; - private readonly _markdownEmitter: CustomMarkdownEmitter; - private readonly _outputFolder: string; - private readonly _pluginLoader: PluginLoader; - private readonly _addFileNameSuffix: boolean; - private readonly _projectName: string; - private readonly _sortFunctions: string; - - public constructor(options: IMarkdownDocumenterOptions) { - this._apiModel = options.apiModel; - this._documenterConfig = options.documenterConfig; - this._outputFolder = options.outputFolder; - this._addFileNameSuffix = options.addFileNameSuffix; - this._projectName = options.projectName; - this._sortFunctions = options.sortFunctions; - this._tsdocConfiguration = CustomDocNodes.configuration; - this._markdownEmitter = new CustomMarkdownEmitter(this._apiModel); - - this._pluginLoader = new PluginLoader(); - } - - public generateFiles(): void { - if (this._documenterConfig) { - this._pluginLoader.load(this._documenterConfig, () => { - return new MarkdownDocumenterFeatureContext({ - apiModel: this._apiModel, - outputFolder: this._outputFolder, - documenter: new MarkdownDocumenterAccessor({ - getLinkForApiItem: (apiItem: ApiItem) => { - return getLinkForApiItem(apiItem, this._addFileNameSuffix); - } - }) - }); - }); - } - - this._deleteOldOutputFiles(); - - this._writeApiItemPage(this._apiModel); - - if (this._pluginLoader.markdownDocumenterFeature) { - this._pluginLoader.markdownDocumenterFeature.onFinished({}); - } - } - - _writeApiItemPage(apiItem: ApiItem): void { - const output: DocSection = new DocSection({ - configuration: this._tsdocConfiguration - }); - const nodes = this._createCompleteOutputForApiItem(apiItem); - - /** - * Remove the heading of the page from md output. (the first item is always a DocHeading) - * Later we will add the heading to the devsite header {% block title %} - */ - const headingNode: DocHeading = nodes[0] as DocHeading; - const pageWithoutHeading = nodes.slice(1); - output.appendNodes(pageWithoutHeading); - - // write to file - const filename: string = path.join( - this._outputFolder, - getFilenameForApiItem(apiItem, this._addFileNameSuffix) - ); - const stringBuilder: StringBuilder = new StringBuilder(); - - // devsite headers - stringBuilder.append( - `Project: /docs/reference/${this._projectName}/_project.yaml -Book: /docs/reference/_book.yaml -page_type: reference -` - ); - - stringBuilder.append(`# ${headingNode.title}\n`); - - this._markdownEmitter.emit(stringBuilder, output, { - contextApiItem: apiItem, - onGetFilenameForApiItem: (apiItemForFilename: ApiItem) => { - return getLinkForApiItem(apiItemForFilename, this._addFileNameSuffix); - } - }); - - let pageContent: string = stringBuilder.toString(); - - if (this._pluginLoader.markdownDocumenterFeature) { - // Allow the plugin to customize the pageContent - const eventArgs: IMarkdownDocumenterFeatureOnBeforeWritePageArgs = { - apiItem: apiItem, - outputFilename: filename, - pageContent: pageContent - }; - this._pluginLoader.markdownDocumenterFeature.onBeforeWritePage(eventArgs); - pageContent = eventArgs.pageContent; - } - - FileSystem.writeFile(filename, pageContent, { - convertLineEndings: NewlineKind.Lf - }); - } - - _functionHeadingLevel(): number { - // If sorting functions by first parameter - // then the function heading will be under - // the parameter heading, so it will be level - // 2. Otherwise, it will be level 1. - return !!this._sortFunctions ? 2 : 1; - } - - _createCompleteOutputForApiItem(apiItem: ApiItem): DocNode[] { - const configuration = this._tsdocConfiguration; - const output: DocNode[] = []; - const scopedName: string = apiItem.getScopedNameWithinPackage(); - - switch (apiItem.kind) { - case ApiItemKind.Class: - output.push( - new DocHeading({ configuration, title: `${scopedName} class` }) - ); - break; - case ApiItemKind.Enum: - output.push(new DocHeading({ configuration, title: `${scopedName}` })); - break; - case ApiItemKind.Interface: - output.push( - new DocHeading({ configuration, title: `${scopedName} interface` }) - ); - break; - case ApiItemKind.Constructor: - case ApiItemKind.ConstructSignature: - output.push(new DocHeading({ configuration, title: scopedName })); - break; - case ApiItemKind.Method: - case ApiItemKind.MethodSignature: - output.push(new DocHeading({ configuration, title: `${scopedName}` })); - break; - case ApiItemKind.Function: - const anchor = getHeadingAnchorForApiItem(apiItem); - output.push( - new DocHeading({ - configuration, - title: Utilities.getConciseSignature(apiItem), - anchor: anchor, - level: this._functionHeadingLevel() - }) - ); - break; - case ApiItemKind.Model: - output.push(new DocHeading({ configuration, title: `API Reference` })); - break; - case ApiItemKind.Namespace: - output.push( - new DocHeading({ configuration, title: `${scopedName} namespace` }) - ); - break; - case ApiItemKind.Package: - const unscopedPackageName: string = PackageName.getUnscopedName( - apiItem.displayName - ); - output.push( - new DocHeading({ - configuration, - title: `${unscopedPackageName} package` - }) - ); - break; - case ApiItemKind.EntryPoint: - const packageName: string = apiItem.parent!.displayName; - output.push( - new DocHeading({ - configuration, - title: `${packageName}${ - apiItem.displayName && '/' + apiItem.displayName - }` - }) - ); - break; - case ApiItemKind.Property: - case ApiItemKind.PropertySignature: - output.push(new DocHeading({ configuration, title: `${scopedName}` })); - break; - case ApiItemKind.TypeAlias: - output.push(new DocHeading({ configuration, title: `${scopedName}` })); - break; - case ApiItemKind.Variable: - output.push(new DocHeading({ configuration, title: `${scopedName}` })); - break; - default: - throw new Error('Unsupported API item kind:1 ' + apiItem.kind); - } - - if (ApiReleaseTagMixin.isBaseClassOf(apiItem)) { - if (apiItem.releaseTag === ReleaseTag.Beta) { - output.push(createBetaWarning(configuration)); - } - } - - if (apiItem instanceof ApiDocumentedItem) { - const tsdocComment: DocComment | undefined = apiItem.tsdocComment; - - if (tsdocComment) { - if (tsdocComment.deprecatedBlock) { - output.push( - new DocNoteBox({ configuration }, [ - new DocParagraph({ configuration }, [ - new DocPlainText({ - configuration, - text: 'Warning: This API is now obsolete. ' - }) - ]), - ...tsdocComment.deprecatedBlock.content.nodes - ]) - ); - } - - output.push(...tsdocComment.summarySection.nodes); - } - } - - // render remark sections - output.push(...createRemarksSection(apiItem, configuration)); - - if (apiItem instanceof ApiDeclaredItem) { - output.push(...this._createSignatureSection(apiItem)); - } - - switch (apiItem.kind) { - case ApiItemKind.Class: - output.push(...this._createClassTables(apiItem as ApiClass)); - break; - case ApiItemKind.Enum: - output.push(...createEnumTables(apiItem as ApiEnum, configuration)); - break; - case ApiItemKind.Interface: - output.push(...this._createInterfaceTables(apiItem as ApiInterface)); - break; - case ApiItemKind.Constructor: - case ApiItemKind.ConstructSignature: - case ApiItemKind.Method: - case ApiItemKind.MethodSignature: - case ApiItemKind.Function: - output.push( - ...this._createParameterTables( - apiItem as ApiParameterListMixin, - this._functionHeadingLevel() - ) - ); - output.push( - ...createThrowsSection( - apiItem, - configuration, - this._functionHeadingLevel() - ) - ); - break; - case ApiItemKind.Namespace: - output.push( - ...this._createEntryPointOrNamespace(apiItem as ApiNamespace) - ); - break; - case ApiItemKind.Model: - output.push(...this._createModelTable(apiItem as ApiModel)); - break; - case ApiItemKind.Package: - output.push(...this._createPackage(apiItem as ApiPackage)); - break; - case ApiItemKind.EntryPoint: - output.push( - ...this._createEntryPointOrNamespace(apiItem as ApiEntryPoint) - ); - break; - case ApiItemKind.Property: - case ApiItemKind.PropertySignature: - break; - case ApiItemKind.TypeAlias: - break; - case ApiItemKind.Variable: - break; - default: - throw new Error('Unsupported API item kind:2 ' + apiItem.kind); - } - - output.push(...createExampleSection(apiItem, configuration)); - - return output; - } - - /** - * GENERATE PAGE: CLASS - * - * TODO: generate member references in the same page - */ - private _createClassTables(apiClass: ApiClass): DocNode[] { - const configuration = this._tsdocConfiguration; - const output: DocNode[] = []; - const eventsTable: DocTable = new DocTable({ - configuration, - headerTitles: ['Property', 'Modifiers', 'Type', 'Description'] - }); - - const constructorsTable: DocTable = new DocTable({ - configuration, - headerTitles: ['Constructor', 'Modifiers', 'Description'] - }); - - const propertiesTable: DocTable = new DocTable({ - configuration, - headerTitles: ['Property', 'Modifiers', 'Type', 'Description'] - }); - - const methodsTable: DocTable = new DocTable({ - configuration, - headerTitles: ['Method', 'Modifiers', 'Description'] - }); - - const constructorsDefinitions: DocNode[] = []; - const methodsDefinitions: DocNode[] = []; - const propertiesDefinitions: DocNode[] = []; - const eventsDefinitions: DocNode[] = []; - - for (const apiMember of apiClass.members) { - switch (apiMember.kind) { - case ApiItemKind.Constructor: { - constructorsTable.addRow( - new DocTableRow({ configuration }, [ - createTitleCell( - apiMember, - configuration, - this._addFileNameSuffix - ), - createModifiersCell(apiMember, configuration), - createDescriptionCell(apiMember, configuration) - ]) - ); - - constructorsDefinitions.push( - ...this._createCompleteOutputForApiItem(apiMember) - ); - break; - } - case ApiItemKind.Method: { - methodsTable.addRow( - new DocTableRow({ configuration }, [ - createTitleCell( - apiMember, - configuration, - this._addFileNameSuffix - ), - createModifiersCell(apiMember, configuration), - createDescriptionCell(apiMember, configuration) - ]) - ); - - methodsDefinitions.push( - ...this._createCompleteOutputForApiItem(apiMember) - ); - break; - } - case ApiItemKind.Property: { - if ((apiMember as ApiPropertyItem).isEventProperty) { - eventsTable.addRow( - new DocTableRow({ configuration }, [ - createTitleCell( - apiMember, - configuration, - this._addFileNameSuffix - ), - createModifiersCell(apiMember, configuration), - this._createPropertyTypeCell(apiMember), - createDescriptionCell(apiMember, configuration) - ]) - ); - - eventsDefinitions.push( - ...this._createCompleteOutputForApiItem(apiMember) - ); - } else { - propertiesTable.addRow( - new DocTableRow({ configuration }, [ - createTitleCell( - apiMember, - configuration, - this._addFileNameSuffix - ), - createModifiersCell(apiMember, configuration), - this._createPropertyTypeCell(apiMember), - createDescriptionCell(apiMember, configuration) - ]) - ); - propertiesDefinitions.push( - ...this._createCompleteOutputForApiItem(apiMember) - ); - } - break; - } - } - } - - if (eventsTable.rows.length > 0) { - output.push(new DocHeading({ configuration, title: 'Events' })); - output.push(eventsTable); - } - - if (constructorsTable.rows.length > 0) { - output.push(new DocHeading({ configuration, title: 'Constructors' })); - output.push(constructorsTable); - } - - if (propertiesTable.rows.length > 0) { - output.push(new DocHeading({ configuration, title: 'Properties' })); - output.push(propertiesTable); - } - - if (methodsTable.rows.length > 0) { - output.push(new DocHeading({ configuration, title: 'Methods' })); - output.push(methodsTable); - } - - output.push(...eventsDefinitions); - output.push(...constructorsDefinitions); - output.push(...propertiesDefinitions); - output.push(...methodsDefinitions); - - return output; - } - - /** - * GENERATE PAGE: INTERFACE - */ - private _createInterfaceTables(apiClass: ApiInterface): DocNode[] { - const configuration = this._tsdocConfiguration; - const output: DocNode[] = []; - const eventsTable: DocTable = new DocTable({ - configuration, - headerTitles: ['Property', 'Type', 'Description'] - }); - - const propertiesTable: DocTable = new DocTable({ - configuration, - headerTitles: ['Property', 'Type', 'Description'] - }); - - const methodsTable: DocTable = new DocTable({ - configuration, - headerTitles: ['Method', 'Description'] - }); - - const methodsDefinitions: DocNode[] = []; - const propertiesDefinitions: DocNode[] = []; - const eventsDefinitions: DocNode[] = []; - - for (const apiMember of apiClass.members) { - switch (apiMember.kind) { - case ApiItemKind.ConstructSignature: - case ApiItemKind.MethodSignature: { - methodsTable.addRow( - new DocTableRow({ configuration }, [ - createTitleCell( - apiMember, - configuration, - this._addFileNameSuffix - ), - createDescriptionCell(apiMember, configuration) - ]) - ); - - methodsDefinitions.push( - ...this._createCompleteOutputForApiItem(apiMember) - ); - break; - } - case ApiItemKind.PropertySignature: { - if ((apiMember as ApiPropertyItem).isEventProperty) { - eventsTable.addRow( - new DocTableRow({ configuration }, [ - createTitleCell( - apiMember, - configuration, - this._addFileNameSuffix - ), - this._createPropertyTypeCell(apiMember), - createDescriptionCell(apiMember, configuration) - ]) - ); - eventsDefinitions.push( - ...this._createCompleteOutputForApiItem(apiMember) - ); - } else { - propertiesTable.addRow( - new DocTableRow({ configuration }, [ - createTitleCell( - apiMember, - configuration, - this._addFileNameSuffix - ), - this._createPropertyTypeCell(apiMember), - createDescriptionCell(apiMember, configuration) - ]) - ); - propertiesDefinitions.push( - ...this._createCompleteOutputForApiItem(apiMember) - ); - } - break; - } - } - } - - if (eventsTable.rows.length > 0) { - output.push(new DocHeading({ configuration, title: 'Events' })); - output.push(eventsTable); - } - - if (propertiesTable.rows.length > 0) { - output.push(new DocHeading({ configuration, title: 'Properties' })); - output.push(propertiesTable); - } - - if (methodsTable.rows.length > 0) { - output.push(new DocHeading({ configuration, title: 'Methods' })); - output.push(methodsTable); - } - - output.push(...eventsDefinitions); - output.push(...propertiesDefinitions); - output.push(...methodsDefinitions); - - return output; - } - - /** - * GENERATE PAGE: FUNCTION-LIKE - */ - private _createParameterTables( - apiParameterListMixin: ApiParameterListMixin, - parentHeadingLevel: number - ): DocNode[] { - const configuration = this._tsdocConfiguration; - const output: DocNode[] = []; - const parametersTable: DocTable = new DocTable({ - configuration, - headerTitles: ['Parameter', 'Type', 'Description'] - }); - for (const apiParameter of apiParameterListMixin.parameters) { - const parameterDescription: DocSection = new DocSection({ - configuration - }); - if (apiParameter.tsdocParamBlock) { - parameterDescription.appendNodes( - apiParameter.tsdocParamBlock.content.nodes - ); - } - - parametersTable.addRow( - new DocTableRow({ configuration }, [ - new DocTableCell({ configuration }, [ - new DocParagraph({ configuration }, [ - new DocPlainText({ configuration, text: apiParameter.name }) - ]) - ]), - new DocTableCell({ configuration }, [ - this._createParagraphForTypeExcerpt( - apiParameter.parameterTypeExcerpt - ) - ]), - new DocTableCell({ configuration }, parameterDescription.nodes) - ]) - ); - } - - if (parametersTable.rows.length > 0) { - output.push( - new DocHeading({ - configuration, - title: 'Parameters', - level: parentHeadingLevel + 1 - }) - ); - output.push(parametersTable); - } - - if (ApiReturnTypeMixin.isBaseClassOf(apiParameterListMixin)) { - const returnTypeExcerpt: Excerpt = - apiParameterListMixin.returnTypeExcerpt; - output.push( - new DocParagraph({ configuration }, [ - new DocEmphasisSpan({ configuration, bold: true }, [ - new DocPlainText({ configuration, text: 'Returns:' }) - ]) - ]) - ); - - output.push(this._createParagraphForTypeExcerpt(returnTypeExcerpt)); - - if (apiParameterListMixin instanceof ApiDocumentedItem) { - if ( - apiParameterListMixin.tsdocComment && - apiParameterListMixin.tsdocComment.returnsBlock - ) { - output.push( - ...apiParameterListMixin.tsdocComment.returnsBlock.content.nodes - ); - } - } - } - - return output; - } - - private _createParagraphForTypeExcerpt(excerpt: Excerpt): DocParagraph { - const configuration = this._tsdocConfiguration; - const paragraph: DocParagraph = new DocParagraph({ configuration }); - - if (!excerpt.text.trim()) { - paragraph.appendNode( - new DocPlainText({ configuration, text: '(not declared)' }) - ); - } else { - this._appendExcerptWithHyperlinks(paragraph, excerpt); - } - - return paragraph; - } - - private _appendExcerptWithHyperlinks( - docNodeContainer: DocNodeContainer, - excerpt: Excerpt - ): void { - const configuration = this._tsdocConfiguration; - for (const token of excerpt.spannedTokens) { - // Markdown doesn't provide a standardized syntax for hyperlinks inside code spans, so we will render - // the type expression as DocPlainText. Instead of creating multiple DocParagraphs, we can simply - // discard any newlines and let the renderer do normal word-wrapping. - const unwrappedTokenText: string = token.text.replace(/[\r\n]+/g, ' '); - - // If it's hyperlinkable, then append a DocLinkTag - if ( - token.kind === ExcerptTokenKind.Reference && - token.canonicalReference - ) { - const apiItemResult: IResolveDeclarationReferenceResult = - this._apiModel.resolveDeclarationReference( - token.canonicalReference, - undefined - ); - - if (apiItemResult.resolvedApiItem) { - docNodeContainer.appendNode( - new DocLinkTag({ - configuration, - tagName: '@link', - linkText: unwrappedTokenText, - urlDestination: getLinkForApiItem( - apiItemResult.resolvedApiItem, - this._addFileNameSuffix - ) - }) - ); - continue; - } - } - - // Otherwise append non-hyperlinked text - docNodeContainer.appendNode( - new DocPlainText({ configuration, text: unwrappedTokenText }) - ); - } - } - - /** - * GENERATE PAGE: MODEL - */ - private _createModelTable(apiModel: ApiModel): DocNode[] { - const configuration = this._tsdocConfiguration; - const output: DocNode[] = []; - - const packagesTable: DocTable = new DocTable({ - configuration, - headerTitles: ['Package', 'Description'] - }); - - for (const apiMember of apiModel.members) { - const row: DocTableRow = new DocTableRow({ configuration }, [ - createTitleCell(apiMember, configuration, this._addFileNameSuffix), - createDescriptionCell(apiMember, configuration) - ]); - - switch (apiMember.kind) { - case ApiItemKind.Package: - packagesTable.addRow(row); - this._writeApiItemPage(apiMember); - break; - } - } - - if (packagesTable.rows.length > 0) { - output.push(new DocHeading({ configuration, title: 'Packages' })); - output.push(packagesTable); - } - - return output; - } - - /**´ - * Generate a table of entry points if there are more than one entry points. - * Otherwise, generate the entry point directly in the package page. - */ - private _createPackage(apiContainer: ApiPackage): DocNode[] { - const configuration = this._tsdocConfiguration; - const output: DocNode[] = []; - // If a package has a single entry point, generate entry point page in the package page directly - if (apiContainer.entryPoints.length === 1) { - return this._createEntryPointOrNamespace( - apiContainer.members[0] as ApiEntryPoint - ); - } - - const entryPointsTable: DocTable = new DocTable({ - configuration, - headerTitles: ['Entry Point', 'Description'] - }); - - for (const entryPoint of apiContainer.entryPoints) { - const row: DocTableRow = new DocTableRow({ configuration }, [ - createEntryPointTitleCell( - entryPoint, - configuration, - this._addFileNameSuffix - ), - createDescriptionCell(entryPoint, configuration) - ]); - - entryPointsTable.addRow(row); - } - - output.push(entryPointsTable); - - // write entry point pages - for (const entryPoint of apiContainer.entryPoints) { - this._writeApiItemPage(entryPoint); - } - - return output; - } - - /** - * GENERATE PAGE: ENTRYPOINT or NAMESPACE - */ - private _createEntryPointOrNamespace( - apiContainer: ApiEntryPoint | ApiNamespace - ): DocNode[] { - const configuration = this._tsdocConfiguration; - const output: DocNode[] = []; - - const classesTable: DocTable = new DocTable({ - configuration, - headerTitles: ['Class', 'Description'] - }); - - const enumerationsTable: DocTable = new DocTable({ - configuration, - headerTitles: ['Enumeration', 'Description'] - }); - - const finalFunctionsTable: DocTable = new DocTable({ - configuration, - headerTitles: ['Function', 'Description'] - }); - - const functionsRowGroup: Record = {}; - - const interfacesTable: DocTable = new DocTable({ - configuration, - headerTitles: ['Interface', 'Description'] - }); - - const namespacesTable: DocTable = new DocTable({ - configuration, - headerTitles: ['Namespace', 'Description'] - }); - - const variablesTable: DocTable = new DocTable({ - configuration, - headerTitles: ['Variable', 'Description'] - }); - - const typeAliasesTable: DocTable = new DocTable({ - configuration, - headerTitles: ['Type Alias', 'Description'] - }); - - const functionsDefinitionsGroup: Record = {}; - const finalFunctionsDefinitions: DocNode[] = []; - const variablesDefinitions: DocNode[] = []; - const typeAliasDefinitions: DocNode[] = []; - const enumsDefinitions: DocNode[] = []; - - const apiMembers: ReadonlyArray = - apiContainer.kind === ApiItemKind.EntryPoint - ? (apiContainer as ApiEntryPoint).members - : (apiContainer as ApiNamespace).members; - - for (const apiMember of apiMembers) { - const row: DocTableRow = new DocTableRow({ configuration }, [ - createTitleCell(apiMember, configuration, this._addFileNameSuffix), - createDescriptionCell(apiMember, configuration) - ]); - - switch (apiMember.kind) { - case ApiItemKind.Class: - classesTable.addRow(row); - this._writeApiItemPage(apiMember); - break; - - case ApiItemKind.Enum: - enumerationsTable.addRow(row); - enumsDefinitions.push( - ...this._createCompleteOutputForApiItem(apiMember) - ); - break; - - case ApiItemKind.Interface: - interfacesTable.addRow(row); - this._writeApiItemPage(apiMember); - break; - - case ApiItemKind.Namespace: - namespacesTable.addRow(row); - this._writeApiItemPage(apiMember); - break; - - case ApiItemKind.Function: - /** - * If this option is set, group functions by first param. - * Organize using a map where the key is the first param. - */ - if (this._sortFunctions) { - const firstParam = (apiMember as ApiParameterListMixin) - .parameters[0] || { name: '' }; - if (!functionsRowGroup[firstParam.name]) { - functionsRowGroup[firstParam.name] = []; - } - if (!functionsDefinitionsGroup[firstParam.name]) { - functionsDefinitionsGroup[firstParam.name] = []; - } - functionsRowGroup[firstParam.name].push(row); - functionsDefinitionsGroup[firstParam.name].push( - ...this._createCompleteOutputForApiItem(apiMember) - ); - } else { - finalFunctionsTable.addRow(row); - finalFunctionsDefinitions.push( - ...this._createCompleteOutputForApiItem(apiMember) - ); - } - break; - - case ApiItemKind.TypeAlias: - typeAliasesTable.addRow(row); - typeAliasDefinitions.push( - ...this._createCompleteOutputForApiItem(apiMember) - ); - break; - - case ApiItemKind.Variable: - variablesTable.addRow(row); - variablesDefinitions.push( - ...this._createCompleteOutputForApiItem(apiMember) - ); - break; - } - } - - /** - * Sort the functions groups by first param. If priority params were - * provided to --sort-functions, will put them first in the order - * given. - */ - if (this._sortFunctions) { - let priorityParams: string[] = []; - if (this._sortFunctions.includes(',')) { - priorityParams = this._sortFunctions.split(','); - } else { - priorityParams = [this._sortFunctions]; - } - const sortedFunctionsFirstParamKeys = Object.keys(functionsRowGroup).sort( - (a, b) => { - if (priorityParams.includes(a) && priorityParams.includes(b)) { - return priorityParams.indexOf(a) - priorityParams.indexOf(b); - } else if (priorityParams.includes(a)) { - return -1; - } else if (priorityParams.includes(b)) { - return 1; - } - return a.localeCompare(b); - } - ); - - for (const paramKey of sortedFunctionsFirstParamKeys) { - // Header for each group of functions grouped by first param. - // Doesn't make sense if there's only one group. - const headerText = paramKey - ? `function(${paramKey}, ...)` - : 'function()'; - if (sortedFunctionsFirstParamKeys.length > 1) { - finalFunctionsTable.addRow( - new DocTableRow({ configuration }, [ - new DocTableCell({ configuration }, [ - new DocParagraph({ configuration }, [ - new DocEmphasisSpan({ configuration, bold: true }, [ - new DocPlainText({ configuration, text: headerText }) - ]) - ]) - ]) - ]) - ); - } - for (const functionsRow of functionsRowGroup[paramKey]) { - finalFunctionsTable.addRow(functionsRow); - } - - // Create a heading that groups functions by the first param - finalFunctionsDefinitions.push( - new DocHeading({ - configuration, - title: headerText - }) - ); - - for (const functionDefinition of functionsDefinitionsGroup[paramKey]) { - // const originalDocHeading = functionDefinition as DocHeading; - - // // Increase the doc heading level so that this is a sub-section - // // of the function grouping heading - // const newDocHeading = new DocHeading({ - // configuration: originalDocHeading.configuration, - // title: originalDocHeading.title, - // level: originalDocHeading.level + 1, - // anchor: originalDocHeading.anchor - // }) - // finalFunctionsDefinitions.push(newDocHeading); - finalFunctionsDefinitions.push(functionDefinition); - } - } - } - - if (finalFunctionsTable.rows.length > 0) { - output.push(new DocHeading({ configuration, title: 'Functions' })); - output.push(finalFunctionsTable); - } - - if (classesTable.rows.length > 0) { - output.push(new DocHeading({ configuration, title: 'Classes' })); - output.push(classesTable); - } - - if (enumerationsTable.rows.length > 0) { - output.push(new DocHeading({ configuration, title: 'Enumerations' })); - output.push(enumerationsTable); - } - - if (interfacesTable.rows.length > 0) { - output.push(new DocHeading({ configuration, title: 'Interfaces' })); - output.push(interfacesTable); - } - - if (namespacesTable.rows.length > 0) { - output.push(new DocHeading({ configuration, title: 'Namespaces' })); - output.push(namespacesTable); - } - - if (variablesTable.rows.length > 0) { - output.push(new DocHeading({ configuration, title: 'Variables' })); - output.push(variablesTable); - } - - if (typeAliasesTable.rows.length > 0) { - output.push(new DocHeading({ configuration, title: 'Type Aliases' })); - output.push(typeAliasesTable); - } - - if (finalFunctionsDefinitions.length > 0) { - output.push(...finalFunctionsDefinitions); - } - - if (variablesDefinitions.length > 0) { - output.push(...variablesDefinitions); - } - - if (typeAliasDefinitions.length > 0) { - output.push(...typeAliasDefinitions); - } - - if (enumsDefinitions.length > 0) { - output.push(...enumsDefinitions); - } - - return output; - } - - private _createPropertyTypeCell(apiItem: ApiItem): DocTableCell { - const section: DocSection = new DocSection({ - configuration: this._tsdocConfiguration - }); - - if (apiItem instanceof ApiPropertyItem) { - section.appendNode( - this._createParagraphForTypeExcerpt(apiItem.propertyTypeExcerpt) - ); - } - - return new DocTableCell( - { configuration: this._tsdocConfiguration }, - section.nodes - ); - } - - private _createSignatureSection(apiItem: ApiDeclaredItem): DocNode[] { - const configuration = this._tsdocConfiguration; - const nodes: DocNode[] = []; - if (apiItem.excerpt.text.length > 0) { - nodes.push( - new DocParagraph({ configuration }, [ - new DocEmphasisSpan({ configuration, bold: true }, [ - new DocPlainText({ configuration, text: 'Signature:' }) - ]) - ]) - ); - nodes.push( - new DocFencedCode({ - configuration, - code: apiItem.getExcerptWithModifiers(), - language: 'typescript' - }) - ); - } - - nodes.push(...this._writeHeritageTypes(apiItem)); - return nodes; - } - - private _writeHeritageTypes(apiItem: ApiDeclaredItem): DocNode[] { - const configuration = this._tsdocConfiguration; - const nodes: DocNode[] = []; - if (apiItem instanceof ApiClass) { - if (apiItem.extendsType) { - const extendsParagraph: DocParagraph = new DocParagraph( - { configuration }, - [ - new DocEmphasisSpan({ configuration, bold: true }, [ - new DocPlainText({ configuration, text: 'Extends: ' }) - ]) - ] - ); - this._appendExcerptWithHyperlinks( - extendsParagraph, - apiItem.extendsType.excerpt - ); - nodes.push(extendsParagraph); - } - if (apiItem.implementsTypes.length > 0) { - const implementsParagraph: DocParagraph = new DocParagraph( - { configuration }, - [ - new DocEmphasisSpan({ configuration, bold: true }, [ - new DocPlainText({ configuration, text: 'Implements: ' }) - ]) - ] - ); - let needsComma: boolean = false; - for (const implementsType of apiItem.implementsTypes) { - if (needsComma) { - implementsParagraph.appendNode( - new DocPlainText({ configuration, text: ', ' }) - ); - } - this._appendExcerptWithHyperlinks( - implementsParagraph, - implementsType.excerpt - ); - needsComma = true; - } - nodes.push(implementsParagraph); - } - } - - if (apiItem instanceof ApiInterface) { - if (apiItem.extendsTypes.length > 0) { - const extendsParagraph: DocParagraph = new DocParagraph( - { configuration }, - [ - new DocEmphasisSpan({ configuration, bold: true }, [ - new DocPlainText({ configuration, text: 'Extends: ' }) - ]) - ] - ); - let needsComma: boolean = false; - for (const extendsType of apiItem.extendsTypes) { - if (needsComma) { - extendsParagraph.appendNode( - new DocPlainText({ configuration, text: ', ' }) - ); - } - this._appendExcerptWithHyperlinks( - extendsParagraph, - extendsType.excerpt - ); - needsComma = true; - } - nodes.push(extendsParagraph); - } - } - - return nodes; - } - - private _deleteOldOutputFiles(): void { - console.log('Deleting old output from ' + this._outputFolder); - FileSystem.ensureEmptyFolder(this._outputFolder); - } -} diff --git a/repo-scripts/api-documenter/src/documenters/MarkdownDocumenterHelpers.ts b/repo-scripts/api-documenter/src/documenters/MarkdownDocumenterHelpers.ts deleted file mode 100644 index eb8798d3705..00000000000 --- a/repo-scripts/api-documenter/src/documenters/MarkdownDocumenterHelpers.ts +++ /dev/null @@ -1,449 +0,0 @@ -/** - * @license - * Copyright 2020 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 { - DocPlainText, - DocLinkTag, - TSDocConfiguration, - DocParagraph, - DocNode, - DocBlock, - DocComment, - DocSection, - DocCodeSpan, - StandardTags, - DocNodeKind -} from '@microsoft/tsdoc'; -import { - ApiItem, - ApiItemKind, - ApiParameterListMixin, - ApiPackage, - ApiReleaseTagMixin, - ReleaseTag, - ApiDocumentedItem, - ApiEntryPoint, - ApiStaticMixin, - ApiEnum -} from 'api-extractor-model-me'; -import { DocEmphasisSpan } from '../nodes/DocEmphasisSpan'; -import { DocHeading } from '../nodes/DocHeading'; -import { DocTable } from '../nodes/DocTable'; -import { Utilities } from '../utils/Utilities'; -import { PackageName } from '@rushstack/node-core-library'; -import { DocNoteBox } from '../nodes/DocNoteBox'; -import { DocTableRow } from '../nodes/DocTableRow'; -import { DocTableCell } from '../nodes/DocTableCell'; -import { createHash } from 'crypto'; - -export function getLinkForApiItem( - apiItem: ApiItem, - addFileNameSuffix: boolean -) { - const fileName = getFilenameForApiItem(apiItem, addFileNameSuffix); - const headingAnchor = getHeadingAnchorForApiItem(apiItem); - return `./${fileName}#${headingAnchor}`; -} - -export function getFilenameForApiItem( - apiItem: ApiItem, - addFileNameSuffix: boolean -): string { - if (apiItem.kind === ApiItemKind.Model) { - return 'index.md'; - } - - let baseName: string = ''; - let multipleEntryPoints: boolean = false; - for (const hierarchyItem of apiItem.getHierarchy()) { - // For overloaded methods, add a suffix such as "MyClass.myMethod_2". - let qualifiedName: string = Utilities.getSafeFilenameForName( - hierarchyItem.displayName - ); - if (ApiParameterListMixin.isBaseClassOf(hierarchyItem)) { - if (hierarchyItem.overloadIndex > 1) { - // Subtract one for compatibility with earlier releases of API Documenter. - // (This will get revamped when we fix GitHub issue #1308) - qualifiedName += `_${hierarchyItem.overloadIndex - 1}`; - } - } - - switch (hierarchyItem.kind) { - case ApiItemKind.Model: - break; - case ApiItemKind.EntryPoint: - const packageName: string = hierarchyItem.parent!.displayName; - let entryPointName: string = PackageName.getUnscopedName(packageName); - if (multipleEntryPoints) { - entryPointName = `${PackageName.getUnscopedName(packageName)}/${ - hierarchyItem.displayName - }`; - } - baseName = Utilities.getSafeFilenameForName(entryPointName); - break; - case ApiItemKind.Package: - baseName = Utilities.getSafeFilenameForName( - PackageName.getUnscopedName(hierarchyItem.displayName) - ); - if ((hierarchyItem as ApiPackage).entryPoints.length > 1) { - multipleEntryPoints = true; - } - break; - case ApiItemKind.Namespace: - baseName += '.' + qualifiedName; - if (addFileNameSuffix) { - baseName += '_n'; - } - break; - case ApiItemKind.Class: - case ApiItemKind.Interface: - baseName += '.' + qualifiedName; - break; - } - } - return baseName + '.md'; -} - -// TODO: handle namespace? -export function getHeadingAnchorForApiItem(apiItem: ApiItem): string { - const scopedName: string = lowercaseAndRemoveSymbols( - apiItem.getScopedNameWithinPackage() - ); - - switch (apiItem.kind) { - case ApiItemKind.Function: - return lowercaseAndRemoveSymbols(getFunctionOverloadAnchor(apiItem)); - case ApiItemKind.Variable: - return `${scopedName}`; - case ApiItemKind.TypeAlias: - return `${scopedName}`; - case ApiItemKind.Enum: - return `${scopedName}`; - case ApiItemKind.Method: - case ApiItemKind.MethodSignature: - return `${scopedName}`; - case ApiItemKind.Property: - case ApiItemKind.PropertySignature: - return `${scopedName}`; - case ApiItemKind.Constructor: - case ApiItemKind.ConstructSignature: - return `${scopedName}`; - case ApiItemKind.Class: - return `${scopedName}_class`; - case ApiItemKind.Interface: - return `${scopedName}_interface`; - case ApiItemKind.Model: - return `api-reference`; - case ApiItemKind.Namespace: - return `${scopedName}_namespace`; - case ApiItemKind.Package: - const unscopedPackageName: string = lowercaseAndRemoveSymbols( - PackageName.getUnscopedName(apiItem.displayName) - ); - return `${unscopedPackageName}_package`; - case ApiItemKind.EntryPoint: - const packageName: string = apiItem.parent!.displayName; - return lowercaseAndRemoveSymbols( - `${packageName}${apiItem.displayName && '/' + apiItem.displayName}` - ); - case ApiItemKind.EnumMember: - return `${scopedName}_enummember`; - default: - throw new Error( - 'Unsupported API item kind:3 ' + apiItem.kind + apiItem.displayName - ); - } -} - -/** - * Generates a unique link for a function. Example: "getArea_paramhashhere" - */ -function getFunctionOverloadAnchor(apiItem: ApiItem): string { - if ( - ApiParameterListMixin.isBaseClassOf(apiItem) && - apiItem.parameters.length > 0 - ) { - // Create a sha256 hash from the parameter names and types. - const hash = createHash('sha256'); - apiItem.parameters.forEach(param => - hash.update(`${param.name}:${param.parameterTypeExcerpt.text}`) - ); - // Use the first 7 characters of the hash for an easier to read URL. - const paramHash = hash.digest('hex').substring(0, 7); - - // Suffix the API item name with the paramHash to generate a unique - // anchor for function overloads - return apiItem.getScopedNameWithinPackage() + '_' + paramHash; - } - return apiItem.getScopedNameWithinPackage(); -} - -function lowercaseAndRemoveSymbols(input: string): string { - return input.replace(/[\.()]/g, '').toLowerCase(); -} - -export function createBetaWarning(configuration: TSDocConfiguration): DocNode { - const betaWarning: string = - 'This API is provided as a preview for developers and may change' + - ' based on feedback that we receive. Do not use this API in a production environment.'; - return new DocNoteBox({ configuration }, [ - new DocParagraph({ configuration }, [ - new DocPlainText({ configuration, text: betaWarning }) - ]) - ]); -} - -export function createRemarksSection( - apiItem: ApiItem, - configuration: TSDocConfiguration -): DocNode[] { - const nodes: DocNode[] = []; - if (apiItem instanceof ApiDocumentedItem) { - const tsdocComment: DocComment | undefined = apiItem.tsdocComment; - - if (tsdocComment) { - // Write the @remarks block - if (tsdocComment.remarksBlock) { - nodes.push(...tsdocComment.remarksBlock.content.nodes); - } - } - } - - return nodes; -} - -export function createExampleSection( - apiItem: ApiItem, - configuration: TSDocConfiguration -): DocNode[] { - const nodes: DocNode[] = []; - if (apiItem instanceof ApiDocumentedItem) { - const tsdocComment: DocComment | undefined = apiItem.tsdocComment; - - if (tsdocComment) { - // Write the @example blocks - const exampleBlocks: DocBlock[] = tsdocComment.customBlocks.filter( - x => - x.blockTag.tagNameWithUpperCase === - StandardTags.example.tagNameWithUpperCase - ); - - let exampleNumber: number = 1; - for (const exampleBlock of exampleBlocks) { - const heading: string = - exampleBlocks.length > 1 ? `Example ${exampleNumber}` : 'Example'; - - nodes.push(new DocHeading({ configuration, title: heading, level: 2 })); - - nodes.push(...exampleBlock.content.nodes); - - ++exampleNumber; - } - } - } - - return nodes; -} - -export function createTitleCell( - apiItem: ApiItem, - configuration: TSDocConfiguration, - addFileNameSuffix: boolean -): DocTableCell { - return new DocTableCell({ configuration }, [ - new DocParagraph({ configuration }, [ - new DocLinkTag({ - configuration, - tagName: '@link', - linkText: Utilities.getConciseSignature(apiItem), - urlDestination: getLinkForApiItem(apiItem, addFileNameSuffix) - }) - ]) - ]); -} - -/** - * This generates a DocTableCell for an ApiItem including the summary section and "(BETA)" annotation. - * - * @remarks - * We mostly assume that the input is an ApiDocumentedItem, but it's easier to perform this as a runtime - * check than to have each caller perform a type cast. - */ -export function createDescriptionCell( - apiItem: ApiItem, - configuration: TSDocConfiguration -): DocTableCell { - const section: DocSection = new DocSection({ configuration }); - - if (ApiReleaseTagMixin.isBaseClassOf(apiItem)) { - if (apiItem.releaseTag === ReleaseTag.Beta) { - section.appendNodesInParagraph([ - new DocEmphasisSpan({ configuration, bold: true, italic: true }, [ - new DocPlainText({ configuration, text: '(BETA)' }) - ]), - new DocPlainText({ configuration, text: ' ' }) - ]); - } - } - - if (apiItem instanceof ApiDocumentedItem) { - if (apiItem.tsdocComment !== undefined) { - appendAndMergeSection(section, apiItem.tsdocComment.summarySection); - } - } - - return new DocTableCell({ configuration }, section.nodes); -} - -export function createModifiersCell( - apiItem: ApiItem, - configuration: TSDocConfiguration -): DocTableCell { - const section: DocSection = new DocSection({ configuration }); - - if (ApiStaticMixin.isBaseClassOf(apiItem)) { - if (apiItem.isStatic) { - section.appendNodeInParagraph( - new DocCodeSpan({ configuration, code: 'static' }) - ); - } - } - - return new DocTableCell({ configuration }, section.nodes); -} - -function appendAndMergeSection( - output: DocSection, - docSection: DocSection -): void { - let firstNode: boolean = true; - for (const node of docSection.nodes) { - if (firstNode) { - if (node.kind === DocNodeKind.Paragraph) { - output.appendNodesInParagraph(node.getChildNodes()); - firstNode = false; - continue; - } - } - firstNode = false; - - output.appendNode(node); - } -} - -export function createThrowsSection( - apiItem: ApiItem, - configuration: TSDocConfiguration, - parentHeadingLevel: number -): DocNode[] { - const output: DocNode[] = []; - if (apiItem instanceof ApiDocumentedItem) { - const tsdocComment: DocComment | undefined = apiItem.tsdocComment; - - if (tsdocComment) { - // Write the @throws blocks - const throwsBlocks: DocBlock[] = tsdocComment.customBlocks.filter( - x => - x.blockTag.tagNameWithUpperCase === - StandardTags.throws.tagNameWithUpperCase - ); - - if (throwsBlocks.length > 0) { - const heading: string = 'Exceptions'; - output.push( - new DocHeading({ - configuration, - title: heading, - level: parentHeadingLevel + 1 - }) - ); - - for (const throwsBlock of throwsBlocks) { - output.push(...throwsBlock.content.nodes); - } - } - } - } - - return output; -} - -export function createEntryPointTitleCell( - apiItem: ApiEntryPoint, - configuration: TSDocConfiguration, - addFileNameSuffix: boolean -): DocTableCell { - return new DocTableCell({ configuration }, [ - new DocParagraph({ configuration }, [ - new DocLinkTag({ - configuration, - tagName: '@link', - linkText: `/${apiItem.displayName}`, - urlDestination: getLinkForApiItem(apiItem, addFileNameSuffix) - }) - ]) - ]); -} - -/** - * GENERATE PAGE: ENUM - */ -export function createEnumTables( - apiEnum: ApiEnum, - configuration: TSDocConfiguration -): DocNode[] { - const output: DocNode[] = []; - const enumMembersTable: DocTable = new DocTable({ - configuration, - headerTitles: ['Member', 'Value', 'Description'] - }); - - for (const apiEnumMember of apiEnum.members) { - enumMembersTable.addRow( - new DocTableRow({ configuration }, [ - new DocTableCell({ configuration }, [ - new DocParagraph({ configuration }, [ - new DocPlainText({ - configuration, - text: Utilities.getConciseSignature(apiEnumMember) - }) - ]) - ]), - - new DocTableCell({ configuration }, [ - new DocParagraph({ configuration }, [ - new DocCodeSpan({ - configuration, - code: apiEnumMember.initializerExcerpt.text - }) - ]) - ]), - - createDescriptionCell(apiEnumMember, configuration) - ]) - ); - } - - if (enumMembersTable.rows.length > 0) { - output.push( - new DocHeading({ configuration, title: 'Enumeration Members' }) - ); - output.push(enumMembersTable); - } - - return output; -} diff --git a/repo-scripts/api-documenter/src/index.ts b/repo-scripts/api-documenter/src/index.ts deleted file mode 100644 index 1845059c0c4..00000000000 --- a/repo-scripts/api-documenter/src/index.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -/** - * API Documenter generates an API reference website from the .api.json files created by API Extractor. - * The `@microsoft/api-documenter` package provides the command-line tool. It also exposes a developer API that you - * can use to create plugins that customize how API Documenter generates documentation. - * - * @packageDocumentation - */ - -export { - IFeatureDefinition, - IApiDocumenterPluginManifest -} from './plugin/IApiDocumenterPluginManifest'; -export { MarkdownDocumenterAccessor } from './plugin/MarkdownDocumenterAccessor'; -export { - MarkdownDocumenterFeatureContext, - IMarkdownDocumenterFeatureOnBeforeWritePageArgs, - IMarkdownDocumenterFeatureOnFinishedArgs, - MarkdownDocumenterFeature -} from './plugin/MarkdownDocumenterFeature'; -export { - PluginFeature, - PluginFeatureContext, - PluginFeatureInitialization -} from './plugin/PluginFeature'; diff --git a/repo-scripts/api-documenter/src/markdown/CustomMarkdownEmitter.ts b/repo-scripts/api-documenter/src/markdown/CustomMarkdownEmitter.ts deleted file mode 100644 index 6f8f4785bdb..00000000000 --- a/repo-scripts/api-documenter/src/markdown/CustomMarkdownEmitter.ts +++ /dev/null @@ -1,232 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -import colors from 'colors'; - -import { DocNode, DocLinkTag, StringBuilder } from '@microsoft/tsdoc'; -import { - ApiModel, - IResolveDeclarationReferenceResult, - ApiItem -} from 'api-extractor-model-me'; - -import { CustomDocNodeKind } from '../nodes/CustomDocNodeKind'; -import { DocHeading } from '../nodes/DocHeading'; -import { DocNoteBox } from '../nodes/DocNoteBox'; -import { DocTable } from '../nodes/DocTable'; -import { DocTableCell } from '../nodes/DocTableCell'; -import { DocEmphasisSpan } from '../nodes/DocEmphasisSpan'; -import { - MarkdownEmitter, - IMarkdownEmitterContext, - IMarkdownEmitterOptions -} from './MarkdownEmitter'; -import { IndentedWriter } from '../utils/IndentedWriter'; - -export interface ICustomMarkdownEmitterOptions extends IMarkdownEmitterOptions { - contextApiItem: ApiItem | undefined; - - onGetFilenameForApiItem: (apiItem: ApiItem) => string | undefined; -} - -export class CustomMarkdownEmitter extends MarkdownEmitter { - private _apiModel: ApiModel; - - public constructor(apiModel: ApiModel) { - super(); - - this._apiModel = apiModel; - } - - public emit( - stringBuilder: StringBuilder, - docNode: DocNode, - options: ICustomMarkdownEmitterOptions - ): string { - return super.emit(stringBuilder, docNode, options); - } - - /** @override */ - protected writeNode( - docNode: DocNode, - context: IMarkdownEmitterContext, - docNodeSiblings: boolean - ): void { - const writer: IndentedWriter = context.writer; - - switch (docNode.kind) { - case CustomDocNodeKind.Heading: { - const docHeading: DocHeading = docNode as DocHeading; - writer.ensureSkippedLine(); - - let prefix: string; - switch (docHeading.level) { - case 1: - prefix = '##'; - break; - case 2: - prefix = '###'; - break; - case 3: - prefix = '####'; - break; - default: - prefix = '####'; - } - - let mdLine = prefix + ' ' + this.getEscapedText(docHeading.title); - if (docHeading.anchor) { - mdLine = mdLine + ` {:#${docHeading.anchor}}`; - } - writer.writeLine(mdLine); - writer.writeLine(); - break; - } - case CustomDocNodeKind.NoteBox: { - const docNoteBox: DocNoteBox = docNode as DocNoteBox; - writer.ensureNewLine(); - - writer.increaseIndent('> '); - - this.writeNode(docNoteBox.content, context, false); - writer.ensureNewLine(); - - writer.decreaseIndent(); - - writer.writeLine(); - break; - } - case CustomDocNodeKind.Table: { - const docTable: DocTable = docNode as DocTable; - // GitHub's markdown renderer chokes on tables that don't have a blank line above them, - // whereas VS Code's renderer is totally fine with it. - writer.ensureSkippedLine(); - - context.insideTable = true; - - // Markdown table rows can have inconsistent cell counts. Size the table based on the longest row. - let columnCount: number = 0; - if (docTable.header) { - columnCount = docTable.header.cells.length; - } - for (const row of docTable.rows) { - if (row.cells.length > columnCount) { - columnCount = row.cells.length; - } - } - - // write the table header (which is required by Markdown) - writer.write('| '); - for (let i: number = 0; i < columnCount; ++i) { - writer.write(' '); - if (docTable.header) { - const cell: DocTableCell | undefined = docTable.header.cells[i]; - if (cell) { - this.writeNode(cell.content, context, false); - } - } - writer.write(' |'); - } - writer.writeLine(); - - // write the divider - writer.write('| '); - for (let i: number = 0; i < columnCount; ++i) { - writer.write(' --- |'); - } - writer.writeLine(); - - for (const row of docTable.rows) { - writer.write('| '); - for (const cell of row.cells) { - writer.write(' '); - this.writeNode(cell.content, context, false); - writer.write(' |'); - } - writer.writeLine(); - } - writer.writeLine(); - - context.insideTable = false; - - break; - } - case CustomDocNodeKind.EmphasisSpan: { - const docEmphasisSpan: DocEmphasisSpan = docNode as DocEmphasisSpan; - const oldBold: boolean = context.boldRequested; - const oldItalic: boolean = context.italicRequested; - context.boldRequested = docEmphasisSpan.bold; - context.italicRequested = docEmphasisSpan.italic; - this.writeNodes(docEmphasisSpan.nodes, context); - context.boldRequested = oldBold; - context.italicRequested = oldItalic; - break; - } - default: - super.writeNode(docNode, context, false); - } - } - - /** @override */ - protected writeLinkTagWithCodeDestination( - docLinkTag: DocLinkTag, - context: IMarkdownEmitterContext - ): void { - const options: ICustomMarkdownEmitterOptions = context.options; - - const result: IResolveDeclarationReferenceResult = - this._apiModel.resolveDeclarationReference( - docLinkTag.codeDestination!, - options.contextApiItem - ); - - if (result.resolvedApiItem) { - const filename: string | undefined = options.onGetFilenameForApiItem( - result.resolvedApiItem - ); - - if (filename) { - let linkText: string = docLinkTag.linkText || ''; - if (linkText.length === 0) { - // Generate a name such as Namespace1.Namespace2.MyClass.myMethod() - linkText = result.resolvedApiItem.getScopedNameWithinPackage(); - } - if (linkText.length > 0) { - const encodedLinkText: string = this.getEscapedText( - linkText.replace(/\s+/g, ' ') - ); - - context.writer.write('['); - context.writer.write(encodedLinkText); - context.writer.write(`](${filename!})`); - } else { - console.log(colors.yellow('WARNING: Unable to determine link text')); - } - } - } else if (result.errorMessage) { - console.log( - colors.yellow( - `WARNING: Unable to resolve reference "${docLinkTag.codeDestination!.emitAsTsdoc()}": ` + - result.errorMessage - ) - ); - } - } -} diff --git a/repo-scripts/api-documenter/src/markdown/MarkdownEmitter.ts b/repo-scripts/api-documenter/src/markdown/MarkdownEmitter.ts deleted file mode 100644 index 1bc306bb044..00000000000 --- a/repo-scripts/api-documenter/src/markdown/MarkdownEmitter.ts +++ /dev/null @@ -1,319 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -import { - DocNode, - DocNodeKind, - StringBuilder, - DocPlainText, - DocHtmlStartTag, - DocHtmlEndTag, - DocCodeSpan, - DocLinkTag, - DocParagraph, - DocFencedCode, - DocSection, - DocNodeTransforms, - DocEscapedText, - DocErrorText, - DocBlockTag -} from '@microsoft/tsdoc'; -import { InternalError } from '@rushstack/node-core-library'; - -import { IndentedWriter } from '../utils/IndentedWriter'; - -export interface IMarkdownEmitterOptions {} - -export interface IMarkdownEmitterContext { - writer: IndentedWriter; - insideTable: boolean; - - boldRequested: boolean; - italicRequested: boolean; - - writingBold: boolean; - writingItalic: boolean; - - options: TOptions; -} - -/** - * Renders MarkupElement content in the Markdown file format. - * For more info: https://en.wikipedia.org/wiki/Markdown - */ -export class MarkdownEmitter { - public emit( - stringBuilder: StringBuilder, - docNode: DocNode, - options: IMarkdownEmitterOptions - ): string { - const writer: IndentedWriter = new IndentedWriter(stringBuilder); - - const context: IMarkdownEmitterContext = { - writer, - insideTable: false, - - boldRequested: false, - italicRequested: false, - - writingBold: false, - writingItalic: false, - - options - }; - - this.writeNode(docNode, context, false); - - writer.ensureNewLine(); // finish the last line - - return writer.toString(); - } - - protected getEscapedText(text: string): string { - const textWithBackslashes: string = text - .replace(/\\/g, '\\\\') // first replace the escape character - .replace(/[*#[\]_|`~]/g, x => '\\' + x) // then escape any special characters - .replace(/---/g, '\\-\\-\\-') // hyphens only if it's 3 or more - .replace(/&/g, '&') - .replace(//g, '>'); - return textWithBackslashes; - } - - protected getTableEscapedText(text: string): string { - return text - .replace(/&/g, '&') - .replace(/"/g, '"') - .replace(//g, '>') - .replace(/\|/g, '|'); - } - - /** - * @virtual - */ - protected writeNode( - docNode: DocNode, - context: IMarkdownEmitterContext, - docNodeSiblings: boolean - ): void { - const writer: IndentedWriter = context.writer; - - switch (docNode.kind) { - case DocNodeKind.PlainText: { - const docPlainText: DocPlainText = docNode as DocPlainText; - this.writePlainText(docPlainText.text, context); - break; - } - case DocNodeKind.HtmlStartTag: - case DocNodeKind.HtmlEndTag: { - const docHtmlTag: DocHtmlStartTag | DocHtmlEndTag = docNode as - | DocHtmlStartTag - | DocHtmlEndTag; - // write the HTML element verbatim into the output - writer.write(docHtmlTag.emitAsHtml()); - break; - } - case DocNodeKind.CodeSpan: { - const docCodeSpan: DocCodeSpan = docNode as DocCodeSpan; - if (context.insideTable) { - writer.write(''); - } else { - writer.write('`'); - } - if (context.insideTable) { - const code: string = this.getTableEscapedText(docCodeSpan.code); - const parts: string[] = code.split(/\r?\n/g); - writer.write(parts.join('
')); - } else { - writer.write(docCodeSpan.code); - } - if (context.insideTable) { - writer.write(''); - } else { - writer.write('`'); - } - break; - } - case DocNodeKind.LinkTag: { - const docLinkTag: DocLinkTag = docNode as DocLinkTag; - if (docLinkTag.codeDestination) { - this.writeLinkTagWithCodeDestination(docLinkTag, context); - } else if (docLinkTag.urlDestination) { - this.writeLinkTagWithUrlDestination(docLinkTag, context); - } else if (docLinkTag.linkText) { - this.writePlainText(docLinkTag.linkText, context); - } - break; - } - case DocNodeKind.Paragraph: { - const docParagraph: DocParagraph = docNode as DocParagraph; - const trimmedParagraph: DocParagraph = - DocNodeTransforms.trimSpacesInParagraph(docParagraph); - if (context.insideTable) { - if (docNodeSiblings) { - writer.write('

'); - this.writeNodes(trimmedParagraph.nodes, context); - writer.write('

'); - } else { - // Special case: If we are the only element inside this table cell, then we can omit the

container. - this.writeNodes(trimmedParagraph.nodes, context); - } - } else { - this.writeNodes(trimmedParagraph.nodes, context); - writer.ensureNewLine(); - writer.writeLine(); - } - break; - } - case DocNodeKind.FencedCode: { - const docFencedCode: DocFencedCode = docNode as DocFencedCode; - writer.ensureNewLine(); - writer.write('```'); - writer.write(docFencedCode.language); - writer.writeLine(); - writer.write(docFencedCode.code); - writer.writeLine(); - writer.writeLine('```'); - break; - } - case DocNodeKind.Section: { - const docSection: DocSection = docNode as DocSection; - this.writeNodes(docSection.nodes, context); - break; - } - case DocNodeKind.SoftBreak: { - if (!/^\s?$/.test(writer.peekLastCharacter())) { - writer.write(' '); - } - break; - } - case DocNodeKind.EscapedText: { - const docEscapedText: DocEscapedText = docNode as DocEscapedText; - this.writePlainText(docEscapedText.decodedText, context); - break; - } - case DocNodeKind.ErrorText: { - const docErrorText: DocErrorText = docNode as DocErrorText; - this.writePlainText(docErrorText.text, context); - break; - } - case DocNodeKind.InlineTag: { - break; - } - case DocNodeKind.BlockTag: { - const tagNode: DocBlockTag = docNode as DocBlockTag; - console.warn('Unsupported block tag: ' + tagNode.tagName); - break; - } - default: - throw new InternalError( - 'Unsupported DocNodeKind kind: ' + docNode.kind - ); - } - } - - /** @virtual */ - protected writeLinkTagWithCodeDestination( - docLinkTag: DocLinkTag, - context: IMarkdownEmitterContext - ): void { - // The subclass needs to implement this to support code destinations - throw new InternalError('writeLinkTagWithCodeDestination()'); - } - - /** @virtual */ - protected writeLinkTagWithUrlDestination( - docLinkTag: DocLinkTag, - context: IMarkdownEmitterContext - ): void { - const linkText: string = - docLinkTag.linkText !== undefined - ? docLinkTag.linkText - : docLinkTag.urlDestination!; - - const encodedLinkText: string = this.getEscapedText( - linkText.replace(/\s+/g, ' ') - ); - - context.writer.write('['); - context.writer.write(encodedLinkText); - context.writer.write(`](${docLinkTag.urlDestination!})`); - } - - protected writePlainText( - text: string, - context: IMarkdownEmitterContext - ): void { - const writer: IndentedWriter = context.writer; - - // split out the [ leading whitespace, content, trailing whitespace ] - const parts: string[] = text.match(/^(\s*)(.*?)(\s*)$/) || []; - - writer.write(parts[1]); // write leading whitespace - - const middle: string = parts[2]; - - if (middle !== '') { - switch (writer.peekLastCharacter()) { - case '': - case '\n': - case ' ': - case '[': - case '>': - // okay to put a symbol - break; - default: - // This is no problem: "**one** *two* **three**" - // But this is trouble: "**one***two***three**" - // The most general solution: "**one***two***three**" - writer.write(''); - break; - } - - if (context.boldRequested) { - writer.write(''); - } - if (context.italicRequested) { - writer.write(''); - } - - writer.write(this.getEscapedText(middle)); - - if (context.italicRequested) { - writer.write(''); - } - if (context.boldRequested) { - writer.write(''); - } - } - - writer.write(parts[3]); // write trailing whitespace - } - - protected writeNodes( - docNodes: ReadonlyArray, - context: IMarkdownEmitterContext - ): void { - for (const docNode of docNodes) { - this.writeNode(docNode, context, docNodes.length > 1); - } - } -} diff --git a/repo-scripts/api-documenter/src/markdown/test/CustomMarkdownEmitter.test.ts b/repo-scripts/api-documenter/src/markdown/test/CustomMarkdownEmitter.test.ts deleted file mode 100644 index ed8799e0023..00000000000 --- a/repo-scripts/api-documenter/src/markdown/test/CustomMarkdownEmitter.test.ts +++ /dev/null @@ -1,237 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -import { - DocSection, - TSDocConfiguration, - DocPlainText, - StringBuilder, - DocParagraph, - DocSoftBreak, - DocLinkTag, - DocHtmlStartTag, - DocHtmlEndTag, - DocBlockTag -} from '@microsoft/tsdoc'; - -import { CustomDocNodes } from '../../nodes/CustomDocNodeKind'; -import { DocHeading } from '../../nodes/DocHeading'; -import { DocEmphasisSpan } from '../../nodes/DocEmphasisSpan'; -import { DocTable } from '../../nodes/DocTable'; -import { DocTableRow } from '../../nodes/DocTableRow'; -import { DocTableCell } from '../../nodes/DocTableCell'; -import { CustomMarkdownEmitter } from '../CustomMarkdownEmitter'; -import { ApiModel, ApiItem } from 'api-extractor-model-me'; -import { expect, use } from 'chai'; -import { jestSnapshotPlugin } from 'mocha-chai-jest-snapshot'; - -use(jestSnapshotPlugin()); - -it('render Markdown from TSDoc', () => { - const configuration: TSDocConfiguration = CustomDocNodes.configuration; - - const output: DocSection = new DocSection({ configuration }); - - output.appendNodes([ - new DocHeading({ configuration, title: 'Simple bold test' }), - new DocParagraph({ configuration }, [ - new DocPlainText({ configuration, text: 'This is a ' }), - new DocEmphasisSpan({ configuration, bold: true }, [ - new DocPlainText({ configuration, text: 'bold' }) - ]), - new DocPlainText({ configuration, text: ' word.' }) - ]) - ]); - - output.appendNodes([ - new DocHeading({ configuration, title: 'All whitespace bold' }), - new DocParagraph({ configuration }, [ - new DocEmphasisSpan({ configuration, bold: true }, [ - new DocPlainText({ configuration, text: ' ' }) - ]) - ]) - ]); - - output.appendNodes([ - new DocHeading({ configuration, title: 'Newline bold' }), - new DocParagraph({ configuration }, [ - new DocEmphasisSpan({ configuration, bold: true }, [ - new DocPlainText({ configuration, text: 'line 1' }), - new DocSoftBreak({ configuration }), - new DocPlainText({ configuration, text: 'line 2' }) - ]) - ]) - ]); - - output.appendNodes([ - new DocHeading({ configuration, title: 'Newline bold with spaces' }), - new DocParagraph({ configuration }, [ - new DocEmphasisSpan({ configuration, bold: true }, [ - new DocPlainText({ configuration, text: ' line 1 ' }), - new DocSoftBreak({ configuration }), - new DocPlainText({ configuration, text: ' line 2 ' }), - new DocSoftBreak({ configuration }), - new DocPlainText({ configuration, text: ' line 3 ' }) - ]) - ]) - ]); - - output.appendNodes([ - new DocHeading({ configuration, title: 'Adjacent bold regions' }), - new DocParagraph({ configuration }, [ - new DocEmphasisSpan({ configuration, bold: true }, [ - new DocPlainText({ configuration, text: 'one' }) - ]), - new DocEmphasisSpan({ configuration, bold: true }, [ - new DocPlainText({ configuration, text: 'two' }) - ]), - new DocEmphasisSpan({ configuration, bold: true }, [ - new DocPlainText({ configuration, text: ' three ' }) - ]), - new DocPlainText({ configuration, text: '' }), - new DocEmphasisSpan({ configuration, bold: true }, [ - new DocPlainText({ configuration, text: 'four' }) - ]), - new DocPlainText({ configuration, text: 'non-bold' }), - new DocEmphasisSpan({ configuration, bold: true }, [ - new DocPlainText({ configuration, text: 'five' }) - ]) - ]) - ]); - - output.appendNodes([ - new DocHeading({ configuration, title: 'Adjacent to other characters' }), - new DocParagraph({ configuration }, [ - new DocLinkTag({ - configuration, - tagName: '@link', - linkText: 'a link', - urlDestination: './index.md' - }), - new DocEmphasisSpan({ configuration, bold: true }, [ - new DocPlainText({ configuration, text: 'bold' }) - ]), - new DocPlainText({ configuration, text: 'non-bold' }), - new DocPlainText({ configuration, text: 'more-non-bold' }) - ]) - ]); - - output.appendNodes([ - new DocHeading({ configuration, title: 'Unknown block tag' }), - new DocParagraph({ configuration }, [ - new DocBlockTag({ - configuration, - tagName: '@unknown' - }), - new DocEmphasisSpan({ configuration, bold: true }, [ - new DocPlainText({ configuration, text: 'bold' }) - ]), - new DocPlainText({ configuration, text: 'non-bold' }), - new DocPlainText({ configuration, text: 'more-non-bold' }) - ]) - ]); - - output.appendNodes([ - new DocHeading({ configuration, title: 'Bad characters' }), - new DocParagraph({ configuration }, [ - new DocEmphasisSpan({ configuration, bold: true }, [ - new DocPlainText({ configuration, text: '*one*two*' }) - ]), - new DocEmphasisSpan({ configuration, bold: true }, [ - new DocPlainText({ configuration, text: 'three*four' }) - ]) - ]) - ]); - - output.appendNodes([ - new DocHeading({ - configuration, - title: 'Characters that should be escaped' - }), - new DocParagraph({ configuration }, [ - new DocPlainText({ - configuration, - text: 'Double-encoded JSON: "{ \\"A\\": 123}"' - }) - ]), - new DocParagraph({ configuration }, [ - new DocPlainText({ - configuration, - text: 'HTML chars: ' - }) - ]), - new DocParagraph({ configuration }, [ - new DocPlainText({ configuration, text: 'HTML escape: "' }) - ]), - new DocParagraph({ configuration }, [ - new DocPlainText({ - configuration, - text: '3 or more hyphens: - -- --- ---- ----- ------' - }) - ]) - ]); - - output.appendNodes([ - new DocHeading({ configuration, title: 'HTML tag' }), - new DocParagraph({ configuration }, [ - new DocHtmlStartTag({ configuration, name: 'b' }), - new DocPlainText({ configuration, text: 'bold' }), - new DocHtmlEndTag({ configuration, name: 'b' }) - ]) - ]); - - output.appendNodes([ - new DocHeading({ configuration, title: 'Table' }), - new DocTable( - { - configuration, - headerTitles: ['Header 1', 'Header 2'] - }, - [ - new DocTableRow({ configuration }, [ - new DocTableCell({ configuration }, [ - new DocParagraph({ configuration }, [ - new DocPlainText({ configuration, text: 'Cell 1' }) - ]) - ]), - new DocTableCell({ configuration }, [ - new DocParagraph({ configuration }, [ - new DocPlainText({ configuration, text: 'Cell 2' }) - ]) - ]) - ]) - ] - ) - ]); - - const stringBuilder: StringBuilder = new StringBuilder(); - const apiModel: ApiModel = new ApiModel(); - const markdownEmitter: CustomMarkdownEmitter = new CustomMarkdownEmitter( - apiModel - ); - markdownEmitter.emit(stringBuilder, output, { - contextApiItem: undefined, - onGetFilenameForApiItem: (apiItem: ApiItem) => { - return '#'; - } - }); - - expect(stringBuilder).toMatchSnapshot(); -}); diff --git a/repo-scripts/api-documenter/src/markdown/test/__snapshots__/CustomMarkdownEmitter.test.ts.snap b/repo-scripts/api-documenter/src/markdown/test/__snapshots__/CustomMarkdownEmitter.test.ts.snap deleted file mode 100644 index ae459a5a8d7..00000000000 --- a/repo-scripts/api-documenter/src/markdown/test/__snapshots__/CustomMarkdownEmitter.test.ts.snap +++ /dev/null @@ -1,62 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`render Markdown from TSDoc 1`] = ` -StringBuilder { - "_chunks": Array [ - " -## Simple bold test - -This is a bold word. - -## All whitespace bold - - - -## Newline bold - -line 1 line 2 - -## Newline bold with spaces - - line 1 line 2 line 3 - -## Adjacent bold regions - -onetwo three fournon-boldfive - -## Adjacent to other characters - -[a link](./index.md)boldnon-boldmore-non-bold - -## Unknown block tag - -boldnon-boldmore-non-bold - -## Bad characters - -\\\\*one\\\\*two\\\\*three\\\\*four - -## Characters that should be escaped - -Double-encoded JSON: \\"{ \\\\\\\\\\"A\\\\\\\\\\": 123}\\" - -HTML chars: <script>alert(\\"\\\\[You\\\\] are \\\\#1!\\");</script> - -HTML escape: &quot; - -3 or more hyphens: - -- \\\\-\\\\-\\\\- \\\\-\\\\-\\\\-- \\\\-\\\\-\\\\--- \\\\-\\\\-\\\\-\\\\-\\\\-\\\\- - -## HTML tag - -bold - -## Table - -| Header 1 | Header 2 | -| --- | --- | -| Cell 1 | Cell 2 | - -", - ], -} -`; diff --git a/repo-scripts/api-documenter/src/nodes/CustomDocNodeKind.ts b/repo-scripts/api-documenter/src/nodes/CustomDocNodeKind.ts deleted file mode 100644 index 744673f484d..00000000000 --- a/repo-scripts/api-documenter/src/nodes/CustomDocNodeKind.ts +++ /dev/null @@ -1,89 +0,0 @@ -/** - * @license - * Copyright 2020 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 { TSDocConfiguration, DocNodeKind } from '@microsoft/tsdoc'; -import { DocEmphasisSpan } from './DocEmphasisSpan'; -import { DocHeading } from './DocHeading'; -import { DocNoteBox } from './DocNoteBox'; -import { DocTable } from './DocTable'; -import { DocTableCell } from './DocTableCell'; -import { DocTableRow } from './DocTableRow'; - -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -/** - * Identifies custom subclasses of {@link DocNode}. - */ -export const enum CustomDocNodeKind { - EmphasisSpan = 'EmphasisSpan', - Heading = 'Heading', - NoteBox = 'NoteBox', - Table = 'Table', - TableCell = 'TableCell', - TableRow = 'TableRow' -} - -export class CustomDocNodes { - private static _configuration: TSDocConfiguration | undefined; - - public static get configuration(): TSDocConfiguration { - if (CustomDocNodes._configuration === undefined) { - const configuration: TSDocConfiguration = new TSDocConfiguration(); - - configuration.docNodeManager.registerDocNodes( - '@micrososft/api-documenter', - [ - { - docNodeKind: CustomDocNodeKind.EmphasisSpan, - constructor: DocEmphasisSpan - }, - { docNodeKind: CustomDocNodeKind.Heading, constructor: DocHeading }, - { docNodeKind: CustomDocNodeKind.NoteBox, constructor: DocNoteBox }, - { docNodeKind: CustomDocNodeKind.Table, constructor: DocTable }, - { - docNodeKind: CustomDocNodeKind.TableCell, - constructor: DocTableCell - }, - { docNodeKind: CustomDocNodeKind.TableRow, constructor: DocTableRow } - ] - ); - - configuration.docNodeManager.registerAllowableChildren( - CustomDocNodeKind.EmphasisSpan, - [DocNodeKind.PlainText, DocNodeKind.SoftBreak] - ); - - configuration.docNodeManager.registerAllowableChildren( - DocNodeKind.Section, - [ - CustomDocNodeKind.Heading, - CustomDocNodeKind.NoteBox, - CustomDocNodeKind.Table - ] - ); - - configuration.docNodeManager.registerAllowableChildren( - DocNodeKind.Paragraph, - [CustomDocNodeKind.EmphasisSpan] - ); - - CustomDocNodes._configuration = configuration; - } - return CustomDocNodes._configuration; - } -} diff --git a/repo-scripts/api-documenter/src/nodes/DocEmphasisSpan.ts b/repo-scripts/api-documenter/src/nodes/DocEmphasisSpan.ts deleted file mode 100644 index 51d6cc79e4e..00000000000 --- a/repo-scripts/api-documenter/src/nodes/DocEmphasisSpan.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -import { - DocNode, - DocNodeContainer, - IDocNodeContainerParameters -} from '@microsoft/tsdoc'; -import { CustomDocNodeKind } from './CustomDocNodeKind'; - -/** - * Constructor parameters for {@link DocEmphasisSpan}. - */ -export interface IDocEmphasisSpanParameters - extends IDocNodeContainerParameters { - bold?: boolean; - italic?: boolean; -} - -/** - * Represents a span of text that is styled with CommonMark emphasis (italics), strong emphasis (boldface), - * or both. - */ -export class DocEmphasisSpan extends DocNodeContainer { - public readonly bold: boolean; - public readonly italic: boolean; - - public constructor( - parameters: IDocEmphasisSpanParameters, - children?: DocNode[] - ) { - super(parameters, children); - this.bold = !!parameters.bold; - this.italic = !!parameters.italic; - } - - /** @override */ - public get kind(): string { - return CustomDocNodeKind.EmphasisSpan; - } -} diff --git a/repo-scripts/api-documenter/src/nodes/DocHeading.ts b/repo-scripts/api-documenter/src/nodes/DocHeading.ts deleted file mode 100644 index 3e13fb01736..00000000000 --- a/repo-scripts/api-documenter/src/nodes/DocHeading.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -import { IDocNodeParameters, DocNode } from '@microsoft/tsdoc'; -import { CustomDocNodeKind } from './CustomDocNodeKind'; - -/** - * Constructor parameters for {@link DocHeading}. - */ -export interface IDocHeadingParameters extends IDocNodeParameters { - title: string; - level?: number; - anchor?: string; -} - -/** - * Represents a section header similar to an HTML `

` or `

` element. - */ -export class DocHeading extends DocNode { - public readonly title: string; - public readonly level: number; - public readonly anchor?: string; - - /** - * Don't call this directly. Instead use {@link TSDocParser} - * @internal - */ - public constructor(parameters: IDocHeadingParameters) { - super(parameters); - this.title = parameters.title; - this.level = parameters.level !== undefined ? parameters.level : 1; - this.anchor = parameters.anchor; - - if (this.level < 1 || this.level > 5) { - throw new Error( - 'IDocHeadingParameters.level must be a number between 1 and 5' - ); - } - } - - /** @override */ - public get kind(): string { - return CustomDocNodeKind.Heading; - } -} diff --git a/repo-scripts/api-documenter/src/nodes/DocNoteBox.ts b/repo-scripts/api-documenter/src/nodes/DocNoteBox.ts deleted file mode 100644 index dd2133828f7..00000000000 --- a/repo-scripts/api-documenter/src/nodes/DocNoteBox.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -import { IDocNodeParameters, DocNode, DocSection } from '@microsoft/tsdoc'; -import { CustomDocNodeKind } from './CustomDocNodeKind'; - -/** - * Constructor parameters for {@link DocNoteBox}. - */ -export interface IDocNoteBoxParameters extends IDocNodeParameters {} - -/** - * Represents a note box, which is typically displayed as a bordered box containing informational text. - */ -export class DocNoteBox extends DocNode { - public readonly content: DocSection; - - public constructor( - parameters: IDocNoteBoxParameters, - sectionChildNodes?: ReadonlyArray - ) { - super(parameters); - this.content = new DocSection( - { configuration: this.configuration }, - sectionChildNodes - ); - } - - /** @override */ - public get kind(): string { - return CustomDocNodeKind.NoteBox; - } - - /** @override */ - protected onGetChildNodes(): ReadonlyArray { - return [this.content]; - } -} diff --git a/repo-scripts/api-documenter/src/nodes/DocTable.ts b/repo-scripts/api-documenter/src/nodes/DocTable.ts deleted file mode 100644 index 49f319f0326..00000000000 --- a/repo-scripts/api-documenter/src/nodes/DocTable.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -import { IDocNodeParameters, DocNode } from '@microsoft/tsdoc'; -import { CustomDocNodeKind } from './CustomDocNodeKind'; -import { DocTableRow } from './DocTableRow'; -import { DocTableCell } from './DocTableCell'; - -/** - * Constructor parameters for {@link DocTable}. - */ -export interface IDocTableParameters extends IDocNodeParameters { - headerCells?: ReadonlyArray; - headerTitles?: string[]; -} - -/** - * Represents table, similar to an HTML `` element. - */ -export class DocTable extends DocNode { - public readonly header: DocTableRow; - - private _rows: DocTableRow[]; - - public constructor( - parameters: IDocTableParameters, - rows?: ReadonlyArray - ) { - super(parameters); - - this.header = new DocTableRow({ configuration: this.configuration }); - this._rows = []; - - if (parameters) { - if (parameters.headerTitles) { - if (parameters.headerCells) { - throw new Error( - 'IDocTableParameters.headerCells and IDocTableParameters.headerTitles' + - ' cannot both be specified' - ); - } - for (const cellText of parameters.headerTitles) { - this.header.addPlainTextCell(cellText); - } - } else if (parameters.headerCells) { - for (const cell of parameters.headerCells) { - this.header.addCell(cell); - } - } - } - - if (rows) { - for (const row of rows) { - this.addRow(row); - } - } - } - - /** @override */ - public get kind(): string { - return CustomDocNodeKind.Table; - } - - public get rows(): ReadonlyArray { - return this._rows; - } - - public addRow(row: DocTableRow): void { - this._rows.push(row); - } - - public createAndAddRow(): DocTableRow { - const row: DocTableRow = new DocTableRow({ - configuration: this.configuration - }); - this.addRow(row); - return row; - } - - /** @override */ - protected onGetChildNodes(): ReadonlyArray { - return [this.header, ...this._rows]; - } -} diff --git a/repo-scripts/api-documenter/src/nodes/DocTableCell.ts b/repo-scripts/api-documenter/src/nodes/DocTableCell.ts deleted file mode 100644 index 6fa4f79159c..00000000000 --- a/repo-scripts/api-documenter/src/nodes/DocTableCell.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -import { IDocNodeParameters, DocNode, DocSection } from '@microsoft/tsdoc'; -import { CustomDocNodeKind } from './CustomDocNodeKind'; - -/** - * Constructor parameters for {@link DocTableCell}. - */ -export interface IDocTableCellParameters extends IDocNodeParameters {} - -/** - * Represents table cell, similar to an HTML `` element. - */ -export class DocTableRow extends DocNode { - private readonly _cells: DocTableCell[]; - - public constructor( - parameters: IDocTableRowParameters, - cells?: ReadonlyArray - ) { - super(parameters); - - this._cells = []; - if (cells) { - for (const cell of cells) { - this.addCell(cell); - } - } - } - - /** @override */ - public get kind(): string { - return CustomDocNodeKind.TableRow; - } - - public get cells(): ReadonlyArray { - return this._cells; - } - - public addCell(cell: DocTableCell): void { - this._cells.push(cell); - } - - public createAndAddCell(): DocTableCell { - const newCell: DocTableCell = new DocTableCell({ - configuration: this.configuration - }); - this.addCell(newCell); - return newCell; - } - - public addPlainTextCell(cellContent: string): DocTableCell { - const cell: DocTableCell = this.createAndAddCell(); - cell.content.appendNodeInParagraph( - new DocPlainText({ - configuration: this.configuration, - text: cellContent - }) - ); - return cell; - } - - /** @override */ - protected onGetChildNodes(): ReadonlyArray { - return this._cells; - } -} diff --git a/repo-scripts/api-documenter/src/plugin/IApiDocumenterPluginManifest.ts b/repo-scripts/api-documenter/src/plugin/IApiDocumenterPluginManifest.ts deleted file mode 100644 index d8a44b95019..00000000000 --- a/repo-scripts/api-documenter/src/plugin/IApiDocumenterPluginManifest.ts +++ /dev/null @@ -1,96 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -import { MarkdownDocumenterFeature } from './MarkdownDocumenterFeature'; -import { PluginFeatureInitialization } from './PluginFeature'; - -/** - * Defines a "feature" that is provided by an API Documenter plugin. A feature is a user-defined module - * that customizes the behavior of API Documenter. - * - * @public - */ -export interface IFeatureDefinition { - /** - * The name of this feature, as it will appear in the config file. - * - * The name should consist of one or more words separated by hyphens. Each word should consist of lower case - * letters and numbers. Example: `my-feature` - */ - featureName: string; - - /** - * Determines the kind of feature. The specified value is the name of the base class that `subclass` inherits from. - * - * @remarks - * For now, `MarkdownDocumenterFeature` is the only supported value. - */ - kind: 'MarkdownDocumenterFeature'; - - /** - * Your subclass that extends from the base class. - */ - subclass: { - new ( - initialization: PluginFeatureInitialization - ): MarkdownDocumenterFeature; - }; -} - -/** - * The manifest for an API Documenter plugin. - * - * @remarks - * An API documenter plugin is an NPM package. By convention, the NPM package name should have the prefix - * `doc-plugin-`. Its main entry point should export an object named `apiDocumenterPluginManifest` which implements - * the `IApiDocumenterPluginManifest` interface. - * - * For example: - * ```ts - * class MyMarkdownDocumenter extends MarkdownDocumenterFeature { - * public onInitialized(): void { - * console.log('MyMarkdownDocumenter: onInitialized()'); - * } - * } - * - * export const apiDocumenterPluginManifest: IApiDocumenterPluginManifest = { - * manifestVersion: 1000, - * features: [ - * { - * featureName: 'my-markdown-documenter', - * kind: 'MarkdownDocumenterFeature', - * subclass: MyMarkdownDocumenter - * } - * ] - * }; - * ``` - * @public - */ -export interface IApiDocumenterPluginManifest { - /** - * The manifest version number. For now, this must always be `1000`. - */ - manifestVersion: 1000; - - /** - * The list of features provided by this plugin. - */ - features: IFeatureDefinition[]; -} diff --git a/repo-scripts/api-documenter/src/plugin/MarkdownDocumenterAccessor.ts b/repo-scripts/api-documenter/src/plugin/MarkdownDocumenterAccessor.ts deleted file mode 100644 index 87e9ca09c0f..00000000000 --- a/repo-scripts/api-documenter/src/plugin/MarkdownDocumenterAccessor.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -import { ApiItem } from 'api-extractor-model-me'; - -/** @internal */ -export interface IMarkdownDocumenterAccessorImplementation { - getLinkForApiItem(apiItem: ApiItem): string | undefined; -} - -/** - * Provides access to the documenter that is generating the output. - * - * @privateRemarks - * This class is wrapper that provides access to the underlying MarkdownDocumenter, while hiding the implementation - * details to ensure that the plugin API contract is stable. - * - * @public - */ -export class MarkdownDocumenterAccessor { - private _implementation: IMarkdownDocumenterAccessorImplementation; - - /** @internal */ - public constructor( - implementation: IMarkdownDocumenterAccessorImplementation - ) { - this._implementation = implementation; - } - - /** - * For a given `ApiItem`, return its markdown hyperlink. - * - * @returns The hyperlink, or `undefined` if the `ApiItem` object does not have a hyperlink. - */ - public getLinkForApiItem(apiItem: ApiItem): string | undefined { - return this._implementation.getLinkForApiItem(apiItem); - } -} diff --git a/repo-scripts/api-documenter/src/plugin/MarkdownDocumenterFeature.ts b/repo-scripts/api-documenter/src/plugin/MarkdownDocumenterFeature.ts deleted file mode 100644 index 00fa9d03785..00000000000 --- a/repo-scripts/api-documenter/src/plugin/MarkdownDocumenterFeature.ts +++ /dev/null @@ -1,124 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -import { ApiItem, ApiModel } from 'api-extractor-model-me'; -import { TypeUuid } from '@rushstack/node-core-library'; -import { PluginFeature } from './PluginFeature'; -import { MarkdownDocumenterAccessor } from './MarkdownDocumenterAccessor'; - -/** - * Context object for {@link MarkdownDocumenterFeature}. - * Exposes various services that can be used by a plugin. - * - * @public - */ -export class MarkdownDocumenterFeatureContext { - /** - * Provides access to the `ApiModel` for the documentation being generated. - */ - public readonly apiModel: ApiModel; - - /** - * The full path to the output folder. - */ - public readonly outputFolder: string; - - /** - * Exposes functionality of the documenter. - */ - public readonly documenter: MarkdownDocumenterAccessor; - - /** @internal */ - public constructor(options: MarkdownDocumenterFeatureContext) { - this.apiModel = options.apiModel; - this.outputFolder = options.outputFolder; - this.documenter = options.documenter; - } -} - -/** - * Event arguments for MarkdownDocumenterFeature.onBeforeWritePage() - * @public - */ -export interface IMarkdownDocumenterFeatureOnBeforeWritePageArgs { - /** - * The API item corresponding to this page. - */ - readonly apiItem: ApiItem; - - /** - * The page content. The {@link MarkdownDocumenterFeature.onBeforeWritePage} handler can reassign this - * string to customize the page appearance. - */ - pageContent: string; - - /** - * The filename where the output will be written. - */ - readonly outputFilename: string; -} - -/** - * Event arguments for MarkdownDocumenterFeature.onFinished() - * @public - */ -export interface IMarkdownDocumenterFeatureOnFinishedArgs {} - -const uuidMarkdownDocumenterFeature: string = - '34196154-9eb3-4de0-a8c8-7e9539dfe216'; - -/** - * Inherit from this base class to implement an API Documenter plugin feature that customizes - * the generation of markdown output. - * - * @public - */ -export class MarkdownDocumenterFeature extends PluginFeature { - /** {@inheritdoc PluginFeature.context} */ - public context!: MarkdownDocumenterFeatureContext; - - /** - * This event occurs before each markdown file is written. It provides an opportunity to customize the - * content of the file. - * @virtual - */ - public onBeforeWritePage( - eventArgs: IMarkdownDocumenterFeatureOnBeforeWritePageArgs - ): void { - // (implemented by child class) - } - - /** - * This event occurs after all output files have been written. - * @virtual - */ - public onFinished(eventArgs: IMarkdownDocumenterFeatureOnFinishedArgs): void { - // (implemented by child class) - } - - public static [Symbol.hasInstance](instance: object): boolean { - return TypeUuid.isInstanceOf(instance, uuidMarkdownDocumenterFeature); - } -} - -TypeUuid.registerClass( - MarkdownDocumenterFeature, - uuidMarkdownDocumenterFeature -); diff --git a/repo-scripts/api-documenter/src/plugin/PluginFeature.ts b/repo-scripts/api-documenter/src/plugin/PluginFeature.ts deleted file mode 100644 index 4fcef9f4eab..00000000000 --- a/repo-scripts/api-documenter/src/plugin/PluginFeature.ts +++ /dev/null @@ -1,85 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -import { TypeUuid } from '@rushstack/node-core-library'; - -/** - * This is an internal part of the plugin infrastructure. - * - * @remarks - * This object is the constructor parameter for API Documenter plugin features. - * - * @public - */ -export class PluginFeatureInitialization { - /** @internal */ - public _context!: PluginFeatureContext; - - /** @internal */ - public constructor() { - // reserved for future use - } -} - -/** - * Context object for {@link PluginFeature}. - * Exposes various services that can be used by a plugin. - * - * @public - */ -export class PluginFeatureContext {} - -const uuidPluginFeature: string = '56876472-7134-4812-819e-533de0ee10e6'; - -/** - * The abstract base class for all API Documenter plugin features. - * @public - */ -export abstract class PluginFeature { - /** - * Exposes various services that can be used by a plugin. - */ - public context: PluginFeatureContext; - - /** - * The subclass should pass the `initialization` through to the base class. - * Do not put custom initialization code in the constructor. Instead perform your initialization in the - * `onInitialized()` event function. - * @internal - */ - public constructor(initialization: PluginFeatureInitialization) { - // reserved for future expansion - this.context = initialization._context; - } - - /** - * This event function is called after the feature is initialized, but before any processing occurs. - * @virtual - */ - public onInitialized(): void { - // (implemented by child class) - } - - public static [Symbol.hasInstance](instance: object): boolean { - return TypeUuid.isInstanceOf(instance, uuidPluginFeature); - } -} - -TypeUuid.registerClass(PluginFeature, uuidPluginFeature); diff --git a/repo-scripts/api-documenter/src/plugin/PluginLoader.ts b/repo-scripts/api-documenter/src/plugin/PluginLoader.ts deleted file mode 100644 index 3bf868c86e2..00000000000 --- a/repo-scripts/api-documenter/src/plugin/PluginLoader.ts +++ /dev/null @@ -1,163 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -import * as path from 'path'; -import * as resolve from 'resolve'; - -import { - IApiDocumenterPluginManifest, - IFeatureDefinition -} from './IApiDocumenterPluginManifest'; -import { - MarkdownDocumenterFeature, - MarkdownDocumenterFeatureContext -} from './MarkdownDocumenterFeature'; -import { PluginFeatureInitialization } from './PluginFeature'; -import { DocumenterConfig } from '../documenters/DocumenterConfig'; - -interface ILoadedPlugin { - packageName: string; - manifest: IApiDocumenterPluginManifest; -} - -export class PluginLoader { - public markdownDocumenterFeature: MarkdownDocumenterFeature | undefined; - - public load( - documenterConfig: DocumenterConfig, - createContext: () => MarkdownDocumenterFeatureContext - ): void { - const configFileFolder: string = path.dirname( - documenterConfig.configFilePath - ); - for (const configPlugin of documenterConfig.configFile.plugins || []) { - try { - // Look for the package name in the same place as the config file - const resolvedEntryPointPath: string = resolve.sync( - configPlugin.packageName, - { - basedir: configFileFolder - } - ); - - // Load the package - const entryPoint: - | { apiDocumenterPluginManifest?: IApiDocumenterPluginManifest } - | undefined = require(resolvedEntryPointPath); - - if (!entryPoint) { - throw new Error('Invalid entry point'); - } - - if (!entryPoint.apiDocumenterPluginManifest) { - throw new Error( - `The package is not an API documenter plugin;` + - ` the "apiDocumenterPluginManifest" export was not found` - ); - } - - const manifest: IApiDocumenterPluginManifest = - entryPoint.apiDocumenterPluginManifest; - - if (manifest.manifestVersion !== 1000) { - throw new Error( - `The plugin is not compatible with this version of API Documenter;` + - ` unsupported manifestVersion` - ); - } - - const loadedPlugin: ILoadedPlugin = { - packageName: configPlugin.packageName, - manifest - }; - - const featureDefinitionsByName: Map = - new Map(); - for (const featureDefinition of manifest.features) { - featureDefinitionsByName.set( - featureDefinition.featureName, - featureDefinition - ); - } - - for (const featureName of configPlugin.enabledFeatureNames) { - const featureDefinition: IFeatureDefinition | undefined = - featureDefinitionsByName.get(featureName); - if (!featureDefinition) { - throw new Error( - `The plugin ${loadedPlugin.packageName} does not have a feature with name "${featureName}"` - ); - } - - if (featureDefinition.kind === 'MarkdownDocumenterFeature') { - if (this.markdownDocumenterFeature) { - throw new Error('A MarkdownDocumenterFeature is already loaded'); - } - - const initialization: PluginFeatureInitialization = - new PluginFeatureInitialization(); - initialization._context = createContext(); - - let markdownDocumenterFeature: - | MarkdownDocumenterFeature - | undefined = undefined; - try { - markdownDocumenterFeature = new featureDefinition.subclass( - initialization - ); - } catch (e) { - throw new Error( - `Failed to construct feature subclass:\n` + - (e as Error)?.toString() - ); - } - if ( - !(markdownDocumenterFeature instanceof MarkdownDocumenterFeature) - ) { - throw new Error( - 'The constructed subclass was not an instance of MarkdownDocumenterFeature' - ); - } - - try { - markdownDocumenterFeature.onInitialized(); - } catch (e) { - throw new Error( - 'Error occurred during the onInitialized() event: ' + - (e as Error)?.toString() - ); - } - - this.markdownDocumenterFeature = markdownDocumenterFeature; - } else { - throw new Error( - `Unknown feature definition kind: "${featureDefinition.kind}"` - ); - } - } - } catch (e) { - throw new Error( - `Error loading plugin ${configPlugin.packageName}: ` + - (e as Error)?.message - ); - } - } - } -} diff --git a/repo-scripts/api-documenter/src/schemas/api-documenter-template.json b/repo-scripts/api-documenter/src/schemas/api-documenter-template.json deleted file mode 100644 index 94da49b03e9..00000000000 --- a/repo-scripts/api-documenter/src/schemas/api-documenter-template.json +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Config file for API Documenter. For more info, please visit: https://api-extractor.com - */ -{ - "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-documenter.schema.json", - - /** - * Specifies the output target. - * Supported values are "docfx" or "markdown" - */ - // "outputTarget": "markdown", - - /** - * Specifies what type of newlines API Documenter should use when writing output files. By default, the output files - * will be written with Windows-style newlines. To use POSIX-style newlines, specify "lf" instead. - * To use the OS's default newline kind, specify "os". - * - * DEFAULT VALUE: "crlf" - */ - "newlineKind": "lf", - - /** - * This enables an experimental feature that will be officially released with the next major version - * of API Documenter. It requires DocFX 2.46 or newer. It enables documentation for namespaces and - * adds them to the table of contents. This will also affect file layout as namespaced items will be nested - * under a directory for the namespace instead of just within the package. - * - * This setting currently only affects the 'docfx' output target. It is equivalent to the `--new-docfx-namespaces` - * command-line parameter. - */ - // "newDocfxNamespaces": false, - - /** - * Describes plugin packages to be loaded, and which features to enable. - */ - "plugins": [ - // { - // "packageName": "doc-plugin-example", - // "enabledFeatureNames": [ "example-feature" ] - // } - ], - - /** - * Configures how the table of contents is generated. - */ - "tableOfContents": { - /** - * Allows hand-coded items to be injected into the table of contents. - * - * DEFAULT VALUE: (none) - */ - // "items": [ - // { "name": "Example Node", "href": "~/homepage/homepage.md" }, - // { - // "name": "API Reference", - // "items": [ - // { "name": "References" } - // ] - // } - // ], - /** - * Optional category name that is recommended to include in the `tocConfig`, - * along with one of the filters: `filterByApiItemName` or `filterByInlineTag`. - * Any items that are not matched to the mentioned filters will be placed under this - * catchAll category. If none provided the items will not be included in the final toc.yml file. - * - * DEFAULT VALUE: (none) - */ - // "catchAllCategory": "References", - /** - * When loading more than one api.json files that might include the same API items, - * toggle either to show duplicates or not. - * - * DEFAULT VALUE: false - */ - // "noDuplicateEntries": true, - /** - * Toggle either sorting of the API items should be made based on category name presence - * in the API item's name. - * - * DEFAULT VALUE: false - */ - // "filterByApiItemName": false, - /** - * Filter that can be used to sort the API items according to an inline custom tag - * that is present on them. - * - * DEFAULT VALUE: (none) - */ - // "filterByInlineTag": "@docCategory" - } -} diff --git a/repo-scripts/api-documenter/src/schemas/api-documenter.schema.json b/repo-scripts/api-documenter/src/schemas/api-documenter.schema.json deleted file mode 100644 index 7282baf3b00..00000000000 --- a/repo-scripts/api-documenter/src/schemas/api-documenter.schema.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "title": "API Documenter Configuration", - "description": "Describes how the API Documenter tool will process a project.", - "type": "object", - "properties": { - "$schema": { - "description": "Part of the JSON Schema standard, this optional keyword declares the URL of the schema that the file conforms to. Editors may download the schema and use it to perform syntax highlighting.", - "type": "string" - }, - - "outputTarget": { - "description": "Specifies what type of documentation will be generated", - "type": "string", - "enum": ["docfx", "markdown"] - }, - - "newlineKind": { - "description": "Specifies what type of newlines API Documenter should use when writing output files. By default, the output files will be written with Windows-style newlines. To use POSIX-style newlines, specify \"lf\" instead. To use the OS's default newline kind, specify \"os\".", - "type": "string", - "enum": ["crlf", "lf", "os"], - "default": "crlf" - }, - - "newDocfxNamespaces": { - "description": "This enables an experimental feature that will be officially released with the next major version of API Documenter. It requires DocFX 2.46 or newer. It enables documentation for namespaces and adds them to the table of contents. This will also affect file layout as namespaced items will be nested under a directory for the namespace instead of just within the package.", - "type": "boolean" - }, - - "plugins": { - "description": "Specifies plugin packages to be loaded", - "type": "array" - }, - - "tableOfContents": { - "description": "Configures how the table of contents is generated.", - "type": "object", - "additionalProperties": true - } - }, - - "additionalProperties": false -} diff --git a/repo-scripts/api-documenter/src/start.ts b/repo-scripts/api-documenter/src/start.ts deleted file mode 100644 index 92d6a75d9b5..00000000000 --- a/repo-scripts/api-documenter/src/start.ts +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env node - -/** - * @license - * Copyright 2020 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. - */ - -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -import * as os from 'os'; -import colors from 'colors'; - -import { PackageJsonLookup } from '@rushstack/node-core-library'; - -import { ApiDocumenterCommandLine } from './cli/ApiDocumenterCommandLine'; - -const myPackageVersion: string = - PackageJsonLookup.loadOwnPackageJson(__dirname).version; - -console.log( - os.EOL + colors.bold(`@firebase/api-documenter ${myPackageVersion} ` + os.EOL) -); - -const parser: ApiDocumenterCommandLine = new ApiDocumenterCommandLine(); - -parser.execute().catch(console.error); // CommandLineParser.execute() should never reject the promise diff --git a/repo-scripts/api-documenter/src/toc.ts b/repo-scripts/api-documenter/src/toc.ts deleted file mode 100644 index b4887949407..00000000000 --- a/repo-scripts/api-documenter/src/toc.ts +++ /dev/null @@ -1,108 +0,0 @@ -/** - * @license - * Copyright 2021 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 yaml from 'js-yaml'; -import { ApiItem, ApiItemKind, ApiModel } from 'api-extractor-model-me'; -import { getFilenameForApiItem } from './documenters/MarkdownDocumenterHelpers'; -import { ModuleSource } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference'; -import { writeFileSync } from 'fs'; -import { resolve } from 'path'; - -export interface ITocGenerationOptions { - apiModel: ApiModel; - g3Path: string; - outputFolder: string; - addFileNameSuffix: boolean; - jsSdk: boolean; -} - -interface ITocItem { - title: string; - path: string; - section?: ITocItem[]; -} - -export function generateToc({ - apiModel, - g3Path, - outputFolder, - addFileNameSuffix, - jsSdk -}: ITocGenerationOptions) { - const toc = []; - - if (jsSdk) { - const firebaseToc: ITocItem = { - title: 'firebase', - path: `${g3Path}/index` - }; - toc.push(firebaseToc); - } - - generateTocRecursively(apiModel, g3Path, addFileNameSuffix, toc); - - writeFileSync( - resolve(outputFolder, 'toc.yaml'), - yaml.dump( - { toc }, - { - quotingType: '"' - } - ) - ); -} - -function generateTocRecursively( - apiItem: ApiItem, - g3Path: string, - addFileNameSuffix: boolean, - toc: ITocItem[] -) { - // generate toc item only for entry points - if (apiItem.kind === ApiItemKind.EntryPoint) { - // Entry point - const entryPointName = ( - apiItem.canonicalReference.source! as ModuleSource - ).escapedPath.replace('@firebase/', ''); - const entryPointToc: ITocItem = { - title: entryPointName, - path: `${g3Path}/${getFilenameForApiItem(apiItem, addFileNameSuffix)}`, - section: [] - }; - - for (const member of apiItem.members) { - // only classes and interfaces have dedicated pages - if ( - member.kind === ApiItemKind.Class || - member.kind === ApiItemKind.Interface - ) { - const fileName = getFilenameForApiItem(member, addFileNameSuffix); - entryPointToc.section!.push({ - title: member.displayName, - path: `${g3Path}/${fileName}` - }); - } - } - - toc.push(entryPointToc); - } else { - // travel the api tree to find the next entry point - for (const member of apiItem.members) { - generateTocRecursively(member, g3Path, addFileNameSuffix, toc); - } - } -} diff --git a/repo-scripts/api-documenter/src/utils/IndentedWriter.ts b/repo-scripts/api-documenter/src/utils/IndentedWriter.ts deleted file mode 100644 index 6b4fc8cbdf5..00000000000 --- a/repo-scripts/api-documenter/src/utils/IndentedWriter.ts +++ /dev/null @@ -1,243 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -import { StringBuilder, IStringBuilder } from '@rushstack/node-core-library'; - -/** - * A utility for writing indented text. - * - * @remarks - * - * Note that the indentation is inserted at the last possible opportunity. - * For example, this code... - * - * ```ts - * writer.write('begin\n'); - * writer.increaseIndent(); - * writer.write('one\ntwo\n'); - * writer.decreaseIndent(); - * writer.increaseIndent(); - * writer.decreaseIndent(); - * writer.write('end'); - * ``` - * - * ...would produce this output: - * - * ``` - * begin - * one - * two - * end - * ``` - */ -export class IndentedWriter { - /** - * The text characters used to create one level of indentation. - * Two spaces by default. - */ - public defaultIndentPrefix: string = ' '; - - private readonly _builder: IStringBuilder; - - private _latestChunk: string | undefined; - private _previousChunk: string | undefined; - private _atStartOfLine: boolean; - - private readonly _indentStack: string[]; - private _indentText: string; - - public constructor(builder?: IStringBuilder) { - this._builder = builder === undefined ? new StringBuilder() : builder; - - this._latestChunk = undefined; - this._previousChunk = undefined; - this._atStartOfLine = true; - - this._indentStack = []; - this._indentText = ''; - } - - /** - * Retrieves the output that was built so far. - */ - public getText(): string { - return this._builder.toString(); - } - - public toString(): string { - return this.getText(); - } - - /** - * Increases the indentation. Normally the indentation is two spaces, - * however an arbitrary prefix can optional be specified. (For example, - * the prefix could be "// " to indent and comment simultaneously.) - * Each call to IndentedWriter.increaseIndent() must be followed by a - * corresponding call to IndentedWriter.decreaseIndent(). - */ - public increaseIndent(indentPrefix?: string): void { - this._indentStack.push( - indentPrefix !== undefined ? indentPrefix : this.defaultIndentPrefix - ); - this._updateIndentText(); - } - - /** - * Decreases the indentation, reverting the effect of the corresponding call - * to IndentedWriter.increaseIndent(). - */ - public decreaseIndent(): void { - this._indentStack.pop(); - this._updateIndentText(); - } - - /** - * A shorthand for ensuring that increaseIndent()/decreaseIndent() occur - * in pairs. - */ - public indentScope(scope: () => void, indentPrefix?: string): void { - this.increaseIndent(indentPrefix); - scope(); - this.decreaseIndent(); - } - - /** - * Adds a newline if the file pointer is not already at the start of the line (or start of the stream). - */ - public ensureNewLine(): void { - const lastCharacter: string = this.peekLastCharacter(); - if (lastCharacter !== '\n' && lastCharacter !== '') { - this._writeNewLine(); - } - } - - /** - * Adds up to two newlines to ensure that there is a blank line above the current line. - */ - public ensureSkippedLine(): void { - if (this.peekLastCharacter() !== '\n') { - this._writeNewLine(); - } - - const secondLastCharacter: string = this.peekSecondLastCharacter(); - if (secondLastCharacter !== '\n' && secondLastCharacter !== '') { - this._writeNewLine(); - } - } - - /** - * Returns the last character that was written, or an empty string if no characters have been written yet. - */ - public peekLastCharacter(): string { - if (this._latestChunk !== undefined) { - return this._latestChunk.substr(-1, 1); - } - return ''; - } - - /** - * Returns the second to last character that was written, or an empty string if less than one characters - * have been written yet. - */ - public peekSecondLastCharacter(): string { - if (this._latestChunk !== undefined) { - if (this._latestChunk.length > 1) { - return this._latestChunk.substr(-2, 1); - } - if (this._previousChunk !== undefined) { - return this._previousChunk.substr(-1, 1); - } - } - return ''; - } - - /** - * Writes some text to the internal string buffer, applying indentation according - * to the current indentation level. If the string contains multiple newlines, - * each line will be indented separately. - */ - public write(message: string): void { - if (message.length === 0) { - return; - } - - // If there are no newline characters, then append the string verbatim - if (!/[\r\n]/.test(message)) { - this._writeLinePart(message); - return; - } - - // Otherwise split the lines and write each one individually - let first: boolean = true; - for (const linePart of message.split('\n')) { - if (!first) { - this._writeNewLine(); - } else { - first = false; - } - if (linePart) { - this._writeLinePart(linePart.replace(/[\r]/g, '')); - } - } - } - - /** - * A shorthand for writing an optional message, followed by a newline. - * Indentation is applied following the semantics of IndentedWriter.write(). - */ - public writeLine(message: string = ''): void { - if (message.length > 0) { - this.write(message); - } - this._writeNewLine(); - } - - /** - * Writes a string that does not contain any newline characters. - */ - private _writeLinePart(message: string): void { - if (message.length > 0) { - if (this._atStartOfLine && this._indentText.length > 0) { - this._write(this._indentText); - } - this._write(message); - this._atStartOfLine = false; - } - } - - private _writeNewLine(): void { - if (this._atStartOfLine && this._indentText.length > 0) { - this._write(this._indentText); - } - - this._write('\n'); - this._atStartOfLine = true; - } - - private _write(s: string): void { - this._previousChunk = this._latestChunk; - this._latestChunk = s; - this._builder.append(s); - } - - private _updateIndentText(): void { - this._indentText = this._indentStack.join(''); - } -} diff --git a/repo-scripts/api-documenter/src/utils/Utilities.ts b/repo-scripts/api-documenter/src/utils/Utilities.ts deleted file mode 100644 index d5a1bd39c13..00000000000 --- a/repo-scripts/api-documenter/src/utils/Utilities.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -import { ApiParameterListMixin, ApiItem } from 'api-extractor-model-me'; - -export class Utilities { - private static readonly _badFilenameCharsRegExp: RegExp = /[^a-z0-9_\-\.]/gi; - /** - * Generates a concise signature for a function. Example: "getArea(width, height)" - */ - public static getConciseSignature(apiItem: ApiItem): string { - if (ApiParameterListMixin.isBaseClassOf(apiItem)) { - return ( - apiItem.displayName + - '(' + - apiItem.parameters.map(x => x.name).join(', ') + - ')' - ); - } - return apiItem.displayName; - } - - /** - * Converts bad filename characters to underscores. - */ - public static getSafeFilenameForName(name: string): string { - // TODO: This can introduce naming collisions. - // We will fix that as part of https://github.com/microsoft/rushstack/issues/1308 - return name.replace(Utilities._badFilenameCharsRegExp, '_').toLowerCase(); - } -} diff --git a/repo-scripts/api-documenter/src/utils/test/IndentedWriter.test.ts b/repo-scripts/api-documenter/src/utils/test/IndentedWriter.test.ts deleted file mode 100644 index e313ffd8123..00000000000 --- a/repo-scripts/api-documenter/src/utils/test/IndentedWriter.test.ts +++ /dev/null @@ -1,99 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -import { IndentedWriter } from '../IndentedWriter'; -import { expect, use } from 'chai'; -import { jestSnapshotPlugin } from 'mocha-chai-jest-snapshot'; - -use(jestSnapshotPlugin()); - -it('01 Demo from docs', () => { - const indentedWriter: IndentedWriter = new IndentedWriter(); - indentedWriter.write('begin\n'); - indentedWriter.increaseIndent(); - indentedWriter.write('one\ntwo\n'); - indentedWriter.decreaseIndent(); - indentedWriter.increaseIndent(); - indentedWriter.decreaseIndent(); - indentedWriter.write('end'); - - expect(indentedWriter.toString()).toMatchSnapshot(); -}); - -it('02 Indent something', () => { - const indentedWriter: IndentedWriter = new IndentedWriter(); - indentedWriter.write('a'); - indentedWriter.write('b'); - indentedWriter.increaseIndent(); - indentedWriter.writeLine('c'); - indentedWriter.writeLine('d'); - indentedWriter.decreaseIndent(); - indentedWriter.writeLine('e'); - - indentedWriter.increaseIndent('>>> '); - indentedWriter.writeLine(); - indentedWriter.writeLine(); - indentedWriter.writeLine('g'); - indentedWriter.decreaseIndent(); - - expect(indentedWriter.toString()).toMatchSnapshot(); -}); - -it('03 Two kinds of indents', () => { - const indentedWriter: IndentedWriter = new IndentedWriter(); - - indentedWriter.writeLine('---'); - indentedWriter.indentScope(() => { - indentedWriter.write('a\nb'); - indentedWriter.indentScope(() => { - indentedWriter.write('c\nd\n'); - }); - indentedWriter.write('e\n'); - }, '> '); - indentedWriter.writeLine('---'); - - expect(indentedWriter.toString()).toMatchSnapshot(); -}); - -it('04 Edge cases for ensureNewLine()', () => { - let indentedWriter: IndentedWriter = new IndentedWriter(); - indentedWriter.ensureNewLine(); - indentedWriter.write('line'); - expect(indentedWriter.toString()).toMatchSnapshot(); - - indentedWriter = new IndentedWriter(); - indentedWriter.write('previous'); - indentedWriter.ensureNewLine(); - indentedWriter.write('line'); - expect(indentedWriter.toString()).toMatchSnapshot(); -}); - -it('04 Edge cases for ensureSkippedLine()', () => { - let indentedWriter: IndentedWriter = new IndentedWriter(); - indentedWriter.ensureSkippedLine(); - indentedWriter.write('line'); - expect(indentedWriter.toString()).toMatchSnapshot(); - - indentedWriter = new IndentedWriter(); - indentedWriter.write('previous'); - indentedWriter.ensureSkippedLine(); - indentedWriter.write('line'); - expect(indentedWriter.toString()).toMatchSnapshot(); -}); diff --git a/repo-scripts/api-documenter/src/utils/test/__snapshots__/IndentedWriter.test.ts.snap b/repo-scripts/api-documenter/src/utils/test/__snapshots__/IndentedWriter.test.ts.snap deleted file mode 100644 index 62778d301c8..00000000000 --- a/repo-scripts/api-documenter/src/utils/test/__snapshots__/IndentedWriter.test.ts.snap +++ /dev/null @@ -1,46 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`01 Demo from docs 1`] = ` -"begin - one - two -end" -`; - -exports[`02 Indent something 1`] = ` -"abc - d -e ->>> ->>> ->>> g -" -`; - -exports[`03 Two kinds of indents 1`] = ` -"--- -> a -> bc -> d -> e ---- -" -`; - -exports[`04 Edge cases for ensureNewLine() 1`] = `"line"`; - -exports[`04 Edge cases for ensureNewLine() 2`] = ` -"previous -line" -`; - -exports[`04 Edge cases for ensureSkippedLine() 1`] = ` -" -line" -`; - -exports[`04 Edge cases for ensureSkippedLine() 2`] = ` -"previous - -line" -`; diff --git a/repo-scripts/api-documenter/tsconfig.json b/repo-scripts/api-documenter/tsconfig.json deleted file mode 100644 index e790468f48f..00000000000 --- a/repo-scripts/api-documenter/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "../../config/tsconfig.base.json", - "compilerOptions": { - "outDir": "dist", - "downlevelIteration": true, - "module": "CommonJS", - "target": "ES6", - "esModuleInterop": true, - "allowSyntheticDefaultImports": true - }, - "exclude": ["dist/**/*"] -} \ No newline at end of file diff --git a/repo-scripts/changelog-generator/.eslintrc.js b/repo-scripts/changelog-generator/.eslintrc.js deleted file mode 100644 index ca80aa0f69a..00000000000 --- a/repo-scripts/changelog-generator/.eslintrc.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -module.exports = { - extends: '../../config/.eslintrc.js', - parserOptions: { - project: 'tsconfig.json', - // to make vscode-eslint work with monorepo - // https://github.com/typescript-eslint/typescript-eslint/issues/251#issuecomment-463943250 - tsconfigRootDir: __dirname - } -}; diff --git a/repo-scripts/changelog-generator/README.md b/repo-scripts/changelog-generator/README.md deleted file mode 100644 index a5d605b7973..00000000000 --- a/repo-scripts/changelog-generator/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# @firebase/changelog-generator - -It works as a plugin for @changesets/cli to generate changelog entries(via running `changeset version`). diff --git a/repo-scripts/changelog-generator/index.ts b/repo-scripts/changelog-generator/index.ts deleted file mode 100644 index 20f3de482f1..00000000000 --- a/repo-scripts/changelog-generator/index.ts +++ /dev/null @@ -1,120 +0,0 @@ -/** - * @license - * Copyright 2020 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 { ChangelogFunctions } from '@changesets/types'; -import { getInfo } from '@changesets/get-github-info'; -import { fetch as undiciFetch, Response as undiciResponse } from 'undici'; - -const changelogFunctions: ChangelogFunctions = { - getDependencyReleaseLine: async ( - changesets, - dependenciesUpdated, - options - ) => { - if (!options.repo) { - throw new Error( - 'Please provide a repo to this changelog generator like this:\n"changelog": ["@changesets/changelog-github", { "repo": "org/repo" }]' - ); - } - if (dependenciesUpdated.length === 0) { - return ''; - } - - const changesetLink = `- Updated dependencies [${( - await Promise.all( - changesets.map(async cs => { - if (cs.commit) { - const { links } = await getInfo({ - repo: options.repo, - commit: cs.commit - }); - return links.commit; - } - }) - ) - ) - .filter(_ => _) - .join(', ')}]:`; - - const updatedDependenciesList = dependenciesUpdated.map( - dependency => ` - ${dependency.name}@${dependency.newVersion}` - ); - - return [changesetLink, ...updatedDependenciesList].join('\n'); - }, - getReleaseLine: async (changeset, type, options) => { - if (!options || !options.repo) { - throw new Error( - 'Please provide a repo to this changelog generator like this:\n"changelog": ["@changesets/changelog-github", { "repo": "org/repo" }]' - ); - } - const [firstLine, ...futureLines] = changeset.summary - .split('\n') - .map(l => l.trimRight()); - - if (changeset.commit) { - const { pull: pullNumber, links } = await getInfo({ - repo: options.repo, - commit: changeset.commit - }); - - let fixedIssueLink = null; - // If the summary didn't mention any issue, we will look at the PR body to try to generate one automatically - if (!/issues\/[\d+]/i.test(changeset.summary) && pullNumber) { - fixedIssueLink = await getFixedIssueLink(pullNumber, options.repo); - } - - return `\n\n- ${links.commit}${ - links.pull === null ? '' : ` ${links.pull}` - }${ - fixedIssueLink === null ? '' : ` ${fixedIssueLink}` - } - ${firstLine}\n${futureLines.map(l => ` ${l}`).join('\n')}`; - } else { - return `\n\n- ${firstLine}\n${futureLines.map(l => ` ${l}`).join('\n')}`; - } - } -}; - -const fixedIssueRegex = - /(close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved) [^\s]*(#|issues\/)([\d]+)/i; -async function getFixedIssueLink( - prNumber: number, - repo: string -): Promise { - const response = await undiciFetch( - `https://api.github.com/repos/${repo}/pulls/${prNumber}`, - { - method: 'GET', - headers: { - 'Authorization': `Bearer ${process.env.GITHUB_TOKEN}` - } - } - ).then(data => data.json()); - - const body = (response as undiciResponse).body; - if (!body) { - return ''; - } - const match = fixedIssueRegex.exec(body.toString()); - if (!match) { - return ''; - } - const issueNumber = match[3]; - return `(fixes [#${issueNumber}](https://github.com/firebase/firebase-js-sdk/issues/${issueNumber}))`; -} - -exports.default = changelogFunctions; diff --git a/repo-scripts/changelog-generator/package.json b/repo-scripts/changelog-generator/package.json deleted file mode 100644 index e04bb6f2ecf..00000000000 --- a/repo-scripts/changelog-generator/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "@firebase/changelog-generator", - "version": "0.1.0", - "private": true, - "type": "commonjs", - "description": "A package for generating changelog", - "author": "Firebase (https://firebase.google.com/)", - "main": "dist/index.js", - "files": [ - "dist" - ], - "scripts": { - "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "build": "tsc", - "build:dev": "tsc -w", - "test": "tsc -p . --noEmit" - }, - "dependencies": { - "@changesets/types": "3.3.0", - "@changesets/get-github-info": "0.5.2", - "@types/node": "20.8.10", - "undici": "5.28.4" - }, - "license": "Apache-2.0", - "devDependencies": { - "typescript": "4.7.4" - }, - "repository": { - "directory": "repo-scripts/changelog-generator", - "type": "git", - "url": "git+https://github.com/firebase/firebase-js-sdk.git" - }, - "bugs": { - "url": "https://github.com/firebase/firebase-js-sdk/issues" - }, - "nyc": { - "extension": [ - ".ts" - ], - "reportDir": "./coverage/node" - } -} diff --git a/repo-scripts/changelog-generator/tsconfig.json b/repo-scripts/changelog-generator/tsconfig.json deleted file mode 100644 index 5c910fcca64..00000000000 --- a/repo-scripts/changelog-generator/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "compilerOptions": { - "strict": true, - "outDir": "dist", - "lib": [ - "ESNext" - ], - "module": "CommonJS", - "moduleResolution": "node", - "esModuleInterop": true, - "resolveJsonModule": true, - "target": "es5" - } -} \ No newline at end of file diff --git a/repo-scripts/prune-dts/.run/AllTests.run.xml b/repo-scripts/prune-dts/.run/AllTests.run.xml deleted file mode 100644 index 3eec9c1bcc3..00000000000 --- a/repo-scripts/prune-dts/.run/AllTests.run.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - project - - $PROJECT_DIR$/../../node_modules/mocha - $PROJECT_DIR$ - true - - - - - - bdd - --require ts-node/register/type-check - PATTERN - *.test.ts - - - diff --git a/repo-scripts/prune-dts/extract-public-api.ts b/repo-scripts/prune-dts/extract-public-api.ts deleted file mode 100644 index c5c202f0734..00000000000 --- a/repo-scripts/prune-dts/extract-public-api.ts +++ /dev/null @@ -1,237 +0,0 @@ -/** - * @license - * Copyright 2020 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 * as fs from 'fs'; -import * as path from 'path'; - -import { Extractor, ExtractorConfig } from 'api-extractor-me'; -import * as tmp from 'tmp'; - -import { addBlankLines, pruneDts, removeUnusedImports } from './prune-dts'; -import * as yargs from 'yargs'; - -/* eslint-disable no-console */ - -// This script takes the output of the API Extractor, post-processes it using -// the pruned-dts script and then invokes API report to generate a report -// that only includes exported symbols. This is all done in temporary folders, -// all configuration is auto-generated for each run. - -const baseApiExtractorConfigFile: string = path.resolve( - __dirname, - '../../config/api-extractor.json' -); -const reportFolder = path.resolve(__dirname, '../../common/api-review'); -const tmpDir = tmp.dirSync().name; - -function writeTypeScriptConfig(packageRoot: string): void { - const tsConfigJson = { - extends: path.resolve(packageRoot, './tsconfig.json'), - include: [path.resolve(packageRoot, './src')], - compilerOptions: { - downlevelIteration: true // Needed for FirebaseApp - } - }; - fs.writeFileSync( - path.resolve(tmpDir, 'tsconfig.json'), - JSON.stringify(tsConfigJson), - { encoding: 'utf-8' } - ); -} - -function writePackageJson(packageName: string): void { - const packageJson = { - 'name': `@firebase/${packageName}` - }; - const packageJsonPath = path.resolve(tmpDir, 'package.json'); - fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson), { - encoding: 'utf-8' - }); -} - -function loadApiExtractorConfig( - packageName: string, - typescriptDtsPath: string, - rollupDtsPath: string, - untrimmedRollupDtsPath: string, - dtsRollupEnabled: boolean, - apiReportEnabled: boolean -): ExtractorConfig { - const apiExtractorJsonPath = path.resolve(tmpDir, 'api-extractor.json'); - const apiExtractorJson = { - extends: baseApiExtractorConfigFile, - mainEntryPointFilePath: typescriptDtsPath, - 'dtsRollup': { - 'enabled': dtsRollupEnabled, - /** - * Include beta APIs in documentation and typings. api-extractor/documenter - * seems to work this way by default but this intermediate step - * seems to restrict it to public tagged APIs. - */ - betaTrimmedFilePath: rollupDtsPath, - untrimmedFilePath: untrimmedRollupDtsPath - }, - 'tsdocMetadata': { - 'enabled': false - }, - 'apiReport': { - 'enabled': apiReportEnabled, - reportFileName: `${packageName}.api.md`, - reportFolder - }, - 'messages': { - 'extractorMessageReporting': { - 'ae-missing-release-tag': { - 'logLevel': 'none' - }, - 'ae-unresolved-link': { - 'logLevel': 'none' - }, - 'ae-forgotten-export': { - 'logLevel': apiReportEnabled ? 'error' : 'none' - } - }, - 'tsdocMessageReporting': { - 'tsdoc-undefined-tag': { - 'logLevel': 'none' - } - } - } - }; - fs.writeFileSync(apiExtractorJsonPath, JSON.stringify(apiExtractorJson), { - encoding: 'utf-8' - }); - console.log(apiExtractorJsonPath); - return ExtractorConfig.loadFileAndPrepare(apiExtractorJsonPath); -} - -/** - * Generates the Public API from generated DTS files. - * - * @param packageName - The name of the Firebase package (e.g. "database" or - * "firestore-lite") - * @param packageRoot - The root path of the package - * @param typescriptDtsPath - The .d.ts file generated by the TypeScript - * compiler as we transpile our sources - * @param rollupDtsPath - A "bundled" version of our d.ts files that includes - * all public and private types - * @param untrimmedRollupDtsPath - A "bundled" version of our d.ts files that - * includes all public and private types, but also include exports marked as - * `@internal`. This file is used by compat APIs to use internal exports - * @param publicDtsPath - The output file for the customer-facing .d.ts file - * that only includes the public APIs - */ -export async function generateApi( - packageName: string, - packageRoot: string, - typescriptDtsPath: string, - rollupDtsPath: string, - untrimmedRollupDtsPath: string, - publicDtsPath: string -): Promise { - console.log(`Configuring API Extractor for ${packageName}`); - writeTypeScriptConfig(packageRoot); - writePackageJson(packageName); - - let extractorConfig = loadApiExtractorConfig( - packageName, - typescriptDtsPath, - rollupDtsPath, - untrimmedRollupDtsPath, - /* dtsRollupEnabled= */ true, - /* apiReportEnabled= */ false - ); - Extractor.invoke(extractorConfig, { - localBuild: true, - showDiagnostics: true, - showVerboseMessages: true - }); - - console.log('Generated rollup DTS'); - pruneDts(rollupDtsPath, publicDtsPath); - console.log('Pruned DTS file'); - await addBlankLines(publicDtsPath); - console.log('Added blank lines after imports'); - await removeUnusedImports(publicDtsPath); - console.log('Removed unused imports'); - - extractorConfig = loadApiExtractorConfig( - packageName, - publicDtsPath, - rollupDtsPath, - untrimmedRollupDtsPath, - /* dtsRollupEnabled= */ false, - /* apiReportEnabled= */ true - ); - Extractor.invoke(extractorConfig, { localBuild: true }); - console.log(`API report for ${packageName} written to ${reportFolder}`); -} - -const argv = yargs - .options({ - package: { - type: 'string', - desc: - 'The name of the Firebase package (e.g. "database" or ' + - '"firestore-lite")', - require: true - }, - packageRoot: { - type: 'string', - desc: 'The root path of the package', - require: true - }, - typescriptDts: { - type: 'string', - desc: - 'The .d.ts file generated by the TypeScript compiler as we transpile ' + - 'our sources', - require: true - }, - rollupDts: { - type: 'string', - desc: - 'A "bundled" version of our d.ts files that include all public and ' + - 'private types', - require: true - }, - untrimmedRollupDts: { - type: 'string', - desc: - ' A "bundled" version of our d.ts files that includes all public ' + - 'and private types, but also include exports marked as `@internal`. ' + - 'This file is used by compat APIs to use internal exports', - require: true - }, - publicDts: { - type: 'string', - desc: - 'The output file for the customer-facing .d.ts file that only ' + - 'includes the public APIs', - require: true - } - }) - .parseSync(); - -void generateApi( - argv.package, - path.resolve(argv.packageRoot), - path.resolve(argv.typescriptDts), - path.resolve(argv.rollupDts), - path.resolve(argv.untrimmedRollupDts), - path.resolve(argv.publicDts) -); diff --git a/repo-scripts/prune-dts/package.json b/repo-scripts/prune-dts/package.json deleted file mode 100644 index d8d4ab6391b..00000000000 --- a/repo-scripts/prune-dts/package.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "firebase-repo-scripts-prune-dts", - "version": "0.1.0", - "private": true, - "engines": { - "node": "^8.13.0 || >=10.10.0" - }, - "description": "A script to prune non-exported types from a d.ts.", - "author": "Firebase (https://firebase.google.com/)", - "scripts": { - "prettier": "prettier --write '**/*.ts'", - "test": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' mocha --require ts-node/register --timeout 5000 *.test.ts" - }, - "license": "Apache-2.0", - "dependencies": { - "eslint": "8.56.0", - "eslint-plugin-unused-imports": "3.2.0", - "prettier": "2.8.7" - }, - "repository": { - "directory": "repo-scripts/prune-dts", - "type": "git", - "url": "git+https://github.com/firebase/firebase-js-sdk.git" - }, - "bugs": { - "url": "https://github.com/firebase/firebase-js-sdk/issues" - }, - "nyc": { - "extension": [ - ".ts" - ], - "reportDir": "./coverage/node" - }, - "devDependencies": { - "@types/eslint": "7.29.0", - "@types/prettier": "2.7.2", - "mocha": "9.2.2" - } -} diff --git a/repo-scripts/prune-dts/prune-dts.test.ts b/repo-scripts/prune-dts/prune-dts.test.ts deleted file mode 100644 index ba770e43cd9..00000000000 --- a/repo-scripts/prune-dts/prune-dts.test.ts +++ /dev/null @@ -1,99 +0,0 @@ -/** - * @license - * Copyright 2020 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 * as os from 'os'; -import * as fs from 'fs'; -import * as path from 'path'; -import { format, resolveConfig } from 'prettier'; -import { expect } from 'chai'; -import { describe, it } from 'mocha'; -import { pruneDts } from './prune-dts'; - -const testCasesDir = path.resolve(__dirname, 'tests'); -const tmpDir = os.tmpdir(); - -const testDataFilter = /(.*).input.d.ts/; -const testCaseFilterRe = /.*/; - -async function runScript(inputFile: string): Promise { - const outputFile = path.resolve(tmpDir, 'output.d.ts'); - pruneDts(inputFile, outputFile); - return outputFile; -} - -interface TestCase { - name: string; - inputFileName: string; - outputFileName: string; -} - -function getTestCases(): TestCase[] { - if ( - !fs.existsSync(testCasesDir) || - !fs.lstatSync(testCasesDir).isDirectory() - ) { - throw new Error(`${testCasesDir} folder does not exist`); - } - - return fs - .readdirSync(testCasesDir) - .filter((fileName: string) => testDataFilter.test(fileName)) - .filter((fileName: string) => testCaseFilterRe.test(fileName)) - .map((fileName: string) => { - const testCaseName = fileName.match(testDataFilter)![1]; - - const inputFileName = `${testCaseName}.input.d.ts`; - const outputFileName = `${testCaseName}.output.d.ts`; - - const name = testCaseName.replace(/-/g, ' '); - return { name, inputFileName, outputFileName }; - }); -} - -describe('Prune DTS', () => { - for (const testCase of getTestCases()) { - it(testCase.name, async () => { - const absoluteInputFile = path.resolve( - testCasesDir, - testCase.inputFileName - ); - const absoluteOutputFile = path.resolve( - testCasesDir, - testCase.outputFileName - ); - - const tmpFile = await runScript(absoluteInputFile); - const prettierConfig = await resolveConfig(absoluteInputFile); - - const expectedDtsUnformatted = fs.readFileSync( - absoluteOutputFile, - 'utf-8' - ); - const expectedDts = format(expectedDtsUnformatted, { - filepath: absoluteOutputFile, - ...prettierConfig - }); - const actualDtsUnformatted = fs.readFileSync(tmpFile, 'utf-8'); - const actualDts = format(actualDtsUnformatted, { - filepath: tmpFile, - ...prettierConfig - }); - - expect(actualDts).to.equal(expectedDts); - }); - } -}); diff --git a/repo-scripts/prune-dts/prune-dts.ts b/repo-scripts/prune-dts/prune-dts.ts deleted file mode 100644 index dfd83a62a91..00000000000 --- a/repo-scripts/prune-dts/prune-dts.ts +++ /dev/null @@ -1,561 +0,0 @@ -/** - * @license - * Copyright 2020 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 * as yargs from 'yargs'; -import * as ts from 'typescript'; -import * as fs from 'fs'; -import { ESLint } from 'eslint'; - -/** - * Prunes a DTS file based on three main rules: - * - Top level types are only included if they are also exported. - * - Underscore-prefixed members of class and interface types are stripped. - * - Constructors are made private or protected if marked with - * `@hideconstructor`/`@hideconstructor protected`. - * - * This function is meant to operate on DTS files generated by API extractor - * and extracts out the API that is relevant for third-party SDK consumers. - * - * @param inputLocation The file path to the .d.ts produced by API explorer. - * @param outputLocation The output location for the pruned .d.ts file. - */ -export function pruneDts(inputLocation: string, outputLocation: string): void { - const compilerOptions = {}; - const host = ts.createCompilerHost(compilerOptions); - const program = ts.createProgram([inputLocation], compilerOptions, host); - const printer: ts.Printer = ts.createPrinter(); - const sourceFile = program.getSourceFile(inputLocation)!; - - const result: ts.TransformationResult = - ts.transform(sourceFile, [ - dropPrivateApiTransformer.bind(null, program, host) - ]); - const transformedSourceFile: ts.SourceFile = result.transformed[0]; - let content = printer.printFile(transformedSourceFile); - - fs.writeFileSync(outputLocation, content); -} - -export async function addBlankLines(outputLocation: string): Promise { - const eslint = new ESLint({ - fix: true, - overrideConfig: { - parserOptions: { - ecmaVersion: 2017, - sourceType: 'module', - tsconfigRootDir: __dirname, - project: ['./tsconfig.eslint.json'] - }, - env: { - es6: true - }, - plugins: ['@typescript-eslint'], - parser: '@typescript-eslint/parser', - rules: { - 'unused-imports/no-unused-imports-ts': ['off'], - // add blank lines after imports. Otherwise removeUnusedImports() will remove the comment - // of the first item after the import block - 'padding-line-between-statements': [ - 'error', - { 'blankLine': 'always', 'prev': 'import', 'next': '*' } - ] - } - } - }); - const results = await eslint.lintFiles(outputLocation); - await ESLint.outputFixes(results); -} - -export async function removeUnusedImports( - outputLocation: string -): Promise { - const eslint = new ESLint({ - fix: true, - overrideConfig: { - parserOptions: { - ecmaVersion: 2017, - sourceType: 'module', - tsconfigRootDir: __dirname, - project: ['./tsconfig.eslint.json'] - }, - env: { - es6: true - }, - plugins: ['unused-imports', '@typescript-eslint'], - parser: '@typescript-eslint/parser', - rules: { - 'unused-imports/no-unused-imports-ts': ['error'] - } - } - }); - const results = await eslint.lintFiles(outputLocation); - await ESLint.outputFixes(results); -} - -/** Determines whether the provided identifier should be hidden. */ -function hasPrivatePrefix(name: ts.Identifier): boolean { - // Identifiers that are prefixed with an underscore are not included in - // the public API. - return !!name.escapedText?.toString().startsWith('_'); -} - -/** Returns whether type identified by `name` is exported. */ -function isExported( - typeChecker: ts.TypeChecker, - sourceFile: ts.SourceFile, - name: ts.Identifier -): boolean { - const declarations = - typeChecker.getSymbolAtLocation(name)?.declarations ?? []; - - // Check is this is a public symbol (e.g. part of the DOM library) - const isTypeScriptType = declarations.find( - d => d.getSourceFile().fileName.indexOf('typescript/lib') != -1 - ); - const isImported = declarations.find(d => ts.isImportSpecifier(d)); - if (isTypeScriptType || isImported) { - return true; - } - - // Check is this is part of the exported symbols of the SDK module - const allExportedSymbols = typeChecker.getExportsOfModule( - typeChecker.getSymbolAtLocation(sourceFile)! - ); - return !!allExportedSymbols.find(s => s.name === name.escapedText); -} - -/** - * Replaces an existing constructor implementation if the constructor is marked - * with the JSDod tag `@hideconstructor`. The replaced constructor can either - * have `private` visibility` or `protected`. To generate a protected - * constructor, specify `@hideconstructor protected`. - * - * Returns either the modified constructor or the existing constructor if no - * modification was needed. - */ -function maybeHideConstructor( - node: ts.ConstructorDeclaration -): ts.ConstructorDeclaration { - const hideConstructorTag = ts - .getJSDocTags(node) - ?.find(t => t.tagName.escapedText === 'hideconstructor'); - - if (hideConstructorTag) { - const modifier = ts.createModifier( - hideConstructorTag.comment === 'protected' - ? ts.SyntaxKind.ProtectedKeyword - : ts.SyntaxKind.PrivateKeyword - ); - return ts.createConstructor( - node.decorators, - [modifier], - /*parameters=*/ [], - /* body= */ undefined - ); - } else { - return node; - } -} - -/** - * Examines `extends` and `implements` clauses and removes or replaces them if - * they refer to a non-exported type. When an export is removed, all members - * from the removed class are merged into the provided class or interface - * declaration. - * - * @example - * Input: - * class Foo { - * foo: string; - * } - * export class Bar extends Foo {} - * - * Output: - * export class Bar { - * foo: string; - * } - */ -function prunePrivateImports< - T extends ts.InterfaceDeclaration | ts.ClassDeclaration ->( - factory: ts.NodeFactory, - program: ts.Program, - host: ts.CompilerHost, - sourceFile: ts.SourceFile, - node: T -): T { - const typeChecker = program.getTypeChecker(); - - // The list of heritage clauses after all private symbols are removed. - const prunedHeritageClauses: ts.HeritageClause[] = []; - // Additional members that are copied from the private symbols into the public - // symbols - const additionalMembers: ts.Node[] = []; - - for (const heritageClause of node.heritageClauses || []) { - const exportedTypes: ts.ExpressionWithTypeArguments[] = []; - for (const type of heritageClause.types) { - if ( - ts.isIdentifier(type.expression) && - isExported(typeChecker, sourceFile, type.expression) - ) { - exportedTypes.push(type); - } else { - // Hide the type we are inheriting from and merge its declarations - // into the current class. - // TODO: We really only need to do this when the type that is extended - // is a class. We should skip this for interfaces. - const privateType = typeChecker.getTypeAtLocation(type); - additionalMembers.push( - ...convertPropertiesForEnclosingClass( - program, - host, - sourceFile, - privateType.getProperties(), - node - ) - ); - } - } - - if (exportedTypes.length > 0) { - prunedHeritageClauses.push( - factory.updateHeritageClause(heritageClause, exportedTypes) - ); - } - } - - if (ts.isClassDeclaration(node)) { - return factory.updateClassDeclaration( - node, - node.decorators, - node.modifiers, - node.name, - node.typeParameters, - prunedHeritageClauses, - [ - ...(node.members as ts.NodeArray), - ...(additionalMembers as ts.ClassElement[]) - ] - ) as T; - } else if (ts.isInterfaceDeclaration(node)) { - return factory.updateInterfaceDeclaration( - node, - node.decorators, - node.modifiers, - node.name, - node.typeParameters, - prunedHeritageClauses, - [ - ...(node.members as ts.NodeArray), - ...(additionalMembers as ts.TypeElement[]) - ] - ) as T; - } else { - throw new Error('Only classes or interfaces are supported'); - } -} - -/** - * Iterates the provided symbols and returns named declarations for these - * symbols if they are missing from `currentClass`. This allows us to merge - * class hierarchies for classes whose inherited types are not part of the - * public API. - * - * This method relies on a private API in TypeScript's `codefix` package. - */ -function convertPropertiesForEnclosingClass( - program: ts.Program, - host: ts.CompilerHost, - sourceFile: ts.SourceFile, - parentClassSymbols: ts.Symbol[], - currentClass: ts.ClassDeclaration | ts.InterfaceDeclaration -): ts.Node[] { - const newMembers: ts.Node[] = []; - // The `codefix` package is not public but it does exactly what we want. It's - // the same package that is used by VSCode to fill in missing members, which - // is what we are using it for in this script. `codefix` handles missing - // properties, methods and correctly deduces generics. - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (ts as any).codefix.createMissingMemberNodes( - currentClass, - parentClassSymbols, - sourceFile, - { program, host }, - /* userPreferences= */ {}, - /* importAdder= */ undefined, - (missingMember: ts.ClassElement) => { - const originalSymbol = parentClassSymbols.find( - symbol => - symbol.escapedName == - (missingMember.name as ts.Identifier).escapedText - ); - const jsDocComment = originalSymbol - ? extractJSDocComment(originalSymbol, newMembers) - : undefined; - if (jsDocComment) { - newMembers.push(jsDocComment, missingMember); - } else { - newMembers.push(missingMember); - } - } - ); - return newMembers; -} - -/** Extracts the JSDoc comment from `symbol`. */ -function extractJSDocComment( - symbol: ts.Symbol, - alreadyAddedMembers: ts.Node[] -): ts.Node | null { - const overloadCount = alreadyAddedMembers.filter( - node => - ts.isClassElement(node) && - (node.name as ts.Identifier).escapedText == symbol.name - ).length; - - // Extract the comment from the overload that we are currently processing. - let targetIndex = 0; - const comments = symbol.getDocumentationComment(undefined).filter(symbol => { - // Overload comments are separated by line breaks. - if (symbol.kind == 'lineBreak') { - ++targetIndex; - return false; - } else { - return overloadCount == targetIndex; - } - }); - - if (comments.length > 0 && symbol.declarations) { - const jsDocTags = ts.getJSDocTags(symbol.declarations[overloadCount]); - const maybeNewline = jsDocTags?.length > 0 ? '\n' : ''; - const joinedComments = comments - .map(comment => { - if (comment.kind === 'linkText') { - return comment.text.trim(); - } - return comment.text; - }) - .join(''); - const formattedComments = joinedComments - .replace('*', '\n') - .replace(' \n', '\n') - .replace('\n ', '\n'); - return ts.factory.createJSDocComment( - formattedComments + maybeNewline, - jsDocTags - ); - } - return null; -} - -/** - * Replaces input types of public APIs that consume non-exported types, which - * allows us to exclude private types from the pruned definitions. Returns the - * the name of the exported API or undefined if no type is found. - * - * @example - * Input: - * class PrivateFoo {} - * export class PublicFoo extends PrivateFoo {} - * export function doFoo(foo: PrivateFoo); - * - * Output: - * export class PublicFoo {} - * export function doFoo(foo: PublicFoo); - */ -function extractExportedSymbol( - typeChecker: ts.TypeChecker, - sourceFile: ts.SourceFile, - typeName: ts.Node -): ts.Symbol | undefined { - if (!ts.isIdentifier(typeName)) { - return undefined; - } - - if (isExported(typeChecker, sourceFile, typeName)) { - // Don't replace the type reference if the type is already part of the - // public API. - return undefined; - } - - const localSymbolName = typeName.escapedText; - const allExportedSymbols = typeChecker.getExportsOfModule( - typeChecker.getSymbolAtLocation(sourceFile)! - ); - - // Examine all exported types and check if they extend or implement the - // provided local type. If so, we can use the exported type in lieu of the - // private type. - - // Short circuit if the local types is already part of the public types. - for (const symbol of allExportedSymbols) { - if (symbol.name === localSymbolName) { - return symbol; - } - } - - // See if there is an exported symbol that extends this private symbol. - // In this case, we can safely use the public symbol instead. - for (const symbol of allExportedSymbols) { - if (symbol.declarations) { - for (const declaration of symbol.declarations) { - if ( - ts.isClassDeclaration(declaration) || - ts.isInterfaceDeclaration(declaration) - ) { - for (const heritageClause of declaration.heritageClauses || []) { - for (const type of heritageClause.types || []) { - if (ts.isIdentifier(type.expression)) { - const subclassName = type.expression.escapedText; - if (subclassName === localSymbolName) { - // TODO: We may need to change this to return a Union type if - // more than one public type corresponds to the private type. - return symbol; - } - } - } - } - } - } - } - } - - // If no symbol was found that extends the private symbol, check the reverse. - // We might find an exported symbol in the inheritance chain of the local - // symbol. Note that this is not always safe as we might replace the local - // symbol with a less restrictive type. - const localSymbol = typeChecker.getSymbolAtLocation(typeName); - if (localSymbol?.declarations) { - for (const declaration of localSymbol.declarations) { - if ( - ts.isClassDeclaration(declaration) || - ts.isInterfaceDeclaration(declaration) - ) { - for (const heritageClause of declaration.heritageClauses || []) { - for (const type of heritageClause.types || []) { - if (ts.isIdentifier(type.expression)) { - if (isExported(typeChecker, sourceFile, type.expression)) { - return typeChecker.getSymbolAtLocation(type.expression); - } - } - } - } - } - } - } - - return undefined; -} - -function dropPrivateApiTransformer( - program: ts.Program, - host: ts.CompilerHost, - context: ts.TransformationContext -): ts.Transformer { - const typeChecker = program.getTypeChecker(); - const { factory } = context; - - return (sourceFile: ts.SourceFile) => { - function visit(node: ts.Node): ts.Node { - if ( - ts.isInterfaceDeclaration(node) || - ts.isClassDeclaration(node) || - ts.isFunctionDeclaration(node) || - ts.isVariableStatement(node) || - ts.isTypeAliasDeclaration(node) || - ts.isModuleDeclaration(node) || - ts.isEnumDeclaration(node) - ) { - // Remove any types that are not exported. - if ( - !node.modifiers?.find(m => m.kind === ts.SyntaxKind.ExportKeyword) - ) { - return factory.createNotEmittedStatement(node); - } - } - - if (ts.isConstructorDeclaration(node)) { - // Replace internal constructors with private constructors. - return maybeHideConstructor(node); - } else if ( - ts.isClassDeclaration(node) || - ts.isInterfaceDeclaration(node) - ) { - // Remove any imports that reference internal APIs, while retaining - // their public members. - return prunePrivateImports(factory, program, host, sourceFile, node); - } else if ( - ts.isPropertyDeclaration(node) || - ts.isMethodDeclaration(node) || - ts.isGetAccessor(node) - ) { - // Remove any class and interface members that are prefixed with - // underscores. - if (hasPrivatePrefix(node.name as ts.Identifier)) { - return factory.createNotEmittedStatement(node); - } - } else if (ts.isTypeReferenceNode(node)) { - // For public types that refer internal types, find a public type that - // we can refer to instead. - const publicName = extractExportedSymbol( - typeChecker, - sourceFile, - node.typeName - ); - return publicName - ? factory.updateTypeReferenceNode( - node, - factory.createIdentifier(publicName.name), - node.typeArguments - ) - : node; - } - - return node; - } - - function visitNodeAndChildren(node: T): T { - return ts.visitEachChild( - visit(node), - childNode => visitNodeAndChildren(childNode), - context - ) as T; - } - return visitNodeAndChildren(sourceFile); - }; -} - -const argv = yargs - .options({ - input: { - type: 'string', - desc: 'The location of the index.ts file' - }, - output: { - type: 'string', - desc: 'The location for the index.d.ts file' - } - }) - .parseSync(); - -if (argv.input && argv.output) { - console.log('Removing private exports...'); - pruneDts(argv.input, argv.output); - console.log('Removing unused imports...'); - removeUnusedImports(argv.output).then(() => console.log('Done.')); -} diff --git a/repo-scripts/prune-dts/tests/dom.input.d.ts b/repo-scripts/prune-dts/tests/dom.input.d.ts deleted file mode 100644 index 80cea14308e..00000000000 --- a/repo-scripts/prune-dts/tests/dom.input.d.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * @license - * Copyright 2021 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. - */ -export class Foo implements PromiseLike { - then( - onfulfilled?: - | ((value: any) => PromiseLike | TResult1) - | undefined - | null, - onrejected?: - | ((reason: any) => PromiseLike | TResult2) - | undefined - | null - ): PromiseLike; -} -export function bar(foo: PromiseLike): PromiseLike; diff --git a/repo-scripts/prune-dts/tests/dom.output.d.ts b/repo-scripts/prune-dts/tests/dom.output.d.ts deleted file mode 100644 index 80cea14308e..00000000000 --- a/repo-scripts/prune-dts/tests/dom.output.d.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * @license - * Copyright 2021 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. - */ -export class Foo implements PromiseLike { - then( - onfulfilled?: - | ((value: any) => PromiseLike | TResult1) - | undefined - | null, - onrejected?: - | ((reason: any) => PromiseLike | TResult2) - | undefined - | null - ): PromiseLike; -} -export function bar(foo: PromiseLike): PromiseLike; diff --git a/repo-scripts/prune-dts/tests/error.input.d.ts b/repo-scripts/prune-dts/tests/error.input.d.ts deleted file mode 100644 index 9a1d8abb6e1..00000000000 --- a/repo-scripts/prune-dts/tests/error.input.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * @license - * Copyright 2021 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 { FirebaseError } from '@firebase/util'; - -export declare interface StorageError extends FirebaseError { - serverResponse: string | null; -} - -export {}; diff --git a/repo-scripts/prune-dts/tests/error.output.d.ts b/repo-scripts/prune-dts/tests/error.output.d.ts deleted file mode 100644 index b45c06687f1..00000000000 --- a/repo-scripts/prune-dts/tests/error.output.d.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @license - * Copyright 2021 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 { FirebaseError } from '@firebase/util'; -export declare interface StorageError extends FirebaseError { - serverResponse: string | null; -} -export {}; diff --git a/repo-scripts/prune-dts/tests/firestore.input.d.ts b/repo-scripts/prune-dts/tests/firestore.input.d.ts deleted file mode 100644 index aa71ccffaf0..00000000000 --- a/repo-scripts/prune-dts/tests/firestore.input.d.ts +++ /dev/null @@ -1,5779 +0,0 @@ -/** - * @license - * Copyright 2021 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 { DocumentData as DocumentData_2 } from '@firebase/firestore-types'; -import { FirebaseApp } from '@firebase/app'; -import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; -import { _FirebaseService } from '@firebase/app'; -import { LogLevelString as LogLevel } from '@firebase/logger'; -import { Provider } from '@firebase/component'; -import { SetOptions as SetOptions_2 } from '@firebase/firestore-types'; - -/** - * Converts Firestore's internal types to the JavaScript types that we expose - * to the user. - */ -declare abstract class AbstractUserDataWriter { - convertValue( - value: Value, - serverTimestampBehavior?: ServerTimestampBehavior - ): unknown; - private convertObject; - private convertGeoPoint; - private convertArray; - private convertServerTimestamp; - private convertTimestamp; - protected convertDocumentKey( - name: string, - expectedDatabaseId: DatabaseId - ): DocumentKey; - protected abstract convertReference(name: string): unknown; - protected abstract convertBytes(bytes: ByteString): unknown; -} - -/** - * Describes a map whose keys are active target ids. We do not care about the type of the - * values. - */ -declare type ActiveTargets = SortedMap; - -/** - * Add a new document to specified `CollectionReference` with the given data, - * assigning it a document ID automatically. - * - * @param reference - A reference to the collection to add this document to. - * @param data - An Object containing the data for the new document. - * @returns A Promise resolved with a `DocumentReference` pointing to the - * newly created document after it has been written to the backend (Note that it - * won't resolve while you're offline). - */ -export declare function addDoc( - reference: CollectionReference, - data: T -): Promise>; - -declare interface ApiClientObjectMap { - [k: string]: T; -} - -/** - * Returns a special value that can be used with {@link (setDoc:1)} or {@link - * updateDoc} that tells the server to remove the given elements from any - * array value that already exists on the server. All instances of each element - * specified will be removed from the array. If the field being modified is not - * already an array it will be overwritten with an empty array. - * - * @param elements - The elements to remove from the array. - * @returns The `FieldValue` sentinel for use in a call to `setDoc()` or - * `updateDoc()` - */ -export declare function arrayRemove(...elements: unknown[]): FieldValue; - -/** - * Returns a special value that can be used with {@link setDoc} or {@link - * updateDoc} that tells the server to union the given elements with any array - * value that already exists on the server. Each specified element that doesn't - * already exist in the array will be added to the end. If the field being - * modified is not already an array it will be overwritten with an array - * containing exactly the specified elements. - * - * @param elements - The elements to union into the array. - * @returns The `FieldValue` sentinel for use in a call to `setDoc()` or - * `updateDoc()`. - */ -export declare function arrayUnion(...elements: unknown[]): FieldValue; - -declare interface AsyncQueue { - readonly isShuttingDown: boolean; - /** - * Adds a new operation to the queue without waiting for it to complete (i.e. - * we ignore the Promise result). - */ - enqueueAndForget(op: () => Promise): void; - /** - * Regardless if the queue has initialized shutdown, adds a new operation to the - * queue without waiting for it to complete (i.e. we ignore the Promise result). - */ - enqueueAndForgetEvenWhileRestricted( - op: () => Promise - ): void; - /** - * Initialize the shutdown of this queue. Once this method is called, the - * only possible way to request running an operation is through - * `enqueueEvenWhileRestricted()`. - */ - enterRestrictedMode(): void; - /** - * Adds a new operation to the queue. Returns a promise that will be resolved - * when the promise returned by the new operation is (with its value). - */ - enqueue(op: () => Promise): Promise; - /** - * Enqueue a retryable operation. - * - * A retryable operation is rescheduled with backoff if it fails with a - * IndexedDbTransactionError (the error type used by SimpleDb). All - * retryable operations are executed in order and only run if all prior - * operations were retried successfully. - */ - enqueueRetryable(op: () => Promise): void; - /** - * Schedules an operation to be queued on the AsyncQueue once the specified - * `delayMs` has elapsed. The returned DelayedOperation can be used to cancel - * or fast-forward the operation prior to its running. - */ - enqueueAfterDelay( - timerId: TimerId, - delayMs: number, - op: () => Promise - ): DelayedOperation; - /** - * Verifies there's an operation currently in-progress on the AsyncQueue. - * Unfortunately we can't verify that the running code is in the promise chain - * of that operation, so this isn't a foolproof check, but it should be enough - * to catch some bugs. - */ - verifyOperationInProgress(): void; -} - -/** - * Path represents an ordered sequence of string segments. - */ -declare abstract class BasePath> { - private segments; - private offset; - private len; - constructor(segments: string[], offset?: number, length?: number); - /** - * Abstract constructor method to construct an instance of B with the given - * parameters. - */ - protected abstract construct( - segments: string[], - offset?: number, - length?: number - ): B; - /** - * Returns a String representation. - * - * Implementing classes are required to provide deterministic implementations as - * the String representation is used to obtain canonical Query IDs. - */ - abstract toString(): string; - get length(): number; - isEqual(other: B): boolean; - child(nameOrPath: string | B): B; - /** The index of one past the last segment of the path. */ - private limit; - popFirst(size?: number): B; - popLast(): B; - firstSegment(): string; - lastSegment(): string; - get(index: number): string; - isEmpty(): boolean; - isPrefixOf(other: this): boolean; - isImmediateParentOf(potentialChild: this): boolean; - forEach(fn: (segment: string) => void): void; - toArray(): string[]; - static comparator>( - p1: BasePath, - p2: BasePath - ): number; -} - -/** - * @license - * Copyright 2017 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. - */ -/** - * BatchID is a locally assigned ID for a batch of mutations that have been - * applied. - */ -declare type BatchId = number; - -/** - * Represents a bound of a query. - * - * The bound is specified with the given components representing a position and - * whether it's just before or just after the position (relative to whatever the - * query order is). - * - * The position represents a logical index position for a query. It's a prefix - * of values for the (potentially implicit) order by clauses of a query. - * - * Bound provides a function to determine whether a document comes before or - * after a bound. This is influenced by whether the position is just before or - * just after the provided values. - */ -declare class Bound { - readonly position: Value[]; - readonly before: boolean; - constructor(position: Value[], before: boolean); -} - -/** - * Provides interfaces to save and read Firestore bundles. - */ -declare interface BundleCache { - /** - * Gets the saved `BundleMetadata` for a given `bundleId`, returns undefined - * if no bundle metadata is found under the given id. - */ - getBundleMetadata( - transaction: PersistenceTransaction, - bundleId: string - ): PersistencePromise; - /** - * Saves a `BundleMetadata` from a bundle into local storage, using its id as - * the persistent key. - */ - saveBundleMetadata( - transaction: PersistenceTransaction, - metadata: BundleMetadata_2 - ): PersistencePromise; - /** - * Gets a saved `NamedQuery` for the given query name. Returns undefined if - * no queries are found under the given name. - */ - getNamedQuery( - transaction: PersistenceTransaction, - queryName: string - ): PersistencePromise; - /** - * Saves a `NamedQuery` from a bundle, using its name as the persistent key. - */ - saveNamedQuery( - transaction: PersistenceTransaction, - query: NamedQuery_2 - ): PersistencePromise; -} - -/** Properties of a BundledQuery. */ -declare interface BundledQuery { - /** BundledQuery parent */ - parent?: string | null; - /** BundledQuery structuredQuery */ - structuredQuery?: StructuredQuery | null; - /** BundledQuery limitType */ - limitType?: LimitType_2 | null; -} - -/** - * Represents a Firestore bundle saved by the SDK in its local storage. - */ -declare interface BundleMetadata { - /** - * Id of the bundle. It is used together with `createTime` to determine if a - * bundle has been loaded by the SDK. - */ - readonly id: string; - /** Schema version of the bundle. */ - readonly version: number; - /** - * Set to the snapshot version of the bundle if created by the Server SDKs. - * Otherwise set to SnapshotVersion.MIN. - */ - readonly createTime: SnapshotVersion; -} - -/** Properties of a BundleMetadata. */ -declare interface BundleMetadata_2 { - /** BundleMetadata id */ - id?: string | null; - /** BundleMetadata createTime */ - createTime?: Timestamp_2 | null; - /** BundleMetadata version */ - version?: number | null; - /** BundleMetadata totalDocuments */ - totalDocuments?: number | null; - /** BundleMetadata totalBytes */ - totalBytes?: number | null; -} - -/** - * An immutable object representing an array of bytes. - */ -export declare class Bytes { - _byteString: ByteString; - /** @hideconstructor */ - constructor(byteString: ByteString); - /** - * Creates a new `Bytes` object from the given Base64 string, converting it to - * bytes. - * - * @param base64 - The Base64 string used to create the `Bytes` object. - */ - static fromBase64String(base64: string): Bytes; - /** - * Creates a new `Bytes` object from the given Uint8Array. - * - * @param array - The Uint8Array used to create the `Bytes` object. - */ - static fromUint8Array(array: Uint8Array): Bytes; - /** - * Returns the underlying bytes as a Base64-encoded string. - * - * @returns The Base64-encoded string created from the `Bytes` object. - */ - toBase64(): string; - /** - * Returns the underlying bytes in a new `Uint8Array`. - * - * @returns The Uint8Array created from the `Bytes` object. - */ - toUint8Array(): Uint8Array; - /** - * Returns a string representation of the `Bytes` object. - * - * @returns A string representation of the `Bytes` object. - */ - toString(): string; - /** - * Returns true if this `Bytes` object is equal to the provided one. - * - * @param other - The `Bytes` object to compare against. - * @returns true if this `Bytes` object is equal to the provided one. - */ - isEqual(other: Bytes): boolean; -} - -/** - * @license - * Copyright 2020 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. - */ -/** - * Immutable class that represents a "proto" byte string. - * - * Proto byte strings can either be Base64-encoded strings or Uint8Arrays when - * sent on the wire. This class abstracts away this differentiation by holding - * the proto byte string in a common class that must be converted into a string - * before being sent as a proto. - */ -declare class ByteString { - private readonly binaryString; - static readonly EMPTY_BYTE_STRING: ByteString; - private constructor(); - static fromBase64String(base64: string): ByteString; - static fromUint8Array(array: Uint8Array): ByteString; - toBase64(): string; - toUint8Array(): Uint8Array; - approximateByteSize(): number; - compareTo(other: ByteString): number; - isEqual(other: ByteString): boolean; -} - -/** - * Constant used to indicate the LRU garbage collection should be disabled. - * Set this value as the `cacheSizeBytes` on the settings passed to the - * `Firestore` instance. - */ -export declare const CACHE_SIZE_UNLIMITED = -1; - -declare const enum ChangeType { - Added = 0, - Removed = 1, - Modified = 2, - Metadata = 3 -} - -/** - * Clears the persistent storage. This includes pending writes and cached - * documents. - * - * Must be called while the `Firestore` instance is not started (after the app is - * terminated or when the app is first initialized). On startup, this function - * must be called before other functions (other than {@link - * initializeFirestore} or {@link getFirestore})). If the `Firestore` - * instance is still running, the promise will be rejected with the error code - * of `failed-precondition`. - * - * Note: `clearIndexedDbPersistence()` is primarily intended to help write - * reliable tests that use Cloud Firestore. It uses an efficient mechanism for - * dropping existing data but does not attempt to securely overwrite or - * otherwise make cached data unrecoverable. For applications that are sensitive - * to the disclosure of cached data in between user sessions, we strongly - * recommend not enabling persistence at all. - * - * @param firestore - The `Firestore` instance to clear persistence for. - * @returns A promise that is resolved when the persistent storage is - * cleared. Otherwise, the promise is rejected with an error. - */ -export declare function clearIndexedDbPersistence( - firestore: FirebaseFirestore -): Promise; - -/** - * A randomly-generated key assigned to each Firestore instance at startup. - */ -declare type ClientId = string; - -/** - * Gets a `CollectionReference` instance that refers to the collection at - * the specified absolute path. - * - * @param firestore - A reference to the root Firestore instance. - * @param path - A slash-separated path to a collection. - * @param pathSegments - Additional path segments to apply relative to the first - * argument. - * @throws If the final path has an even number of segments and does not point - * to a collection. - * @returns The `CollectionReference` instance. - */ -export declare function collection( - firestore: FirebaseFirestore_2, - path: string, - ...pathSegments: string[] -): CollectionReference; - -/** - * Gets a `CollectionReference` instance that refers to a subcollection of - * `reference` at the specified relative path. - * - * @param reference - A reference to a collection. - * @param path - A slash-separated path to a collection. - * @param pathSegments - Additional path segments to apply relative to the first - * argument. - * @throws If the final path has an even number of segments and does not point - * to a collection. - * @returns The `CollectionReference` instance. - */ -export declare function collection( - reference: CollectionReference, - path: string, - ...pathSegments: string[] -): CollectionReference; - -/** - * Gets a `CollectionReference` instance that refers to a subcollection of - * `reference` at the specified relative path. - * - * @param reference - A reference to a Firestore document. - * @param path - A slash-separated path to a collection. - * @param pathSegments - Additional path segments that will be applied relative - * to the first argument. - * @throws If the final path has an even number of segments and does not point - * to a collection. - * @returns The `CollectionReference` instance. - */ -export declare function collection( - reference: DocumentReference, - path: string, - ...pathSegments: string[] -): CollectionReference; - -/** - * Creates and returns a new `Query` instance that includes all documents in the - * database that are contained in a collection or subcollection with the - * given `collectionId`. - * - * @param firestore - A reference to the root Firestore instance. - * @param collectionId - Identifies the collections to query over. Every - * collection or subcollection with this ID as the last segment of its path - * will be included. Cannot contain a slash. - * @returns The created `Query`. - */ -export declare function collectionGroup( - firestore: FirebaseFirestore_2, - collectionId: string -): Query; - -/** - * A `CollectionReference` object can be used for adding documents, getting - * document references, and querying for documents (using {@link query}). - */ -export declare class CollectionReference extends Query { - readonly firestore: FirebaseFirestore_2; - readonly _path: ResourcePath; - readonly type = 'collection'; - /** @hideconstructor */ - constructor( - firestore: FirebaseFirestore_2, - converter: FirestoreDataConverter_2 | null, - _path: ResourcePath - ); - /** The collection's identifier. */ - get id(): string; - /** - * A string representing the path of the referenced collection (relative - * to the root of the database). - */ - get path(): string; - /** - * A reference to the containing `DocumentReference` if this is a - * subcollection. If this isn't a subcollection, the reference is null. - */ - get parent(): DocumentReference | null; - /** - * Applies a custom data converter to this CollectionReference, allowing you - * to use your own custom model objects with Firestore. When you call {@link - * addDoc} with the returned `CollectionReference` instance, the provided - * converter will convert between Firestore data and your custom type `U`. - * - * @param converter - Converts objects to and from Firestore. - * @returns A `CollectionReference` that uses the provided converter. - */ - withConverter( - converter: FirestoreDataConverter_2 - ): CollectionReference; -} - -/** - * @license - * Copyright 2017 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. - */ -declare type Comparator = (key1: K, key2: K) => number; - -declare interface ComponentConfiguration { - asyncQueue: AsyncQueue; - databaseInfo: DatabaseInfo; - credentials: CredentialsProvider; - clientId: ClientId; - initialUser: User; - maxConcurrentLimboResolutions: number; -} - -declare type CompositeFilterOp = 'OPERATOR_UNSPECIFIED' | 'AND'; - -/** - * A Listener for credential change events. The listener should fetch a new - * token and may need to invalidate other state if the current user has also - * changed. - */ -declare type CredentialChangeListener = (user: User) => void; - -/** - * Provides methods for getting the uid and token for the current user and - * listening for changes. - */ -declare interface CredentialsProvider { - /** Requests a token for the current user. */ - getToken(): Promise; - /** - * Marks the last retrieved token as invalid, making the next GetToken request - * force-refresh the token. - */ - invalidateToken(): void; - /** - * Specifies a listener to be notified of credential changes - * (sign-in / sign-out, token changes). It is immediately called once with the - * initial user. - */ - setChangeListener(changeListener: CredentialChangeListener): void; - /** Removes the previously-set change listener. */ - removeChangeListener(): void; -} - -/** Settings for private credentials */ -declare type CredentialsSettings = - | FirstPartyCredentialsSettings - | ProviderCredentialsSettings; - -/** Represents the database ID a Firestore client is associated with. */ -declare class DatabaseId { - readonly projectId: string; - readonly database: string; - constructor(projectId: string, database?: string); - get isDefaultDatabase(): boolean; - isEqual(other: {}): boolean; -} - -/** - * @license - * Copyright 2017 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. - */ -declare class DatabaseInfo { - readonly databaseId: DatabaseId; - readonly persistenceKey: string; - readonly host: string; - readonly ssl: boolean; - readonly forceLongPolling: boolean; - readonly autoDetectLongPolling: boolean; - /** - * Constructs a DatabaseInfo using the provided host, databaseId and - * persistenceKey. - * - * @param databaseId - The database to use. - * @param persistenceKey - A unique identifier for this Firestore's local - * storage (used in conjunction with the databaseId). - * @param host - The Firestore backend host to connect to. - * @param ssl - Whether to use SSL when connecting. - * @param forceLongPolling - Whether to use the forceLongPolling option - * when using WebChannel as the network transport. - * @param autoDetectLongPolling - Whether to use the detectBufferingProxy - * option when using WebChannel as the network transport. - */ - constructor( - databaseId: DatabaseId, - persistenceKey: string, - host: string, - ssl: boolean, - forceLongPolling: boolean, - autoDetectLongPolling: boolean - ); -} - -/** - * Datastore and its related methods are a wrapper around the external Google - * Cloud Datastore grpc API, which provides an interface that is more convenient - * for the rest of the client SDK architecture to consume. - */ -declare abstract class Datastore { - abstract terminate(): void; -} - -/** - * Represents an operation scheduled to be run in the future on an AsyncQueue. - * - * It is created via DelayedOperation.createAndSchedule(). - * - * Supports cancellation (via cancel()) and early execution (via skipDelay()). - * - * Note: We implement `PromiseLike` instead of `Promise`, as the `Promise` type - * in newer versions of TypeScript defines `finally`, which is not available in - * IE. - */ -declare class DelayedOperation implements PromiseLike { - private readonly asyncQueue; - readonly timerId: TimerId; - readonly targetTimeMs: number; - private readonly op; - private readonly removalCallback; - private timerHandle; - private readonly deferred; - private constructor(); - /** - * Creates and returns a DelayedOperation that has been scheduled to be - * executed on the provided asyncQueue after the provided delayMs. - * - * @param asyncQueue - The queue to schedule the operation on. - * @param id - A Timer ID identifying the type of operation this is. - * @param delayMs - The delay (ms) before the operation should be scheduled. - * @param op - The operation to run. - * @param removalCallback - A callback to be called synchronously once the - * operation is executed or canceled, notifying the AsyncQueue to remove it - * from its delayedOperations list. - * PORTING NOTE: This exists to prevent making removeDelayedOperation() and - * the DelayedOperation class public. - */ - static createAndSchedule( - asyncQueue: AsyncQueue, - timerId: TimerId, - delayMs: number, - op: () => Promise, - removalCallback: (op: DelayedOperation) => void - ): DelayedOperation; - /** - * Starts the timer. This is called immediately after construction by - * createAndSchedule(). - */ - private start; - /** - * Queues the operation to run immediately (if it hasn't already been run or - * canceled). - */ - skipDelay(): void; - /** - * Cancels the operation if it hasn't already been executed or canceled. The - * promise will be rejected. - * - * As long as the operation has not yet been run, calling cancel() provides a - * guarantee that the operation will not be run. - */ - cancel(reason?: string): void; - then: ( - onfulfilled?: - | ((value: T) => TResult1 | PromiseLike) - | null - | undefined, - onrejected?: - | ((reason: any) => TResult2 | PromiseLike) - | null - | undefined - ) => Promise; - private handleDelayElapsed; - private clearTimeout; -} - -/** - * Deletes the document referred to by the specified `DocumentReference`. - * - * @param reference - A reference to the document to delete. - * @returns A Promise resolved once the document has been successfully - * deleted from the backend (note that it won't resolve while you're offline). - */ -export declare function deleteDoc( - reference: DocumentReference -): Promise; - -/** - * Returns a sentinel for use with {@link updateDoc} or - * {@link setDoc} with `{merge: true}` to mark a field for deletion. - */ -export declare function deleteField(): FieldValue; - -/** - * The direction of sorting in an order by. - */ -declare const enum Direction { - ASCENDING = 'asc', - DESCENDING = 'desc' -} - -/** - * Disables network usage for this instance. It can be re-enabled via {@link - * enableNetwork}. While the network is disabled, any snapshot listeners, - * `getDoc()` or `getDocs()` calls will return results from cache, and any write - * operations will be queued until the network is restored. - * - * @returns A promise that is resolved once the network has been disabled. - */ -export declare function disableNetwork( - firestore: FirebaseFirestore -): Promise; - -/** - * Gets a `DocumentReference` instance that refers to the document at the - * specified absolute path. - * - * @param firestore - A reference to the root Firestore instance. - * @param path - A slash-separated path to a document. - * @param pathSegments - Additional path segments that will be applied relative - * to the first argument. - * @throws If the final path has an odd number of segments and does not point to - * a document. - * @returns The `DocumentReference` instance. - */ -export declare function doc( - firestore: FirebaseFirestore_2, - path: string, - ...pathSegments: string[] -): DocumentReference; - -/** - * Gets a `DocumentReference` instance that refers to a document within - * `reference` at the specified relative path. If no path is specified, an - * automatically-generated unique ID will be used for the returned - * `DocumentReference`. - * - * @param reference - A reference to a collection. - * @param path - A slash-separated path to a document. Has to be omitted to use - * auto-generated IDs. - * @param pathSegments - Additional path segments that will be applied relative - * to the first argument. - * @throws If the final path has an odd number of segments and does not point to - * a document. - * @returns The `DocumentReference` instance. - */ -export declare function doc( - reference: CollectionReference, - path?: string, - ...pathSegments: string[] -): DocumentReference; - -/** - * Gets a `DocumentReference` instance that refers to a document within - * `reference` at the specified relative path. - * - * @param reference - A reference to a Firestore document. - * @param path - A slash-separated path to a document. - * @param pathSegments - Additional path segments that will be applied relative - * to the first argument. - * @throws If the final path has an odd number of segments and does not point to - * a document. - * @returns The `DocumentReference` instance. - */ -export declare function doc( - reference: DocumentReference, - path: string, - ...pathSegments: string[] -): DocumentReference; - -/** - * Represents a document in Firestore with a key, version, data and whether the - * data has local mutations applied to it. - */ -declare class Document_2 extends MaybeDocument { - private readonly objectValue; - readonly hasLocalMutations: boolean; - readonly hasCommittedMutations: boolean; - constructor( - key: DocumentKey, - version: SnapshotVersion, - objectValue: ObjectValue, - options: DocumentOptions - ); - field(path: FieldPath_2): Value | null; - data(): ObjectValue; - toProto(): { - mapValue: MapValue; - }; - isEqual(other: MaybeDocument | null | undefined): boolean; - toString(): string; - get hasPendingWrites(): boolean; -} - -/** - * A `DocumentChange` represents a change to the documents matching a query. - * It contains the document affected and the type of change that occurred. - */ -export declare interface DocumentChange { - /** The type of change ('added', 'modified', or 'removed'). */ - readonly type: DocumentChangeType; - /** The document affected by this change. */ - readonly doc: QueryDocumentSnapshot; - /** - * The index of the changed document in the result set immediately prior to - * this `DocumentChange` (i.e. supposing that all prior `DocumentChange` objects - * have been applied). Is `-1` for 'added' events. - */ - readonly oldIndex: number; - /** - * The index of the changed document in the result set immediately after - * this `DocumentChange` (i.e. supposing that all prior `DocumentChange` - * objects and the current `DocumentChange` object have been applied). - * Is -1 for 'removed' events. - */ - readonly newIndex: number; -} - -/** - * The type of a `DocumentChange` may be 'added', 'removed', or 'modified'. - */ -export declare type DocumentChangeType = 'added' | 'removed' | 'modified'; - -declare type DocumentComparator = ( - doc1: Document_2, - doc2: Document_2 -) => number; - -/** - * Document data (for use with {@link setDoc}) consists of fields mapped to - * values. - */ -export declare interface DocumentData { - [field: string]: any; -} - -/** - * Returns a special sentinel `FieldPath` to refer to the ID of a document. - * It can be used in queries to sort or filter by the document ID. - */ -export declare function documentId(): FieldPath; - -declare class DocumentKey { - readonly path: ResourcePath; - constructor(path: ResourcePath); - static fromPath(path: string): DocumentKey; - static fromName(name: string): DocumentKey; - /** Returns true if the document is in the specified collectionId. */ - hasCollectionId(collectionId: string): boolean; - isEqual(other: DocumentKey | null): boolean; - toString(): string; - static comparator(k1: DocumentKey, k2: DocumentKey): number; - static isDocumentKey(path: ResourcePath): boolean; - /** - * Creates and returns a new document key with the given segments. - * - * @param segments - The segments of the path to the document - * @returns A new instance of DocumentKey - */ - static fromSegments(segments: string[]): DocumentKey; -} - -declare type DocumentKeySet = SortedSet; - -declare type DocumentMap = SortedMap; - -declare interface DocumentOptions { - hasLocalMutations?: boolean; - hasCommittedMutations?: boolean; -} - -/** - * A `DocumentReference` refers to a document location in a Firestore database - * and can be used to write, read, or listen to the location. The document at - * the referenced location may or may not exist. - */ -export declare class DocumentReference { - readonly _converter: FirestoreDataConverter_2 | null; - readonly _key: DocumentKey; - /** The type of this Firestore reference. */ - readonly type = 'document'; - /** - * The {@link FirebaseFirestore} the document is in. - * This is useful for performing transactions, for example. - */ - readonly firestore: FirebaseFirestore_2; - /** @hideconstructor */ - constructor( - firestore: FirebaseFirestore_2, - _converter: FirestoreDataConverter_2 | null, - _key: DocumentKey - ); - get _path(): ResourcePath; - /** - * The document's identifier within its collection. - */ - get id(): string; - /** - * A string representing the path of the referenced document (relative - * to the root of the database). - */ - get path(): string; - /** - * The collection this `DocumentReference` belongs to. - */ - get parent(): CollectionReference; - /** - * Applies a custom data converter to this `DocumentReference`, allowing you - * to use your own custom model objects with Firestore. When you call {@link - * setDoc}, {@link getDoc}, etc. with the returned `DocumentReference` - * instance, the provided converter will convert between Firestore data and - * your custom type `U`. - * - * @param converter - Converts objects to and from Firestore. - * @returns A `DocumentReference` that uses the provided converter. - */ - withConverter( - converter: FirestoreDataConverter_2 - ): DocumentReference; -} - -/** - * DocumentSet is an immutable (copy-on-write) collection that holds documents - * in order specified by the provided comparator. We always add a document key - * comparator on top of what is provided to guarantee document equality based on - * the key. - */ -declare class DocumentSet { - /** - * Returns an empty copy of the existing DocumentSet, using the same - * comparator. - */ - static emptySet(oldSet: DocumentSet): DocumentSet; - private comparator; - private keyedMap; - private sortedSet; - /** The default ordering is by key if the comparator is omitted */ - constructor(comp?: DocumentComparator); - has(key: DocumentKey): boolean; - get(key: DocumentKey): Document_2 | null; - first(): Document_2 | null; - last(): Document_2 | null; - isEmpty(): boolean; - /** - * Returns the index of the provided key in the document set, or -1 if the - * document key is not present in the set; - */ - indexOf(key: DocumentKey): number; - get size(): number; - /** Iterates documents in order defined by "comparator" */ - forEach(cb: (doc: Document_2) => void): void; - /** Inserts or updates a document with the same key */ - add(doc: Document_2): DocumentSet; - /** Deletes a document with a given key */ - delete(key: DocumentKey): DocumentSet; - isEqual(other: DocumentSet | null | undefined): boolean; - toString(): string; - private copy; -} - -/** - * A `DocumentSnapshot` contains data read from a document in your Firestore - * database. The data can be extracted with `.data()` or `.get()` to - * get a specific field. - * - * For a `DocumentSnapshot` that points to a non-existing document, any data - * access will return 'undefined'. You can use the `exists()` method to - * explicitly verify a document's existence. - */ -export declare class DocumentSnapshot< - T = DocumentData -> extends DocumentSnapshot_2 { - readonly _firestore: FirebaseFirestore; - private readonly _firestoreImpl; - /** - * Metadata about the `DocumentSnapshot`, including information about its - * source and local modifications. - */ - readonly metadata: SnapshotMetadata; - /** @hideconstructor protected */ - constructor( - _firestore: FirebaseFirestore, - userDataWriter: AbstractUserDataWriter, - key: DocumentKey, - document: Document_2 | null, - metadata: SnapshotMetadata, - converter: UntypedFirestoreDataConverter | null - ); - /** - * Property of the `DocumentSnapshot` that signals whether or not the data - * exists. True if the document exists. - */ - exists(): this is QueryDocumentSnapshot; - /** - * Retrieves all fields in the document as an `Object`. Returns `undefined` if - * the document doesn't exist. - * - * By default, `FieldValue.serverTimestamp()` values that have not yet been - * set to their final value will be returned as `null`. You can override - * this by passing an options object. - * - * @param options - An options object to configure how data is retrieved from - * the snapshot (for example the desired behavior for server timestamps that - * have not yet been set to their final value). - * @returns An `Object` containing all fields in the document or `undefined` if - * the document doesn't exist. - */ - data(options?: SnapshotOptions): T | undefined; - /** - * Retrieves the field specified by `fieldPath`. Returns `undefined` if the - * document or field doesn't exist. - * - * By default, a `FieldValue.serverTimestamp()` that has not yet been set to - * its final value will be returned as `null`. You can override this by - * passing an options object. - * - * @param fieldPath - The path (for example 'foo' or 'foo.bar') to a specific - * field. - * @param options - An options object to configure how the field is retrieved - * from the snapshot (for example the desired behavior for server timestamps - * that have not yet been set to their final value). - * @returns The data at the specified field location or undefined if no such - * field exists in the document. - */ - get(fieldPath: string | FieldPath, options?: SnapshotOptions): any; -} - -/** - * A `DocumentSnapshot` contains data read from a document in your Firestore - * database. The data can be extracted with `.data()` or `.get()` to - * get a specific field. - * - * For a `DocumentSnapshot` that points to a non-existing document, any data - * access will return 'undefined'. You can use the `exists()` method to - * explicitly verify a document's existence. - */ -declare class DocumentSnapshot_2 { - _firestore: FirebaseFirestore_2; - _userDataWriter: AbstractUserDataWriter; - _key: DocumentKey; - _document: Document_2 | null; - _converter: UntypedFirestoreDataConverter | null; - /** @hideconstructor protected */ - constructor( - _firestore: FirebaseFirestore_2, - _userDataWriter: AbstractUserDataWriter, - _key: DocumentKey, - _document: Document_2 | null, - _converter: UntypedFirestoreDataConverter | null - ); - /** Property of the `DocumentSnapshot` that provides the document's ID. */ - get id(): string; - /** - * The `DocumentReference` for the document included in the `DocumentSnapshot`. - */ - get ref(): DocumentReference; - /** - * Signals whether or not the document at the snapshot's location exists. - * - * @returns true if the document exists. - */ - exists(): this is QueryDocumentSnapshot_2; - /** - * Retrieves all fields in the document as an `Object`. Returns `undefined` if - * the document doesn't exist. - * - * @returns An `Object` containing all fields in the document or `undefined` - * if the document doesn't exist. - */ - data(): T | undefined; - /** - * Retrieves the field specified by `fieldPath`. Returns `undefined` if the - * document or field doesn't exist. - * - * @param fieldPath - The path (for example 'foo' or 'foo.bar') to a specific - * field. - * @returns The data at the specified field location or undefined if no such - * field exists in the document. - */ - get(fieldPath: string | FieldPath): any; -} - -declare type DocumentVersionMap = SortedMap; - -declare interface DocumentViewChange { - type: ChangeType; - doc: Document_2; -} - -/** - * Attempts to enable persistent storage, if possible. - * - * Must be called before any other functions (other than - * {@link initializeFirestore}, {@link getFirestore} or - * {@link clearIndexedDbPersistence}. - * - * If this fails, `enableIndexedDbPersistence()` will reject the promise it - * returns. Note that even after this failure, the `Firestore` instance will - * remain usable, however offline persistence will be disabled. - * - * There are several reasons why this can fail, which can be identified by - * the `code` on the error. - * - * * failed-precondition: The app is already open in another browser tab. - * * unimplemented: The browser is incompatible with the offline - * persistence implementation. - * - * @param firestore - The `Firestore` instance to enable persistence for. - * @param persistenceSettings - Optional settings object to configure - * persistence. - * @returns A promise that represents successfully enabling persistent storage. - */ -export declare function enableIndexedDbPersistence( - firestore: FirebaseFirestore, - persistenceSettings?: PersistenceSettings -): Promise; - -/** - * Attempts to enable multi-tab persistent storage, if possible. If enabled - * across all tabs, all operations share access to local persistence, including - * shared execution of queries and latency-compensated local document updates - * across all connected instances. - * - * If this fails, `enableMultiTabIndexedDbPersistence()` will reject the promise - * it returns. Note that even after this failure, the `Firestore` instance will - * remain usable, however offline persistence will be disabled. - * - * There are several reasons why this can fail, which can be identified by - * the `code` on the error. - * - * * failed-precondition: The app is already open in another browser tab and - * multi-tab is not enabled. - * * unimplemented: The browser is incompatible with the offline - * persistence implementation. - * - * @param firestore - The `Firestore` instance to enable persistence for. - * @returns A promise that represents successfully enabling persistent - * storage. - */ -export declare function enableMultiTabIndexedDbPersistence( - firestore: FirebaseFirestore -): Promise; - -/** - * Re-enables use of the network for this Firestore instance after a prior - * call to {@link disableNetwork}. - * - * @returns A promise that is resolved once the network has been enabled. - */ -export declare function enableNetwork( - firestore: FirebaseFirestore -): Promise; - -/** - * Creates a `QueryConstraint` that modifies the result set to end at the - * provided document (inclusive). 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. - * - * @param snapshot - The snapshot of the document to end at. - * @returns A `QueryConstraint` to pass to `query()` - */ -export declare function endAt( - snapshot: DocumentSnapshot_2 -): QueryConstraint; - -/** - * Creates a `QueryConstraint` that modifies the result set to end at the - * provided fields relative to the order of the query. The order of the field - * values must match the order of the order by clauses of the query. - * - * @param fieldValues - The field values to end this query at, in order - * of the query's order by. - * @returns A `QueryConstraint` to pass to `query()` - */ -export declare function endAt(...fieldValues: unknown[]): QueryConstraint; - -/** - * Creates a `QueryConstraint` 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. - * - * @param snapshot - The snapshot of the document to end before. - * @returns A `QueryConstraint` to pass to `query()` - */ -export declare function endBefore( - snapshot: DocumentSnapshot_2 -): QueryConstraint; - -/** - * Creates a `QueryConstraint` that modifies the result set to end before the - * provided fields relative to the order of the query. The order of the field - * values must match the order of the order by clauses of the query. - * - * @param fieldValues - The field values to end this query before, in order - * of the query's order by. - * @returns A `QueryConstraint` to pass to `query()` - */ -export declare function endBefore(...fieldValues: unknown[]): QueryConstraint; - -declare interface Entry { - key: K; - value: V; -} - -/** - * EventManager is responsible for mapping queries to query event emitters. - * It handles "fan-out". -- Identical queries will re-use the same watch on the - * backend. - * - * PORTING NOTE: On Web, EventManager `onListen` and `onUnlisten` need to be - * assigned to SyncEngine's `listen()` and `unlisten()` API before usage. This - * allows users to tree-shake the Watch logic. - */ -declare interface EventManager { - onListen?: (query: Query_2) => Promise; - onUnlisten?: (query: Query_2) => Promise; -} - -declare type FieldFilterOp = - | 'OPERATOR_UNSPECIFIED' - | 'LESS_THAN' - | 'LESS_THAN_OR_EQUAL' - | 'GREATER_THAN' - | 'GREATER_THAN_OR_EQUAL' - | 'EQUAL' - | 'NOT_EQUAL' - | 'ARRAY_CONTAINS' - | 'IN' - | 'ARRAY_CONTAINS_ANY' - | 'NOT_IN'; - -/** - * Provides a set of fields that can be used to partially patch a document. - * FieldMask is used in conjunction with ObjectValue. - * Examples: - * foo - Overwrites foo entirely with the provided value. If foo is not - * present in the companion ObjectValue, the field is deleted. - * foo.bar - Overwrites only the field bar of the object foo. - * If foo is not an object, foo is replaced with an object - * containing foo - */ -declare class FieldMask { - readonly fields: FieldPath_2[]; - constructor(fields: FieldPath_2[]); - /** - * Verifies that `fieldPath` is included by at least one field in this field - * mask. - * - * This is an O(n) operation, where `n` is the size of the field mask. - */ - covers(fieldPath: FieldPath_2): boolean; - isEqual(other: FieldMask): boolean; -} - -/** - * A `FieldPath` refers to a field in a document. The path may consist of a - * single field name (referring to a top-level field in the document), or a - * list of field names (referring to a nested field in the document). - * - * Create a `FieldPath` by providing field names. If more than one field - * name is provided, the path will point to a nested field in a document. - */ -export declare class FieldPath { - /** Internal representation of a Firestore field path. */ - readonly _internalPath: FieldPath_2; - /** - * Creates a FieldPath from the provided field names. If more than one field - * name is provided, the path will point to a nested field in a document. - * - * @param fieldNames - A list of field names. - */ - constructor(...fieldNames: string[]); - /** - * Returns true if this `FieldPath` is equal to the provided one. - * - * @param other - The `FieldPath` to compare against. - * @returns true if this `FieldPath` is equal to the provided one. - */ - isEqual(other: FieldPath): boolean; -} - -/** A dot-separated path for navigating sub-objects within a document. */ -declare class FieldPath_2 extends BasePath { - protected construct( - segments: string[], - offset?: number, - length?: number - ): FieldPath_2; - /** - * Returns true if the string could be used as a segment in a field path - * without escaping. - */ - private static isValidIdentifier; - canonicalString(): string; - toString(): string; - /** - * Returns true if this field references the key of a document. - */ - isKeyField(): boolean; - /** - * The field designating the key of a document. - */ - static keyField(): FieldPath_2; - /** - * Parses a field string from the given server-formatted string. - * - * - Splitting the empty string is not allowed (for now at least). - * - Empty segments within the string (e.g. if there are two consecutive - * separators) are not allowed. - * - * TODO(b/37244157): we should make this more strict. Right now, it allows - * non-identifier path components, even if they aren't escaped. - */ - static fromServerFormat(path: string): FieldPath_2; - static emptyPath(): FieldPath_2; -} - -/** A field path and the TransformOperation to perform upon it. */ -declare class FieldTransform { - readonly field: FieldPath_2; - readonly transform: TransformOperation; - constructor(field: FieldPath_2, transform: TransformOperation); -} - -declare type FieldTransformSetToServerValue = - | 'SERVER_VALUE_UNSPECIFIED' - | 'REQUEST_TIME'; - -/** - * Sentinel values that can be used when writing document fields with `set()` - * or `update()`. - */ -export declare abstract class FieldValue { - _methodName: string; - /** - * @param _methodName - The public API endpoint that returns this class. - */ - constructor(_methodName: string); - abstract isEqual(other: FieldValue): boolean; - abstract _toFieldTransform(context: ParseContext): FieldTransform | null; -} - -declare abstract class Filter { - abstract matches(doc: Document_2): boolean; -} - -/** - * The Cloud Firestore service interface. - * - * Do not call this constructor directly. Instead, use {@link getFirestore}. - */ -export declare class FirebaseFirestore extends FirebaseFirestore_2 { - readonly _queue: AsyncQueue; - readonly _persistenceKey: string; - _firestoreClient: FirestoreClient | undefined; - /** @hideconstructor */ - constructor( - databaseIdOrApp: DatabaseId | FirebaseApp, - authProvider: Provider - ); - _terminate(): Promise; -} - -/** - * The Cloud Firestore service interface. - * - * Do not call this constructor directly. Instead, use {@link getFirestore}. - */ -declare class FirebaseFirestore_2 implements FirestoreService { - readonly _databaseId: DatabaseId; - readonly _persistenceKey: string; - _credentials: CredentialsProvider; - private _settings; - private _settingsFrozen; - private _terminateTask?; - private _app?; - /** @hideconstructor */ - constructor( - databaseIdOrApp: DatabaseId | FirebaseApp, - authProvider: Provider - ); - /** - * The {@link FirebaseApp} associated with this `Firestore` service - * instance. - */ - get app(): FirebaseApp; - get _initialized(): boolean; - get _terminated(): boolean; - _setSettings(settings: PrivateSettings): void; - _getSettings(): FirestoreSettings; - _freezeSettings(): FirestoreSettings; - _delete(): Promise; - toJSON(): object; - /** - * Terminates all components used by this client. Subclasses can override - * this method to clean up their own dependencies, but must also call this - * method. - * - * Only ever called once. - */ - protected _terminate(): Promise; -} - -/** - * FirestoreClient is a top-level class that constructs and owns all of the - * pieces of the client SDK architecture. It is responsible for creating the - * async queue that is shared by all of the other components in the system. - */ -declare class FirestoreClient { - private credentials; - /** - * Asynchronous queue responsible for all of our internal processing. When - * we get incoming work from the user (via public API) or the network - * (incoming GRPC messages), we should always schedule onto this queue. - * This ensures all of our work is properly serialized (e.g. we don't - * start processing a new operation while the previous one is waiting for - * an async I/O to complete). - */ - asyncQueue: AsyncQueue; - private databaseInfo; - private user; - private readonly clientId; - private credentialListener; - private readonly receivedInitialUser; - offlineComponents?: OfflineComponentProvider; - onlineComponents?: OnlineComponentProvider; - constructor( - credentials: CredentialsProvider, - /** - * Asynchronous queue responsible for all of our internal processing. When - * we get incoming work from the user (via public API) or the network - * (incoming GRPC messages), we should always schedule onto this queue. - * This ensures all of our work is properly serialized (e.g. we don't - * start processing a new operation while the previous one is waiting for - * an async I/O to complete). - */ - asyncQueue: AsyncQueue, - databaseInfo: DatabaseInfo - ); - getConfiguration(): Promise; - setCredentialChangeListener(listener: (user: User) => void): void; - /** - * Checks that the client has not been terminated. Ensures that other methods on - * this class cannot be called after the client is terminated. - */ - verifyNotTerminated(): void; - terminate(): Promise; -} - -/** - * Converter used by `withConverter()` to transform user objects of type `T` - * into Firestore data. - * - * Using the converter allows you to specify generic type arguments when - * storing and retrieving objects from Firestore. - * - * @example - * ```typescript - * class Post { - * constructor(readonly title: string, readonly author: string) {} - * - * toString(): string { - * return this.title + ', by ' + this.author; - * } - * } - * - * const postConverter = { - * toFirestore(post: Post): firebase.firestore.DocumentData { - * return {title: post.title, author: post.author}; - * }, - * fromFirestore( - * snapshot: firebase.firestore.QueryDocumentSnapshot, - * options: firebase.firestore.SnapshotOptions - * ): Post { - * const data = snapshot.data(options)!; - * return new Post(data.title, data.author); - * } - * }; - * - * const postSnap = await firebase.firestore() - * .collection('posts') - * .withConverter(postConverter) - * .doc().get(); - * const post = postSnap.data(); - * if (post !== undefined) { - * post.title; // string - * post.toString(); // Should be defined - * post.someNonExistentProperty; // TS error - * } - * ``` - */ -export declare interface FirestoreDataConverter - extends FirestoreDataConverter_2 { - /** - * Called by the Firestore SDK to convert a custom model object of type `T` - * into a plain JavaScript object (suitable for writing directly to the - * Firestore database). To use `set()` with `merge` and `mergeFields`, - * `toFirestore()` must be defined with `Partial`. - */ - toFirestore(modelObject: T): DocumentData; - /** - * Called by the Firestore SDK to convert a custom model object of type `T` - * into a plain JavaScript object (suitable for writing directly to the - * Firestore database). Used with {@link setData}, {@link WriteBatch#set} - * and {@link Transaction#set} with `merge:true` or `mergeFields`. - */ - toFirestore(modelObject: Partial, options: SetOptions): DocumentData; - /** - * Called by the Firestore SDK to convert Firestore data into an object of - * type T. You can access your data by calling: `snapshot.data(options)`. - * - * @param snapshot - A `QueryDocumentSnapshot` containing your data and metadata. - * @param options - The `SnapshotOptions` from the initial call to `data()`. - */ - fromFirestore( - snapshot: QueryDocumentSnapshot, - options?: SnapshotOptions - ): T; -} - -/** - * Converter used by `withConverter()` to transform user objects of type `T` - * into Firestore data. - * - * Using the converter allows you to specify generic type arguments when - * storing and retrieving objects from Firestore. - * - * @example - * ```typescript - * class Post { - * constructor(readonly title: string, readonly author: string) {} - * - * toString(): string { - * return this.title + ', by ' + this.author; - * } - * } - * - * const postConverter = { - * toFirestore(post: Post): firebase.firestore.DocumentData { - * return {title: post.title, author: post.author}; - * }, - * fromFirestore(snapshot: firebase.firestore.QueryDocumentSnapshot): Post { - * const data = snapshot.data(options)!; - * return new Post(data.title, data.author); - * } - * }; - * - * const postSnap = await firebase.firestore() - * .collection('posts') - * .withConverter(postConverter) - * .doc().get(); - * const post = postSnap.data(); - * if (post !== undefined) { - * post.title; // string - * post.toString(); // Should be defined - * post.someNonExistentProperty; // TS error - * } - * ``` - */ -declare interface FirestoreDataConverter_2 { - /** - * Called by the Firestore SDK to convert a custom model object of type `T` - * into a plain JavaScript object (suitable for writing directly to the - * Firestore database). Used with {@link setData}, {@link WriteBatch#set} - * and {@link Transaction#set}. - */ - toFirestore(modelObject: T): DocumentData; - /** - * Called by the Firestore SDK to convert a custom model object of type `T` - * into a plain JavaScript object (suitable for writing directly to the - * Firestore database). Used with {@link setData}, {@link WriteBatch#set} - * and {@link Transaction#set} with `merge:true` or `mergeFields`. - */ - toFirestore(modelObject: Partial, options: SetOptions): DocumentData; - /** - * Called by the Firestore SDK to convert Firestore data into an object of - * type T. You can access your data by calling: `snapshot.data()`. - * - * @param snapshot - A `QueryDocumentSnapshot` containing your data and - * metadata. - */ - fromFirestore(snapshot: QueryDocumentSnapshot_2): T; -} - -/** An error returned by a Firestore operation. */ -export declare class FirestoreError extends Error { - readonly code: FirestoreErrorCode; - readonly message: string; - readonly name: string; - readonly stack?: string; - /** @hideconstructor */ - constructor(code: FirestoreErrorCode, message: string); -} - -/** - * @license - * Copyright 2017 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. - */ -/** - * The set of Firestore status codes. The codes are the same at the ones - * exposed by gRPC here: - * https://github.com/grpc/grpc/blob/master/doc/statuscodes.md - * - * Possible values: - * - 'cancelled': The operation was cancelled (typically by the caller). - * - 'unknown': Unknown error or an error from a different error domain. - * - 'invalid-argument': Client specified an invalid argument. Note that this - * differs from 'failed-precondition'. 'invalid-argument' indicates - * arguments that are problematic regardless of the state of the system - * (e.g. an invalid field name). - * - 'deadline-exceeded': Deadline expired before operation could complete. - * For operations that change the state of the system, this error may be - * returned even if the operation has completed successfully. For example, - * a successful response from a server could have been delayed long enough - * for the deadline to expire. - * - 'not-found': Some requested document was not found. - * - 'already-exists': Some document that we attempted to create already - * exists. - * - 'permission-denied': The caller does not have permission to execute the - * specified operation. - * - 'resource-exhausted': Some resource has been exhausted, perhaps a - * per-user quota, or perhaps the entire file system is out of space. - * - 'failed-precondition': Operation was rejected because the system is not - * in a state required for the operation's execution. - * - 'aborted': The operation was aborted, typically due to a concurrency - * issue like transaction aborts, etc. - * - 'out-of-range': Operation was attempted past the valid range. - * - 'unimplemented': Operation is not implemented or not supported/enabled. - * - 'internal': Internal errors. Means some invariants expected by - * underlying system has been broken. If you see one of these errors, - * something is very broken. - * - 'unavailable': The service is currently unavailable. This is most likely - * a transient condition and may be corrected by retrying with a backoff. - * - 'data-loss': Unrecoverable data loss or corruption. - * - 'unauthenticated': The request does not have valid authentication - * credentials for the operation. - */ -export declare type FirestoreErrorCode = - | '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'; - -/** - * An interface implemented by FirebaseFirestore that provides compatibility - * with the usage in this file. - * - * This interface mainly exists to remove a cyclic dependency. - */ -declare interface FirestoreService extends _FirebaseService { - _credentials: CredentialsProvider; - _persistenceKey: string; - _databaseId: DatabaseId; - _terminated: boolean; - _freezeSettings(): FirestoreSettings; -} - -/** - * A concrete type describing all the values that can be applied via a - * user-supplied firestore.Settings object. This is a separate type so that - * defaults can be supplied and the value can be checked for equality. - */ -declare class FirestoreSettings { - /** The hostname to connect to. */ - readonly host: string; - /** Whether to use SSL when connecting. */ - readonly ssl: boolean; - readonly cacheSizeBytes: number; - readonly experimentalForceLongPolling: boolean; - readonly experimentalAutoDetectLongPolling: boolean; - readonly ignoreUndefinedProperties: boolean; - credentials?: any; - constructor(settings: PrivateSettings); - isEqual(other: FirestoreSettings): boolean; -} - -declare namespace firestoreV1ApiClientInterfaces { - interface ArrayValue { - values?: Value[]; - } - interface BatchGetDocumentsRequest { - database?: string; - documents?: string[]; - mask?: DocumentMask; - transaction?: string; - newTransaction?: TransactionOptions; - readTime?: string; - } - interface BatchGetDocumentsResponse { - found?: Document; - missing?: string; - transaction?: string; - readTime?: string; - } - interface BeginTransactionRequest { - options?: TransactionOptions; - } - interface BeginTransactionResponse { - transaction?: string; - } - interface CollectionSelector { - collectionId?: string; - allDescendants?: boolean; - } - interface CommitRequest { - database?: string; - writes?: Write[]; - transaction?: string; - } - interface CommitResponse { - writeResults?: WriteResult[]; - commitTime?: string; - } - interface CompositeFilter { - op?: CompositeFilterOp; - filters?: Filter[]; - } - interface Cursor { - values?: Value[]; - before?: boolean; - } - interface Document { - name?: string; - fields?: ApiClientObjectMap; - createTime?: Timestamp_2; - updateTime?: Timestamp_2; - } - interface DocumentChange { - document?: Document; - targetIds?: number[]; - removedTargetIds?: number[]; - } - interface DocumentDelete { - document?: string; - removedTargetIds?: number[]; - readTime?: Timestamp_2; - } - interface DocumentMask { - fieldPaths?: string[]; - } - interface DocumentRemove { - document?: string; - removedTargetIds?: number[]; - readTime?: string; - } - interface DocumentTransform { - document?: string; - fieldTransforms?: FieldTransform[]; - } - interface DocumentsTarget { - documents?: string[]; - } - interface Empty {} - interface ExistenceFilter { - targetId?: number; - count?: number; - } - interface FieldFilter { - field?: FieldReference; - op?: FieldFilterOp; - value?: Value; - } - interface FieldReference { - fieldPath?: string; - } - interface FieldTransform { - fieldPath?: string; - setToServerValue?: FieldTransformSetToServerValue; - appendMissingElements?: ArrayValue; - removeAllFromArray?: ArrayValue; - increment?: Value; - } - interface Filter { - compositeFilter?: CompositeFilter; - fieldFilter?: FieldFilter; - unaryFilter?: UnaryFilter; - } - interface Index { - name?: string; - collectionId?: string; - fields?: IndexField[]; - state?: IndexState; - } - interface IndexField { - fieldPath?: string; - mode?: IndexFieldMode; - } - interface LatLng { - latitude?: number; - longitude?: number; - } - interface ListCollectionIdsRequest { - pageSize?: number; - pageToken?: string; - } - interface ListCollectionIdsResponse { - collectionIds?: string[]; - nextPageToken?: string; - } - interface ListDocumentsResponse { - documents?: Document[]; - nextPageToken?: string; - } - interface ListIndexesResponse { - indexes?: Index[]; - nextPageToken?: string; - } - interface ListenRequest { - addTarget?: Target; - removeTarget?: number; - labels?: ApiClientObjectMap; - } - interface ListenResponse { - targetChange?: TargetChange; - documentChange?: DocumentChange; - documentDelete?: DocumentDelete; - documentRemove?: DocumentRemove; - filter?: ExistenceFilter; - } - interface MapValue { - fields?: ApiClientObjectMap; - } - interface Operation { - name?: string; - metadata?: ApiClientObjectMap; - done?: boolean; - error?: Status; - response?: ApiClientObjectMap; - } - interface Order { - field?: FieldReference; - direction?: OrderDirection; - } - interface Precondition { - exists?: boolean; - updateTime?: Timestamp_2; - } - interface Projection { - fields?: FieldReference[]; - } - interface QueryTarget { - parent?: string; - structuredQuery?: StructuredQuery; - } - interface ReadOnly { - readTime?: string; - } - interface ReadWrite { - retryTransaction?: string; - } - interface RollbackRequest { - transaction?: string; - } - interface RunQueryRequest { - parent?: string; - structuredQuery?: StructuredQuery; - transaction?: string; - newTransaction?: TransactionOptions; - readTime?: string; - } - interface RunQueryResponse { - transaction?: string; - document?: Document; - readTime?: string; - skippedResults?: number; - } - interface Status { - code?: number; - message?: string; - details?: Array>; - } - interface StructuredQuery { - select?: Projection; - from?: CollectionSelector[]; - where?: Filter; - orderBy?: Order[]; - startAt?: Cursor; - endAt?: Cursor; - offset?: number; - limit?: - | number - | { - value: number; - }; - } - interface Target { - query?: QueryTarget; - documents?: DocumentsTarget; - resumeToken?: string | Uint8Array; - readTime?: Timestamp_2; - targetId?: number; - once?: boolean; - } - interface TargetChange { - targetChangeType?: TargetChangeTargetChangeType; - targetIds?: number[]; - cause?: Status; - resumeToken?: string | Uint8Array; - readTime?: Timestamp_2; - } - interface TransactionOptions { - readOnly?: ReadOnly; - readWrite?: ReadWrite; - } - interface UnaryFilter { - op?: UnaryFilterOp; - field?: FieldReference; - } - interface Value { - nullValue?: ValueNullValue; - booleanValue?: boolean; - integerValue?: string | number; - doubleValue?: string | number; - timestampValue?: Timestamp_2; - stringValue?: string; - bytesValue?: string | Uint8Array; - referenceValue?: string; - geoPointValue?: LatLng; - arrayValue?: ArrayValue; - mapValue?: MapValue; - } - interface Write { - update?: Document; - delete?: string; - verify?: string; - transform?: DocumentTransform; - updateMask?: DocumentMask; - updateTransforms?: FieldTransform[]; - currentDocument?: Precondition; - } - interface WriteRequest { - streamId?: string; - writes?: Write[]; - streamToken?: string | Uint8Array; - labels?: ApiClientObjectMap; - } - interface WriteResponse { - streamId?: string; - streamToken?: string | Uint8Array; - writeResults?: WriteResult[]; - commitTime?: Timestamp_2; - } - interface WriteResult { - updateTime?: Timestamp_2; - transformResults?: Value[]; - } -} - -declare interface FirstPartyCredentialsSettings { - ['type']: 'gapi'; - ['client']: unknown; - ['sessionIndex']: string; -} - -/** - * @license - * Copyright 2017 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. - */ -declare type FulfilledHandler = - | ((result: T) => R | PersistencePromise) - | null; - -/** - * Interface implemented by the LRU scheduler to start(), stop() and restart - * garbage collection. - */ -declare interface GarbageCollectionScheduler { - readonly started: boolean; - start(localStore: LocalStore): void; - stop(): void; -} - -/** - * @license - * Copyright 2017 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. - */ -/** - * An immutable object representing a geographic location in Firestore. The - * location is represented as latitude/longitude pair. - * - * Latitude values are in the range of [-90, 90]. - * Longitude values are in the range of [-180, 180]. - */ -export declare class GeoPoint { - private _lat; - private _long; - /** - * Creates a new immutable `GeoPoint` object with the provided latitude and - * longitude values. - * @param latitude - The latitude as number between -90 and 90. - * @param longitude - The longitude as number between -180 and 180. - */ - constructor(latitude: number, longitude: number); - /** - * The latitude of this `GeoPoint` instance. - */ - get latitude(): number; - /** - * The longitude of this `GeoPoint` instance. - */ - get longitude(): number; - /** - * Returns true if this `GeoPoint` is equal to the provided one. - * - * @param other - The `GeoPoint` to compare against. - * @returns true if this `GeoPoint` is equal to the provided one. - */ - isEqual(other: GeoPoint): boolean; - toJSON(): { - latitude: number; - longitude: number; - }; - /** - * Actually private to JS consumers of our API, so this function is prefixed - * with an underscore. - */ - _compareTo(other: GeoPoint): number; -} - -/** - * Reads the document referred to by this `DocumentReference`. - * - * Note: `getDoc()` attempts to provide up-to-date data when possible by waiting - * for data from the server, but it may return cached data or fail if you are - * offline and the server cannot be reached. To specify this behavior, invoke - * {@link getDocFromCache} or {@link getDocFromServer}. - * - * @param reference - The reference of the document to fetch. - * @returns A Promise resolved with a `DocumentSnapshot` containing the - * current document contents. - */ -export declare function getDoc( - reference: DocumentReference -): Promise>; - -/** - * Reads the document referred to by this `DocumentReference` from cache. - * Returns an error if the document is not currently cached. - * - * @returns A Promise resolved with a `DocumentSnapshot` containing the - * current document contents. - */ -export declare function getDocFromCache( - reference: DocumentReference -): Promise>; - -/** - * Reads the document referred to by this `DocumentReference` from the server. - * Returns an error if the network is not available. - * - * @returns A Promise resolved with a `DocumentSnapshot` containing the - * current document contents. - */ -export declare function getDocFromServer( - reference: DocumentReference -): Promise>; - -/** - * Executes the query and returns the results as a `QuerySnapshot`. - * - * Note: `getDocs()` attempts to provide up-to-date data when possible by - * waiting for data from the server, but it may return cached data or fail if - * you are offline and the server cannot be reached. To specify this behavior, - * invoke {@link getDocsFromCache} or {@link getDocsFromServer}. - * - * @returns A Promise that will be resolved with the results of the query. - */ -export declare function getDocs(query: Query): Promise>; - -/** - * Executes the query and returns the results as a `QuerySnapshot` from cache. - * Returns an error if the document is not currently cached. - * - * @returns A Promise that will be resolved with the results of the query. - */ -export declare function getDocsFromCache( - query: Query -): Promise>; - -/** - * Executes the query and returns the results as a `QuerySnapshot` from the - * server. Returns an error if the network is not available. - * - * @returns A Promise that will be resolved with the results of the query. - */ -export declare function getDocsFromServer( - query: Query -): Promise>; - -/** - * Returns the existing instance of Firestore that is associated with the - * provided {@link FirebaseApp}. If no instance exists, initializes a new - * instance with default settings. - * - * @param app - The {@link FirebaseApp} instance that the returned Firestore - * instance is associated with. - * @returns The `Firestore` instance of the provided app. - */ -export declare function getFirestore(app: FirebaseApp): FirebaseFirestore; - -/** - * Returns a special value that can be used with {@link setDoc} or {@link - * updateDoc} that tells the server to increment the field's current value by - * the given value. - * - * If either the operand or the current field value uses floating point - * precision, all arithmetic follows IEEE 754 semantics. If both values are - * integers, values outside of JavaScript's safe number range - * (`Number.MIN_SAFE_INTEGER` to `Number.MAX_SAFE_INTEGER`) are also subject to - * precision loss. Furthermore, once processed by the Firestore backend, all - * integer operations are capped between -2^63 and 2^63-1. - * - * If the current field value is not of type `number`, or if the field does not - * yet exist, the transformation sets the field to the given value. - * - * @param n - The value to increment by. - * @returns The `FieldValue` sentinel for use in a call to `setDoc()` or - * `updateDoc()` - */ -export declare function increment(n: number): FieldValue; - -declare type IndexFieldMode = 'MODE_UNSPECIFIED' | 'ASCENDING' | 'DESCENDING'; - -/** - * Represents a set of indexes that are used to execute queries efficiently. - * - * Currently the only index is a [collection id] => [parent path] index, used - * to execute Collection Group queries. - */ -declare interface IndexManager { - /** - * Creates an index entry mapping the collectionId (last segment of the path) - * to the parent path (either the containing document location or the empty - * path for root-level collections). Index entries can be retrieved via - * getCollectionParents(). - * - * NOTE: Currently we don't remove index entries. If this ends up being an - * issue we can devise some sort of GC strategy. - */ - addToCollectionParentIndex( - transaction: PersistenceTransaction, - collectionPath: ResourcePath - ): PersistencePromise; - /** - * Retrieves all parent locations containing the given collectionId, as a - * list of paths (each path being either a document location or the empty - * path for a root-level collection). - */ - getCollectionParents( - transaction: PersistenceTransaction, - collectionId: string - ): PersistencePromise; -} - -declare type IndexState = 'STATE_UNSPECIFIED' | 'CREATING' | 'READY' | 'ERROR'; - -/** - * Initializes a new instance of Cloud Firestore with the provided settings. - * Can only be called before any other function, including - * {@link getFirestore}. If the custom settings are empty, this function is - * equivalent to calling {@link getFirestore}. - * - * @param app - The {@link FirebaseApp} with which the `Firestore` instance will - * be associated. - * @param settings - A settings object to configure the `Firestore` instance. - * @returns A newly initialized `Firestore` instance. - */ -export declare function initializeFirestore( - app: FirebaseApp, - settings: Settings -): FirebaseFirestore; - -/** - * Creates a `QueryConstraint` that only returns the first matching documents. - * - * @param limit - The maximum number of items to return. - * @returns The created `Query`. - */ -export declare function limit(limit: number): QueryConstraint; - -/** - * Creates a `QueryConstraint` that only returns the last matching documents. - * - * You must specify at least one `orderBy` clause for `limitToLast` queries, - * otherwise an exception will be thrown during execution. - * - * @param limit - The maximum number of items to return. - * @returns The created `Query`. - */ -export declare function limitToLast(limit: number): QueryConstraint; - -declare const enum LimitType { - First = 'F', - Last = 'L' -} - -/** LimitType enum. */ -declare type LimitType_2 = 'FIRST' | 'LAST'; - -declare type ListenSequenceNumber = number; - -declare class LLRBEmptyNode { - get key(): never; - get value(): never; - get color(): never; - get left(): never; - get right(): never; - size: number; - copy( - key: K | null, - value: V | null, - color: boolean | null, - left: LLRBNode | LLRBEmptyNode | null, - right: LLRBNode | LLRBEmptyNode | null - ): LLRBEmptyNode; - insert(key: K, value: V, comparator: Comparator): LLRBNode; - remove(key: K, comparator: Comparator): LLRBEmptyNode; - isEmpty(): boolean; - inorderTraversal(action: (k: K, v: V) => boolean): boolean; - reverseTraversal(action: (k: K, v: V) => boolean): boolean; - minKey(): K | null; - maxKey(): K | null; - isRed(): boolean; - checkMaxDepth(): boolean; - protected check(): 0; -} - -declare class LLRBNode { - key: K; - value: V; - readonly color: boolean; - readonly left: LLRBNode | LLRBEmptyNode; - readonly right: LLRBNode | LLRBEmptyNode; - readonly size: number; - static EMPTY: LLRBEmptyNode; - static RED: boolean; - static BLACK: boolean; - constructor( - key: K, - value: V, - color?: boolean, - left?: LLRBNode | LLRBEmptyNode, - right?: LLRBNode | LLRBEmptyNode - ); - copy( - key: K | null, - value: V | null, - color: boolean | null, - left: LLRBNode | LLRBEmptyNode | null, - right: LLRBNode | LLRBEmptyNode | null - ): LLRBNode; - isEmpty(): boolean; - inorderTraversal(action: (k: K, v: V) => T): T; - reverseTraversal(action: (k: K, v: V) => T): T; - private min; - minKey(): K | null; - maxKey(): K | null; - insert(key: K, value: V, comparator: Comparator): LLRBNode; - private removeMin; - remove( - key: K, - comparator: Comparator - ): LLRBNode | LLRBEmptyNode; - isRed(): boolean; - private fixUp; - private moveRedLeft; - private moveRedRight; - private rotateLeft; - private rotateRight; - private colorFlip; - checkMaxDepth(): boolean; - protected check(): number; -} - -/** - * Loads a Firestore bundle into the local cache. - * - * @param firestore - The `Firestore` instance to load bundles for for. - * @param bundleData - An object representing the bundle to be loaded. Valid objects are - * `ArrayBuffer`, `ReadableStream` or `string`. - * - * @return - * A `LoadBundleTask` object, which notifies callers with progress updates, and completion - * or error events. It can be used as a `Promise`. - */ -export declare function loadBundle( - firestore: FirebaseFirestore, - bundleData: ReadableStream | ArrayBuffer | string -): LoadBundleTask; - -/** - * Represents the task of loading a Firestore bundle. It provides progress of bundle - * loading, as well as task completion and error events. - * - * The API is compatible with `Promise`. - */ -export declare class LoadBundleTask - implements PromiseLike -{ - private _progressObserver; - private _taskCompletionResolver; - private _lastProgress; - /** - * Registers functions to listen to bundle loading progress events. - * @param next - Called when there is a progress update from bundle loading. Typically `next` calls occur - * each time a Firestore document is loaded from the bundle. - * @param error - Called when an error occurs during bundle loading. The task aborts after reporting the - * error, and there should be no more updates after this. - * @param complete - Called when the loading task is complete. - */ - onProgress( - next?: (progress: LoadBundleTaskProgress) => unknown, - error?: (err: Error) => unknown, - complete?: () => void - ): void; - /** - * Implements the `Promise.catch` interface. - * - * @param onRejected - Called when an error occurs during bundle loading. - */ - catch( - onRejected: (a: Error) => R | PromiseLike - ): Promise; - /** - * Implements the `Promise.then` interface. - * - * @param onFulfilled - Called on the completion of the loading task with a final `LoadBundleTaskProgress` update. - * The update will always have its `taskState` set to `"Success"`. - * @param onRejected - Called when an error occurs during bundle loading. - */ - then( - onFulfilled?: (a: LoadBundleTaskProgress) => T | PromiseLike, - onRejected?: (a: Error) => R | PromiseLike - ): Promise; - /** - * Notifies all observers that bundle loading has completed, with a provided - * `LoadBundleTaskProgress` object. - * - * @private - */ - _completeWith(progress: LoadBundleTaskProgress): void; - /** - * Notifies all observers that bundle loading has failed, with a provided - * `Error` as the reason. - * - * @private - */ - _failWith(error: FirestoreError): void; - /** - * Notifies a progress update of loading a bundle. - * @param progress - The new progress. - * - * @private - */ - _updateProgress(progress: LoadBundleTaskProgress): void; -} - -/** - * Represents a progress update or a final state from loading bundles. - */ -export declare interface LoadBundleTaskProgress { - /** How many documents have been loaded. */ - documentsLoaded: number; - /** How many documents are in the bundle being loaded. */ - totalDocuments: number; - /** How many bytes have been loaded. */ - bytesLoaded: number; - /** How many bytes are in the bundle being loaded. */ - totalBytes: number; - /** Current task state. */ - taskState: TaskState; -} - -declare interface LocalStore { - collectGarbage(garbageCollector: LruGarbageCollector): Promise; -} -export { LogLevel }; - -declare interface LruGarbageCollector { - readonly params: LruParams; - collect( - txn: PersistenceTransaction, - activeTargetIds: ActiveTargets - ): PersistencePromise; - /** Given a percentile of target to collect, returns the number of targets to collect. */ - calculateTargetCount( - txn: PersistenceTransaction, - percentile: number - ): PersistencePromise; - /** Returns the nth sequence number, counting in order from the smallest. */ - nthSequenceNumber( - txn: PersistenceTransaction, - n: number - ): PersistencePromise; - /** - * Removes documents that have a sequence number equal to or less than the - * upper bound and are not otherwise pinned. - */ - removeOrphanedDocuments( - txn: PersistenceTransaction, - upperBound: ListenSequenceNumber - ): PersistencePromise; - getCacheSize(txn: PersistenceTransaction): PersistencePromise; - /** - * Removes targets with a sequence number equal to or less than the given - * upper bound, and removes document associations with those targets. - */ - removeTargets( - txn: PersistenceTransaction, - upperBound: ListenSequenceNumber, - activeTargetIds: ActiveTargets - ): PersistencePromise; -} - -declare class LruParams { - readonly cacheSizeCollectionThreshold: number; - readonly percentileToCollect: number; - readonly maximumSequenceNumbersToCollect: number; - private static readonly DEFAULT_COLLECTION_PERCENTILE; - private static readonly DEFAULT_MAX_SEQUENCE_NUMBERS_TO_COLLECT; - static withCacheSize(cacheSize: number): LruParams; - static readonly DEFAULT: LruParams; - static readonly DISABLED: LruParams; - constructor( - cacheSizeCollectionThreshold: number, - percentileToCollect: number, - maximumSequenceNumbersToCollect: number - ); -} - -/** - * Describes the results of a garbage collection run. `didRun` will be set to - * `false` if collection was skipped (either it is disabled or the cache size - * has not hit the threshold). If collection ran, the other fields will be - * filled in with the details of the results. - */ -declare interface LruResults { - readonly didRun: boolean; - readonly sequenceNumbersCollected: number; - readonly targetsRemoved: number; - readonly documentsRemoved: number; -} - -declare type MapValue = firestoreV1ApiClientInterfaces.MapValue; - -/** - * The result of a lookup for a given path may be an existing document or a - * marker that this document does not exist at a given version. - */ -declare abstract class MaybeDocument { - readonly key: DocumentKey; - readonly version: SnapshotVersion; - constructor(key: DocumentKey, version: SnapshotVersion); - /** - * Whether this document had a local mutation applied that has not yet been - * acknowledged by Watch. - */ - abstract get hasPendingWrites(): boolean; - abstract isEqual(other: MaybeDocument | null | undefined): boolean; - abstract toString(): string; -} - -declare type MaybeDocumentMap = SortedMap; - -/** - * A mutation describes a self-contained change to a document. Mutations can - * create, replace, delete, and update subsets of documents. - * - * Mutations not only act on the value of the document but also its version. - * - * For local mutations (mutations that haven't been committed yet), we preserve - * the existing version for Set and Patch mutations. For Delete mutations, we - * reset the version to 0. - * - * Here's the expected transition table. - * - * MUTATION APPLIED TO RESULTS IN - * - * SetMutation Document(v3) Document(v3) - * SetMutation NoDocument(v3) Document(v0) - * SetMutation null Document(v0) - * PatchMutation Document(v3) Document(v3) - * PatchMutation NoDocument(v3) NoDocument(v3) - * PatchMutation null null - * DeleteMutation Document(v3) NoDocument(v0) - * DeleteMutation NoDocument(v3) NoDocument(v0) - * DeleteMutation null NoDocument(v0) - * - * For acknowledged mutations, we use the updateTime of the WriteResponse as - * the resulting version for Set and Patch mutations. As deletes have no - * explicit update time, we use the commitTime of the WriteResponse for - * Delete mutations. - * - * If a mutation is acknowledged by the backend but fails the precondition check - * locally, we return an `UnknownDocument` and rely on Watch to send us the - * updated version. - * - * Field transforms are used only with Patch and Set Mutations. We use the - * `updateTransforms` message to store transforms, rather than the `transforms`s - * messages. - * - * ## Subclassing Notes - * - * Subclasses of Mutation need to implement applyToRemoteDocument() and - * applyToLocalView() to implement the actual behavior of applying the mutation - * to some source document. - */ -declare abstract class Mutation { - abstract readonly type: MutationType; - abstract readonly key: DocumentKey; - abstract readonly precondition: Precondition; - abstract readonly fieldTransforms: FieldTransform[]; -} - -/** - * A batch of mutations that will be sent as one unit to the backend. - */ -declare class MutationBatch { - batchId: BatchId; - localWriteTime: Timestamp; - baseMutations: Mutation[]; - mutations: Mutation[]; - /** - * @param batchId - The unique ID of this mutation batch. - * @param localWriteTime - The original write time of this mutation. - * @param baseMutations - Mutations that are used to populate the base - * values when this mutation is applied locally. This can be used to locally - * overwrite values that are persisted in the remote document cache. Base - * mutations are never sent to the backend. - * @param mutations - The user-provided mutations in this mutation batch. - * User-provided mutations are applied both locally and remotely on the - * backend. - */ - constructor( - batchId: BatchId, - localWriteTime: Timestamp, - baseMutations: Mutation[], - mutations: Mutation[] - ); - /** - * Applies all the mutations in this MutationBatch to the specified document - * to create a new remote document - * - * @param docKey - The key of the document to apply mutations to. - * @param maybeDoc - The document to apply mutations to. - * @param batchResult - The result of applying the MutationBatch to the - * backend. - */ - applyToRemoteDocument( - docKey: DocumentKey, - maybeDoc: MaybeDocument | null, - batchResult: MutationBatchResult - ): MaybeDocument | null; - /** - * Computes the local view of a document given all the mutations in this - * batch. - * - * @param docKey - The key of the document to apply mutations to. - * @param maybeDoc - The document to apply mutations to. - */ - applyToLocalView( - docKey: DocumentKey, - maybeDoc: MaybeDocument | null - ): MaybeDocument | null; - /** - * Computes the local view for all provided documents given the mutations in - * this batch. - */ - applyToLocalDocumentSet(maybeDocs: MaybeDocumentMap): MaybeDocumentMap; - keys(): DocumentKeySet; - isEqual(other: MutationBatch): boolean; -} - -/** The result of applying a mutation batch to the backend. */ -declare class MutationBatchResult { - readonly batch: MutationBatch; - readonly commitVersion: SnapshotVersion; - readonly mutationResults: MutationResult[]; - /** - * A pre-computed mapping from each mutated document to the resulting - * version. - */ - readonly docVersions: DocumentVersionMap; - private constructor(); - /** - * Creates a new MutationBatchResult for the given batch and results. There - * must be one result for each mutation in the batch. This static factory - * caches a document=>version mapping (docVersions). - */ - static from( - batch: MutationBatch, - commitVersion: SnapshotVersion, - results: MutationResult[] - ): MutationBatchResult; -} - -/** A queue of mutations to apply to the remote store. */ -declare interface MutationQueue { - /** Returns true if this queue contains no mutation batches. */ - checkEmpty(transaction: PersistenceTransaction): PersistencePromise; - /** - * Creates a new mutation batch and adds it to this mutation queue. - * - * @param transaction - The transaction this operation is scoped to. - * @param localWriteTime - The original write time of this mutation. - * @param baseMutations - Mutations that are used to populate the base values - * when this mutation is applied locally. These mutations are used to locally - * overwrite values that are persisted in the remote document cache. - * @param mutations - The user-provided mutations in this mutation batch. - */ - addMutationBatch( - transaction: PersistenceTransaction, - localWriteTime: Timestamp, - baseMutations: Mutation[], - mutations: Mutation[] - ): PersistencePromise; - /** - * Loads the mutation batch with the given batchId. - */ - lookupMutationBatch( - transaction: PersistenceTransaction, - batchId: BatchId - ): PersistencePromise; - /** - * Gets the first unacknowledged mutation batch after the passed in batchId - * in the mutation queue or null if empty. - * - * @param batchId - The batch to search after, or BATCHID_UNKNOWN for the - * first mutation in the queue. - * - * @returns the next mutation or null if there wasn't one. - */ - getNextMutationBatchAfterBatchId( - transaction: PersistenceTransaction, - batchId: BatchId - ): PersistencePromise; - /** - * Gets the largest (latest) batch id in mutation queue for the current user - * that is pending server response, returns `BATCHID_UNKNOWN` if the queue is - * empty. - * - * @returns the largest batch id in the mutation queue that is not - * acknowledged. - */ - getHighestUnacknowledgedBatchId( - transaction: PersistenceTransaction - ): PersistencePromise; - /** Gets all mutation batches in the mutation queue. */ - getAllMutationBatches( - transaction: PersistenceTransaction - ): PersistencePromise; - /** - * Finds all mutation batches that could possibly affect the given - * document key. Not all mutations in a batch will necessarily affect the - * document key, so when looping through the batch you'll need to check that - * the mutation itself matches the key. - * - * Batches are guaranteed to be in sorted order. - * - * Note that because of this requirement implementations are free to return - * mutation batches that don't contain the document key at all if it's - * convenient. - */ - getAllMutationBatchesAffectingDocumentKey( - transaction: PersistenceTransaction, - documentKey: DocumentKey - ): PersistencePromise; - /** - * Finds all mutation batches that could possibly affect the given set of - * document keys. Not all mutations in a batch will necessarily affect each - * key, so when looping through the batch you'll need to check that the - * mutation itself matches the key. - * - * Batches are guaranteed to be in sorted order. - * - * Note that because of this requirement implementations are free to return - * mutation batches that don't contain any of the document keys at all if it's - * convenient. - */ - getAllMutationBatchesAffectingDocumentKeys( - transaction: PersistenceTransaction, - documentKeys: SortedMap - ): PersistencePromise; - /** - * Finds all mutation batches that could affect the results for the given - * query. Not all mutations in a batch will necessarily affect the query, so - * when looping through the batch you'll need to check that the mutation - * itself matches the query. - * - * Batches are guaranteed to be in sorted order. - * - * Note that because of this requirement implementations are free to return - * mutation batches that don't match the query at all if it's convenient. - * - * NOTE: A PatchMutation does not need to include all fields in the query - * filter criteria in order to be a match (but any fields it does contain do - * need to match). - */ - getAllMutationBatchesAffectingQuery( - transaction: PersistenceTransaction, - query: Query_2 - ): PersistencePromise; - /** - * Removes the given mutation batch from the queue. This is useful in two - * circumstances: - * - * + Removing an applied mutation from the head of the queue - * + Removing a rejected mutation from anywhere in the queue - * - * Multi-Tab Note: This operation should only be called by the primary client. - */ - removeMutationBatch( - transaction: PersistenceTransaction, - batch: MutationBatch - ): PersistencePromise; - /** - * Performs a consistency check, examining the mutation queue for any - * leaks, if possible. - */ - performConsistencyCheck( - transaction: PersistenceTransaction - ): PersistencePromise; -} - -/** The result of successfully applying a mutation to the backend. */ -declare class MutationResult { - /** - * The version at which the mutation was committed: - * - * - For most operations, this is the updateTime in the WriteResult. - * - For deletes, the commitTime of the WriteResponse (because deletes are - * not stored and have no updateTime). - * - * Note that these versions can be different: No-op writes will not change - * the updateTime even though the commitTime advances. - */ - readonly version: SnapshotVersion; - /** - * The resulting fields returned from the backend after a mutation - * containing field transforms has been committed. Contains one FieldValue - * for each FieldTransform that was in the mutation. - * - * Will be null if the mutation did not contain any field transforms. - */ - readonly transformResults: Array | null; - constructor( - /** - * The version at which the mutation was committed: - * - * - For most operations, this is the updateTime in the WriteResult. - * - For deletes, the commitTime of the WriteResponse (because deletes are - * not stored and have no updateTime). - * - * Note that these versions can be different: No-op writes will not change - * the updateTime even though the commitTime advances. - */ - version: SnapshotVersion, - /** - * The resulting fields returned from the backend after a mutation - * containing field transforms has been committed. Contains one FieldValue - * for each FieldTransform that was in the mutation. - * - * Will be null if the mutation did not contain any field transforms. - */ - transformResults: Array | null - ); -} - -declare const enum MutationType { - Set = 0, - Patch = 1, - Delete = 2, - Verify = 3 -} - -/** - * Represents a Query saved by the SDK in its local storage. - */ -declare interface NamedQuery { - /** The name of the query. */ - readonly name: string; - /** The underlying query associated with `name`. */ - readonly query: Query_2; - /** The time at which the results for this query were read. */ - readonly readTime: SnapshotVersion; -} - -/** - * Reads a Firestore `Query` from local cache, identified by the given name. - * - * The named queries are packaged into bundles on the server side (along - * with resulting documents), and loaded to local cache using `loadBundle`. Once in local - * cache, use this method to extract a `Query` by name. - */ -export declare function namedQuery( - firestore: FirebaseFirestore, - name: string -): Promise; - -/** Properties of a NamedQuery. */ -declare interface NamedQuery_2 { - /** NamedQuery name */ - name?: string | null; - /** NamedQuery bundledQuery */ - bundledQuery?: BundledQuery | null; - /** NamedQuery readTime */ - readTime?: Timestamp_2 | null; -} - -declare type NullableMaybeDocumentMap = SortedMap< - DocumentKey, - MaybeDocument | null ->; - -/** - * @license - * Copyright 2017 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. - */ -/** - * A map implementation that uses objects as keys. Objects must have an - * associated equals function and must be immutable. Entries in the map are - * stored together with the key being produced from the mapKeyFn. This map - * automatically handles collisions of keys. - */ -declare class ObjectMap { - private mapKeyFn; - private equalsFn; - /** - * The inner map for a key/value pair. Due to the possibility of collisions we - * keep a list of entries that we do a linear search through to find an actual - * match. Note that collisions should be rare, so we still expect near - * constant time lookups in practice. - */ - private inner; - constructor( - mapKeyFn: (key: KeyType) => string, - equalsFn: (l: KeyType, r: KeyType) => boolean - ); - /** Get a value for this key, or undefined if it does not exist. */ - get(key: KeyType): ValueType | undefined; - has(key: KeyType): boolean; - /** Put this key and value in the map. */ - set(key: KeyType, value: ValueType): void; - /** - * Remove this key from the map. Returns a boolean if anything was deleted. - */ - delete(key: KeyType): boolean; - forEach(fn: (key: KeyType, val: ValueType) => void): void; - isEmpty(): boolean; -} - -/** - * An ObjectValue represents a MapValue in the Firestore Proto and offers the - * ability to add and remove fields (via the ObjectValueBuilder). - */ -declare class ObjectValue { - readonly proto: { - mapValue: MapValue; - }; - constructor(proto: { mapValue: MapValue }); - static empty(): ObjectValue; - /** - * Returns the value at the given path or null. - * - * @param path - the path to search - * @returns The value at the path or if there it doesn't exist. - */ - field(path: FieldPath_2): Value | null; - isEqual(other: ObjectValue): boolean; -} - -/** - * Initializes and wires components that are needed to interface with the local - * cache. Implementations override `initialize()` to provide all components. - */ -declare interface OfflineComponentProvider { - persistence: Persistence; - sharedClientState: SharedClientState; - localStore: LocalStore; - gcScheduler: GarbageCollectionScheduler | null; - synchronizeTabs: boolean; - initialize(cfg: ComponentConfiguration): Promise; - terminate(): Promise; -} - -/** - * Initializes and wires the components that are needed to interface with the - * network. - */ -declare class OnlineComponentProvider { - protected localStore: LocalStore; - protected sharedClientState: SharedClientState; - datastore: Datastore; - eventManager: EventManager; - remoteStore: RemoteStore; - syncEngine: SyncEngine; - initialize( - offlineComponentProvider: OfflineComponentProvider, - cfg: ComponentConfiguration - ): Promise; - createEventManager(cfg: ComponentConfiguration): EventManager; - createDatastore(cfg: ComponentConfiguration): Datastore; - createRemoteStore(cfg: ComponentConfiguration): RemoteStore; - createSyncEngine( - cfg: ComponentConfiguration, - startAsPrimary: boolean - ): SyncEngine; - terminate(): Promise; -} - -/** - * Describes the online state of the Firestore client. Note that this does not - * indicate whether or not the remote store is trying to connect or not. This is - * primarily used by the View / EventManager code to change their behavior while - * offline (e.g. get() calls shouldn't wait for data from the server and - * snapshot events should set metadata.isFromCache=true). - * - * The string values should not be changed since they are persisted in - * WebStorage. - */ -declare const enum OnlineState { - /** - * The Firestore client is in an unknown online state. This means the client - * is either not actively trying to establish a connection or it is currently - * trying to establish a connection, but it has not succeeded or failed yet. - * Higher-level components should not operate in offline mode. - */ - Unknown = 'Unknown', - /** - * The client is connected and the connections are healthy. This state is - * reached after a successful connection and there has been at least one - * successful message received from the backends. - */ - Online = 'Online', - /** - * The client is either trying to establish a connection but failing, or it - * has been explicitly marked offline via a call to disableNetwork(). - * Higher-level components should operate in offline mode. - */ - Offline = 'Offline' -} - -/** - * Attaches a listener for `DocumentSnapshot` events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single observer - * object with `next` and `error` callbacks. - * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. - * - * @param reference - A reference to the document to listen to. - * @param observer - A single object containing `next` and `error` callbacks. - * @returns An unsubscribe function that can be called to cancel - * the snapshot listener. - */ -export declare function onSnapshot( - reference: DocumentReference, - observer: { - next?: (snapshot: DocumentSnapshot) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; - } -): Unsubscribe; - -/** - * Attaches a listener for `DocumentSnapshot` events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single observer - * object with `next` and `error` callbacks. - * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. - * - * @param reference - A reference to the document to listen to. - * @param options - Options controlling the listen behavior. - * @param observer - A single object containing `next` and `error` callbacks. - * @returns An unsubscribe function that can be called to cancel - * the snapshot listener. - */ -export declare function onSnapshot( - reference: DocumentReference, - options: SnapshotListenOptions, - observer: { - next?: (snapshot: DocumentSnapshot) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; - } -): Unsubscribe; - -/** - * Attaches a listener for `DocumentSnapshot` events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single observer - * object with `next` and `error` callbacks. - * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. - * - * @param reference - A reference to the document to listen to. - * @param onNext - A callback to be called every time a new `DocumentSnapshot` - * is available. - * @param onError - A callback to be called if the listen fails or is - * cancelled. No further callbacks will occur. - * @param onCompletion - Can be provided, but will not be called since streams are - * never ending. - * @returns An unsubscribe function that can be called to cancel - * the snapshot listener. - */ -export declare function onSnapshot( - reference: DocumentReference, - onNext: (snapshot: DocumentSnapshot) => void, - onError?: (error: FirestoreError) => void, - onCompletion?: () => void -): Unsubscribe; - -/** - * Attaches a listener for `DocumentSnapshot` events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single observer - * object with `next` and `error` callbacks. - * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. - * - * @param reference - A reference to the document to listen to. - * @param options - Options controlling the listen behavior. - * @param onNext - A callback to be called every time a new `DocumentSnapshot` - * is available. - * @param onError - A callback to be called if the listen fails or is - * cancelled. No further callbacks will occur. - * @param onCompletion - Can be provided, but will not be called since streams are - * never ending. - * @returns An unsubscribe function that can be called to cancel - * the snapshot listener. - */ -export declare function onSnapshot( - reference: DocumentReference, - options: SnapshotListenOptions, - onNext: (snapshot: DocumentSnapshot) => void, - onError?: (error: FirestoreError) => void, - onCompletion?: () => void -): Unsubscribe; - -/** - * Attaches a listener for `QuerySnapshot` events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single observer - * object with `next` and `error` callbacks. The listener can be cancelled by - * calling the function that is returned when `onSnapshot` is called. - * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. - * - * @param query - The query to listen to. - * @param observer - A single object containing `next` and `error` callbacks. - * @returns An unsubscribe function that can be called to cancel - * the snapshot listener. - */ -export declare function onSnapshot( - query: Query, - observer: { - next?: (snapshot: QuerySnapshot) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; - } -): Unsubscribe; - -/** - * Attaches a listener for `QuerySnapshot` events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single observer - * object with `next` and `error` callbacks. The listener can be cancelled by - * calling the function that is returned when `onSnapshot` is called. - * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. - * - * @param query - The query to listen to. - * @param options - Options controlling the listen behavior. - * @param observer - A single object containing `next` and `error` callbacks. - * @returns An unsubscribe function that can be called to cancel - * the snapshot listener. - */ -export declare function onSnapshot( - query: Query, - options: SnapshotListenOptions, - observer: { - next?: (snapshot: QuerySnapshot) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; - } -): Unsubscribe; - -/** - * Attaches a listener for `QuerySnapshot` events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single observer - * object with `next` and `error` callbacks. The listener can be cancelled by - * calling the function that is returned when `onSnapshot` is called. - * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. - * - * @param query - The query to listen to. - * @param onNext - A callback to be called every time a new `QuerySnapshot` - * is available. - * @param onCompletion - Can be provided, but will not be called since streams are - * never ending. - * @param onError - A callback to be called if the listen fails or is - * cancelled. No further callbacks will occur. - * @returns An unsubscribe function that can be called to cancel - * the snapshot listener. - */ -export declare function onSnapshot( - query: Query, - onNext: (snapshot: QuerySnapshot) => void, - onError?: (error: FirestoreError) => void, - onCompletion?: () => void -): Unsubscribe; - -/** - * Attaches a listener for `QuerySnapshot` events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single observer - * object with `next` and `error` callbacks. The listener can be cancelled by - * calling the function that is returned when `onSnapshot` is called. - * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. - * - * @param query - The query to listen to. - * @param options - Options controlling the listen behavior. - * @param onNext - A callback to be called every time a new `QuerySnapshot` - * is available. - * @param onCompletion - Can be provided, but will not be called since streams are - * never ending. - * @param onError - A callback to be called if the listen fails or is - * cancelled. No further callbacks will occur. - * @returns An unsubscribe function that can be called to cancel - * the snapshot listener. - */ -export declare function onSnapshot( - query: Query, - options: SnapshotListenOptions, - onNext: (snapshot: QuerySnapshot) => void, - onError?: (error: FirestoreError) => void, - onCompletion?: () => void -): Unsubscribe; - -/** - * Attaches a listener for a snapshots-in-sync event. The snapshots-in-sync - * event indicates that all listeners affected by a given change have fired, - * even if a single server-generated change affects multiple listeners. - * - * NOTE: The snapshots-in-sync event only indicates that listeners are in sync - * with each other, but does not relate to whether those snapshots are in sync - * with the server. Use SnapshotMetadata in the individual listeners to - * determine if a snapshot is from the cache or the server. - * - * @param firestore - The instance of Firestore for synchronizing snapshots. - * @param observer - A single object containing `next` and `error` callbacks. - * @returns An unsubscribe function that can be called to cancel the snapshot - * listener. - */ -export declare function onSnapshotsInSync( - firestore: FirebaseFirestore, - observer: { - next?: (value: void) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; - } -): Unsubscribe; - -/** - * Attaches a listener for a snapshots-in-sync event. The snapshots-in-sync - * event indicates that all listeners affected by a given change have fired, - * even if a single server-generated change affects multiple listeners. - * - * NOTE: The snapshots-in-sync event only indicates that listeners are in sync - * with each other, but does not relate to whether those snapshots are in sync - * with the server. Use SnapshotMetadata in the individual listeners to - * determine if a snapshot is from the cache or the server. - * - * @param firestore - The instance of Firestore for synchronizing snapshots. - * @param onSync - A callback to be called every time all snapshot listeners are - * in sync with each other. - * @returns An unsubscribe function that can be called to cancel the snapshot - * listener. - */ -export declare function onSnapshotsInSync( - firestore: FirebaseFirestore, - onSync: () => void -): Unsubscribe; - -/** - * An ordering on a field, in some Direction. Direction defaults to ASCENDING. - */ -declare class OrderBy { - readonly field: FieldPath_2; - readonly dir: Direction; - constructor(field: FieldPath_2, dir?: Direction); -} - -/** - * Creates a `QueryConstraint` that sorts the query result by the - * specified field, optionally in descending order instead of ascending. - * - * @param fieldPath - The field to sort by. - * @param directionStr - Optional direction to sort by ('asc' or 'desc'). If - * not specified, order will be ascending. - * @returns The created `Query`. - */ -export declare function orderBy( - fieldPath: string | FieldPath, - directionStr?: OrderByDirection -): QueryConstraint; - -/** - * The direction of a {@link orderBy} clause is specified as 'desc' or 'asc' - * (descending or ascending). - */ -export declare type OrderByDirection = 'desc' | 'asc'; - -declare type OrderDirection = - | 'DIRECTION_UNSPECIFIED' - | 'ASCENDING' - | 'DESCENDING'; - -declare interface ParseContext { - readonly databaseId: DatabaseId; - readonly ignoreUndefinedProperties: boolean; -} - -/** The result of parsing document data (e.g. for a setData call). */ -declare class ParsedSetData { - readonly data: ObjectValue; - readonly fieldMask: FieldMask | null; - readonly fieldTransforms: FieldTransform[]; - constructor( - data: ObjectValue, - fieldMask: FieldMask | null, - fieldTransforms: FieldTransform[] - ); - toMutation(key: DocumentKey, precondition: Precondition): Mutation; -} - -/** The result of parsing "update" data (i.e. for an updateData call). */ -declare class ParsedUpdateData { - readonly data: ObjectValue; - readonly fieldMask: FieldMask; - readonly fieldTransforms: FieldTransform[]; - constructor( - data: ObjectValue, - fieldMask: FieldMask, - fieldTransforms: FieldTransform[] - ); - toMutation(key: DocumentKey, precondition: Precondition): Mutation; -} - -/** - * Persistence is the lowest-level shared interface to persistent storage in - * Firestore. - * - * Persistence is used to create MutationQueue and RemoteDocumentCache - * instances backed by persistence (which might be in-memory or LevelDB). - * - * Persistence also exposes an API to create and run PersistenceTransactions - * against persistence. All read / write operations must be wrapped in a - * transaction. Implementations of PersistenceTransaction / Persistence only - * need to guarantee that writes made against the transaction are not made to - * durable storage until the transaction resolves its PersistencePromise. - * Since memory-only storage components do not alter durable storage, they are - * free to ignore the transaction. - * - * This contract is enough to allow the LocalStore be be written - * independently of whether or not the stored state actually is durably - * persisted. If persistent storage is enabled, writes are grouped together to - * avoid inconsistent state that could cause crashes. - * - * Concretely, when persistent storage is enabled, the persistent versions of - * MutationQueue, RemoteDocumentCache, and others (the mutators) will - * defer their writes into a transaction. Once the local store has completed - * one logical operation, it commits the transaction. - * - * When persistent storage is disabled, the non-persistent versions of the - * mutators ignore the transaction. This short-cut is allowed because - * memory-only storage leaves no state so it cannot be inconsistent. - * - * This simplifies the implementations of the mutators and allows memory-only - * implementations to supplement the persistent ones without requiring any - * special dual-store implementation of Persistence. The cost is that the - * LocalStore needs to be slightly careful about the order of its reads and - * writes in order to avoid relying on being able to read back uncommitted - * writes. - */ -declare interface Persistence { - /** - * Whether or not this persistence instance has been started. - */ - readonly started: boolean; - readonly referenceDelegate: ReferenceDelegate; - /** Starts persistence. */ - start(): Promise; - /** - * Releases any resources held during eager shutdown. - */ - shutdown(): Promise; - /** - * Registers a listener that gets called when the database receives a - * version change event indicating that it has deleted. - * - * PORTING NOTE: This is only used for Web multi-tab. - */ - setDatabaseDeletedListener( - databaseDeletedListener: () => Promise - ): void; - /** - * Adjusts the current network state in the client's metadata, potentially - * affecting the primary lease. - * - * PORTING NOTE: This is only used for Web multi-tab. - */ - setNetworkEnabled(networkEnabled: boolean): void; - /** - * Returns a MutationQueue representing the persisted mutations for the - * given user. - * - * Note: The implementation is free to return the same instance every time - * this is called for a given user. In particular, the memory-backed - * implementation does this to emulate the persisted implementation to the - * extent possible (e.g. in the case of uid switching from - * sally=>jack=>sally, sally's mutation queue will be preserved). - */ - getMutationQueue(user: User): MutationQueue; - /** - * Returns a TargetCache representing the persisted cache of targets. - * - * Note: The implementation is free to return the same instance every time - * this is called. In particular, the memory-backed implementation does this - * to emulate the persisted implementation to the extent possible. - */ - getTargetCache(): TargetCache; - /** - * Returns a RemoteDocumentCache representing the persisted cache of remote - * documents. - * - * Note: The implementation is free to return the same instance every time - * this is called. In particular, the memory-backed implementation does this - * to emulate the persisted implementation to the extent possible. - */ - getRemoteDocumentCache(): RemoteDocumentCache; - /** - * Returns a BundleCache representing the persisted cache of loaded bundles. - * - * Note: The implementation is free to return the same instance every time - * this is called. In particular, the memory-backed implementation does this - * to emulate the persisted implementation to the extent possible. - */ - getBundleCache(): BundleCache; - /** - * Returns an IndexManager instance that manages our persisted query indexes. - * - * Note: The implementation is free to return the same instance every time - * this is called. In particular, the memory-backed implementation does this - * to emulate the persisted implementation to the extent possible. - */ - getIndexManager(): IndexManager; - /** - * Performs an operation inside a persistence transaction. Any reads or writes - * against persistence must be performed within a transaction. Writes will be - * committed atomically once the transaction completes. - * - * Persistence operations are asynchronous and therefore the provided - * transactionOperation must return a PersistencePromise. When it is resolved, - * the transaction will be committed and the Promise returned by this method - * will resolve. - * - * @param action - A description of the action performed by this transaction, - * used for logging. - * @param mode - The underlying mode of the IndexedDb transaction. Can be - * 'readonly', 'readwrite' or 'readwrite-primary'. Transactions marked - * 'readwrite-primary' can only be executed by the primary client. In this - * mode, the transactionOperation will not be run if the primary lease cannot - * be acquired and the returned promise will be rejected with a - * FAILED_PRECONDITION error. - * @param transactionOperation - The operation to run inside a transaction. - * @returns A promise that is resolved once the transaction completes. - */ - runTransaction( - action: string, - mode: PersistenceTransactionMode, - transactionOperation: ( - transaction: PersistenceTransaction - ) => PersistencePromise - ): Promise; -} - -/** - * PersistencePromise is essentially a re-implementation of Promise except - * it has a .next() method instead of .then() and .next() and .catch() callbacks - * are executed synchronously when a PersistencePromise resolves rather than - * asynchronously (Promise implementations use setImmediate() or similar). - * - * This is necessary to interoperate with IndexedDB which will automatically - * commit transactions if control is returned to the event loop without - * synchronously initiating another operation on the transaction. - * - * NOTE: .then() and .catch() only allow a single consumer, unlike normal - * Promises. - */ -declare class PersistencePromise { - private nextCallback; - private catchCallback; - private result; - private error; - private isDone; - private callbackAttached; - constructor(callback: (resolve: Resolver, reject: Rejector) => void); - catch( - fn: (error: Error) => R | PersistencePromise - ): PersistencePromise; - next( - nextFn?: FulfilledHandler, - catchFn?: RejectedHandler - ): PersistencePromise; - toPromise(): Promise; - private wrapUserFunction; - private wrapSuccess; - private wrapFailure; - static resolve(): PersistencePromise; - static resolve(result: R): PersistencePromise; - static reject(error: Error): PersistencePromise; - static waitFor(all: { - forEach: (cb: (el: PersistencePromise) => void) => void; - }): PersistencePromise; - /** - * Given an array of predicate functions that asynchronously evaluate to a - * boolean, implements a short-circuiting `or` between the results. Predicates - * will be evaluated until one of them returns `true`, then stop. The final - * result will be whether any of them returned `true`. - */ - static or( - predicates: Array<() => PersistencePromise> - ): PersistencePromise; - /** - * Given an iterable, call the given function on each element in the - * collection and wait for all of the resulting concurrent PersistencePromises - * to resolve. - */ - static forEach( - collection: { - forEach: (cb: (r: R, s: S) => void) => void; - }, - f: - | ((r: R, s: S) => PersistencePromise) - | ((r: R) => PersistencePromise) - ): PersistencePromise; - static forEach( - collection: { - forEach: (cb: (r: R) => void) => void; - }, - f: (r: R) => PersistencePromise - ): PersistencePromise; -} - -export declare interface PersistenceSettings { - forceOwnership?: boolean; -} - -/** - * A base class representing a persistence transaction, encapsulating both the - * transaction's sequence numbers as well as a list of onCommitted listeners. - * - * When you call Persistence.runTransaction(), it will create a transaction and - * pass it to your callback. You then pass it to any method that operates - * on persistence. - */ -declare abstract class PersistenceTransaction { - private readonly onCommittedListeners; - abstract readonly currentSequenceNumber: ListenSequenceNumber; - addOnCommittedListener(listener: () => void): void; - raiseOnCommittedEvent(): void; -} - -/** The different modes supported by `Persistence.runTransaction()`. */ -declare type PersistenceTransactionMode = - | 'readonly' - | 'readwrite' - | 'readwrite-primary'; - -/** - * Encodes a precondition for a mutation. This follows the model that the - * backend accepts with the special case of an explicit "empty" precondition - * (meaning no precondition). - */ -declare class Precondition { - readonly updateTime?: SnapshotVersion | undefined; - readonly exists?: boolean | undefined; - private constructor(); - /** Creates a new empty Precondition. */ - static none(): Precondition; - /** Creates a new Precondition with an exists flag. */ - static exists(exists: boolean): Precondition; - /** Creates a new Precondition based on a version a document exists at. */ - static updateTime(version: SnapshotVersion): Precondition; - /** Returns whether this Precondition is empty. */ - get isNone(): boolean; - isEqual(other: Precondition): boolean; -} - -/** Undocumented, private additional settings not exposed in our public API. */ -declare interface PrivateSettings extends Settings_2 { - credentials?: CredentialsSettings; -} - -declare interface ProviderCredentialsSettings { - ['type']: 'provider'; - ['client']: CredentialsProvider; -} - -/** - * A `Query` refers to a Query which you can read or listen to. You can also - * construct refined `Query` objects by adding filters and ordering. - */ -export declare class Query { - readonly _converter: FirestoreDataConverter_2 | null; - readonly _query: Query_2; - /** The type of this Firestore reference. */ - readonly type: 'query' | 'collection'; - /** - * The `FirebaseFirestore` for the Firestore database (useful for performing - * transactions, etc.). - */ - readonly firestore: FirebaseFirestore_2; - /** @hideconstructor protected */ - constructor( - firestore: FirebaseFirestore_2, - _converter: FirestoreDataConverter_2 | null, - _query: Query_2 - ); - /** - * Applies a custom data converter to this query, allowing you to use your own - * custom model objects with Firestore. When you call {@link getDocs} with - * the returned query, the provided converter will convert between Firestore - * data and your custom type `U`. - * - * @param converter - Converts objects to and from Firestore. - * @returns A `Query` that uses the provided converter. - */ - withConverter(converter: FirestoreDataConverter_2): Query; -} - -/** - * Creates a new immutable instance of `query` that is extended to also include - * additional query constraints. - * - * @param query - The query instance to use as a base for the new constraints. - * @param queryConstraints - The list of `QueryConstraint`s to apply. - * @throws if any of the provided query constraints cannot be combined with the - * existing or new constraints. - */ -export declare function query( - query: Query, - ...queryConstraints: QueryConstraint[] -): Query; - -/** - * The Query interface defines all external properties of a query. - * - * QueryImpl implements this interface to provide memoization for `queryOrderBy` - * and `queryToTarget`. - */ -declare interface Query_2 { - readonly path: ResourcePath; - readonly collectionGroup: string | null; - readonly explicitOrderBy: OrderBy[]; - readonly filters: Filter[]; - readonly limit: number | null; - readonly limitType: LimitType; - readonly startAt: Bound | null; - readonly endAt: Bound | null; -} - -/** - * A `QueryConstraint` is used to narrow the set of documents returned by a - * Firestore query. `QueryConstraint`s are created by invoking {@link where}, - * {@link orderBy}, {@link startAt}, {@link startAfter}, {@link - * endBefore}, {@link endAt}, {@link limit} or {@link limitToLast} and - * can then be passed to {@link query} to create a new query instance that - * also contains this `QueryConstraint`. - */ -export declare abstract class QueryConstraint { - /** The type of this query constraints */ - abstract readonly type: QueryConstraintType; - /** - * Takes the provided `Query` and returns a copy of the `Query` with this - * `QueryConstraint` applied. - */ - abstract _apply(query: Query): Query; -} - -/** Describes the different query constraints available in this SDK. */ -export declare type QueryConstraintType = - | 'where' - | 'orderBy' - | 'limit' - | 'limitToLast' - | 'startAt' - | 'startAfter' - | 'endAt' - | 'endBefore'; - -/** - * A `QueryDocumentSnapshot` contains data read from a document in your - * Firestore database as part of a query. The document is guaranteed to exist - * and its data can be extracted with `.data()` or `.get()` to get a - * specific field. - * - * A `QueryDocumentSnapshot` offers the same API surface as a - * `DocumentSnapshot`. Since query results contain only existing documents, the - * `exists` property will always be true and `data()` will never return - * 'undefined'. - */ -export declare class QueryDocumentSnapshot< - T = DocumentData -> extends DocumentSnapshot { - /** - * Retrieves all fields in the document as an `Object`. - * - * By default, `FieldValue.serverTimestamp()` values that have not yet been - * set to their final value will be returned as `null`. You can override - * this by passing an options object. - * - * @override - * @param options - An options object to configure how data is retrieved from - * the snapshot (for example the desired behavior for server timestamps that - * have not yet been set to their final value). - * @returns An `Object` containing all fields in the document. - */ - data(options?: SnapshotOptions): T; -} - -/** - * A `QueryDocumentSnapshot` contains data read from a document in your - * Firestore database as part of a query. The document is guaranteed to exist - * and its data can be extracted with `.data()` or `.get()` to get a - * specific field. - * - * A `QueryDocumentSnapshot` offers the same API surface as a - * `DocumentSnapshot`. Since query results contain only existing documents, the - * `exists` property will always be true and `data()` will never return - * 'undefined'. - */ -declare class QueryDocumentSnapshot_2< - T = DocumentData -> extends DocumentSnapshot_2 { - /** - * Retrieves all fields in the document as an `Object`. - * - * @override - * @returns An `Object` containing all fields in the document. - */ - data(): T; -} - -/** - * Returns true if the provided queries point to the same collection and apply - * the same constraints. - * - * @param left - A `Query` to compare. - * @param right - A `Query` to compare. - * @returns true if the references point to the same location in the same - * Firestore database. - */ -export declare function queryEqual(left: Query, right: Query): boolean; - -/** - * A `QuerySnapshot` contains zero or more `DocumentSnapshot` objects - * representing the results of a query. The documents can be accessed as an - * array via the `docs` property or enumerated using the `forEach` method. The - * number of documents can be determined via the `empty` and `size` - * properties. - */ -export declare class QuerySnapshot { - readonly _firestore: FirebaseFirestore; - readonly _userDataWriter: AbstractUserDataWriter; - readonly _snapshot: ViewSnapshot; - /** - * Metadata about this snapshot, concerning its source and if it has local - * modifications. - */ - readonly metadata: SnapshotMetadata; - /** - * The query on which you called `get` or `onSnapshot` in order to get this - * `QuerySnapshot`. - */ - readonly query: Query; - private _cachedChanges?; - private _cachedChangesIncludeMetadataChanges?; - /** @hideconstructor */ - constructor( - _firestore: FirebaseFirestore, - _userDataWriter: AbstractUserDataWriter, - query: Query, - _snapshot: ViewSnapshot - ); - /** An array of all the documents in the `QuerySnapshot`. */ - get docs(): Array>; - /** The number of documents in the `QuerySnapshot`. */ - get size(): number; - /** True if there are no documents in the `QuerySnapshot`. */ - get empty(): boolean; - /** - * Enumerates all of the documents in the `QuerySnapshot`. - * - * @param callback - A callback to be called with a `QueryDocumentSnapshot` for - * each document in the snapshot. - * @param thisArg - The `this` binding for the callback. - */ - forEach( - callback: (result: QueryDocumentSnapshot) => void, - thisArg?: unknown - ): void; - /** - * Returns an array of the documents changes since the last snapshot. If this - * is the first snapshot, all documents will be in the list as 'added' - * changes. - * - * @param options - `SnapshotListenOptions` that control whether metadata-only - * changes (i.e. only `DocumentSnapshot.metadata` changed) should trigger - * snapshot events. - */ - docChanges(options?: SnapshotListenOptions): Array>; -} - -/** The different states of a watch target. */ -declare type QueryTargetState = 'not-current' | 'current' | 'rejected'; - -/** - * Returns true if the provided references are equal. - * - * @param left - A reference to compare. - * @param right - A reference to compare. - * @returns true if the references point to the same location in the same - * Firestore database. - */ -export declare function refEqual( - left: DocumentReference | CollectionReference, - right: DocumentReference | CollectionReference -): boolean; - -/** - * A ReferenceDelegate instance handles all of the hooks into the document-reference lifecycle. This - * includes being added to a target, being removed from a target, being subject to mutation, and - * being mutated by the user. - * - * Different implementations may do different things with each of these events. Not every - * implementation needs to do something with every lifecycle hook. - * - * PORTING NOTE: since sequence numbers are attached to transactions in this - * client, the ReferenceDelegate does not need to deal in transactional - * semantics (onTransactionStarted/Committed()), nor does it need to track and - * generate sequence numbers (getCurrentSequenceNumber()). - */ -declare interface ReferenceDelegate { - /** Notify the delegate that the given document was added to a target. */ - addReference( - txn: PersistenceTransaction, - targetId: TargetId, - doc: DocumentKey - ): PersistencePromise; - /** Notify the delegate that the given document was removed from a target. */ - removeReference( - txn: PersistenceTransaction, - targetId: TargetId, - doc: DocumentKey - ): PersistencePromise; - /** - * Notify the delegate that a target was removed. The delegate may, but is not obligated to, - * actually delete the target and associated data. - */ - removeTarget( - txn: PersistenceTransaction, - targetData: TargetData - ): PersistencePromise; - /** - * Notify the delegate that a document may no longer be part of any views or - * have any mutations associated. - */ - markPotentiallyOrphaned( - txn: PersistenceTransaction, - doc: DocumentKey - ): PersistencePromise; - /** Notify the delegate that a limbo document was updated. */ - updateLimboDocument( - txn: PersistenceTransaction, - doc: DocumentKey - ): PersistencePromise; -} - -declare type RejectedHandler = - | ((reason: Error) => R | PersistencePromise) - | null; - -declare type Rejector = (error: Error) => void; - -/** - * Represents cached documents received from the remote backend. - * - * The cache is keyed by DocumentKey and entries in the cache are MaybeDocument - * instances, meaning we can cache both Document instances (an actual document - * with data) as well as NoDocument instances (indicating that the document is - * known to not exist). - */ -declare interface RemoteDocumentCache { - /** - * Looks up an entry in the cache. - * - * @param documentKey - The key of the entry to look up.* - * @returns The cached Document or NoDocument entry, or null if we have - * nothing cached. - */ - getEntry( - transaction: PersistenceTransaction, - documentKey: DocumentKey - ): PersistencePromise; - /** - * Looks up a set of entries in the cache. - * - * @param documentKeys - The keys of the entries to look up. - * @returns The cached Document or NoDocument entries indexed by key. If an - * entry is not cached, the corresponding key will be mapped to a null value. - */ - getEntries( - transaction: PersistenceTransaction, - documentKeys: DocumentKeySet - ): PersistencePromise; - /** - * Executes a query against the cached Document entries. - * - * Implementations may return extra documents if convenient. The results - * should be re-filtered by the consumer before presenting them to the user. - * - * Cached NoDocument entries have no bearing on query results. - * - * @param query - The query to match documents against. - * @param sinceReadTime - If not set to SnapshotVersion.min(), return only - * documents that have been read since this snapshot version (exclusive). - * @returns The set of matching documents. - */ - getDocumentsMatchingQuery( - transaction: PersistenceTransaction, - query: Query_2, - sinceReadTime: SnapshotVersion - ): PersistencePromise; - /** - * Provides access to add or update the contents of the cache. The buffer - * handles proper size accounting for the change. - * - * Multi-Tab Note: This should only be called by the primary client. - * - * @param options - Specify `trackRemovals` to create sentinel entries for - * removed documents, which allows removals to be tracked by - * `getNewDocumentChanges()`. - */ - newChangeBuffer(options?: { - trackRemovals: boolean; - }): RemoteDocumentChangeBuffer; - /** - * Get an estimate of the size of the document cache. Note that for eager - * garbage collection, we don't track sizes so this will return 0. - */ - getSize(transaction: PersistenceTransaction): PersistencePromise; -} - -/** - * Represents a document change to be applied to remote document cache. - */ -declare interface RemoteDocumentChange { - readonly maybeDocument: MaybeDocument | null; - readonly readTime: SnapshotVersion | null; -} - -/** - * An in-memory buffer of entries to be written to a RemoteDocumentCache. - * It can be used to batch up a set of changes to be written to the cache, but - * additionally supports reading entries back with the `getEntry()` method, - * falling back to the underlying RemoteDocumentCache if no entry is - * buffered. - * - * Entries added to the cache *must* be read first. This is to facilitate - * calculating the size delta of the pending changes. - * - * PORTING NOTE: This class was implemented then removed from other platforms. - * If byte-counting ends up being needed on the other platforms, consider - * porting this class as part of that implementation work. - */ -declare abstract class RemoteDocumentChangeBuffer { - protected changes: ObjectMap; - private changesApplied; - protected abstract getFromCache( - transaction: PersistenceTransaction, - documentKey: DocumentKey - ): PersistencePromise; - protected abstract getAllFromCache( - transaction: PersistenceTransaction, - documentKeys: DocumentKeySet - ): PersistencePromise; - protected abstract applyChanges( - transaction: PersistenceTransaction - ): PersistencePromise; - protected getReadTime(key: DocumentKey): SnapshotVersion; - /** - * Buffers a `RemoteDocumentCache.addEntry()` call. - * - * You can only modify documents that have already been retrieved via - * `getEntry()/getEntries()` (enforced via IndexedDbs `apply()`). - */ - addEntry(maybeDocument: MaybeDocument, readTime: SnapshotVersion): void; - /** - * Buffers a `RemoteDocumentCache.removeEntry()` call. - * - * You can only remove documents that have already been retrieved via - * `getEntry()/getEntries()` (enforced via IndexedDbs `apply()`). - */ - removeEntry(key: DocumentKey, readTime?: SnapshotVersion | null): void; - /** - * Looks up an entry in the cache. The buffered changes will first be checked, - * and if no buffered change applies, this will forward to - * `RemoteDocumentCache.getEntry()`. - * - * @param transaction - The transaction in which to perform any persistence - * operations. - * @param documentKey - The key of the entry to look up. - * @returns The cached Document or NoDocument entry, or null if we have - * nothing cached. - */ - getEntry( - transaction: PersistenceTransaction, - documentKey: DocumentKey - ): PersistencePromise; - /** - * Looks up several entries in the cache, forwarding to - * `RemoteDocumentCache.getEntry()`. - * - * @param transaction - The transaction in which to perform any persistence - * operations. - * @param documentKeys - The keys of the entries to look up. - * @returns A map of cached `Document`s or `NoDocument`s, indexed by key. If - * an entry cannot be found, the corresponding key will be mapped to a - * null value. - */ - getEntries( - transaction: PersistenceTransaction, - documentKeys: DocumentKeySet - ): PersistencePromise; - /** - * Applies buffered changes to the underlying RemoteDocumentCache, using - * the provided transaction. - */ - apply(transaction: PersistenceTransaction): PersistencePromise; - /** Helper to assert this.changes is not null */ - protected assertNotApplied(): void; -} - -/** - * An event from the RemoteStore. It is split into targetChanges (changes to the - * state or the set of documents in our watched targets) and documentUpdates - * (changes to the actual documents). - */ -declare class RemoteEvent { - /** - * The snapshot version this event brings us up to, or MIN if not set. - */ - readonly snapshotVersion: SnapshotVersion; - /** - * A map from target to changes to the target. See TargetChange. - */ - readonly targetChanges: Map; - /** - * A set of targets that is known to be inconsistent. Listens for these - * targets should be re-established without resume tokens. - */ - readonly targetMismatches: SortedSet; - /** - * A set of which documents have changed or been deleted, along with the - * doc's new values (if not deleted). - */ - readonly documentUpdates: MaybeDocumentMap; - /** - * A set of which document updates are due only to limbo resolution targets. - */ - readonly resolvedLimboDocuments: DocumentKeySet; - constructor( - /** - * The snapshot version this event brings us up to, or MIN if not set. - */ - snapshotVersion: SnapshotVersion, - /** - * A map from target to changes to the target. See TargetChange. - */ - targetChanges: Map, - /** - * A set of targets that is known to be inconsistent. Listens for these - * targets should be re-established without resume tokens. - */ - targetMismatches: SortedSet, - /** - * A set of which documents have changed or been deleted, along with the - * doc's new values (if not deleted). - */ - documentUpdates: MaybeDocumentMap, - /** - * A set of which document updates are due only to limbo resolution targets. - */ - resolvedLimboDocuments: DocumentKeySet - ); - /** - * HACK: Views require RemoteEvents in order to determine whether the view is - * CURRENT, but secondary tabs don't receive remote events. So this method is - * used to create a synthesized RemoteEvent that can be used to apply a - * CURRENT status change to a View, for queries executed in a different tab. - */ - static createSynthesizedRemoteEventForCurrentChange( - targetId: TargetId, - current: boolean - ): RemoteEvent; -} - -/** - * RemoteStore - An interface to remotely stored data, basically providing a - * wrapper around the Datastore that is more reliable for the rest of the - * system. - * - * RemoteStore is responsible for maintaining the connection to the server. - * - maintaining a list of active listens. - * - reconnecting when the connection is dropped. - * - resuming all the active listens on reconnect. - * - * RemoteStore handles all incoming events from the Datastore. - * - listening to the watch stream and repackaging the events as RemoteEvents - * - notifying SyncEngine of any changes to the active listens. - * - * RemoteStore takes writes from other components and handles them reliably. - * - pulling pending mutations from LocalStore and sending them to Datastore. - * - retrying mutations that failed because of network problems. - * - acking mutations to the SyncEngine once they are accepted or rejected. - */ -declare interface RemoteStore { - /** - * SyncEngine to notify of watch and write events. This must be set - * immediately after construction. - */ - remoteSyncer: RemoteSyncer; -} - -/** - * An interface that describes the actions the RemoteStore needs to perform on - * a cooperating synchronization engine. - */ -declare interface RemoteSyncer { - /** - * Applies one remote event to the sync engine, notifying any views of the - * changes, and releasing any pending mutation batches that would become - * visible because of the snapshot version the remote event contains. - */ - applyRemoteEvent?(remoteEvent: RemoteEvent): Promise; - /** - * Rejects the listen for the given targetID. This can be triggered by the - * backend for any active target. - * - * @param targetId - The targetID corresponds to one previously initiated by - * the user as part of TargetData passed to listen() on RemoteStore. - * @param error - A description of the condition that has forced the rejection. - * Nearly always this will be an indication that the user is no longer - * authorized to see the data matching the target. - */ - rejectListen?(targetId: TargetId, error: FirestoreError): Promise; - /** - * Applies the result of a successful write of a mutation batch to the sync - * engine, emitting snapshots in any views that the mutation applies to, and - * removing the batch from the mutation queue. - */ - applySuccessfulWrite?(result: MutationBatchResult): Promise; - /** - * Rejects the batch, removing the batch from the mutation queue, recomputing - * the local view of any documents affected by the batch and then, emitting - * snapshots with the reverted value. - */ - rejectFailedWrite?(batchId: BatchId, error: FirestoreError): Promise; - /** - * Returns the set of remote document keys for the given target ID. This list - * includes the documents that were assigned to the target when we received - * the last snapshot. - */ - getRemoteKeysForTarget?(targetId: TargetId): DocumentKeySet; - /** - * Updates all local state to match the pending mutations for the given user. - * May be called repeatedly for the same user. - */ - handleCredentialChange?(user: User): Promise; -} - -declare type Resolver = (value?: T) => void; - -/** - * A slash-separated path for navigating resources (documents and collections) - * within Firestore. - */ -declare class ResourcePath extends BasePath { - protected construct( - segments: string[], - offset?: number, - length?: number - ): ResourcePath; - canonicalString(): string; - toString(): string; - /** - * Creates a resource path from the given slash-delimited string. If multiple - * arguments are provided, all components are combined. Leading and trailing - * slashes from all components are ignored. - */ - static fromString(...pathComponents: string[]): ResourcePath; - static emptyPath(): ResourcePath; -} - -/** - * Executes the given `updateFunction` and then attempts to commit the changes - * applied within the transaction. If any document read within the transaction - * has changed, Cloud Firestore retries the `updateFunction`. If it fails to - * commit after 5 attempts, the transaction fails. - * - * The maximum number of writes allowed in a single transaction is 500. - * - * @param firestore - A reference to the Firestore database to run this - * transaction against. - * @param updateFunction - The function to execute within the transaction - * context. - * @returns If the transaction completed successfully or was explicitly aborted - * (the `updateFunction` returned a failed promise), the promise returned by the - * `updateFunction `is returned here. Otherwise, if the transaction failed, a - * rejected promise with the corresponding failure error is returned. - */ -export declare function runTransaction( - firestore: FirebaseFirestore, - updateFunction: (transaction: Transaction) => Promise -): Promise; - -/** - * Returns a sentinel used with {@link setDoc} or {@link updateDoc} to - * include a server-generated timestamp in the written data. - */ -export declare function serverTimestamp(): FieldValue; - -declare type ServerTimestampBehavior = 'estimate' | 'previous' | 'none'; - -/** - * Writes to the document referred to by this `DocumentReference`. If the - * document does not yet exist, it will be created. - * - * @param reference - A reference to the document to write. - * @param data - A map of the fields and values for the document. - * @returns A Promise resolved once the data has been successfully written - * to the backend (note that it won't resolve while you're offline). - */ -export declare function setDoc( - reference: DocumentReference, - data: T -): Promise; - -/** - * Writes to the document referred to by the specified `DocumentReference`. If - * the document does not yet exist, it will be created. If you provide `merge` - * or `mergeFields`, the provided data can be merged into an existing document. - * - * @param reference - A reference to the document to write. - * @param data - A map of the fields and values for the document. - * @param options - An object to configure the set behavior. - * @returns A Promise resolved once the data has been successfully written - * to the backend (note that it won't resolve while you're offline). - */ -export declare function setDoc( - reference: DocumentReference, - data: Partial, - options: SetOptions -): Promise; - -/** - * Sets the verbosity of Cloud Firestore logs (debug, error, or silent). - * - * @param logLevel - The verbosity you set for activity and error logging. Can - * be any of the following values: - * - *
    - *
  • `debug` for the most verbose logging level, primarily for - * debugging.
  • - *
  • `error` to log errors only.
  • - *
  • `silent` to turn off logging.
  • - *
- */ -export declare function setLogLevel(logLevel: LogLevel): void; - -/** - * An options object that configures the behavior of {@link setDoc}, {@link - * WriteBatch#set} and {@link Transaction#set} calls. These calls can be - * configured to perform granular merges instead of overwriting the target - * documents in their entirety by providing a `SetOptions` with `merge: true`. - * - * @param merge - Changes the behavior of a `setDoc()` call to only replace the - * values specified in its data argument. Fields omitted from the `setDoc()` - * call remain untouched. - * @param mergeFields - Changes the behavior of `setDoc()` calls to only replace - * the specified field paths. Any field path that is not specified is ignored - * and remains untouched. - */ -export declare type SetOptions = - | { - readonly merge?: boolean; - } - | { - readonly mergeFields?: Array; - }; - -export declare interface Settings extends Settings_2 { - cacheSizeBytes?: number; -} - -declare interface Settings_2 { - host?: string; - ssl?: boolean; - ignoreUndefinedProperties?: boolean; - cacheSizeBytes?: number; - experimentalForceLongPolling?: boolean; - experimentalAutoDetectLongPolling?: boolean; -} - -/** - * A `SharedClientState` keeps track of the global state of the mutations - * and query targets for all active clients with the same persistence key (i.e. - * project ID and FirebaseApp name). It relays local changes to other clients - * and updates its local state as new state is observed. - * - * `SharedClientState` is primarily used for synchronization in Multi-Tab - * environments. Each tab is responsible for registering its active query - * targets and mutations. `SharedClientState` will then notify the listener - * assigned to `.syncEngine` for updates to mutations and queries that - * originated in other clients. - * - * To receive notifications, `.syncEngine` and `.onlineStateHandler` has to be - * assigned before calling `start()`. - */ -declare interface SharedClientState { - onlineStateHandler: ((onlineState: OnlineState) => void) | null; - sequenceNumberHandler: - | ((sequenceNumber: ListenSequenceNumber) => void) - | null; - /** Registers the Mutation Batch ID of a newly pending mutation. */ - addPendingMutation(batchId: BatchId): void; - /** - * Records that a pending mutation has been acknowledged or rejected. - * Called by the primary client to notify secondary clients of mutation - * results as they come back from the backend. - */ - updateMutationState( - batchId: BatchId, - state: 'acknowledged' | 'rejected', - error?: FirestoreError - ): void; - /** - * Associates a new Query Target ID with the local Firestore client. Returns - * the new query state for the query (which can be 'current' if the query is - * already associated with another tab). - * - * If the target id is already associated with local client, the method simply - * returns its `QueryTargetState`. - */ - addLocalQueryTarget(targetId: TargetId): QueryTargetState; - /** Removes the Query Target ID association from the local client. */ - removeLocalQueryTarget(targetId: TargetId): void; - /** Checks whether the target is associated with the local client. */ - isLocalQueryTarget(targetId: TargetId): boolean; - /** - * Processes an update to a query target. - * - * Called by the primary client to notify secondary clients of document - * changes or state transitions that affect the provided query target. - */ - updateQueryState( - targetId: TargetId, - state: QueryTargetState, - error?: FirestoreError - ): void; - /** - * Removes the target's metadata entry. - * - * Called by the primary client when all clients stopped listening to a query - * target. - */ - clearQueryState(targetId: TargetId): void; - /** - * Gets the active Query Targets IDs for all active clients. - * - * The implementation for this may require O(n) runtime, where 'n' is the size - * of the result set. - */ - getAllActiveQueryTargets(): SortedSet; - /** - * Checks whether the provided target ID is currently being listened to by - * any of the active clients. - * - * The implementation may require O(n*log m) runtime, where 'n' is the number - * of clients and 'm' the number of targets. - */ - isActiveQueryTarget(targetId: TargetId): boolean; - /** - * Starts the SharedClientState, reads existing client data and registers - * listeners for updates to new and existing clients. - */ - start(): Promise; - /** Shuts down the `SharedClientState` and its listeners. */ - shutdown(): void; - /** - * Changes the active user and removes all existing user-specific data. The - * user change does not call back into SyncEngine (for example, no mutations - * will be marked as removed). - */ - handleUserChange( - user: User, - removedBatchIds: BatchId[], - addedBatchIds: BatchId[] - ): void; - /** Changes the shared online state of all clients. */ - setOnlineState(onlineState: OnlineState): void; - writeSequenceNumber(sequenceNumber: ListenSequenceNumber): void; - /** - * Notifies other clients when remote documents have changed due to loading - * a bundle. - */ - notifyBundleLoaded(): void; -} - -/** - * Returns true if the provided snapshots are equal. - * - * @param left - A snapshot to compare. - * @param right - A snapshot to compare. - * @returns true if the snapshots are equal. - */ -export declare function snapshotEqual( - left: DocumentSnapshot | QuerySnapshot, - right: DocumentSnapshot | QuerySnapshot -): boolean; - -/** - * An options object that can be passed to {@link onSnapshot} and {@link - * QuerySnapshot#docChanges} to control which types of changes to include in the - * result set. - */ -export declare interface SnapshotListenOptions { - /** - * Include a change even if only the metadata of the query or of a document - * changed. Default is false. - */ - readonly includeMetadataChanges?: boolean; -} - -/** - * Metadata about a snapshot, describing the state of the snapshot. - */ -export declare class SnapshotMetadata { - /** - * True if the snapshot contains the result of local writes (for example - * `set()` or `update()` calls) that have not yet been committed to the - * backend. If your listener has opted into metadata updates (via - * `SnapshotListenOptions`) you will receive another snapshot with - * `hasPendingWrites` equal to false once the writes have been committed to - * the backend. - */ - readonly hasPendingWrites: boolean; - /** - * True if the snapshot was created from cached data rather than guaranteed - * up-to-date server data. If your listener has opted into metadata updates - * (via `SnapshotListenOptions`) you will receive another snapshot with - * `fromCache` set to false once the client has received up-to-date data from - * the backend. - */ - readonly fromCache: boolean; - /** @hideconstructor */ - constructor(hasPendingWrites: boolean, fromCache: boolean); - /** - * Returns true if this `SnapshotMetadata` is equal to the provided one. - * - * @param other - The `SnapshotMetadata` to compare against. - * @returns true if this `SnapshotMetadata` is equal to the provided one. - */ - isEqual(other: SnapshotMetadata): boolean; -} - -/** - * Options that configure how data is retrieved from a `DocumentSnapshot` (for - * example the desired behavior for server timestamps that have not yet been set - * to their final value). - */ -export declare interface SnapshotOptions { - /** - * If set, controls the return value for server timestamps that have not yet - * been set to their final value. - * - * By specifying 'estimate', pending server timestamps return an estimate - * based on the local clock. This estimate will differ from the final value - * and cause these values to change once the server result becomes available. - * - * By specifying 'previous', pending timestamps will be ignored and return - * their previous value instead. - * - * If omitted or set to 'none', `null` will be returned by default until the - * server value becomes available. - */ - readonly serverTimestamps?: 'estimate' | 'previous' | 'none'; -} - -/** - * A version of a document in Firestore. This corresponds to the version - * timestamp, such as update_time or read_time. - */ -declare class SnapshotVersion { - private timestamp; - static fromTimestamp(value: Timestamp): SnapshotVersion; - static min(): SnapshotVersion; - private constructor(); - compareTo(other: SnapshotVersion): number; - isEqual(other: SnapshotVersion): boolean; - /** Returns a number representation of the version for use in spec tests. */ - toMicroseconds(): number; - toString(): string; - toTimestamp(): Timestamp; -} - -declare class SortedMap { - comparator: Comparator; - root: LLRBNode | LLRBEmptyNode; - constructor( - comparator: Comparator, - root?: LLRBNode | LLRBEmptyNode - ); - insert(key: K, value: V): SortedMap; - remove(key: K): SortedMap; - get(key: K): V | null; - indexOf(key: K): number; - isEmpty(): boolean; - get size(): number; - minKey(): K | null; - maxKey(): K | null; - inorderTraversal(action: (k: K, v: V) => T): T; - forEach(fn: (k: K, v: V) => void): void; - toString(): string; - reverseTraversal(action: (k: K, v: V) => T): T; - getIterator(): SortedMapIterator; - getIteratorFrom(key: K): SortedMapIterator; - getReverseIterator(): SortedMapIterator; - getReverseIteratorFrom(key: K): SortedMapIterator; -} - -declare class SortedMapIterator { - private isReverse; - private nodeStack; - constructor( - node: LLRBNode | LLRBEmptyNode, - startKey: K | null, - comparator: Comparator, - isReverse: boolean - ); - getNext(): Entry; - hasNext(): boolean; - peek(): Entry | null; -} - -/** - * SortedSet is an immutable (copy-on-write) collection that holds elements - * in order specified by the provided comparator. - * - * NOTE: if provided comparator returns 0 for two elements, we consider them to - * be equal! - */ -declare class SortedSet { - private comparator; - private data; - constructor(comparator: (left: T, right: T) => number); - has(elem: T): boolean; - first(): T | null; - last(): T | null; - get size(): number; - indexOf(elem: T): number; - /** Iterates elements in order defined by "comparator" */ - forEach(cb: (elem: T) => void): void; - /** Iterates over `elem`s such that: range[0] <= elem < range[1]. */ - forEachInRange(range: [T, T], cb: (elem: T) => void): void; - /** - * Iterates over `elem`s such that: start <= elem until false is returned. - */ - forEachWhile(cb: (elem: T) => boolean, start?: T): void; - /** Finds the least element greater than or equal to `elem`. */ - firstAfterOrEqual(elem: T): T | null; - getIterator(): SortedSetIterator; - getIteratorFrom(key: T): SortedSetIterator; - /** Inserts or updates an element */ - add(elem: T): SortedSet; - /** Deletes an element */ - delete(elem: T): SortedSet; - isEmpty(): boolean; - unionWith(other: SortedSet): SortedSet; - isEqual(other: SortedSet): boolean; - toArray(): T[]; - toString(): string; - private copy; -} - -declare class SortedSetIterator { - private iter; - constructor(iter: SortedMapIterator); - getNext(): T; - hasNext(): boolean; -} - -/** - * Creates a `QueryConstraint` 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. - * - * @param snapshot - The snapshot of the document to start after. - * @returns A `QueryConstraint` to pass to `query()` - */ -export declare function startAfter( - snapshot: DocumentSnapshot_2 -): QueryConstraint; - -/** - * Creates a `QueryConstraint` that modifies the result set to start after the - * provided fields relative to the order of the query. The order of the field - * values must match the order of the order by clauses of the query. - * - * @param fieldValues - The field values to start this query after, in order - * of the query's order by. - * @returns A `QueryConstraint` to pass to `query()` - */ -export declare function startAfter(...fieldValues: unknown[]): QueryConstraint; - -/** - * Creates a `QueryConstraint` 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. - * - * @param snapshot - The snapshot of the document to start at. - * @returns A `QueryConstraint` to pass to `query()`. - */ -export declare function startAt( - snapshot: DocumentSnapshot_2 -): QueryConstraint; - -/** - * Creates a `QueryConstraint` that modifies the result set to start at the - * provided fields relative to the order of the query. The order of the field - * values must match the order of the order by clauses of the query. - * - * @param fieldValues - The field values to start this query at, in order - * of the query's order by. - * @returns A `QueryConstraint` to pass to `query()`. - */ -export declare function startAt(...fieldValues: unknown[]): QueryConstraint; - -declare type StructuredQuery = firestoreV1ApiClientInterfaces.StructuredQuery; - -/** - * @license - * Copyright 2017 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. - */ -/** - * SyncEngine is the central controller in the client SDK architecture. It is - * the glue code between the EventManager, LocalStore, and RemoteStore. Some of - * SyncEngine's responsibilities include: - * 1. Coordinating client requests and remote events between the EventManager - * and the local and remote data stores. - * 2. Managing a View object for each query, providing the unified view between - * the local and remote data stores. - * 3. Notifying the RemoteStore when the LocalStore has new mutations in its - * queue that need sending to the backend. - * - * The SyncEngine’s methods should only ever be called by methods running in the - * global async queue. - * - * PORTING NOTE: On Web, SyncEngine does not have an explicit subscribe() - * function. Instead, it directly depends on EventManager's tree-shakeable API - * (via `ensureWatchStream()`). - */ -declare interface SyncEngine { - isPrimaryClient: boolean; -} - -/** - * A Target represents the WatchTarget representation of a Query, which is used - * by the LocalStore and the RemoteStore to keep track of and to execute - * backend queries. While a Query can represent multiple Targets, each Targets - * maps to a single WatchTarget in RemoteStore and a single TargetData entry - * in persistence. - */ -declare interface Target { - readonly path: ResourcePath; - readonly collectionGroup: string | null; - readonly orderBy: OrderBy[]; - readonly filters: Filter[]; - readonly limit: number | null; - readonly startAt: Bound | null; - readonly endAt: Bound | null; -} - -/** - * Represents cached targets received from the remote backend. - * - * The cache is keyed by `Target` and entries in the cache are `TargetData` - * instances. - */ -declare interface TargetCache { - /** - * A global snapshot version representing the last consistent snapshot we - * received from the backend. This is monotonically increasing and any - * snapshots received from the backend prior to this version (e.g. for targets - * resumed with a resume_token) should be suppressed (buffered) until the - * backend has caught up to this snapshot version again. This prevents our - * cache from ever going backwards in time. - * - * This is updated whenever our we get a TargetChange with a read_time and - * empty target_ids. - */ - getLastRemoteSnapshotVersion( - transaction: PersistenceTransaction - ): PersistencePromise; - /** - * @returns The highest sequence number observed, including any that might be - * persisted on-disk. - */ - getHighestSequenceNumber( - transaction: PersistenceTransaction - ): PersistencePromise; - /** - * Call provided function with each `TargetData` that we have cached. - */ - forEachTarget( - txn: PersistenceTransaction, - f: (q: TargetData) => void - ): PersistencePromise; - /** - * Set the highest listen sequence number and optionally updates the - * snapshot version of the last consistent snapshot received from the backend - * (see getLastRemoteSnapshotVersion() for more details). - * - * @param highestListenSequenceNumber - The new maximum listen sequence number. - * @param lastRemoteSnapshotVersion - The new snapshot version. Optional. - */ - setTargetsMetadata( - transaction: PersistenceTransaction, - highestListenSequenceNumber: number, - lastRemoteSnapshotVersion?: SnapshotVersion - ): PersistencePromise; - /** - * Adds an entry in the cache. - * - * The cache key is extracted from `targetData.target`. The key must not already - * exist in the cache. - * - * @param targetData - A TargetData instance to put in the cache. - */ - addTargetData( - transaction: PersistenceTransaction, - targetData: TargetData - ): PersistencePromise; - /** - * Updates an entry in the cache. - * - * The cache key is extracted from `targetData.target`. The entry must already - * exist in the cache, and it will be replaced. - * @param targetData - The TargetData to be replaced into the cache. - */ - updateTargetData( - transaction: PersistenceTransaction, - targetData: TargetData - ): PersistencePromise; - /** - * Removes the cached entry for the given target data. It is an error to remove - * a target data that does not exist. - * - * Multi-Tab Note: This operation should only be called by the primary client. - */ - removeTargetData( - transaction: PersistenceTransaction, - targetData: TargetData - ): PersistencePromise; - /** - * The number of targets currently in the cache. - */ - getTargetCount( - transaction: PersistenceTransaction - ): PersistencePromise; - /** - * Looks up a TargetData entry by target. - * - * @param target - The query target corresponding to the entry to look up. - * @returns The cached TargetData entry, or null if the cache has no entry for - * the target. - */ - getTargetData( - transaction: PersistenceTransaction, - target: Target - ): PersistencePromise; - /** - * Adds the given document keys to cached query results of the given target - * ID. - * - * Multi-Tab Note: This operation should only be called by the primary client. - */ - addMatchingKeys( - transaction: PersistenceTransaction, - keys: DocumentKeySet, - targetId: TargetId - ): PersistencePromise; - /** - * Removes the given document keys from the cached query results of the - * given target ID. - * - * Multi-Tab Note: This operation should only be called by the primary client. - */ - removeMatchingKeys( - transaction: PersistenceTransaction, - keys: DocumentKeySet, - targetId: TargetId - ): PersistencePromise; - /** - * Removes all the keys in the query results of the given target ID. - * - * Multi-Tab Note: This operation should only be called by the primary client. - */ - removeMatchingKeysForTargetId( - transaction: PersistenceTransaction, - targetId: TargetId - ): PersistencePromise; - /** - * Returns the document keys that match the provided target ID. - */ - getMatchingKeysForTargetId( - transaction: PersistenceTransaction, - targetId: TargetId - ): PersistencePromise; - /** - * Returns a new target ID that is higher than any query in the cache. If - * there are no queries in the cache, returns the first valid target ID. - * Allocated target IDs are persisted and `allocateTargetId()` will never - * return the same ID twice. - */ - allocateTargetId( - transaction: PersistenceTransaction - ): PersistencePromise; - containsKey( - transaction: PersistenceTransaction, - key: DocumentKey - ): PersistencePromise; -} - -/** - * A TargetChange specifies the set of changes for a specific target as part of - * a RemoteEvent. These changes track which documents are added, modified or - * removed, as well as the target's resume token and whether the target is - * marked CURRENT. - * The actual changes *to* documents are not part of the TargetChange since - * documents may be part of multiple targets. - */ -declare class TargetChange { - /** - * An opaque, server-assigned token that allows watching a query to be resumed - * after disconnecting without retransmitting all the data that matches the - * query. The resume token essentially identifies a point in time from which - * the server should resume sending results. - */ - readonly resumeToken: ByteString; - /** - * The "current" (synced) status of this target. Note that "current" - * has special meaning in the RPC protocol that implies that a target is - * both up-to-date and consistent with the rest of the watch stream. - */ - readonly current: boolean; - /** - * The set of documents that were newly assigned to this target as part of - * this remote event. - */ - readonly addedDocuments: DocumentKeySet; - /** - * The set of documents that were already assigned to this target but received - * an update during this remote event. - */ - readonly modifiedDocuments: DocumentKeySet; - /** - * The set of documents that were removed from this target as part of this - * remote event. - */ - readonly removedDocuments: DocumentKeySet; - constructor( - /** - * An opaque, server-assigned token that allows watching a query to be resumed - * after disconnecting without retransmitting all the data that matches the - * query. The resume token essentially identifies a point in time from which - * the server should resume sending results. - */ - resumeToken: ByteString, - /** - * The "current" (synced) status of this target. Note that "current" - * has special meaning in the RPC protocol that implies that a target is - * both up-to-date and consistent with the rest of the watch stream. - */ - current: boolean, - /** - * The set of documents that were newly assigned to this target as part of - * this remote event. - */ - addedDocuments: DocumentKeySet, - /** - * The set of documents that were already assigned to this target but received - * an update during this remote event. - */ - modifiedDocuments: DocumentKeySet, - /** - * The set of documents that were removed from this target as part of this - * remote event. - */ - removedDocuments: DocumentKeySet - ); - /** - * This method is used to create a synthesized TargetChanges that can be used to - * apply a CURRENT status change to a View (for queries executed in a different - * tab) or for new queries (to raise snapshots with correct CURRENT status). - */ - static createSynthesizedTargetChangeForCurrentChange( - targetId: TargetId, - current: boolean - ): TargetChange; -} - -declare type TargetChangeTargetChangeType = - | 'NO_CHANGE' - | 'ADD' - | 'REMOVE' - | 'CURRENT' - | 'RESET'; - -/** - * An immutable set of metadata that the local store tracks for each target. - */ -declare class TargetData { - /** The target being listened to. */ - readonly target: Target; - /** - * The target ID to which the target corresponds; Assigned by the - * LocalStore for user listens and by the SyncEngine for limbo watches. - */ - readonly targetId: TargetId; - /** The purpose of the target. */ - readonly purpose: TargetPurpose; - /** - * The sequence number of the last transaction during which this target data - * was modified. - */ - readonly sequenceNumber: ListenSequenceNumber; - /** The latest snapshot version seen for this target. */ - readonly snapshotVersion: SnapshotVersion; - /** - * The maximum snapshot version at which the associated view - * contained no limbo documents. - */ - readonly lastLimboFreeSnapshotVersion: SnapshotVersion; - /** - * An opaque, server-assigned token that allows watching a target to be - * resumed after disconnecting without retransmitting all the data that - * matches the target. The resume token essentially identifies a point in - * time from which the server should resume sending results. - */ - readonly resumeToken: ByteString; - constructor( - /** The target being listened to. */ - target: Target, - /** - * The target ID to which the target corresponds; Assigned by the - * LocalStore for user listens and by the SyncEngine for limbo watches. - */ - targetId: TargetId, - /** The purpose of the target. */ - purpose: TargetPurpose, - /** - * The sequence number of the last transaction during which this target data - * was modified. - */ - sequenceNumber: ListenSequenceNumber, - /** The latest snapshot version seen for this target. */ - snapshotVersion?: SnapshotVersion, - /** - * The maximum snapshot version at which the associated view - * contained no limbo documents. - */ - lastLimboFreeSnapshotVersion?: SnapshotVersion, - /** - * An opaque, server-assigned token that allows watching a target to be - * resumed after disconnecting without retransmitting all the data that - * matches the target. The resume token essentially identifies a point in - * time from which the server should resume sending results. - */ - resumeToken?: ByteString - ); - /** Creates a new target data instance with an updated sequence number. */ - withSequenceNumber(sequenceNumber: number): TargetData; - /** - * Creates a new target data instance with an updated resume token and - * snapshot version. - */ - withResumeToken( - resumeToken: ByteString, - snapshotVersion: SnapshotVersion - ): TargetData; - /** - * Creates a new target data instance with an updated last limbo free - * snapshot version number. - */ - withLastLimboFreeSnapshotVersion( - lastLimboFreeSnapshotVersion: SnapshotVersion - ): TargetData; -} - -/** - * A locally-assigned ID used to refer to a target being watched via the - * Watch service. - */ -declare type TargetId = number; - -/** An enumeration of the different purposes we have for targets. */ -declare const enum TargetPurpose { - /** A regular, normal query target. */ - Listen = 0, - /** - * The query target was used to refill a query after an existence filter mismatch. - */ - ExistenceFilterMismatch = 1, - /** The query target was used to resolve a limbo document. */ - LimboResolution = 2 -} - -/** - * Represents the state of bundle loading tasks. - * - * Both 'Error' and 'Success' are sinking state: task will abort or complete and there will - * be no more updates after they are reported. - */ -export declare type TaskState = 'Error' | 'Running' | 'Success'; - -/** - * Terminates the provided Firestore instance. - * - * After calling `terminate()` only the `clearIndexedDbPersistence()` function - * may be used. Any other function will throw a `FirestoreError`. - * - * To restart after termination, create a new instance of FirebaseFirestore with - * {@link getFirestore}. - * - * Termination does not cancel any pending writes, and any promises that are - * awaiting a response from the server will not be resolved. If you have - * persistence enabled, the next time you start this instance, it will resume - * sending these writes to the server. - * - * Note: Under normal circumstances, calling `terminate()` is not required. This - * function is useful only when you want to force this instance to release all - * of its resources or in combination with `clearIndexedDbPersistence()` to - * ensure that all local state is destroyed between test runs. - * - * @returns A promise that is resolved when the instance has been successfully - * terminated. - */ -export declare function terminate(firestore: FirebaseFirestore): Promise; - -/** - * Wellknown "timer" IDs used when scheduling delayed operations on the - * AsyncQueue. These IDs can then be used from tests to check for the presence - * of operations or to run them early. - * - * The string values are used when encoding these timer IDs in JSON spec tests. - */ -declare const enum TimerId { - /** All can be used with runDelayedOperationsEarly() to run all timers. */ - All = 'all', - /** - * The following 4 timers are used in persistent_stream.ts for the listen and - * write streams. The "Idle" timer is used to close the stream due to - * inactivity. The "ConnectionBackoff" timer is used to restart a stream once - * the appropriate backoff delay has elapsed. - */ - ListenStreamIdle = 'listen_stream_idle', - ListenStreamConnectionBackoff = 'listen_stream_connection_backoff', - WriteStreamIdle = 'write_stream_idle', - WriteStreamConnectionBackoff = 'write_stream_connection_backoff', - /** - * A timer used in online_state_tracker.ts to transition from - * OnlineState.Unknown to Offline after a set timeout, rather than waiting - * indefinitely for success or failure. - */ - OnlineStateTimeout = 'online_state_timeout', - /** - * A timer used to update the client metadata in IndexedDb, which is used - * to determine the primary leaseholder. - */ - ClientMetadataRefresh = 'client_metadata_refresh', - /** A timer used to periodically attempt LRU Garbage collection */ - LruGarbageCollection = 'lru_garbage_collection', - /** - * A timer used to retry transactions. Since there can be multiple concurrent - * transactions, multiple of these may be in the queue at a given time. - */ - TransactionRetry = 'transaction_retry', - /** - * A timer used to retry operations scheduled via retryable AsyncQueue - * operations. - */ - AsyncQueueRetry = 'async_queue_retry' -} - -/** - * @license - * Copyright 2017 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. - */ -/** - * 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 - * {@link https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto | Timestamp definition}. - */ -export declare class Timestamp { - readonly seconds: number; - readonly nanoseconds: number; - /** - * Creates a new timestamp with the current date, with millisecond precision. - * - * @returns a new timestamp representing the current date. - */ - static now(): Timestamp; - /** - * Creates a new timestamp from the given date. - * - * @param date - The date to initialize the `Timestamp` from. - * @returns A new `Timestamp` representing the same point in time as the given - * date. - */ - static fromDate(date: Date): Timestamp; - /** - * Creates a new timestamp from the given number of milliseconds. - * - * @param milliseconds - Number of milliseconds since Unix epoch - * 1970-01-01T00:00:00Z. - * @returns A new `Timestamp` representing the same point in time as the given - * number of milliseconds. - */ - static fromMillis(milliseconds: number): Timestamp; - /** - * Creates a new timestamp. - * - * @param seconds - The number of seconds of UTC time since Unix epoch - * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to - * 9999-12-31T23:59:59Z inclusive. - * @param nanoseconds - The non-negative fractions of a second at nanosecond - * resolution. Negative second values with fractions must still have - * non-negative nanoseconds values that count forward in time. Must be - * from 0 to 999,999,999 inclusive. - */ - constructor(seconds: number, nanoseconds: number); - /** - * Converts a `Timestamp` to a JavaScript `Date` object. This conversion causes - * a loss of precision since `Date` objects only support millisecond precision. - * - * @returns JavaScript `Date` object representing the same point in time as - * this `Timestamp`, with millisecond precision. - */ - toDate(): Date; - /** - * Converts a `Timestamp` to a numeric timestamp (in milliseconds since - * epoch). This operation causes a loss of precision. - * - * @returns The point in time corresponding to this timestamp, represented as - * the number of milliseconds since Unix epoch 1970-01-01T00:00:00Z. - */ - toMillis(): number; - _compareTo(other: Timestamp): number; - /** - * Returns true if this `Timestamp` is equal to the provided one. - * - * @param other - The `Timestamp` to compare against. - * @returns true if this `Timestamp` is equal to the provided one. - */ - isEqual(other: Timestamp): boolean; - toString(): string; - toJSON(): { - seconds: number; - nanoseconds: number; - }; - /** - * Converts this object to a primitive string, which allows Timestamp objects to be compared - * using the `>`, `<=`, `>=` and `>` operators. - */ - valueOf(): string; -} - -declare type Timestamp_2 = - | string - | { - seconds?: string | number; - nanos?: number; - }; - -declare interface Token { - /** Type of token. */ - type: TokenType; - /** - * The user with which the token is associated (used for persisting user - * state on disk, etc.). - */ - user: User; - /** Extra header values to be passed along with a request */ - authHeaders: { - [header: string]: string; - }; -} - -declare type TokenType = 'OAuth' | 'FirstParty'; - -/** - * 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 - * {@link runTransaction}. - */ -export declare class Transaction extends Transaction_2 { - protected readonly _firestore: FirebaseFirestore; - /** @hideconstructor */ - constructor(_firestore: FirebaseFirestore, _transaction: Transaction_3); - /** - * Reads the document referenced by the provided {@link DocumentReference}. - * - * @param documentRef - A reference to the document to be read. - * @returns A `DocumentSnapshot` with the read data. - */ - get(documentRef: DocumentReference): Promise>; -} - -/** - * 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 - * {@link runTransaction}. - */ -declare class Transaction_2 { - protected readonly _firestore: FirebaseFirestore_2; - private readonly _transaction; - private readonly _dataReader; - /** @hideconstructor */ - constructor(_firestore: FirebaseFirestore_2, _transaction: Transaction_3); - /** - * Reads the document referenced by the provided {@link DocumentReference}. - * - * @param documentRef - A reference to the document to be read. - * @returns A `DocumentSnapshot` with the read data. - */ - get(documentRef: DocumentReference): Promise>; - /** - * Writes to the document referred to by the provided {@link - * DocumentReference}. If the document does not exist yet, it will be created. - * - * @param documentRef - A reference to the document to be set. - * @param data - An object of the fields and values for the document. - * @returns This `Transaction` instance. Used for chaining method calls. - */ - set(documentRef: DocumentReference, data: T): this; - /** - * Writes to the document referred to by the provided {@link - * DocumentReference}. If the document does not exist yet, it will be created. - * If you provide `merge` or `mergeFields`, the provided data can be merged - * into an existing document. - * - * @param documentRef - A reference to the document to be set. - * @param data - An object of the fields and values for the document. - * @param options - An object to configure the set behavior. - * @returns This `Transaction` instance. Used for chaining method calls. - */ - set( - documentRef: DocumentReference, - data: Partial, - options: SetOptions - ): this; - /** - * Updates fields in the document referred to by the provided {@link - * DocumentReference}. The update will fail if applied to a document that does - * not exist. - * - * @param documentRef - A reference to the document to be updated. - * @param data - An object containing the fields and values with which to - * update the document. Fields can contain dots to reference nested fields - * within the document. - * @returns This `Transaction` instance. Used for chaining method calls. - */ - update(documentRef: DocumentReference, data: UpdateData): this; - /** - * Updates fields in the document referred to by the provided {@link - * DocumentReference}. The update will fail if applied to a document that does - * not exist. - * - * Nested fields can be updated by providing dot-separated field path - * strings or by providing `FieldPath` objects. - * - * @param documentRef - A reference to the document to be updated. - * @param field - The first field to update. - * @param value - The first value. - * @param moreFieldsAndValues - Additional key/value pairs. - * @returns This `Transaction` instance. Used for chaining method calls. - */ - update( - documentRef: DocumentReference, - field: string | FieldPath, - value: unknown, - ...moreFieldsAndValues: unknown[] - ): this; - /** - * Deletes the document referred to by the provided {@link DocumentReference}. - * - * @param documentRef - A reference to the document to be deleted. - * @returns This `Transaction` instance. Used for chaining method calls. - */ - delete(documentRef: DocumentReference): this; -} - -/** - * Internal transaction object responsible for accumulating the mutations to - * perform and the base versions for any documents read. - */ -declare class Transaction_3 { - private datastore; - private readVersions; - private mutations; - private committed; - /** - * A deferred usage error that occurred previously in this transaction that - * will cause the transaction to fail once it actually commits. - */ - private lastWriteError; - /** - * Set of documents that have been written in the transaction. - * - * When there's more than one write to the same key in a transaction, any - * writes after the first are handled differently. - */ - private writtenDocs; - constructor(datastore: Datastore); - lookup(keys: DocumentKey[]): Promise; - set(key: DocumentKey, data: ParsedSetData): void; - update(key: DocumentKey, data: ParsedUpdateData): void; - delete(key: DocumentKey): void; - commit(): Promise; - private recordVersion; - /** - * Returns the version of this document when it was read in this transaction, - * as a precondition, or no precondition if it was not read. - */ - private precondition; - /** - * Returns the precondition for a document if the operation is an update. - */ - private preconditionForUpdate; - private write; - private ensureCommitNotCalled; -} - -/** Used to represent a field transform on a mutation. */ -declare class TransformOperation { - private _; -} - -declare type UnaryFilterOp = - | 'OPERATOR_UNSPECIFIED' - | 'IS_NAN' - | 'IS_NULL' - | 'IS_NOT_NAN' - | 'IS_NOT_NULL'; - -export declare interface Unsubscribe { - (): void; -} - -/** - * An untyped Firestore Data Converter interface that is shared between the - * lite, firestore-exp and classic SDK. - */ -declare interface UntypedFirestoreDataConverter { - toFirestore(modelObject: T): DocumentData_2; - toFirestore(modelObject: Partial, options: SetOptions_2): DocumentData_2; - fromFirestore(snapshot: unknown, options?: unknown): T; -} - -/** - * Update data (for use with {@link updateDoc}) consists of field paths (e.g. - * 'foo' or 'foo.baz') mapped to values. Fields that contain dots reference - * nested fields within the document. - */ -export declare interface UpdateData { - [fieldPath: string]: any; -} - -/** - * Updates fields in the document referred to by the specified - * `DocumentReference`. The update will fail if applied to a document that does - * not exist. - * - * @param reference - A reference to the document to update. - * @param data - An object containing the fields and values with which to - * update the document. Fields can contain dots to reference nested fields - * within the document. - * @returns A Promise resolved once the data has been successfully written - * to the backend (note that it won't resolve while you're offline). - */ -export declare function updateDoc( - reference: DocumentReference, - data: UpdateData -): Promise; - -/** - * Updates fields in the document referred to by the specified - * `DocumentReference` The update will fail if applied to a document that does - * not exist. - * - * Nested fields can be updated by providing dot-separated field path - * strings or by providing `FieldPath` objects. - * - * @param reference - A reference to the document to update. - * @param field - The first field to update. - * @param value - The first value. - * @param moreFieldsAndValues - Additional key value pairs. - * @returns A Promise resolved once the data has been successfully written - * to the backend (note that it won't resolve while you're offline). - */ -export declare function updateDoc( - reference: DocumentReference, - field: string | FieldPath, - value: unknown, - ...moreFieldsAndValues: unknown[] -): Promise; - -/** - * Modify this instance to communicate with the Cloud Firestore emulator. - * - * Note: This must be called before this instance has been used to do any - * operations. - * - * @param firestore - The Firestore instance to configure to connect to the - * emulator. - * @param host - the emulator host (ex: localhost). - * @param port - the emulator port (ex: 9000). - */ -export declare function useFirestoreEmulator( - firestore: FirebaseFirestore_2, - host: string, - port: number -): void; - -/** - * @license - * Copyright 2017 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. - */ -/** - * Simple wrapper around a nullable UID. Mostly exists to make code more - * readable. - */ -declare class User { - readonly uid: string | null; - /** A user with a null UID. */ - static readonly UNAUTHENTICATED: User; - static readonly GOOGLE_CREDENTIALS: User; - static readonly FIRST_PARTY: User; - constructor(uid: string | null); - isAuthenticated(): boolean; - /** - * Returns a key representing this user, suitable for inclusion in a - * dictionary. - */ - toKey(): string; - isEqual(otherUser: User): boolean; -} - -declare type Value = firestoreV1ApiClientInterfaces.Value; - -declare type ValueNullValue = 'NULL_VALUE'; - -declare class ViewSnapshot { - readonly query: Query_2; - readonly docs: DocumentSet; - readonly oldDocs: DocumentSet; - readonly docChanges: DocumentViewChange[]; - readonly mutatedKeys: DocumentKeySet; - readonly fromCache: boolean; - readonly syncStateChanged: boolean; - readonly excludesMetadataChanges: boolean; - constructor( - query: Query_2, - docs: DocumentSet, - oldDocs: DocumentSet, - docChanges: DocumentViewChange[], - mutatedKeys: DocumentKeySet, - fromCache: boolean, - syncStateChanged: boolean, - excludesMetadataChanges: boolean - ); - /** Returns a view snapshot as if all documents in the snapshot were added. */ - static fromInitialDocuments( - query: Query_2, - documents: DocumentSet, - mutatedKeys: DocumentKeySet, - fromCache: boolean - ): ViewSnapshot; - get hasPendingWrites(): boolean; - isEqual(other: ViewSnapshot): boolean; -} - -/** - * Waits until all currently pending writes for the active user have been - * acknowledged by the backend. - * - * The returned Promise resolves immediately if there are no outstanding writes. - * Otherwise, the Promise waits for all previously issued writes (including - * those written in a previous app session), but it does not wait for writes - * that were added after the function is called. If you want to wait for - * additional writes, call `waitForPendingWrites()` again. - * - * Any outstanding `waitForPendingWrites()` Promises are rejected during user - * changes. - * - * @returns A Promise which resolves when all currently pending writes have been - * acknowledged by the backend. - */ -export declare function waitForPendingWrites( - firestore: FirebaseFirestore -): Promise; - -/** - * Creates a `QueryConstraint` that enforces that documents must contain the - * specified field and that the value should satisfy the relation constraint - * provided. - * - * @param fieldPath - The path to compare - * @param opStr - The operation string (e.g "<", "<=", "==", "<", - * "<=", "!="). - * @param value - The value for comparison - * @returns The created `Query`. - */ -export declare function where( - fieldPath: string | FieldPath, - opStr: WhereFilterOp, - value: unknown -): QueryConstraint; - -/** - * Filter conditions in a {@link where} clause are specified using the - * strings '<', '<=', '==', '!=', '>=', '>', 'array-contains', 'in', - * 'array-contains-any', and 'not-in'. - */ -export declare type WhereFilterOp = - | '<' - | '<=' - | '==' - | '!=' - | '>=' - | '>' - | 'array-contains' - | 'in' - | 'array-contains-any' - | 'not-in'; - -/** - * A write batch, used to perform multiple writes as a single atomic unit. - * - * A `WriteBatch` object can be acquired by calling {@link writeBatch}. It - * provides methods for adding writes to the write batch. None of the writes - * will be committed (or visible locally) until {@link WriteBatch#commit} is - * called. - */ -export declare class WriteBatch { - private readonly _firestore; - private readonly _commitHandler; - private readonly _dataReader; - private _mutations; - private _committed; - /** @hideconstructor */ - constructor( - _firestore: FirebaseFirestore_2, - _commitHandler: (m: Mutation[]) => Promise - ); - /** - * Writes to the document referred to by the provided {@link - * DocumentReference}. If the document does not exist yet, it will be created. - * - * @param documentRef - A reference to the document to be set. - * @param data - An object of the fields and values for the document. - * @returns This `WriteBatch` instance. Used for chaining method calls. - */ - set(documentRef: DocumentReference, data: T): WriteBatch; - /** - * Writes to the document referred to by the provided {@link - * DocumentReference}. If the document does not exist yet, it will be created. - * If you provide `merge` or `mergeFields`, the provided data can be merged - * into an existing document. - * - * @param documentRef - A reference to the document to be set. - * @param data - An object of the fields and values for the document. - * @param options - An object to configure the set behavior. - * @returns This `WriteBatch` instance. Used for chaining method calls. - */ - set( - documentRef: DocumentReference, - data: Partial, - options: SetOptions - ): WriteBatch; - /** - * Updates fields in the document referred to by the provided {@link - * DocumentReference}. The update will fail if applied to a document that does - * not exist. - * - * @param documentRef - A reference to the document to be updated. - * @param data - An object containing the fields and values with which to - * update the document. Fields can contain dots to reference nested fields - * within the document. - * @returns This `WriteBatch` instance. Used for chaining method calls. - */ - update(documentRef: DocumentReference, data: UpdateData): WriteBatch; - /** - * Updates fields in the document referred to by this {@link - * DocumentReference}. The update will fail if applied to a document that does - * not exist. - * - * Nested fields can be update by providing dot-separated field path strings - * or by providing `FieldPath` objects. - * - * @param documentRef - A reference to the document to be updated. - * @param field - The first field to update. - * @param value - The first value. - * @param moreFieldsAndValues - Additional key value pairs. - * @returns This `WriteBatch` instance. Used for chaining method calls. - */ - update( - documentRef: DocumentReference, - field: string | FieldPath, - value: unknown, - ...moreFieldsAndValues: unknown[] - ): WriteBatch; - /** - * Deletes the document referred to by the provided {@link DocumentReference}. - * - * @param documentRef - A reference to the document to be deleted. - * @returns This `WriteBatch` instance. Used for chaining method calls. - */ - delete(documentRef: DocumentReference): WriteBatch; - /** - * Commits all of the writes in this write batch as a single atomic unit. - * - * The result of these writes will only be reflected in document reads that - * occur after the returned Promise resolves. If the client is offline, the - * write fails. If you would like to see local modifications or buffer writes - * until the client is online, use the full Firestore SDK. - * - * @returns A Promise resolved once all of the writes in the batch have been - * successfully written to the backend as an atomic unit (note that it won't - * resolve while you're offline). - */ - commit(): Promise; - private _verifyNotCommitted; -} - -/** - * Creates a write batch, used for performing multiple writes as a single - * atomic operation. The maximum number of writes allowed in a single WriteBatch - * is 500. - * - * Unlike transactions, write batches are persisted offline and therefore are - * preferable when you don't need to condition your writes on read data. - * - * @returns A `WriteBatch` that can be used to atomically execute multiple - * writes. - */ -export declare function writeBatch(firestore: FirebaseFirestore): WriteBatch; - -export {}; diff --git a/repo-scripts/prune-dts/tests/firestore.output.d.ts b/repo-scripts/prune-dts/tests/firestore.output.d.ts deleted file mode 100644 index 58411538953..00000000000 --- a/repo-scripts/prune-dts/tests/firestore.output.d.ts +++ /dev/null @@ -1,2078 +0,0 @@ -/** - * @license - * Copyright 2021 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 { DocumentData as DocumentData_2 } from '@firebase/firestore-types'; -import { FirebaseApp } from '@firebase/app'; -import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; -import { _FirebaseService } from '@firebase/app'; -import { LogLevelString as LogLevel } from '@firebase/logger'; -import { Provider } from '@firebase/component'; -import { SetOptions as SetOptions_2 } from '@firebase/firestore-types'; -/** - * Add a new document to specified `CollectionReference` with the given data, - * assigning it a document ID automatically. - * - * @param reference - A reference to the collection to add this document to. - * @param data - An Object containing the data for the new document. - * @returns A Promise resolved with a `DocumentReference` pointing to the - * newly created document after it has been written to the backend (Note that it - * won't resolve while you're offline). - */ -export declare function addDoc( - reference: CollectionReference, - data: T -): Promise>; -/** - * Returns a special value that can be used with {@link (setDoc:1)} or {@link - * updateDoc} that tells the server to remove the given elements from any - * array value that already exists on the server. All instances of each element - * specified will be removed from the array. If the field being modified is not - * already an array it will be overwritten with an empty array. - * - * @param elements - The elements to remove from the array. - * @returns The `FieldValue` sentinel for use in a call to `setDoc()` or - * `updateDoc()` - */ -export declare function arrayRemove(...elements: unknown[]): FieldValue; -/** - * Returns a special value that can be used with {@link setDoc} or {@link - * updateDoc} that tells the server to union the given elements with any array - * value that already exists on the server. Each specified element that doesn't - * already exist in the array will be added to the end. If the field being - * modified is not already an array it will be overwritten with an array - * containing exactly the specified elements. - * - * @param elements - The elements to union into the array. - * @returns The `FieldValue` sentinel for use in a call to `setDoc()` or - * `updateDoc()`. - */ -export declare function arrayUnion(...elements: unknown[]): FieldValue; -/** - * An immutable object representing an array of bytes. - */ -export declare class Bytes { - private constructor(); - /** - * Creates a new `Bytes` object from the given Base64 string, converting it to - * bytes. - * - * @param base64 - The Base64 string used to create the `Bytes` object. - */ - static fromBase64String(base64: string): Bytes; - /** - * Creates a new `Bytes` object from the given Uint8Array. - * - * @param array - The Uint8Array used to create the `Bytes` object. - */ - static fromUint8Array(array: Uint8Array): Bytes; - /** - * Returns the underlying bytes as a Base64-encoded string. - * - * @returns The Base64-encoded string created from the `Bytes` object. - */ - toBase64(): string; - /** - * Returns the underlying bytes in a new `Uint8Array`. - * - * @returns The Uint8Array created from the `Bytes` object. - */ - toUint8Array(): Uint8Array; - /** - * Returns a string representation of the `Bytes` object. - * - * @returns A string representation of the `Bytes` object. - */ - toString(): string; - /** - * Returns true if this `Bytes` object is equal to the provided one. - * - * @param other - The `Bytes` object to compare against. - * @returns true if this `Bytes` object is equal to the provided one. - */ - isEqual(other: Bytes): boolean; -} -/** - * Constant used to indicate the LRU garbage collection should be disabled. - * Set this value as the `cacheSizeBytes` on the settings passed to the - * `Firestore` instance. - */ -export declare const CACHE_SIZE_UNLIMITED = -1; -/** - * Clears the persistent storage. This includes pending writes and cached - * documents. - * - * Must be called while the `Firestore` instance is not started (after the app is - * terminated or when the app is first initialized). On startup, this function - * must be called before other functions (other than {@link - * initializeFirestore} or {@link getFirestore})). If the `Firestore` - * instance is still running, the promise will be rejected with the error code - * of `failed-precondition`. - * - * Note: `clearIndexedDbPersistence()` is primarily intended to help write - * reliable tests that use Cloud Firestore. It uses an efficient mechanism for - * dropping existing data but does not attempt to securely overwrite or - * otherwise make cached data unrecoverable. For applications that are sensitive - * to the disclosure of cached data in between user sessions, we strongly - * recommend not enabling persistence at all. - * - * @param firestore - The `Firestore` instance to clear persistence for. - * @returns A promise that is resolved when the persistent storage is - * cleared. Otherwise, the promise is rejected with an error. - */ -export declare function clearIndexedDbPersistence( - firestore: FirebaseFirestore -): Promise; -/** - * Gets a `CollectionReference` instance that refers to the collection at - * the specified absolute path. - * - * @param firestore - A reference to the root Firestore instance. - * @param path - A slash-separated path to a collection. - * @param pathSegments - Additional path segments to apply relative to the first - * argument. - * @throws If the final path has an even number of segments and does not point - * to a collection. - * @returns The `CollectionReference` instance. - */ -export declare function collection( - firestore: FirebaseFirestore, - path: string, - ...pathSegments: string[] -): CollectionReference; -/** - * Gets a `CollectionReference` instance that refers to a subcollection of - * `reference` at the specified relative path. - * - * @param reference - A reference to a collection. - * @param path - A slash-separated path to a collection. - * @param pathSegments - Additional path segments to apply relative to the first - * argument. - * @throws If the final path has an even number of segments and does not point - * to a collection. - * @returns The `CollectionReference` instance. - */ -export declare function collection( - reference: CollectionReference, - path: string, - ...pathSegments: string[] -): CollectionReference; -/** - * Gets a `CollectionReference` instance that refers to a subcollection of - * `reference` at the specified relative path. - * - * @param reference - A reference to a Firestore document. - * @param path - A slash-separated path to a collection. - * @param pathSegments - Additional path segments that will be applied relative - * to the first argument. - * @throws If the final path has an even number of segments and does not point - * to a collection. - * @returns The `CollectionReference` instance. - */ -export declare function collection( - reference: DocumentReference, - path: string, - ...pathSegments: string[] -): CollectionReference; -/** - * Creates and returns a new `Query` instance that includes all documents in the - * database that are contained in a collection or subcollection with the - * given `collectionId`. - * - * @param firestore - A reference to the root Firestore instance. - * @param collectionId - Identifies the collections to query over. Every - * collection or subcollection with this ID as the last segment of its path - * will be included. Cannot contain a slash. - * @returns The created `Query`. - */ -export declare function collectionGroup( - firestore: FirebaseFirestore, - collectionId: string -): Query; -/** - * A `CollectionReference` object can be used for adding documents, getting - * document references, and querying for documents (using {@link query}). - */ -export declare class CollectionReference extends Query { - readonly firestore: FirebaseFirestore; - readonly type = 'collection'; - private constructor(); - /** The collection's identifier. */ - get id(): string; - /** - * A string representing the path of the referenced collection (relative - * to the root of the database). - */ - get path(): string; - /** - * A reference to the containing `DocumentReference` if this is a - * subcollection. If this isn't a subcollection, the reference is null. - */ - get parent(): DocumentReference | null; - /** - * Applies a custom data converter to this CollectionReference, allowing you - * to use your own custom model objects with Firestore. When you call {@link - * addDoc} with the returned `CollectionReference` instance, the provided - * converter will convert between Firestore data and your custom type `U`. - * - * @param converter - Converts objects to and from Firestore. - * @returns A `CollectionReference` that uses the provided converter. - */ - withConverter( - converter: FirestoreDataConverter - ): CollectionReference; -} -/** - * Deletes the document referred to by the specified `DocumentReference`. - * - * @param reference - A reference to the document to delete. - * @returns A Promise resolved once the document has been successfully - * deleted from the backend (note that it won't resolve while you're offline). - */ -export declare function deleteDoc( - reference: DocumentReference -): Promise; -/** - * Returns a sentinel for use with {@link updateDoc} or - * {@link setDoc} with `{merge: true}` to mark a field for deletion. - */ -export declare function deleteField(): FieldValue; -/** - * Disables network usage for this instance. It can be re-enabled via {@link - * enableNetwork}. While the network is disabled, any snapshot listeners, - * `getDoc()` or `getDocs()` calls will return results from cache, and any write - * operations will be queued until the network is restored. - * - * @returns A promise that is resolved once the network has been disabled. - */ -export declare function disableNetwork( - firestore: FirebaseFirestore -): Promise; -/** - * Gets a `DocumentReference` instance that refers to the document at the - * specified absolute path. - * - * @param firestore - A reference to the root Firestore instance. - * @param path - A slash-separated path to a document. - * @param pathSegments - Additional path segments that will be applied relative - * to the first argument. - * @throws If the final path has an odd number of segments and does not point to - * a document. - * @returns The `DocumentReference` instance. - */ -export declare function doc( - firestore: FirebaseFirestore, - path: string, - ...pathSegments: string[] -): DocumentReference; -/** - * Gets a `DocumentReference` instance that refers to a document within - * `reference` at the specified relative path. If no path is specified, an - * automatically-generated unique ID will be used for the returned - * `DocumentReference`. - * - * @param reference - A reference to a collection. - * @param path - A slash-separated path to a document. Has to be omitted to use - * auto-generated IDs. - * @param pathSegments - Additional path segments that will be applied relative - * to the first argument. - * @throws If the final path has an odd number of segments and does not point to - * a document. - * @returns The `DocumentReference` instance. - */ -export declare function doc( - reference: CollectionReference, - path?: string, - ...pathSegments: string[] -): DocumentReference; -/** - * Gets a `DocumentReference` instance that refers to a document within - * `reference` at the specified relative path. - * - * @param reference - A reference to a Firestore document. - * @param path - A slash-separated path to a document. - * @param pathSegments - Additional path segments that will be applied relative - * to the first argument. - * @throws If the final path has an odd number of segments and does not point to - * a document. - * @returns The `DocumentReference` instance. - */ -export declare function doc( - reference: DocumentReference, - path: string, - ...pathSegments: string[] -): DocumentReference; -/** - * A `DocumentChange` represents a change to the documents matching a query. - * It contains the document affected and the type of change that occurred. - */ -export declare interface DocumentChange { - /** The type of change ('added', 'modified', or 'removed'). */ - readonly type: DocumentChangeType; - /** The document affected by this change. */ - readonly doc: QueryDocumentSnapshot; - /** - * The index of the changed document in the result set immediately prior to - * this `DocumentChange` (i.e. supposing that all prior `DocumentChange` objects - * have been applied). Is `-1` for 'added' events. - */ - readonly oldIndex: number; - /** - * The index of the changed document in the result set immediately after - * this `DocumentChange` (i.e. supposing that all prior `DocumentChange` - * objects and the current `DocumentChange` object have been applied). - * Is -1 for 'removed' events. - */ - readonly newIndex: number; -} -/** - * The type of a `DocumentChange` may be 'added', 'removed', or 'modified'. - */ -export declare type DocumentChangeType = 'added' | 'removed' | 'modified'; -/** - * Document data (for use with {@link setDoc}) consists of fields mapped to - * values. - */ -export declare interface DocumentData { - [field: string]: any; -} -/** - * Returns a special sentinel `FieldPath` to refer to the ID of a document. - * It can be used in queries to sort or filter by the document ID. - */ -export declare function documentId(): FieldPath; -/** - * A `DocumentReference` refers to a document location in a Firestore database - * and can be used to write, read, or listen to the location. The document at - * the referenced location may or may not exist. - */ -export declare class DocumentReference { - /** The type of this Firestore reference. */ - readonly type = 'document'; - /** - * The {@link FirebaseFirestore} the document is in. - * This is useful for performing transactions, for example. - */ - readonly firestore: FirebaseFirestore; - private constructor(); - /** - * The document's identifier within its collection. - */ - get id(): string; - /** - * A string representing the path of the referenced document (relative - * to the root of the database). - */ - get path(): string; - /** - * The collection this `DocumentReference` belongs to. - */ - get parent(): CollectionReference; - /** - * Applies a custom data converter to this `DocumentReference`, allowing you - * to use your own custom model objects with Firestore. When you call {@link - * setDoc}, {@link getDoc}, etc. with the returned `DocumentReference` - * instance, the provided converter will convert between Firestore data and - * your custom type `U`. - * - * @param converter - Converts objects to and from Firestore. - * @returns A `DocumentReference` that uses the provided converter. - */ - withConverter(converter: FirestoreDataConverter): DocumentReference; -} -/** - * A `DocumentSnapshot` contains data read from a document in your Firestore - * database. The data can be extracted with `.data()` or `.get()` to - * get a specific field. - * - * For a `DocumentSnapshot` that points to a non-existing document, any data - * access will return 'undefined'. You can use the `exists()` method to - * explicitly verify a document's existence. - */ -export declare class DocumentSnapshot { - /** - * Metadata about the `DocumentSnapshot`, including information about its - * source and local modifications. - */ - readonly metadata: SnapshotMetadata; - protected constructor(); - /** - * Property of the `DocumentSnapshot` that signals whether or not the data - * exists. True if the document exists. - */ - exists(): this is QueryDocumentSnapshot; - /** - * Retrieves all fields in the document as an `Object`. Returns `undefined` if - * the document doesn't exist. - * - * By default, `FieldValue.serverTimestamp()` values that have not yet been - * set to their final value will be returned as `null`. You can override - * this by passing an options object. - * - * @param options - An options object to configure how data is retrieved from - * the snapshot (for example the desired behavior for server timestamps that - * have not yet been set to their final value). - * @returns An `Object` containing all fields in the document or `undefined` if - * the document doesn't exist. - */ - data(options?: SnapshotOptions): T | undefined; - /** - * Retrieves the field specified by `fieldPath`. Returns `undefined` if the - * document or field doesn't exist. - * - * By default, a `FieldValue.serverTimestamp()` that has not yet been set to - * its final value will be returned as `null`. You can override this by - * passing an options object. - * - * @param fieldPath - The path (for example 'foo' or 'foo.bar') to a specific - * field. - * @param options - An options object to configure how the field is retrieved - * from the snapshot (for example the desired behavior for server timestamps - * that have not yet been set to their final value). - * @returns The data at the specified field location or undefined if no such - * field exists in the document. - */ - get(fieldPath: string | FieldPath, options?: SnapshotOptions): any; - /** - * Property of the `DocumentSnapshot` that provides the document's ID. - */ - get id(): string; - /** - * The `DocumentReference` for the document included in the `DocumentSnapshot`. - */ - get ref(): DocumentReference; -} -/** - * Attempts to enable persistent storage, if possible. - * - * Must be called before any other functions (other than - * {@link initializeFirestore}, {@link getFirestore} or - * {@link clearIndexedDbPersistence}. - * - * If this fails, `enableIndexedDbPersistence()` will reject the promise it - * returns. Note that even after this failure, the `Firestore` instance will - * remain usable, however offline persistence will be disabled. - * - * There are several reasons why this can fail, which can be identified by - * the `code` on the error. - * - * * failed-precondition: The app is already open in another browser tab. - * * unimplemented: The browser is incompatible with the offline - * persistence implementation. - * - * @param firestore - The `Firestore` instance to enable persistence for. - * @param persistenceSettings - Optional settings object to configure - * persistence. - * @returns A promise that represents successfully enabling persistent storage. - */ -export declare function enableIndexedDbPersistence( - firestore: FirebaseFirestore, - persistenceSettings?: PersistenceSettings -): Promise; -/** - * Attempts to enable multi-tab persistent storage, if possible. If enabled - * across all tabs, all operations share access to local persistence, including - * shared execution of queries and latency-compensated local document updates - * across all connected instances. - * - * If this fails, `enableMultiTabIndexedDbPersistence()` will reject the promise - * it returns. Note that even after this failure, the `Firestore` instance will - * remain usable, however offline persistence will be disabled. - * - * There are several reasons why this can fail, which can be identified by - * the `code` on the error. - * - * * failed-precondition: The app is already open in another browser tab and - * multi-tab is not enabled. - * * unimplemented: The browser is incompatible with the offline - * persistence implementation. - * - * @param firestore - The `Firestore` instance to enable persistence for. - * @returns A promise that represents successfully enabling persistent - * storage. - */ -export declare function enableMultiTabIndexedDbPersistence( - firestore: FirebaseFirestore -): Promise; -/** - * Re-enables use of the network for this Firestore instance after a prior - * call to {@link disableNetwork}. - * - * @returns A promise that is resolved once the network has been enabled. - */ -export declare function enableNetwork( - firestore: FirebaseFirestore -): Promise; -/** - * Creates a `QueryConstraint` that modifies the result set to end at the - * provided document (inclusive). 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. - * - * @param snapshot - The snapshot of the document to end at. - * @returns A `QueryConstraint` to pass to `query()` - */ -export declare function endAt( - snapshot: DocumentSnapshot -): QueryConstraint; -/** - * Creates a `QueryConstraint` that modifies the result set to end at the - * provided fields relative to the order of the query. The order of the field - * values must match the order of the order by clauses of the query. - * - * @param fieldValues - The field values to end this query at, in order - * of the query's order by. - * @returns A `QueryConstraint` to pass to `query()` - */ -export declare function endAt(...fieldValues: unknown[]): QueryConstraint; -/** - * Creates a `QueryConstraint` 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. - * - * @param snapshot - The snapshot of the document to end before. - * @returns A `QueryConstraint` to pass to `query()` - */ -export declare function endBefore( - snapshot: DocumentSnapshot -): QueryConstraint; -/** - * Creates a `QueryConstraint` that modifies the result set to end before the - * provided fields relative to the order of the query. The order of the field - * values must match the order of the order by clauses of the query. - * - * @param fieldValues - The field values to end this query before, in order - * of the query's order by. - * @returns A `QueryConstraint` to pass to `query()` - */ -export declare function endBefore(...fieldValues: unknown[]): QueryConstraint; -/** - * A `FieldPath` refers to a field in a document. The path may consist of a - * single field name (referring to a top-level field in the document), or a - * list of field names (referring to a nested field in the document). - * - * Create a `FieldPath` by providing field names. If more than one field - * name is provided, the path will point to a nested field in a document. - */ -export declare class FieldPath { - /** - * Creates a FieldPath from the provided field names. If more than one field - * name is provided, the path will point to a nested field in a document. - * - * @param fieldNames - A list of field names. - */ - constructor(...fieldNames: string[]); - /** - * Returns true if this `FieldPath` is equal to the provided one. - * - * @param other - The `FieldPath` to compare against. - * @returns true if this `FieldPath` is equal to the provided one. - */ - isEqual(other: FieldPath): boolean; -} -/** - * Sentinel values that can be used when writing document fields with `set()` - * or `update()`. - */ -export declare abstract class FieldValue { - /** - * @param _methodName - The public API endpoint that returns this class. - */ - constructor(_methodName: string); - abstract isEqual(other: FieldValue): boolean; -} -/** - * The Cloud Firestore service interface. - * - * Do not call this constructor directly. Instead, use {@link getFirestore}. - */ -export declare class FirebaseFirestore { - private constructor(); - /** - * The {@link FirebaseApp} associated with this `Firestore` service - * instance. - */ - get app(): FirebaseApp; - toJSON(): object; -} -/** - * Converter used by `withConverter()` to transform user objects of type `T` - * into Firestore data. - * - * Using the converter allows you to specify generic type arguments when - * storing and retrieving objects from Firestore. - * - * @example - * ```typescript - * class Post { - * constructor(readonly title: string, readonly author: string) {} - * - * toString(): string { - * return this.title + ', by ' + this.author; - * } - * } - * - * const postConverter = { - * toFirestore(post: Post): firebase.firestore.DocumentData { - * return {title: post.title, author: post.author}; - * }, - * fromFirestore( - * snapshot: firebase.firestore.QueryDocumentSnapshot, - * options: firebase.firestore.SnapshotOptions - * ): Post { - * const data = snapshot.data(options)!; - * return new Post(data.title, data.author); - * } - * }; - * - * const postSnap = await firebase.firestore() - * .collection('posts') - * .withConverter(postConverter) - * .doc().get(); - * const post = postSnap.data(); - * if (post !== undefined) { - * post.title; // string - * post.toString(); // Should be defined - * post.someNonExistentProperty; // TS error - * } - * ``` - */ -export declare interface FirestoreDataConverter { - /** - * Called by the Firestore SDK to convert a custom model object of type `T` - * into a plain JavaScript object (suitable for writing directly to the - * Firestore database). To use `set()` with `merge` and `mergeFields`, - * `toFirestore()` must be defined with `Partial`. - */ - toFirestore(modelObject: T): DocumentData; - /** - * Called by the Firestore SDK to convert a custom model object of type `T` - * into a plain JavaScript object (suitable for writing directly to the - * Firestore database). Used with {@link setData}, {@link WriteBatch#set} - * and {@link Transaction#set} with `merge:true` or `mergeFields`. - */ - toFirestore(modelObject: Partial, options: SetOptions): DocumentData; - /** - * Called by the Firestore SDK to convert Firestore data into an object of - * type T. You can access your data by calling: `snapshot.data(options)`. - * - * @param snapshot - A `QueryDocumentSnapshot` containing your data and metadata. - * @param options - The `SnapshotOptions` from the initial call to `data()`. - */ - fromFirestore( - snapshot: QueryDocumentSnapshot, - options?: SnapshotOptions - ): T; -} -/** An error returned by a Firestore operation. */ -export declare class FirestoreError extends Error { - readonly code: FirestoreErrorCode; - readonly message: string; - readonly name: string; - readonly stack?: string; - private constructor(); -} -/** - * @license - * Copyright 2017 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. - */ -/** - * The set of Firestore status codes. The codes are the same at the ones - * exposed by gRPC here: - * https://github.com/grpc/grpc/blob/master/doc/statuscodes.md - * - * Possible values: - * - 'cancelled': The operation was cancelled (typically by the caller). - * - 'unknown': Unknown error or an error from a different error domain. - * - 'invalid-argument': Client specified an invalid argument. Note that this - * differs from 'failed-precondition'. 'invalid-argument' indicates - * arguments that are problematic regardless of the state of the system - * (e.g. an invalid field name). - * - 'deadline-exceeded': Deadline expired before operation could complete. - * For operations that change the state of the system, this error may be - * returned even if the operation has completed successfully. For example, - * a successful response from a server could have been delayed long enough - * for the deadline to expire. - * - 'not-found': Some requested document was not found. - * - 'already-exists': Some document that we attempted to create already - * exists. - * - 'permission-denied': The caller does not have permission to execute the - * specified operation. - * - 'resource-exhausted': Some resource has been exhausted, perhaps a - * per-user quota, or perhaps the entire file system is out of space. - * - 'failed-precondition': Operation was rejected because the system is not - * in a state required for the operation's execution. - * - 'aborted': The operation was aborted, typically due to a concurrency - * issue like transaction aborts, etc. - * - 'out-of-range': Operation was attempted past the valid range. - * - 'unimplemented': Operation is not implemented or not supported/enabled. - * - 'internal': Internal errors. Means some invariants expected by - * underlying system has been broken. If you see one of these errors, - * something is very broken. - * - 'unavailable': The service is currently unavailable. This is most likely - * a transient condition and may be corrected by retrying with a backoff. - * - 'data-loss': Unrecoverable data loss or corruption. - * - 'unauthenticated': The request does not have valid authentication - * credentials for the operation. - */ -export declare type FirestoreErrorCode = - | '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'; -/** - * @license - * Copyright 2017 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. - */ -/** - * An immutable object representing a geographic location in Firestore. The - * location is represented as latitude/longitude pair. - * - * Latitude values are in the range of [-90, 90]. - * Longitude values are in the range of [-180, 180]. - */ -export declare class GeoPoint { - /** - * Creates a new immutable `GeoPoint` object with the provided latitude and - * longitude values. - * @param latitude - The latitude as number between -90 and 90. - * @param longitude - The longitude as number between -180 and 180. - */ - constructor(latitude: number, longitude: number); - /** - * The latitude of this `GeoPoint` instance. - */ - get latitude(): number; - /** - * The longitude of this `GeoPoint` instance. - */ - get longitude(): number; - /** - * Returns true if this `GeoPoint` is equal to the provided one. - * - * @param other - The `GeoPoint` to compare against. - * @returns true if this `GeoPoint` is equal to the provided one. - */ - isEqual(other: GeoPoint): boolean; - toJSON(): { - latitude: number; - longitude: number; - }; -} -/** - * Reads the document referred to by this `DocumentReference`. - * - * Note: `getDoc()` attempts to provide up-to-date data when possible by waiting - * for data from the server, but it may return cached data or fail if you are - * offline and the server cannot be reached. To specify this behavior, invoke - * {@link getDocFromCache} or {@link getDocFromServer}. - * - * @param reference - The reference of the document to fetch. - * @returns A Promise resolved with a `DocumentSnapshot` containing the - * current document contents. - */ -export declare function getDoc( - reference: DocumentReference -): Promise>; -/** - * Reads the document referred to by this `DocumentReference` from cache. - * Returns an error if the document is not currently cached. - * - * @returns A Promise resolved with a `DocumentSnapshot` containing the - * current document contents. - */ -export declare function getDocFromCache( - reference: DocumentReference -): Promise>; -/** - * Reads the document referred to by this `DocumentReference` from the server. - * Returns an error if the network is not available. - * - * @returns A Promise resolved with a `DocumentSnapshot` containing the - * current document contents. - */ -export declare function getDocFromServer( - reference: DocumentReference -): Promise>; -/** - * Executes the query and returns the results as a `QuerySnapshot`. - * - * Note: `getDocs()` attempts to provide up-to-date data when possible by - * waiting for data from the server, but it may return cached data or fail if - * you are offline and the server cannot be reached. To specify this behavior, - * invoke {@link getDocsFromCache} or {@link getDocsFromServer}. - * - * @returns A Promise that will be resolved with the results of the query. - */ -export declare function getDocs(query: Query): Promise>; -/** - * Executes the query and returns the results as a `QuerySnapshot` from cache. - * Returns an error if the document is not currently cached. - * - * @returns A Promise that will be resolved with the results of the query. - */ -export declare function getDocsFromCache( - query: Query -): Promise>; -/** - * Executes the query and returns the results as a `QuerySnapshot` from the - * server. Returns an error if the network is not available. - * - * @returns A Promise that will be resolved with the results of the query. - */ -export declare function getDocsFromServer( - query: Query -): Promise>; -/** - * Returns the existing instance of Firestore that is associated with the - * provided {@link FirebaseApp}. If no instance exists, initializes a new - * instance with default settings. - * - * @param app - The {@link FirebaseApp} instance that the returned Firestore - * instance is associated with. - * @returns The `Firestore` instance of the provided app. - */ -export declare function getFirestore(app: FirebaseApp): FirebaseFirestore; -/** - * Returns a special value that can be used with {@link setDoc} or {@link - * updateDoc} that tells the server to increment the field's current value by - * the given value. - * - * If either the operand or the current field value uses floating point - * precision, all arithmetic follows IEEE 754 semantics. If both values are - * integers, values outside of JavaScript's safe number range - * (`Number.MIN_SAFE_INTEGER` to `Number.MAX_SAFE_INTEGER`) are also subject to - * precision loss. Furthermore, once processed by the Firestore backend, all - * integer operations are capped between -2^63 and 2^63-1. - * - * If the current field value is not of type `number`, or if the field does not - * yet exist, the transformation sets the field to the given value. - * - * @param n - The value to increment by. - * @returns The `FieldValue` sentinel for use in a call to `setDoc()` or - * `updateDoc()` - */ -export declare function increment(n: number): FieldValue; -/** - * Initializes a new instance of Cloud Firestore with the provided settings. - * Can only be called before any other function, including - * {@link getFirestore}. If the custom settings are empty, this function is - * equivalent to calling {@link getFirestore}. - * - * @param app - The {@link FirebaseApp} with which the `Firestore` instance will - * be associated. - * @param settings - A settings object to configure the `Firestore` instance. - * @returns A newly initialized `Firestore` instance. - */ -export declare function initializeFirestore( - app: FirebaseApp, - settings: Settings -): FirebaseFirestore; -/** - * Creates a `QueryConstraint` that only returns the first matching documents. - * - * @param limit - The maximum number of items to return. - * @returns The created `Query`. - */ -export declare function limit(limit: number): QueryConstraint; -/** - * Creates a `QueryConstraint` that only returns the last matching documents. - * - * You must specify at least one `orderBy` clause for `limitToLast` queries, - * otherwise an exception will be thrown during execution. - * - * @param limit - The maximum number of items to return. - * @returns The created `Query`. - */ -export declare function limitToLast(limit: number): QueryConstraint; -/** - * Loads a Firestore bundle into the local cache. - * - * @param firestore - The `Firestore` instance to load bundles for for. - * @param bundleData - An object representing the bundle to be loaded. Valid objects are - * `ArrayBuffer`, `ReadableStream` or `string`. - * - * @return - * A `LoadBundleTask` object, which notifies callers with progress updates, and completion - * or error events. It can be used as a `Promise`. - */ -export declare function loadBundle( - firestore: FirebaseFirestore, - bundleData: ReadableStream | ArrayBuffer | string -): LoadBundleTask; -/** - * Represents the task of loading a Firestore bundle. It provides progress of bundle - * loading, as well as task completion and error events. - * - * The API is compatible with `Promise`. - */ -export declare class LoadBundleTask - implements PromiseLike -{ - /** - * Registers functions to listen to bundle loading progress events. - * @param next - Called when there is a progress update from bundle loading. Typically `next` calls occur - * each time a Firestore document is loaded from the bundle. - * @param error - Called when an error occurs during bundle loading. The task aborts after reporting the - * error, and there should be no more updates after this. - * @param complete - Called when the loading task is complete. - */ - onProgress( - next?: (progress: LoadBundleTaskProgress) => unknown, - error?: (err: Error) => unknown, - complete?: () => void - ): void; - /** - * Implements the `Promise.catch` interface. - * - * @param onRejected - Called when an error occurs during bundle loading. - */ - catch( - onRejected: (a: Error) => R | PromiseLike - ): Promise; - /** - * Implements the `Promise.then` interface. - * - * @param onFulfilled - Called on the completion of the loading task with a final `LoadBundleTaskProgress` update. - * The update will always have its `taskState` set to `"Success"`. - * @param onRejected - Called when an error occurs during bundle loading. - */ - then( - onFulfilled?: (a: LoadBundleTaskProgress) => T | PromiseLike, - onRejected?: (a: Error) => R | PromiseLike - ): Promise; -} -/** - * Represents a progress update or a final state from loading bundles. - */ -export declare interface LoadBundleTaskProgress { - /** How many documents have been loaded. */ - documentsLoaded: number; - /** How many documents are in the bundle being loaded. */ - totalDocuments: number; - /** How many bytes have been loaded. */ - bytesLoaded: number; - /** How many bytes are in the bundle being loaded. */ - totalBytes: number; - /** Current task state. */ - taskState: TaskState; -} -export { LogLevel }; -/** - * Reads a Firestore `Query` from local cache, identified by the given name. - * - * The named queries are packaged into bundles on the server side (along - * with resulting documents), and loaded to local cache using `loadBundle`. Once in local - * cache, use this method to extract a `Query` by name. - */ -export declare function namedQuery( - firestore: FirebaseFirestore, - name: string -): Promise; -/** - * Attaches a listener for `DocumentSnapshot` events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single observer - * object with `next` and `error` callbacks. - * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. - * - * @param reference - A reference to the document to listen to. - * @param observer - A single object containing `next` and `error` callbacks. - * @returns An unsubscribe function that can be called to cancel - * the snapshot listener. - */ -export declare function onSnapshot( - reference: DocumentReference, - observer: { - next?: (snapshot: DocumentSnapshot) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; - } -): Unsubscribe; -/** - * Attaches a listener for `DocumentSnapshot` events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single observer - * object with `next` and `error` callbacks. - * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. - * - * @param reference - A reference to the document to listen to. - * @param options - Options controlling the listen behavior. - * @param observer - A single object containing `next` and `error` callbacks. - * @returns An unsubscribe function that can be called to cancel - * the snapshot listener. - */ -export declare function onSnapshot( - reference: DocumentReference, - options: SnapshotListenOptions, - observer: { - next?: (snapshot: DocumentSnapshot) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; - } -): Unsubscribe; -/** - * Attaches a listener for `DocumentSnapshot` events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single observer - * object with `next` and `error` callbacks. - * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. - * - * @param reference - A reference to the document to listen to. - * @param onNext - A callback to be called every time a new `DocumentSnapshot` - * is available. - * @param onError - A callback to be called if the listen fails or is - * cancelled. No further callbacks will occur. - * @param onCompletion - Can be provided, but will not be called since streams are - * never ending. - * @returns An unsubscribe function that can be called to cancel - * the snapshot listener. - */ -export declare function onSnapshot( - reference: DocumentReference, - onNext: (snapshot: DocumentSnapshot) => void, - onError?: (error: FirestoreError) => void, - onCompletion?: () => void -): Unsubscribe; -/** - * Attaches a listener for `DocumentSnapshot` events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single observer - * object with `next` and `error` callbacks. - * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. - * - * @param reference - A reference to the document to listen to. - * @param options - Options controlling the listen behavior. - * @param onNext - A callback to be called every time a new `DocumentSnapshot` - * is available. - * @param onError - A callback to be called if the listen fails or is - * cancelled. No further callbacks will occur. - * @param onCompletion - Can be provided, but will not be called since streams are - * never ending. - * @returns An unsubscribe function that can be called to cancel - * the snapshot listener. - */ -export declare function onSnapshot( - reference: DocumentReference, - options: SnapshotListenOptions, - onNext: (snapshot: DocumentSnapshot) => void, - onError?: (error: FirestoreError) => void, - onCompletion?: () => void -): Unsubscribe; -/** - * Attaches a listener for `QuerySnapshot` events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single observer - * object with `next` and `error` callbacks. The listener can be cancelled by - * calling the function that is returned when `onSnapshot` is called. - * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. - * - * @param query - The query to listen to. - * @param observer - A single object containing `next` and `error` callbacks. - * @returns An unsubscribe function that can be called to cancel - * the snapshot listener. - */ -export declare function onSnapshot( - query: Query, - observer: { - next?: (snapshot: QuerySnapshot) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; - } -): Unsubscribe; -/** - * Attaches a listener for `QuerySnapshot` events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single observer - * object with `next` and `error` callbacks. The listener can be cancelled by - * calling the function that is returned when `onSnapshot` is called. - * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. - * - * @param query - The query to listen to. - * @param options - Options controlling the listen behavior. - * @param observer - A single object containing `next` and `error` callbacks. - * @returns An unsubscribe function that can be called to cancel - * the snapshot listener. - */ -export declare function onSnapshot( - query: Query, - options: SnapshotListenOptions, - observer: { - next?: (snapshot: QuerySnapshot) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; - } -): Unsubscribe; -/** - * Attaches a listener for `QuerySnapshot` events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single observer - * object with `next` and `error` callbacks. The listener can be cancelled by - * calling the function that is returned when `onSnapshot` is called. - * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. - * - * @param query - The query to listen to. - * @param onNext - A callback to be called every time a new `QuerySnapshot` - * is available. - * @param onCompletion - Can be provided, but will not be called since streams are - * never ending. - * @param onError - A callback to be called if the listen fails or is - * cancelled. No further callbacks will occur. - * @returns An unsubscribe function that can be called to cancel - * the snapshot listener. - */ -export declare function onSnapshot( - query: Query, - onNext: (snapshot: QuerySnapshot) => void, - onError?: (error: FirestoreError) => void, - onCompletion?: () => void -): Unsubscribe; -/** - * Attaches a listener for `QuerySnapshot` events. You may either pass - * individual `onNext` and `onError` callbacks or pass a single observer - * object with `next` and `error` callbacks. The listener can be cancelled by - * calling the function that is returned when `onSnapshot` is called. - * - * NOTE: Although an `onCompletion` callback can be provided, it will - * never be called because the snapshot stream is never-ending. - * - * @param query - The query to listen to. - * @param options - Options controlling the listen behavior. - * @param onNext - A callback to be called every time a new `QuerySnapshot` - * is available. - * @param onCompletion - Can be provided, but will not be called since streams are - * never ending. - * @param onError - A callback to be called if the listen fails or is - * cancelled. No further callbacks will occur. - * @returns An unsubscribe function that can be called to cancel - * the snapshot listener. - */ -export declare function onSnapshot( - query: Query, - options: SnapshotListenOptions, - onNext: (snapshot: QuerySnapshot) => void, - onError?: (error: FirestoreError) => void, - onCompletion?: () => void -): Unsubscribe; -/** - * Attaches a listener for a snapshots-in-sync event. The snapshots-in-sync - * event indicates that all listeners affected by a given change have fired, - * even if a single server-generated change affects multiple listeners. - * - * NOTE: The snapshots-in-sync event only indicates that listeners are in sync - * with each other, but does not relate to whether those snapshots are in sync - * with the server. Use SnapshotMetadata in the individual listeners to - * determine if a snapshot is from the cache or the server. - * - * @param firestore - The instance of Firestore for synchronizing snapshots. - * @param observer - A single object containing `next` and `error` callbacks. - * @returns An unsubscribe function that can be called to cancel the snapshot - * listener. - */ -export declare function onSnapshotsInSync( - firestore: FirebaseFirestore, - observer: { - next?: (value: void) => void; - error?: (error: FirestoreError) => void; - complete?: () => void; - } -): Unsubscribe; -/** - * Attaches a listener for a snapshots-in-sync event. The snapshots-in-sync - * event indicates that all listeners affected by a given change have fired, - * even if a single server-generated change affects multiple listeners. - * - * NOTE: The snapshots-in-sync event only indicates that listeners are in sync - * with each other, but does not relate to whether those snapshots are in sync - * with the server. Use SnapshotMetadata in the individual listeners to - * determine if a snapshot is from the cache or the server. - * - * @param firestore - The instance of Firestore for synchronizing snapshots. - * @param onSync - A callback to be called every time all snapshot listeners are - * in sync with each other. - * @returns An unsubscribe function that can be called to cancel the snapshot - * listener. - */ -export declare function onSnapshotsInSync( - firestore: FirebaseFirestore, - onSync: () => void -): Unsubscribe; -/** - * Creates a `QueryConstraint` that sorts the query result by the - * specified field, optionally in descending order instead of ascending. - * - * @param fieldPath - The field to sort by. - * @param directionStr - Optional direction to sort by ('asc' or 'desc'). If - * not specified, order will be ascending. - * @returns The created `Query`. - */ -export declare function orderBy( - fieldPath: string | FieldPath, - directionStr?: OrderByDirection -): QueryConstraint; -/** - * The direction of a {@link orderBy} clause is specified as 'desc' or 'asc' - * (descending or ascending). - */ -export declare type OrderByDirection = 'desc' | 'asc'; -export declare interface PersistenceSettings { - forceOwnership?: boolean; -} -/** - * A `Query` refers to a Query which you can read or listen to. You can also - * construct refined `Query` objects by adding filters and ordering. - */ -export declare class Query { - /** The type of this Firestore reference. */ - readonly type: 'query' | 'collection'; - /** - * The `FirebaseFirestore` for the Firestore database (useful for performing - * transactions, etc.). - */ - readonly firestore: FirebaseFirestore; - protected constructor(); - /** - * Applies a custom data converter to this query, allowing you to use your own - * custom model objects with Firestore. When you call {@link getDocs} with - * the returned query, the provided converter will convert between Firestore - * data and your custom type `U`. - * - * @param converter - Converts objects to and from Firestore. - * @returns A `Query` that uses the provided converter. - */ - withConverter(converter: FirestoreDataConverter): Query; -} -/** - * Creates a new immutable instance of `query` that is extended to also include - * additional query constraints. - * - * @param query - The query instance to use as a base for the new constraints. - * @param queryConstraints - The list of `QueryConstraint`s to apply. - * @throws if any of the provided query constraints cannot be combined with the - * existing or new constraints. - */ -export declare function query( - query: Query, - ...queryConstraints: QueryConstraint[] -): Query; -/** - * A `QueryConstraint` is used to narrow the set of documents returned by a - * Firestore query. `QueryConstraint`s are created by invoking {@link where}, - * {@link orderBy}, {@link startAt}, {@link startAfter}, {@link - * endBefore}, {@link endAt}, {@link limit} or {@link limitToLast} and - * can then be passed to {@link query} to create a new query instance that - * also contains this `QueryConstraint`. - */ -export declare abstract class QueryConstraint { - /** The type of this query constraints */ - abstract readonly type: QueryConstraintType; -} -/** Describes the different query constraints available in this SDK. */ -export declare type QueryConstraintType = - | 'where' - | 'orderBy' - | 'limit' - | 'limitToLast' - | 'startAt' - | 'startAfter' - | 'endAt' - | 'endBefore'; -/** - * A `QueryDocumentSnapshot` contains data read from a document in your - * Firestore database as part of a query. The document is guaranteed to exist - * and its data can be extracted with `.data()` or `.get()` to get a - * specific field. - * - * A `QueryDocumentSnapshot` offers the same API surface as a - * `DocumentSnapshot`. Since query results contain only existing documents, the - * `exists` property will always be true and `data()` will never return - * 'undefined'. - */ -export declare class QueryDocumentSnapshot< - T = DocumentData -> extends DocumentSnapshot { - /** - * Retrieves all fields in the document as an `Object`. - * - * By default, `FieldValue.serverTimestamp()` values that have not yet been - * set to their final value will be returned as `null`. You can override - * this by passing an options object. - * - * @override - * @param options - An options object to configure how data is retrieved from - * the snapshot (for example the desired behavior for server timestamps that - * have not yet been set to their final value). - * @returns An `Object` containing all fields in the document. - */ - data(options?: SnapshotOptions): T; -} -/** - * Returns true if the provided queries point to the same collection and apply - * the same constraints. - * - * @param left - A `Query` to compare. - * @param right - A `Query` to compare. - * @returns true if the references point to the same location in the same - * Firestore database. - */ -export declare function queryEqual(left: Query, right: Query): boolean; -/** - * A `QuerySnapshot` contains zero or more `DocumentSnapshot` objects - * representing the results of a query. The documents can be accessed as an - * array via the `docs` property or enumerated using the `forEach` method. The - * number of documents can be determined via the `empty` and `size` - * properties. - */ -export declare class QuerySnapshot { - /** - * Metadata about this snapshot, concerning its source and if it has local - * modifications. - */ - readonly metadata: SnapshotMetadata; - /** - * The query on which you called `get` or `onSnapshot` in order to get this - * `QuerySnapshot`. - */ - readonly query: Query; - private constructor(); - /** An array of all the documents in the `QuerySnapshot`. */ - get docs(): Array>; - /** The number of documents in the `QuerySnapshot`. */ - get size(): number; - /** True if there are no documents in the `QuerySnapshot`. */ - get empty(): boolean; - /** - * Enumerates all of the documents in the `QuerySnapshot`. - * - * @param callback - A callback to be called with a `QueryDocumentSnapshot` for - * each document in the snapshot. - * @param thisArg - The `this` binding for the callback. - */ - forEach( - callback: (result: QueryDocumentSnapshot) => void, - thisArg?: unknown - ): void; - /** - * Returns an array of the documents changes since the last snapshot. If this - * is the first snapshot, all documents will be in the list as 'added' - * changes. - * - * @param options - `SnapshotListenOptions` that control whether metadata-only - * changes (i.e. only `DocumentSnapshot.metadata` changed) should trigger - * snapshot events. - */ - docChanges(options?: SnapshotListenOptions): Array>; -} -/** - * Returns true if the provided references are equal. - * - * @param left - A reference to compare. - * @param right - A reference to compare. - * @returns true if the references point to the same location in the same - * Firestore database. - */ -export declare function refEqual( - left: DocumentReference | CollectionReference, - right: DocumentReference | CollectionReference -): boolean; -/** - * Executes the given `updateFunction` and then attempts to commit the changes - * applied within the transaction. If any document read within the transaction - * has changed, Cloud Firestore retries the `updateFunction`. If it fails to - * commit after 5 attempts, the transaction fails. - * - * The maximum number of writes allowed in a single transaction is 500. - * - * @param firestore - A reference to the Firestore database to run this - * transaction against. - * @param updateFunction - The function to execute within the transaction - * context. - * @returns If the transaction completed successfully or was explicitly aborted - * (the `updateFunction` returned a failed promise), the promise returned by the - * `updateFunction `is returned here. Otherwise, if the transaction failed, a - * rejected promise with the corresponding failure error is returned. - */ -export declare function runTransaction( - firestore: FirebaseFirestore, - updateFunction: (transaction: Transaction) => Promise -): Promise; -/** - * Returns a sentinel used with {@link setDoc} or {@link updateDoc} to - * include a server-generated timestamp in the written data. - */ -export declare function serverTimestamp(): FieldValue; -/** - * Writes to the document referred to by this `DocumentReference`. If the - * document does not yet exist, it will be created. - * - * @param reference - A reference to the document to write. - * @param data - A map of the fields and values for the document. - * @returns A Promise resolved once the data has been successfully written - * to the backend (note that it won't resolve while you're offline). - */ -export declare function setDoc( - reference: DocumentReference, - data: T -): Promise; -/** - * Writes to the document referred to by the specified `DocumentReference`. If - * the document does not yet exist, it will be created. If you provide `merge` - * or `mergeFields`, the provided data can be merged into an existing document. - * - * @param reference - A reference to the document to write. - * @param data - A map of the fields and values for the document. - * @param options - An object to configure the set behavior. - * @returns A Promise resolved once the data has been successfully written - * to the backend (note that it won't resolve while you're offline). - */ -export declare function setDoc( - reference: DocumentReference, - data: Partial, - options: SetOptions -): Promise; -/** - * Sets the verbosity of Cloud Firestore logs (debug, error, or silent). - * - * @param logLevel - The verbosity you set for activity and error logging. Can - * be any of the following values: - * - *
    - *
  • `debug` for the most verbose logging level, primarily for - * debugging.
  • - *
  • `error` to log errors only.
  • - *
  • `silent` to turn off logging.
  • - *
- */ -export declare function setLogLevel(logLevel: LogLevel): void; -/** - * An options object that configures the behavior of {@link setDoc}, {@link - * WriteBatch#set} and {@link Transaction#set} calls. These calls can be - * configured to perform granular merges instead of overwriting the target - * documents in their entirety by providing a `SetOptions` with `merge: true`. - * - * @param merge - Changes the behavior of a `setDoc()` call to only replace the - * values specified in its data argument. Fields omitted from the `setDoc()` - * call remain untouched. - * @param mergeFields - Changes the behavior of `setDoc()` calls to only replace - * the specified field paths. Any field path that is not specified is ignored - * and remains untouched. - */ -export declare type SetOptions = - | { - readonly merge?: boolean; - } - | { - readonly mergeFields?: Array; - }; -export declare interface Settings { - cacheSizeBytes?: number; - host?: string; - ssl?: boolean; - ignoreUndefinedProperties?: boolean; - experimentalForceLongPolling?: boolean; - experimentalAutoDetectLongPolling?: boolean; -} -/** - * Returns true if the provided snapshots are equal. - * - * @param left - A snapshot to compare. - * @param right - A snapshot to compare. - * @returns true if the snapshots are equal. - */ -export declare function snapshotEqual( - left: DocumentSnapshot | QuerySnapshot, - right: DocumentSnapshot | QuerySnapshot -): boolean; -/** - * An options object that can be passed to {@link onSnapshot} and {@link - * QuerySnapshot#docChanges} to control which types of changes to include in the - * result set. - */ -export declare interface SnapshotListenOptions { - /** - * Include a change even if only the metadata of the query or of a document - * changed. Default is false. - */ - readonly includeMetadataChanges?: boolean; -} -/** - * Metadata about a snapshot, describing the state of the snapshot. - */ -export declare class SnapshotMetadata { - /** - * True if the snapshot contains the result of local writes (for example - * `set()` or `update()` calls) that have not yet been committed to the - * backend. If your listener has opted into metadata updates (via - * `SnapshotListenOptions`) you will receive another snapshot with - * `hasPendingWrites` equal to false once the writes have been committed to - * the backend. - */ - readonly hasPendingWrites: boolean; - /** - * True if the snapshot was created from cached data rather than guaranteed - * up-to-date server data. If your listener has opted into metadata updates - * (via `SnapshotListenOptions`) you will receive another snapshot with - * `fromCache` set to false once the client has received up-to-date data from - * the backend. - */ - readonly fromCache: boolean; - private constructor(); - /** - * Returns true if this `SnapshotMetadata` is equal to the provided one. - * - * @param other - The `SnapshotMetadata` to compare against. - * @returns true if this `SnapshotMetadata` is equal to the provided one. - */ - isEqual(other: SnapshotMetadata): boolean; -} -/** - * Options that configure how data is retrieved from a `DocumentSnapshot` (for - * example the desired behavior for server timestamps that have not yet been set - * to their final value). - */ -export declare interface SnapshotOptions { - /** - * If set, controls the return value for server timestamps that have not yet - * been set to their final value. - * - * By specifying 'estimate', pending server timestamps return an estimate - * based on the local clock. This estimate will differ from the final value - * and cause these values to change once the server result becomes available. - * - * By specifying 'previous', pending timestamps will be ignored and return - * their previous value instead. - * - * If omitted or set to 'none', `null` will be returned by default until the - * server value becomes available. - */ - readonly serverTimestamps?: 'estimate' | 'previous' | 'none'; -} -/** - * Creates a `QueryConstraint` 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. - * - * @param snapshot - The snapshot of the document to start after. - * @returns A `QueryConstraint` to pass to `query()` - */ -export declare function startAfter( - snapshot: DocumentSnapshot -): QueryConstraint; -/** - * Creates a `QueryConstraint` that modifies the result set to start after the - * provided fields relative to the order of the query. The order of the field - * values must match the order of the order by clauses of the query. - * - * @param fieldValues - The field values to start this query after, in order - * of the query's order by. - * @returns A `QueryConstraint` to pass to `query()` - */ -export declare function startAfter(...fieldValues: unknown[]): QueryConstraint; -/** - * Creates a `QueryConstraint` 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. - * - * @param snapshot - The snapshot of the document to start at. - * @returns A `QueryConstraint` to pass to `query()`. - */ -export declare function startAt( - snapshot: DocumentSnapshot -): QueryConstraint; -/** - * Creates a `QueryConstraint` that modifies the result set to start at the - * provided fields relative to the order of the query. The order of the field - * values must match the order of the order by clauses of the query. - * - * @param fieldValues - The field values to start this query at, in order - * of the query's order by. - * @returns A `QueryConstraint` to pass to `query()`. - */ -export declare function startAt(...fieldValues: unknown[]): QueryConstraint; -/** - * Represents the state of bundle loading tasks. - * - * Both 'Error' and 'Success' are sinking state: task will abort or complete and there will - * be no more updates after they are reported. - */ -export declare type TaskState = 'Error' | 'Running' | 'Success'; -/** - * Terminates the provided Firestore instance. - * - * After calling `terminate()` only the `clearIndexedDbPersistence()` function - * may be used. Any other function will throw a `FirestoreError`. - * - * To restart after termination, create a new instance of FirebaseFirestore with - * {@link getFirestore}. - * - * Termination does not cancel any pending writes, and any promises that are - * awaiting a response from the server will not be resolved. If you have - * persistence enabled, the next time you start this instance, it will resume - * sending these writes to the server. - * - * Note: Under normal circumstances, calling `terminate()` is not required. This - * function is useful only when you want to force this instance to release all - * of its resources or in combination with `clearIndexedDbPersistence()` to - * ensure that all local state is destroyed between test runs. - * - * @returns A promise that is resolved when the instance has been successfully - * terminated. - */ -export declare function terminate(firestore: FirebaseFirestore): Promise; -/** - * @license - * Copyright 2017 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. - */ -/** - * 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 - * {@link https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto | Timestamp definition}. - */ -export declare class Timestamp { - readonly seconds: number; - readonly nanoseconds: number; - /** - * Creates a new timestamp with the current date, with millisecond precision. - * - * @returns a new timestamp representing the current date. - */ - static now(): Timestamp; - /** - * Creates a new timestamp from the given date. - * - * @param date - The date to initialize the `Timestamp` from. - * @returns A new `Timestamp` representing the same point in time as the given - * date. - */ - static fromDate(date: Date): Timestamp; - /** - * Creates a new timestamp from the given number of milliseconds. - * - * @param milliseconds - Number of milliseconds since Unix epoch - * 1970-01-01T00:00:00Z. - * @returns A new `Timestamp` representing the same point in time as the given - * number of milliseconds. - */ - static fromMillis(milliseconds: number): Timestamp; - /** - * Creates a new timestamp. - * - * @param seconds - The number of seconds of UTC time since Unix epoch - * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to - * 9999-12-31T23:59:59Z inclusive. - * @param nanoseconds - The non-negative fractions of a second at nanosecond - * resolution. Negative second values with fractions must still have - * non-negative nanoseconds values that count forward in time. Must be - * from 0 to 999,999,999 inclusive. - */ - constructor(seconds: number, nanoseconds: number); - /** - * Converts a `Timestamp` to a JavaScript `Date` object. This conversion causes - * a loss of precision since `Date` objects only support millisecond precision. - * - * @returns JavaScript `Date` object representing the same point in time as - * this `Timestamp`, with millisecond precision. - */ - toDate(): Date; - /** - * Converts a `Timestamp` to a numeric timestamp (in milliseconds since - * epoch). This operation causes a loss of precision. - * - * @returns The point in time corresponding to this timestamp, represented as - * the number of milliseconds since Unix epoch 1970-01-01T00:00:00Z. - */ - toMillis(): number; - /** - * Returns true if this `Timestamp` is equal to the provided one. - * - * @param other - The `Timestamp` to compare against. - * @returns true if this `Timestamp` is equal to the provided one. - */ - isEqual(other: Timestamp): boolean; - toString(): string; - toJSON(): { - seconds: number; - nanoseconds: number; - }; - /** - * Converts this object to a primitive string, which allows Timestamp objects to be compared - * using the `>`, `<=`, `>=` and `>` operators. - */ - valueOf(): string; -} -/** - * 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 - * {@link runTransaction}. - */ -export declare class Transaction { - private constructor(); - /** - * Reads the document referenced by the provided {@link DocumentReference}. - * - * @param documentRef - A reference to the document to be read. - * @returns A `DocumentSnapshot` with the read data. - */ - get(documentRef: DocumentReference): Promise>; - /** - * Writes to the document referred to by the provided {@link - * DocumentReference}. If the document does not exist yet, it will be created. - * - * @param documentRef - A reference to the document to be set. - * @param data - An object of the fields and values for the document. - * @returns This `Transaction` instance. Used for chaining method calls. - */ - set(documentRef: DocumentReference, data: T): this; - /** - * Writes to the document referred to by the provided {@link - * DocumentReference}. If the document does not exist yet, it will be created. - * If you provide `merge` or `mergeFields`, the provided data can be merged - * into an existing document. - * - * @param documentRef - A reference to the document to be set. - * @param data - An object of the fields and values for the document. - * @param options - An object to configure the set behavior. - * @returns This `Transaction` instance. Used for chaining method calls. - */ - set( - documentRef: DocumentReference, - data: Partial, - options: SetOptions - ): this; - /** - * Updates fields in the document referred to by the provided {@link - * DocumentReference}. The update will fail if applied to a document that does - * not exist. - * - * @param documentRef - A reference to the document to be updated. - * @param data - An object containing the fields and values with which to -update the document. Fields can contain dots to reference nested fields -within the document. - * @returns This `Transaction` instance. Used for chaining method calls. - */ - update(documentRef: DocumentReference, data: UpdateData): this; - /** - * Updates fields in the document referred to by the provided {@link - * DocumentReference}. The update will fail if applied to a document that does - * not exist. - * - * Nested fields can be updated by providing dot-separated field path - * strings or by providing `FieldPath` objects. - * - * @param documentRef - A reference to the document to be updated. - * @param field - The first field to update. - * @param value - The first value. - * @param moreFieldsAndValues - Additional key/value pairs. - * @returns This `Transaction` instance. Used for chaining method calls. - */ - update( - documentRef: DocumentReference, - field: string | FieldPath, - value: unknown, - ...moreFieldsAndValues: unknown[] - ): this; - /** - * Deletes the document referred to by the provided {@link DocumentReference}. - * - * @param documentRef - A reference to the document to be deleted. - * @returns This `Transaction` instance. Used for chaining method calls. - */ - delete(documentRef: DocumentReference): this; -} -export declare interface Unsubscribe { - (): void; -} -/** - * Update data (for use with {@link updateDoc}) consists of field paths (e.g. - * 'foo' or 'foo.baz') mapped to values. Fields that contain dots reference - * nested fields within the document. - */ -export declare interface UpdateData { - [fieldPath: string]: any; -} -/** - * Updates fields in the document referred to by the specified - * `DocumentReference`. The update will fail if applied to a document that does - * not exist. - * - * @param reference - A reference to the document to update. - * @param data - An object containing the fields and values with which to - * update the document. Fields can contain dots to reference nested fields - * within the document. - * @returns A Promise resolved once the data has been successfully written - * to the backend (note that it won't resolve while you're offline). - */ -export declare function updateDoc( - reference: DocumentReference, - data: UpdateData -): Promise; -/** - * Updates fields in the document referred to by the specified - * `DocumentReference` The update will fail if applied to a document that does - * not exist. - * - * Nested fields can be updated by providing dot-separated field path - * strings or by providing `FieldPath` objects. - * - * @param reference - A reference to the document to update. - * @param field - The first field to update. - * @param value - The first value. - * @param moreFieldsAndValues - Additional key value pairs. - * @returns A Promise resolved once the data has been successfully written - * to the backend (note that it won't resolve while you're offline). - */ -export declare function updateDoc( - reference: DocumentReference, - field: string | FieldPath, - value: unknown, - ...moreFieldsAndValues: unknown[] -): Promise; -/** - * Modify this instance to communicate with the Cloud Firestore emulator. - * - * Note: This must be called before this instance has been used to do any - * operations. - * - * @param firestore - The Firestore instance to configure to connect to the - * emulator. - * @param host - the emulator host (ex: localhost). - * @param port - the emulator port (ex: 9000). - */ -export declare function useFirestoreEmulator( - firestore: FirebaseFirestore, - host: string, - port: number -): void; -/** - * Waits until all currently pending writes for the active user have been - * acknowledged by the backend. - * - * The returned Promise resolves immediately if there are no outstanding writes. - * Otherwise, the Promise waits for all previously issued writes (including - * those written in a previous app session), but it does not wait for writes - * that were added after the function is called. If you want to wait for - * additional writes, call `waitForPendingWrites()` again. - * - * Any outstanding `waitForPendingWrites()` Promises are rejected during user - * changes. - * - * @returns A Promise which resolves when all currently pending writes have been - * acknowledged by the backend. - */ -export declare function waitForPendingWrites( - firestore: FirebaseFirestore -): Promise; -/** - * Creates a `QueryConstraint` that enforces that documents must contain the - * specified field and that the value should satisfy the relation constraint - * provided. - * - * @param fieldPath - The path to compare - * @param opStr - The operation string (e.g "<", "<=", "==", "<", - * "<=", "!="). - * @param value - The value for comparison - * @returns The created `Query`. - */ -export declare function where( - fieldPath: string | FieldPath, - opStr: WhereFilterOp, - value: unknown -): QueryConstraint; -/** - * Filter conditions in a {@link where} clause are specified using the - * strings '<', '<=', '==', '!=', '>=', '>', 'array-contains', 'in', - * 'array-contains-any', and 'not-in'. - */ -export declare type WhereFilterOp = - | '<' - | '<=' - | '==' - | '!=' - | '>=' - | '>' - | 'array-contains' - | 'in' - | 'array-contains-any' - | 'not-in'; -/** - * A write batch, used to perform multiple writes as a single atomic unit. - * - * A `WriteBatch` object can be acquired by calling {@link writeBatch}. It - * provides methods for adding writes to the write batch. None of the writes - * will be committed (or visible locally) until {@link WriteBatch#commit} is - * called. - */ -export declare class WriteBatch { - private constructor(); - /** - * Writes to the document referred to by the provided {@link - * DocumentReference}. If the document does not exist yet, it will be created. - * - * @param documentRef - A reference to the document to be set. - * @param data - An object of the fields and values for the document. - * @returns This `WriteBatch` instance. Used for chaining method calls. - */ - set(documentRef: DocumentReference, data: T): WriteBatch; - /** - * Writes to the document referred to by the provided {@link - * DocumentReference}. If the document does not exist yet, it will be created. - * If you provide `merge` or `mergeFields`, the provided data can be merged - * into an existing document. - * - * @param documentRef - A reference to the document to be set. - * @param data - An object of the fields and values for the document. - * @param options - An object to configure the set behavior. - * @returns This `WriteBatch` instance. Used for chaining method calls. - */ - set( - documentRef: DocumentReference, - data: Partial, - options: SetOptions - ): WriteBatch; - /** - * Updates fields in the document referred to by the provided {@link - * DocumentReference}. The update will fail if applied to a document that does - * not exist. - * - * @param documentRef - A reference to the document to be updated. - * @param data - An object containing the fields and values with which to - * update the document. Fields can contain dots to reference nested fields - * within the document. - * @returns This `WriteBatch` instance. Used for chaining method calls. - */ - update(documentRef: DocumentReference, data: UpdateData): WriteBatch; - /** - * Updates fields in the document referred to by this {@link - * DocumentReference}. The update will fail if applied to a document that does - * not exist. - * - * Nested fields can be update by providing dot-separated field path strings - * or by providing `FieldPath` objects. - * - * @param documentRef - A reference to the document to be updated. - * @param field - The first field to update. - * @param value - The first value. - * @param moreFieldsAndValues - Additional key value pairs. - * @returns This `WriteBatch` instance. Used for chaining method calls. - */ - update( - documentRef: DocumentReference, - field: string | FieldPath, - value: unknown, - ...moreFieldsAndValues: unknown[] - ): WriteBatch; - /** - * Deletes the document referred to by the provided {@link DocumentReference}. - * - * @param documentRef - A reference to the document to be deleted. - * @returns This `WriteBatch` instance. Used for chaining method calls. - */ - delete(documentRef: DocumentReference): WriteBatch; - /** - * Commits all of the writes in this write batch as a single atomic unit. - * - * The result of these writes will only be reflected in document reads that - * occur after the returned Promise resolves. If the client is offline, the - * write fails. If you would like to see local modifications or buffer writes - * until the client is online, use the full Firestore SDK. - * - * @returns A Promise resolved once all of the writes in the batch have been - * successfully written to the backend as an atomic unit (note that it won't - * resolve while you're offline). - */ - commit(): Promise; -} -/** - * Creates a write batch, used for performing multiple writes as a single - * atomic operation. The maximum number of writes allowed in a single WriteBatch - * is 500. - * - * Unlike transactions, write batches are persisted offline and therefore are - * preferable when you don't need to condition your writes on read data. - * - * @returns A `WriteBatch` that can be used to atomically execute multiple - * writes. - */ -export declare function writeBatch(firestore: FirebaseFirestore): WriteBatch; -export {}; diff --git a/repo-scripts/prune-dts/tests/hide-constructor.input.d.ts b/repo-scripts/prune-dts/tests/hide-constructor.input.d.ts deleted file mode 100644 index e69af1bd2c0..00000000000 --- a/repo-scripts/prune-dts/tests/hide-constructor.input.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -export class A { - /** @hideconstructor */ - constructor(b: string); -} -export {}; diff --git a/repo-scripts/prune-dts/tests/hide-constructor.output.d.ts b/repo-scripts/prune-dts/tests/hide-constructor.output.d.ts deleted file mode 100644 index 352ba1ffda7..00000000000 --- a/repo-scripts/prune-dts/tests/hide-constructor.output.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ -export class A { - private constructor(); -} -export {}; diff --git a/repo-scripts/prune-dts/tests/inherits-non-exported-members.input.d.ts b/repo-scripts/prune-dts/tests/inherits-non-exported-members.input.d.ts deleted file mode 100644 index b51730a18aa..00000000000 --- a/repo-scripts/prune-dts/tests/inherits-non-exported-members.input.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -declare class B { - b: string; -} -export class A extends B { - a: string; -} -export {}; diff --git a/repo-scripts/prune-dts/tests/inherits-non-exported-members.output.d.ts b/repo-scripts/prune-dts/tests/inherits-non-exported-members.output.d.ts deleted file mode 100644 index 59f57065189..00000000000 --- a/repo-scripts/prune-dts/tests/inherits-non-exported-members.output.d.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ -export class A { - a: string; - b: string; -} -export {}; diff --git a/repo-scripts/prune-dts/tests/keeps-inheritance.input.d.ts b/repo-scripts/prune-dts/tests/keeps-inheritance.input.d.ts deleted file mode 100644 index 5d41bf41c37..00000000000 --- a/repo-scripts/prune-dts/tests/keeps-inheritance.input.d.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -declare class C { - get c(): T; -} -export declare class B extends C { - get b(): T; -} -export declare class A extends B { - get a(): T; -} -export {}; diff --git a/repo-scripts/prune-dts/tests/keeps-inheritance.output.d.ts b/repo-scripts/prune-dts/tests/keeps-inheritance.output.d.ts deleted file mode 100644 index 0b9a9da6d4f..00000000000 --- a/repo-scripts/prune-dts/tests/keeps-inheritance.output.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ -export declare class B { - get b(): T; - get c(): T; -} -export declare class A extends B { - get a(): T; -} -export {}; diff --git a/repo-scripts/prune-dts/tests/only-modifies-non-exported-types.input.d.ts b/repo-scripts/prune-dts/tests/only-modifies-non-exported-types.input.d.ts deleted file mode 100644 index 6e854a0d717..00000000000 --- a/repo-scripts/prune-dts/tests/only-modifies-non-exported-types.input.d.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -declare class A { - a: string; -} -export class C extends A { - c: number; -} -export class B extends A {} -export {}; diff --git a/repo-scripts/prune-dts/tests/only-modifies-non-exported-types.output.d.ts b/repo-scripts/prune-dts/tests/only-modifies-non-exported-types.output.d.ts deleted file mode 100644 index f25cc9216b4..00000000000 --- a/repo-scripts/prune-dts/tests/only-modifies-non-exported-types.output.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ -export class C { - c: number; - a: string; -} -export class B { - a: string; -} -export {}; diff --git a/repo-scripts/prune-dts/tests/private-interface.input.d.ts b/repo-scripts/prune-dts/tests/private-interface.input.d.ts deleted file mode 100644 index 925bb41e3e4..00000000000 --- a/repo-scripts/prune-dts/tests/private-interface.input.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -declare interface A { - a: string; -} -export class B implements A { - a: string; -} -export {}; diff --git a/repo-scripts/prune-dts/tests/private-interface.output.d.ts b/repo-scripts/prune-dts/tests/private-interface.output.d.ts deleted file mode 100644 index 9d7aaf0f1e6..00000000000 --- a/repo-scripts/prune-dts/tests/private-interface.output.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ -export class B { - a: string; -} -export {}; diff --git a/repo-scripts/prune-dts/tests/propagates-comments.input.d.ts b/repo-scripts/prune-dts/tests/propagates-comments.input.d.ts deleted file mode 100644 index 6460b68a3e1..00000000000 --- a/repo-scripts/prune-dts/tests/propagates-comments.input.d.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -declare class A { - /** a */ - a: string; - /** - * b1 first line - * - * b1 second line - * - * @param b1 b1 param - * @returns b1 return - */ - b(b1: string): string; - /** - * b2 first line - * - * b2 second line - * - * @param b2 b2 param - * @returns b2 return - */ - b(b2: string): string; -} -export class B extends A {} -export {}; diff --git a/repo-scripts/prune-dts/tests/propagates-comments.output.d.ts b/repo-scripts/prune-dts/tests/propagates-comments.output.d.ts deleted file mode 100644 index 1f7ba0a2cca..00000000000 --- a/repo-scripts/prune-dts/tests/propagates-comments.output.d.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ -export class B { - /** - * a - */ - a: string; - /** - * b1 first line - * - * b1 second line - * - * @param b1 b1 param - * @returns b1 return - */ - b(b1: string): string; - /** - * b2 first line - * - * b2 second line - * - * @param b2 b2 param - * @returns b2 return - */ - b(b2: string): string; -} -export {}; diff --git a/repo-scripts/prune-dts/tests/propagates-members.input.d.ts b/repo-scripts/prune-dts/tests/propagates-members.input.d.ts deleted file mode 100644 index 8870cbe22e2..00000000000 --- a/repo-scripts/prune-dts/tests/propagates-members.input.d.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -declare class A { - a: string; -} -export class B extends A { - b: string; -} -export class C extends B { - c: string; -} -export {}; diff --git a/repo-scripts/prune-dts/tests/propagates-members.output.d.ts b/repo-scripts/prune-dts/tests/propagates-members.output.d.ts deleted file mode 100644 index 07f579f865f..00000000000 --- a/repo-scripts/prune-dts/tests/propagates-members.output.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ -export class B { - b: string; - a: string; -} -export class C extends B { - c: string; -} -export {}; diff --git a/repo-scripts/prune-dts/tests/prunes-non-exported-types.input.d.ts b/repo-scripts/prune-dts/tests/prunes-non-exported-types.input.d.ts deleted file mode 100644 index 54cd7c31224..00000000000 --- a/repo-scripts/prune-dts/tests/prunes-non-exported-types.input.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -export function a(): void; -declare function b(): void; -export class A {} -declare class B {} -export {}; diff --git a/repo-scripts/prune-dts/tests/prunes-non-exported-types.output.d.ts b/repo-scripts/prune-dts/tests/prunes-non-exported-types.output.d.ts deleted file mode 100644 index 67d55413794..00000000000 --- a/repo-scripts/prune-dts/tests/prunes-non-exported-types.output.d.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ -export function a(): void; -export class A {} -export {}; diff --git a/repo-scripts/prune-dts/tests/prunes-underscores.input.d.ts b/repo-scripts/prune-dts/tests/prunes-underscores.input.d.ts deleted file mode 100644 index a390ad2f8c5..00000000000 --- a/repo-scripts/prune-dts/tests/prunes-underscores.input.d.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -export class A { - a: string; - _b: string; - _bMethod(): string; - get _bGetter(): string; - readonly _bProperty: string; -} -export {}; diff --git a/repo-scripts/prune-dts/tests/prunes-underscores.output.d.ts b/repo-scripts/prune-dts/tests/prunes-underscores.output.d.ts deleted file mode 100644 index c8e8d7963f0..00000000000 --- a/repo-scripts/prune-dts/tests/prunes-underscores.output.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ -export class A { - a: string; -} -export {}; diff --git a/repo-scripts/prune-dts/tests/references-public-interfaces.input.d.ts b/repo-scripts/prune-dts/tests/references-public-interfaces.input.d.ts deleted file mode 100644 index 1cf7bac1055..00000000000 --- a/repo-scripts/prune-dts/tests/references-public-interfaces.input.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -export interface A { - a: T; -} -declare interface B extends A {} -export function c(a: B): void; -export {}; diff --git a/repo-scripts/prune-dts/tests/references-public-interfaces.output.d.ts b/repo-scripts/prune-dts/tests/references-public-interfaces.output.d.ts deleted file mode 100644 index 901c0113fae..00000000000 --- a/repo-scripts/prune-dts/tests/references-public-interfaces.output.d.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ -export interface A { - a: T; -} -export function c(a: A): void; -export {}; diff --git a/repo-scripts/prune-dts/tests/references-public-types.input.d.ts b/repo-scripts/prune-dts/tests/references-public-types.input.d.ts deleted file mode 100644 index a37b3567796..00000000000 --- a/repo-scripts/prune-dts/tests/references-public-types.input.d.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -declare class B {} -export class A extends B {} -export function a(b: B): B; -export class Ab { - constructor(b: B); - b: B; - readonly bProperty: B; - get bGetter(): B; - bMethod(b: B): B; -} -export {}; diff --git a/repo-scripts/prune-dts/tests/references-public-types.output.d.ts b/repo-scripts/prune-dts/tests/references-public-types.output.d.ts deleted file mode 100644 index bdd0de28bfe..00000000000 --- a/repo-scripts/prune-dts/tests/references-public-types.output.d.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ -export class A {} -export function a(b: A): A; -export class Ab { - constructor(b: A); - b: A; - readonly bProperty: A; - get bGetter(): A; - bMethod(b: A): A; -} -export {}; diff --git a/repo-scripts/prune-dts/tests/resolves-generics-different-name.input.d.ts b/repo-scripts/prune-dts/tests/resolves-generics-different-name.input.d.ts deleted file mode 100644 index 4389960f78b..00000000000 --- a/repo-scripts/prune-dts/tests/resolves-generics-different-name.input.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -declare class A { - a: T; -} -export class B extends A { - b: K; -} -export {}; diff --git a/repo-scripts/prune-dts/tests/resolves-generics-different-name.output.d.ts b/repo-scripts/prune-dts/tests/resolves-generics-different-name.output.d.ts deleted file mode 100644 index 2926cd75565..00000000000 --- a/repo-scripts/prune-dts/tests/resolves-generics-different-name.output.d.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ -export class B { - b: K; - a: K; -} -export {}; diff --git a/repo-scripts/prune-dts/tests/resolves-generics-through-inheritance.input.d.ts b/repo-scripts/prune-dts/tests/resolves-generics-through-inheritance.input.d.ts deleted file mode 100644 index 7208f2cf528..00000000000 --- a/repo-scripts/prune-dts/tests/resolves-generics-through-inheritance.input.d.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -declare class A { - a: T; -} -export class B extends A { - b: T; -} -export class C extends A {} -export {}; diff --git a/repo-scripts/prune-dts/tests/resolves-generics-through-inheritence.output.d.ts b/repo-scripts/prune-dts/tests/resolves-generics-through-inheritence.output.d.ts deleted file mode 100644 index 3dca92f7d56..00000000000 --- a/repo-scripts/prune-dts/tests/resolves-generics-through-inheritence.output.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ -export class B { - b: T; - a: string; -} -export class C { - a: T; -} -export {}; diff --git a/repo-scripts/prune-dts/tests/resolves-generics.input.d.ts b/repo-scripts/prune-dts/tests/resolves-generics.input.d.ts deleted file mode 100644 index 8c840eafffc..00000000000 --- a/repo-scripts/prune-dts/tests/resolves-generics.input.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -declare class B { - b: T; -} -export class A extends B {} -export {}; diff --git a/repo-scripts/prune-dts/tests/resolves-generics.output.d.ts b/repo-scripts/prune-dts/tests/resolves-generics.output.d.ts deleted file mode 100644 index d97cf28da79..00000000000 --- a/repo-scripts/prune-dts/tests/resolves-generics.output.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ -export class A { - b: string; -} -export {}; diff --git a/repo-scripts/prune-dts/tests/swaps-generics.input.d.ts b/repo-scripts/prune-dts/tests/swaps-generics.input.d.ts deleted file mode 100644 index 43f9d6a004c..00000000000 --- a/repo-scripts/prune-dts/tests/swaps-generics.input.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -declare class B { - b: T; -} -export class A extends B { - a: T; -} -export {}; diff --git a/repo-scripts/prune-dts/tests/swaps-generics.output.d.ts b/repo-scripts/prune-dts/tests/swaps-generics.output.d.ts deleted file mode 100644 index 32e47ed96b9..00000000000 --- a/repo-scripts/prune-dts/tests/swaps-generics.output.d.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ -export class A { - a: T; - b: string; -} -export {}; diff --git a/repo-scripts/prune-dts/tsconfig.eslint.json b/repo-scripts/prune-dts/tsconfig.eslint.json deleted file mode 100644 index 814c97c2f85..00000000000 --- a/repo-scripts/prune-dts/tsconfig.eslint.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "extends": "../../config/tsconfig.base.json", - "include": ["../../packages/*/dist/**/*.d.ts"] -} diff --git a/repo-scripts/prune-dts/tsconfig.json b/repo-scripts/prune-dts/tsconfig.json deleted file mode 100644 index 236dfb5b9d8..00000000000 --- a/repo-scripts/prune-dts/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "compilerOptions": { - "outDir": "dist", - "importHelpers": true, - "module": "commonjs", - "moduleResolution": "node", - "resolveJsonModule": true, - "target": "es2017", - "esModuleInterop": true, - "declaration": true, - "strict": true - }, - "exclude": ["dist/**/*"] -} diff --git a/repo-scripts/size-analysis/.eslintrc.js b/repo-scripts/size-analysis/.eslintrc.js deleted file mode 100644 index ca80aa0f69a..00000000000 --- a/repo-scripts/size-analysis/.eslintrc.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -module.exports = { - extends: '../../config/.eslintrc.js', - parserOptions: { - project: 'tsconfig.json', - // to make vscode-eslint work with monorepo - // https://github.com/typescript-eslint/typescript-eslint/issues/251#issuecomment-463943250 - tsconfigRootDir: __dirname - } -}; diff --git a/repo-scripts/size-analysis/README.md b/repo-scripts/size-analysis/README.md deleted file mode 100644 index 6f81162b789..00000000000 --- a/repo-scripts/size-analysis/README.md +++ /dev/null @@ -1,117 +0,0 @@ -# Modular Export Binary Size Calculator - -The library supports two main features: -- Analyze the size of individual exports from a given package. -- Analyze the size of predefined bundle definitions. A bundle definition is a group of imports from different Firebase packages to support a certain use case. For example, to support Google signin and read from Firestore once. - -## Analyze the size of individual exports -### CLI Usage - -Flags - -- `--version` Show version number [boolean] -- `--inputModule, --im` The name of the module(s) to be analyzed. example: --inputModule "@firebase/functions" "firebase/auth" [array] -- `--inputDtsFile, --if` Support for adhoc analysis. requires a path to dts file [string] -- `--inputBundleFile, --ib` Support for adhoc analysis. requires a path to a bundle file [string] -- `--output, -o` The location where report(s) will be generated, a directory path if module(s) are analyzed; a file path if ad hoc analysis is to be performed [string] -- `--help ` Show help [boolean] - -#### To Do Analysis On One or Multiple Firebase Packages - -$firebase-js-sdk/repo-scripts/size-analysis `ts-node-script cli.ts --im "@firebase/app" "@firebase/auth" -o `. - -#### To Do Analysis On All Firebase Packages - -$firebase-js-sdk/repo-scripts/size-analysis `ts-node-script cli.ts -o ` -#### Adhoc Support - -$firebase-js-sdk/repo-scripts/size-analysis `ts-node-script cli.ts --if --ib -o ` - - -### Use the Tool Programmatically -### `async generateReportForModule(moduleLocation: string): Promise` -#### This function generates size analysis report for the given module specified by the `moduleLocation` argument. -#### `@param moduleLocation: an absolute path to location of a firebase module` -``` -try { - const moduleLocation: string = "absolute/path/to/firebase/module"; - const report: Report = await generateReportForModule(moduleLocation); - console.log(report); - - -}catch (error) { - - - console.log(error); -} - - -``` - -### `async generateReportForModules(moduleLocations: string[]): Promise` -#### This function recursively generates a size analysis report for every module (and its submodules) listed in moduleLocations array -#### `@param moduleLocations: an array of strings where each is a path to location of a firebase module` - -``` -try { - const moduleLocations: string[] = ['...module1', '...module2']; - const reports: Report[] = await generateReportForModules(moduleLocations); - console.log(reports); - - -}catch (error) { - - - console.log(error); -} - - -``` - -### `async generateReport(name: string, dtsFile: string, bundleFile: string): Promise` -#### Use this function for adhoc analysis. This function generates a size analysis report from the given definition file. -#### `@param name: name to be displayed on the report` -#### `@param: dtsFile: absolute path to a definition file of interest.` -#### `@param bundleFile: absolute path to the bundle file of given definition file` - - -``` -try { - const name: string = "adhoc"; - const dtsFile: string = '.../index.d.ts'; - const bundleFile: string = '.../index.esm2017.js'; - const report: Report = await generateReport(name, dtsFile, bundleFile); - console.log(report); - - -}catch (error) { - - - console.log(error); -} - - -``` - - -## Analyze the Size of Bundles - -A bundle is defined by a bundle definition. A bundle definition is a group of imports from different Firebase packages to support a certain use case. - -You can find some sample bundle definitions in the `/bundle-definitions` folder. - -### CLI Usage - -To analyze bundles, you would use the command `bundle`: -$firebase-js-sdk/repo-scripts/size-analysis `ts-node-script cli.ts bundle [FLAGs]`. - -Flags - -- `--input`, or `-i` Path to the bundle definition file' - -- `--mode`, or `-m` Possible values are `npm` and `local` - - `npm`: analyze packages published to npm - - `local`: ignore the version defined in bundle definition and analyze the corresponding local packages -- `--bundler`, or `-b`. Possible values are `rollup`, `webpack`, `both`. Decides which bundlers to be used to create bundles. -- `--output`, or `-o` The output file location of the bundle analysis result. -- `--debug`, or `-d` Enables the debug mode which output additional data in the bundle analysis result. diff --git a/repo-scripts/size-analysis/analysis-helper.ts b/repo-scripts/size-analysis/analysis-helper.ts deleted file mode 100644 index 9507bfe253c..00000000000 --- a/repo-scripts/size-analysis/analysis-helper.ts +++ /dev/null @@ -1,670 +0,0 @@ -/** - * @license - * Copyright 2020 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 * as tmp from 'tmp'; -import * as path from 'path'; -import * as fs from 'fs'; -import * as rollup from 'rollup'; -import * as terser from 'terser'; -import * as ts from 'typescript'; -import resolve from '@rollup/plugin-node-resolve'; -import commonjs from '@rollup/plugin-commonjs'; -import { projectRoot } from '../../scripts/utils'; - -export const enum ErrorCode { - INVALID_FLAG_COMBINATION = 'Invalid command flag combinations!', - BUNDLE_FILE_DOES_NOT_EXIST = 'Module does not have a bundle file!', - DTS_FILE_DOES_NOT_EXIST = 'Module does not have a dts file!', - OUTPUT_DIRECTORY_REQUIRED = 'An output directory is required but a file given!', - OUTPUT_FILE_REQUIRED = 'An output file is required but a directory given!', - INPUT_FILE_DOES_NOT_EXIST = 'Input file does not exist!', - INPUT_DTS_FILE_DOES_NOT_EXIST = 'Input dts file does not exist!', - INPUT_BUNDLE_FILE_DOES_NOT_EXIST = 'Input bundle file does not exist!', - FILE_PARSING_ERROR = 'Failed to parse js file!', - PKG_JSON_DOES_NOT_EXIST = 'Module does not have a package.json file!', - TYPINGS_FIELD_NOT_DEFINED = 'Module does not have typings field defined in its package.json!' -} - -/** Contains a list of members by type. */ -export interface MemberList { - classes: string[]; - functions: string[]; - variables: string[]; - enums: string[]; - unknown: string[]; -} -/** Contains the dependencies and the size of their code for a single export. */ -export interface ExportData { - name: string; - classes: string[]; - functions: string[]; - variables: string[]; - enums: string[]; - unknown: string[]; - externals: { [key: string]: string[] }; - size: number; - sizeWithExtDeps: number; -} - -export interface Report { - name: string; - symbols: ExportData[]; -} -/** - * Helper for extractDependencies that extracts the dependencies and the size - * of the minified build. - */ -export async function extractDependenciesAndSize( - exportName: string, - jsBundle: string -): Promise { - const input = tmp.fileSync().name + '.js'; - const externalDepsResolvedOutput = tmp.fileSync().name + '.js'; - const externalDepsNotResolvedOutput = tmp.fileSync().name + '.js'; - - const exportStatement = `export { ${exportName} } from '${path.resolve( - jsBundle - )}';`; - fs.writeFileSync(input, exportStatement); - - // Run Rollup on the JavaScript above to produce a tree-shaken build - const externalDepsResolvedBundle = await rollup.rollup({ - input, - plugins: [ - resolve({ - mainFields: ['esm2017', 'module', 'main'] - }), - commonjs() - ] - }); - await externalDepsResolvedBundle.write({ - file: externalDepsResolvedOutput, - format: 'es' - }); - const externalDepsNotResolvedBundle = await rollup.rollup({ - input, - // exclude all firebase dependencies and tslib - external: id => id.startsWith('@firebase') || id === 'tslib' - }); - await externalDepsNotResolvedBundle.write({ - file: externalDepsNotResolvedOutput, - format: 'es' - }); - const dependencies: MemberList = extractAllTopLevelSymbols( - externalDepsNotResolvedOutput - ); - - const externalDepsResolvedOutputContent = fs.readFileSync( - externalDepsResolvedOutput, - 'utf-8' - ); - // Extract size of minified build - const externalDepsNotResolvedOutputContent = fs.readFileSync( - externalDepsNotResolvedOutput, - 'utf-8' - ); - const externalDepsResolvedOutputContentMinimized = await terser.minify( - externalDepsResolvedOutputContent, - { - format: { - comments: false - }, - mangle: { toplevel: true }, - compress: false - } - ); - const externalDepsNotResolvedOutputContentMinimized = await terser.minify( - externalDepsNotResolvedOutputContent, - { - format: { - comments: false - }, - mangle: { toplevel: true }, - compress: false - } - ); - const exportData: ExportData = { - name: '', - classes: [], - functions: [], - variables: [], - enums: [], - unknown: [], - externals: {}, - size: 0, - sizeWithExtDeps: 0 - }; - exportData.name = exportName; - for (const key of Object.keys(dependencies) as Array) { - exportData[key] = dependencies[key]; - } - - exportData.externals = extractExternalDependencies( - externalDepsNotResolvedOutput - ); - exportData.size = Buffer.byteLength( - externalDepsNotResolvedOutputContentMinimized.code!, - 'utf-8' - ); - exportData.sizeWithExtDeps = Buffer.byteLength( - externalDepsResolvedOutputContentMinimized.code!, - 'utf-8' - ); - fs.unlinkSync(input); - fs.unlinkSync(externalDepsNotResolvedOutput); - fs.unlinkSync(externalDepsResolvedOutput); - return exportData; -} - -/** - * Check what symbols are being pulled into a bundle - */ -export function extractAllTopLevelSymbols(filePath: string): MemberList { - const program = ts.createProgram([filePath], { allowJs: true }); - const sourceFile = program.getSourceFile(filePath); - if (!sourceFile) { - throw new Error(`${ErrorCode.FILE_PARSING_ERROR} ${filePath}`); - } - - const declarations: MemberList = { - functions: [], - classes: [], - variables: [], - enums: [], - unknown: [] - }; - - ts.forEachChild(sourceFile, node => { - if (ts.isFunctionDeclaration(node)) { - declarations.functions.push(node.name!.text); - } else if (ts.isClassDeclaration(node)) { - declarations.classes.push(node.name!.text); - } else if (ts.isVariableDeclaration(node)) { - declarations.variables.push(node.name!.getText()); - } else if (ts.isEnumDeclaration(node)) { - // `const enum`s should not be analyzed. They do not add to bundle size and - // creating a file that imports them causes an error during the rollup step. - if ( - // Identifies if this enum had a "const" modifier attached. - !node.modifiers?.some(mod => mod.kind === ts.SyntaxKind.ConstKeyword) - ) { - declarations.enums.push(node.name.escapedText.toString()); - } - } else if (ts.isVariableStatement(node)) { - const variableDeclarations = node.declarationList.declarations; - - variableDeclarations.forEach(variableDeclaration => { - //variableDeclaration.name could be of Identifier type or of BindingPattern type - // Identifier Example: export const a: string = "aString"; - if (ts.isIdentifier(variableDeclaration.name)) { - declarations.variables.push( - variableDeclaration.name.getText(sourceFile) - ); - } - // Binding Pattern Example: export const {a, b} = {a: 1, b: 1}; - else { - const elements = variableDeclaration.name - .elements as ts.NodeArray; - elements.forEach((node: ts.BindingElement) => { - declarations.variables.push(node.name.getText(sourceFile)); - }); - } - }); - } - }); - - //Sort to ensure stable output - Object.values(declarations).forEach(each => { - each.sort(); - }); - return declarations; -} - -/** - * Extract exports of a module - */ -export function extractExports(filePath: string): MemberList { - const exportDeclarations: MemberList = { - functions: [], - classes: [], - variables: [], - enums: [], - unknown: [] - }; - - const program = ts.createProgram([filePath], { - allowJs: true, - baseUrl: path.resolve(`${projectRoot}/node_modules`) - }); - const checker = program.getTypeChecker(); - const sourceFile = program.getSourceFile(filePath)!; - const module = checker.getSymbolAtLocation(sourceFile); - // no export from the file - if (!module) { - return exportDeclarations; - } - - const exports = checker.getExportsOfModule(module); - - for (const expt of exports) { - // get the source declaration where we can determine the type of the export. e.g. class vs function - let sourceSymbol = expt; - if (sourceSymbol.declarations?.[0].kind === ts.SyntaxKind.ExportSpecifier) { - sourceSymbol = checker.getAliasedSymbol(expt); - } - - if (!sourceSymbol.declarations || sourceSymbol.declarations.length === 0) { - console.log('Could not find the source symbol for ', expt.name); - continue; - } - const sourceDeclaration = sourceSymbol.declarations[0]; - - if (ts.isFunctionDeclaration(sourceDeclaration)) { - exportDeclarations.functions.push(expt.name); - } else if (ts.isClassDeclaration(sourceDeclaration)) { - exportDeclarations.classes.push(expt.name); - } else if (ts.isVariableDeclaration(sourceDeclaration)) { - exportDeclarations.variables.push(expt.name); - } else if (ts.isEnumDeclaration(sourceDeclaration)) { - // `const enum`s should not be analyzed. They do not add to bundle size and - // creating a file that imports them causes an error during the rollup step. - if ( - // Identifies if this enum had a "const" modifier attached. - !sourceDeclaration.modifiers?.some( - mod => mod.kind === ts.SyntaxKind.ConstKeyword - ) - ) { - exportDeclarations.enums.push(expt.name); - } - } else { - console.log(`export of unknown type: ${expt.name}`); - exportDeclarations.unknown.push(expt.name); - } - } - - Object.values(exportDeclarations).forEach(each => { - each.sort(); - }); - - return exportDeclarations; -} - -/** - * To Make sure symbols of every category are unique. - */ -export function dedup(memberList: MemberList): MemberList { - for (const key of Object.keys(memberList) as Array) { - const set: Set = new Set(memberList[key]); - memberList[key] = Array.from(set); - } - return memberList; -} - -export function mapSymbolToType( - map: Map, - memberList: MemberList -): MemberList { - const newMemberList: MemberList = { - functions: [], - classes: [], - variables: [], - enums: [], - unknown: [] - }; - - for (const key of Object.keys(memberList) as Array) { - memberList[key].forEach((element: string) => { - if (map.has(element)) { - newMemberList[map.get(element)! as keyof MemberList].push(element); - } else { - newMemberList[key].push(element); - } - }); - } - return newMemberList; -} - -export function replaceAll( - memberList: MemberList, - original: string, - current: string -): void { - for (const key of Object.keys(memberList) as Array) { - memberList[key] = replaceWith(memberList[key], original, current); - } -} - -function replaceWith( - arr: string[], - original: string, - current: string -): string[] { - const rv: string[] = []; - for (const each of arr) { - if (each.localeCompare(original) === 0) { - rv.push(current); - } else { - rv.push(each); - } - } - return rv; -} - -/** - * - * This functions writes generated json report(s) to a file - */ -export function writeReportToFile(report: Report, outputFile: string): void { - if (fs.existsSync(outputFile) && !fs.lstatSync(outputFile).isFile()) { - throw new Error(ErrorCode.OUTPUT_FILE_REQUIRED); - } - const directoryPath = path.dirname(outputFile); - //for output file path like ./dir/dir1/dir2/file, we need to make sure parent dirs exist. - if (!fs.existsSync(directoryPath)) { - fs.mkdirSync(directoryPath, { recursive: true }); - } - fs.writeFileSync(outputFile, JSON.stringify(report, null, 4)); -} -/** - * - * This functions writes generated json report(s) to a file of given directory - */ -export function writeReportToDirectory( - report: Report, - fileName: string, - directoryPath: string -): void { - if ( - fs.existsSync(directoryPath) && - !fs.lstatSync(directoryPath).isDirectory() - ) { - throw new Error(ErrorCode.OUTPUT_DIRECTORY_REQUIRED); - } - writeReportToFile(report, `${directoryPath}/${fileName}`); -} - -/** - * This function extract unresolved external module symbols from bundle file import statements. - * - */ -export function extractExternalDependencies(minimizedBundleFile: string): { - [key: string]: string[]; -} { - const program = ts.createProgram([minimizedBundleFile], { allowJs: true }); - - const sourceFile = program.getSourceFile(minimizedBundleFile); - if (!sourceFile) { - throw new Error(`${ErrorCode.FILE_PARSING_ERROR} ${minimizedBundleFile}`); - } - - const externalsMap: Map = new Map(); - ts.forEachChild(sourceFile, node => { - if (ts.isImportDeclaration(node) && node.importClause) { - const moduleName: string = node.moduleSpecifier.getText(sourceFile); - if (!externalsMap.has(moduleName)) { - externalsMap.set(moduleName, []); - } - - //import {a, b } from '@firebase/dummy-exp'; - // import {a as c, b } from '@firebase/dummy-exp'; - if ( - node.importClause.namedBindings && - ts.isNamedImports(node.importClause.namedBindings) - ) { - node.importClause.namedBindings.elements.forEach(each => { - // if imported symbol is renamed, we want its original name which is stored in propertyName - if (each.propertyName) { - externalsMap - .get(moduleName)! - .push(each.propertyName.getText(sourceFile)); - } else { - externalsMap.get(moduleName)!.push(each.name.getText(sourceFile)); - } - }); - // import * as fs from 'fs' - } else if ( - node.importClause.namedBindings && - ts.isNamespaceImport(node.importClause.namedBindings) - ) { - externalsMap.get(moduleName)!.push('*'); - // import a from '@firebase/dummy-exp' - } else if ( - node.importClause.name && - ts.isIdentifier(node.importClause.name) - ) { - externalsMap.get(moduleName)!.push('default export'); - } - } - }); - const externals: { [key: string]: string[] } = {}; - externalsMap.forEach((value, key) => { - externals[key.replace(/'/g, '')] = value; - }); - return externals; -} - -/** - * This function generates a binary size report for the given module specified by the moduleLocation argument. - * @param moduleLocation a path to location of a firebase module - */ -export async function generateReportForModule( - moduleLocation: string -): Promise { - const packageJsonPath = `${moduleLocation}/package.json`; - if (!fs.existsSync(packageJsonPath)) { - throw new Error( - `Firebase Module locates at ${moduleLocation}: ${ErrorCode.PKG_JSON_DOES_NOT_EXIST}` - ); - } - const packageJson = JSON.parse( - fs.readFileSync(packageJsonPath, { encoding: 'utf-8' }) - ); - // to exclude -types modules - const TYPINGS: string = 'typings'; - if (packageJson[TYPINGS]) { - const dtsFile = `${moduleLocation}/${packageJson[TYPINGS]}`; - const bundleLocation: string = retrieveBundleFileLocation(packageJson); - if (!bundleLocation) { - throw new Error(ErrorCode.BUNDLE_FILE_DOES_NOT_EXIST); - } - const bundleFile = `${moduleLocation}/${bundleLocation}`; - const jsonReport: Report = await generateReport( - packageJson.name, - dtsFile, - bundleFile - ); - - return jsonReport; - } - throw new Error( - `Firebase Module locates at: ${moduleLocation}: ${ErrorCode.TYPINGS_FIELD_NOT_DEFINED}` - ); -} -/** - * - * @param pkgJson package.json of the module. - * - * This function implements a fallback of locating module's bundle file. - * It first looks at esm2017 field of package.json, then module field. Main - * field at the last. - * - */ -function retrieveBundleFileLocation(pkgJson: { - [key: string]: string; -}): string { - if (pkgJson['esm2017']) { - return pkgJson['esm2017']; - } - if (pkgJson['module']) { - return pkgJson['module']; - } - if (pkgJson['main']) { - return pkgJson['main']; - } - return ''; -} - -/** - * A recursive function that locates and generates reports for sub-modules - */ -async function traverseDirs( - moduleLocation: string, - // eslint-disable-next-line @typescript-eslint/ban-types - executor: Function, - level: number, - levelLimit: number -): Promise { - if (level > levelLimit) { - return []; - } - - const reports: Report[] = []; - const report: Report = await executor(moduleLocation); - if (report != null) { - reports.push(report); - } - - for (const name of fs.readdirSync(moduleLocation)) { - const p = `${moduleLocation}/${name}`; - const generateSizeAnalysisReportPkgJsonField: string = - 'generate-size-analysis-report'; - // submodules of a firebase module should set generate-size-analysis-report field of package.json to true - // in order to be analyzed - if ( - fs.lstatSync(p).isDirectory() && - fs.existsSync(`${p}/package.json`) && - JSON.parse(fs.readFileSync(`${p}/package.json`, { encoding: 'utf-8' }))[ - generateSizeAnalysisReportPkgJsonField - ] - ) { - const subModuleReports: Report[] = await traverseDirs( - p, - executor, - level + 1, - levelLimit - ); - if (subModuleReports !== null && subModuleReports.length !== 0) { - reports.push(...subModuleReports); - } - } - } - return reports; -} - -/** - * - * This functions generates the final json report for the module. - * @param publicApi all symbols extracted from the input dts file. - * @param jsFile a bundle file generated by rollup according to the input dts file. - * @param map maps every symbol listed in publicApi to its type. eg: aVariable -> variable. - */ -export async function buildJsonReport( - moduleName: string, - publicApi: MemberList, - jsFile: string -): Promise { - const result: Report = { - name: moduleName, - symbols: [] - }; - for (const exp of publicApi.classes) { - try { - result.symbols.push(await extractDependenciesAndSize(exp, jsFile)); - } catch (e) { - console.log(e); - } - } - - for (const exp of publicApi.functions) { - try { - result.symbols.push(await extractDependenciesAndSize(exp, jsFile)); - } catch (e) { - console.log(e); - } - } - for (const exp of publicApi.variables) { - try { - result.symbols.push(await extractDependenciesAndSize(exp, jsFile)); - } catch (e) { - console.log(e); - } - } - - for (const exp of publicApi.enums) { - try { - result.symbols.push(await extractDependenciesAndSize(exp, jsFile)); - } catch (e) { - console.log(e); - } - } - return result; -} -/** - * - * This function generates a report from given dts file. - * @param name a name to be displayed on the report. a module name if for a firebase module; a random name if for adhoc analysis. - * @param dtsFile absolute path to the definition file of interest. - * @param bundleFile absolute path to the bundle file of the given definition file. - */ -export async function generateReport( - name: string, - dtsFile: string, - bundleFile: string -): Promise { - const resolvedDtsFile = path.resolve(dtsFile); - const resolvedBundleFile = path.resolve(bundleFile); - if (!fs.existsSync(resolvedDtsFile)) { - throw new Error(ErrorCode.INPUT_DTS_FILE_DOES_NOT_EXIST); - } - if (!fs.existsSync(resolvedBundleFile)) { - throw new Error(ErrorCode.INPUT_BUNDLE_FILE_DOES_NOT_EXIST); - } - - console.log('generating report for ', name); - const publicAPI = extractExports(resolvedBundleFile); - return buildJsonReport(name, publicAPI, bundleFile); -} - -/** - * This function recursively generates a binary size report for every module listed in moduleLocations array. - * - * @param moduleLocations an array of strings where each is a path to location of a firebase module - * - */ -export async function generateReportForModules( - moduleLocations: string[] -): Promise { - const reportCollection: Report[] = []; - - for (const moduleLocation of moduleLocations) { - // we traverse the dir in order to include binaries for submodules, e.g. @firebase/firestore/memory - // Currently we only traverse 1 level deep because we don't have any submodule deeper than that. - const reportsForModuleAndItsSubModule: Report[] = await traverseDirs( - moduleLocation, - generateReportForModule, - 0, - 1 - ); - if ( - reportsForModuleAndItsSubModule !== null && - reportsForModuleAndItsSubModule.length !== 0 - ) { - reportCollection.push(...reportsForModuleAndItsSubModule); - } - } - return reportCollection; -} diff --git a/repo-scripts/size-analysis/analyze-all-bundles.ts b/repo-scripts/size-analysis/analyze-all-bundles.ts deleted file mode 100644 index 74390732980..00000000000 --- a/repo-scripts/size-analysis/analyze-all-bundles.ts +++ /dev/null @@ -1,117 +0,0 @@ -/** - * @license - * Copyright 2021 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 * as fs from 'fs'; -import * as path from 'path'; -import * as tmp from 'tmp'; - -import { Bundler, Mode, run as runBundleAnalysis } from './bundle-analysis'; -import { Report } from '../../scripts/size_report/report_binary_size'; - -/** - * Runs bundle analysis for all bundle definition files under: - * - * `firebase-js-sdk/repo-scripts/size-analysis/bundle-definitions` - * - * The method accepts an optional parameter `version`: - * 1. when presented (for example, `version` = '9.0.0'), this method measures the bundle size by - * building test bundles with dependencies at that specific version downloaded from npm - * 2. when omitted (in this case, `version` = null), this method measures the bundle size by - * building test bundles with dependencies from local artifacts (e.g. produced by `yarn build`) - * - * #1 is intended only for manual runs for the purpose of back-filling historical size data. #2 is - * intended for CI runs that measure size for the current commit. - * - * More details on how a test bundle is built can be found in `bundle-analysis.ts`. - * - * @param version - If present, the SDK version to run measurement against - * @returns A list of bundle size measurements - */ -export async function generateReportForBundles( - version?: string -): Promise { - const definitionDir = `${__dirname}/bundle-definitions`; - const outputDir = tmp.dirSync().name; - console.log(`Bundle definitions are located at "${definitionDir}".`); - console.log(`Analysis output are located at "${outputDir}".`); - - const bundles = fs.readdirSync(definitionDir); - const results: Report[] = []; - for (const bundle of bundles) { - const product = path.basename(bundle, '.json'); - const output = `${outputDir}/${product}.analysis.json`; - if (version) { - overwriteVersion(definitionDir, bundle, outputDir, version); - } - const option = { - input: version ? `${outputDir}/${bundle}` : `${definitionDir}/${bundle}`, - bundler: Bundler.Rollup, - mode: version ? Mode.Npm : Mode.Local, - output, - debug: true - }; - console.log(`Running for bundle "${bundle}" with mode "${option.mode}".`); - await runBundleAnalysis(option); - const measurements = parseAnalysisOutput(product, output); - results.push(...measurements); - } - console.log(results); - return results; -} - -function overwriteVersion( - definitionDir: string, - bundle: string, - temp: string, - version: string -): void { - const definitions = JSON.parse( - fs.readFileSync(`${definitionDir}/${bundle}`, { encoding: 'utf-8' }) - ); - for (const definition of definitions) { - const dependencies = definition.dependencies; - for (const dependency of dependencies) { - dependency.versionOrTag = version; - } - } - fs.writeFileSync(`${temp}/${bundle}`, JSON.stringify(definitions, null, 2), { - encoding: 'utf-8' - }); -} - -function parseAnalysisOutput(product: string, output: string): Report[] { - const analyses = JSON.parse(fs.readFileSync(output, { encoding: 'utf-8' })); - const results: Report[] = []; - for (const analysis of analyses) { - // The API of the backend for persisting size measurements currently requires data to be - // organized strictly in the below json format: - // - // { - // sdk: , - // type: , - // value: - // } - // - // We are reusing this API here, although its semantics does not make sense in the context of - // bundle-analysis. - const sdk = 'bundle'; // to accommodate above API syntax, can be any string - const value = analysis.results[0].size; - const type = `${product} (${analysis.name})`; - results.push({ sdk, type, value }); - } - return results; -} diff --git a/repo-scripts/size-analysis/bundle-analysis.ts b/repo-scripts/size-analysis/bundle-analysis.ts deleted file mode 100644 index 98505ab6859..00000000000 --- a/repo-scripts/size-analysis/bundle-analysis.ts +++ /dev/null @@ -1,500 +0,0 @@ -/** - * @license - * Copyright 2020 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 * as tmp from 'tmp'; -import { existsSync, lstatSync, readFileSync, writeFileSync } from 'fs'; -import { spawn } from 'child-process-promise'; -import { ordinal } from '@firebase/util'; -import { bundleWithRollup } from './bundle/rollup'; -import { bundleWithWebpack } from './bundle/webpack'; -import { calculateContentSize } from './util'; -import { minify } from './bundle/minify'; -import { extractAllTopLevelSymbols, MemberList } from './analysis-helper'; - -interface BundleAnalysisArgs { - input: string; - bundler: 'webpack' | 'rollup' | 'both'; - mode: 'npm' | 'local'; - output: string; - debug: boolean; -} - -interface BundleAnalysisOptions { - bundleDefinitions: BundleDefinition[]; - bundler: Bundler; - mode: Mode; - output: string; - debug: boolean; -} - -interface DebugOptions { - output: string; // output folder for debug files -} - -interface BundleDefinition { - name: string; - description?: string; - dependencies: BundleDependency[]; -} - -interface BundleDependency { - packageName: string; - /** - * npm version or tag - */ - versionOrTag: string; - imports: string | SubModuleImport[]; -} - -interface SubModuleImport { - path: string; - imports: string[]; -} - -export enum Bundler { - Rollup = 'rollup', - Webpack = 'webpack', - Both = 'both' -} - -export enum Mode { - Npm = 'npm', - Local = 'local' -} - -enum SpecialImport { - Default = 'default import', - Sizeeffect = 'side effect import', - Namespace = 'namespace import' -} - -export async function run({ - input, - bundler, - mode, - output, - debug -}: BundleAnalysisArgs): Promise { - const options = { - bundleDefinitions: loadBundleDefinitions(input), - bundler: toBundlerEnum(bundler), - mode: toModeEnum(mode), - output, - debug - }; - - return analyze(options); -} - -function loadBundleDefinitions(path: string): BundleDefinition[] { - if (!existsSync(path)) { - throw new Error( - `${path} doesn't exist. Please provide a valid path to the bundle definition file.` - ); - } - - if (lstatSync(path).isDirectory()) { - throw new Error( - `Expecting a file, but ${path} is a directory. Please provide a valid path to the bundle definition file.` - ); - } - - const def = parseBundleDefinition(readFileSync(path, { encoding: 'utf-8' })); - - return def; -} - -function toBundlerEnum(bundler: 'webpack' | 'rollup' | 'both'): Bundler { - switch (bundler) { - case 'rollup': - return Bundler.Rollup; - case 'webpack': - return Bundler.Webpack; - case 'both': - return Bundler.Both; - default: - throw new Error('impossible!'); - } -} - -function toModeEnum(mode: 'npm' | 'local'): Mode { - switch (mode) { - case 'npm': - return Mode.Npm; - case 'local': - return Mode.Local; - default: - throw new Error('impossible'); - } -} - -/** - * - * @param input - * @returns - an array of error messages. Empty if the bundle definition is valid - */ -function parseBundleDefinition(input: string): BundleDefinition[] { - const bundleDefinitions: BundleDefinition[] = JSON.parse(input); - - const errorMessages = []; - if (!Array.isArray(bundleDefinitions)) { - throw new Error('Bundle definition must be defined in an array'); - } - - for (let i = 0; i < bundleDefinitions.length; i++) { - const bundleDefinition = bundleDefinitions[i]; - if (!bundleDefinition.name) { - errorMessages.push( - `Missing field 'name' in the ${ordinal(i + 1)} bundle definition` - ); - } - - if (!bundleDefinition.dependencies) { - errorMessages.push( - `Missing field 'dependencies' in the ${ordinal( - i + 1 - )} bundle definition` - ); - } - - if (!Array.isArray(bundleDefinition.dependencies)) { - errorMessages.push( - `Expecting an array for field 'dependencies', but it is not an array in the ${ordinal( - i + 1 - )} bundle definition` - ); - } - - for (let j = 0; j < bundleDefinition.dependencies.length; j++) { - const dependency = bundleDefinition.dependencies[j]; - - if (!dependency.packageName) { - errorMessages.push( - `Missing field 'packageName' in the ${ordinal( - j + 1 - )} dependency of the ${ordinal(i + 1)} bundle definition` - ); - } - - if (!dependency.imports) { - errorMessages.push( - `Missing field 'imports' in the ${ordinal( - j + 1 - )} dependency of the ${ordinal(i + 1)} bundle definition` - ); - } - - if (!Array.isArray(dependency.imports)) { - errorMessages.push( - `Expecting an array for field 'imports', but it is not an array in the ${ordinal( - j + 1 - )} dependency of the ${ordinal(i + 1)} bundle definition` - ); - } - - if (!dependency.versionOrTag) { - dependency.versionOrTag = 'latest'; - } - } - } - - if (errorMessages.length > 0) { - throw new Error(errorMessages.join('\n')); - } - - return bundleDefinitions; -} - -async function analyze({ - bundleDefinitions, - bundler, - output, - mode, - debug -}: BundleAnalysisOptions): Promise { - const analyses: BundleAnalysis[] = []; - - let debugOptions: DebugOptions | undefined; - if (debug) { - const tmpDir = tmp.dirSync(); - debugOptions = { - output: tmpDir.name - }; - } - - for (const bundleDefinition of bundleDefinitions) { - analyses.push( - await analyzeBundle(bundleDefinition, bundler, mode, debugOptions) - ); - } - - writeFileSync(output, JSON.stringify(analyses, null, 2), { - encoding: 'utf-8' - }); -} - -async function analyzeBundle( - bundleDefinition: BundleDefinition, - bundler: Bundler, - mode: Mode, - debugOptions?: DebugOptions -): Promise { - const analysis: BundleAnalysis = { - name: bundleDefinition.name, - description: bundleDefinition.description ?? '', - results: [], - dependencies: bundleDefinition.dependencies - }; - - let moduleDirectory: string | undefined; - let tmpDir: tmp.DirResult | undefined; - if (mode === Mode.Npm) { - tmpDir = await setupTempProject(bundleDefinition); - moduleDirectory = `${tmpDir.name}/node_modules`; - } - - const entryFileContent = createEntryFileContent(bundleDefinition); - - switch (bundler) { - case Bundler.Rollup: - case Bundler.Webpack: - analysis.results.push( - await analyzeBundleWithBundler( - bundleDefinition.name, - entryFileContent, - bundler, - moduleDirectory, - debugOptions - ) - ); - break; - case Bundler.Both: - analysis.results.push( - await analyzeBundleWithBundler( - bundleDefinition.name, - entryFileContent, - Bundler.Rollup, - moduleDirectory, - debugOptions - ) - ); - analysis.results.push( - await analyzeBundleWithBundler( - bundleDefinition.name, - entryFileContent, - Bundler.Webpack, - moduleDirectory, - debugOptions - ) - ); - break; - default: - throw new Error('impossible!'); - } - - if (tmpDir) { - tmpDir.removeCallback(); - } - - return analysis; -} - -/** - * Create a temp project and install dependencies the bundleDefinition defines - * @returns - the path to the temp project - */ -async function setupTempProject( - bundleDefinition: BundleDefinition -): Promise { - /// set up a temporary project to install dependencies - const tmpDir = tmp.dirSync({ unsafeCleanup: true }); - console.log(tmpDir.name); - // create package.json - const pkgJson: { - name: string; - version: string; - dependencies: Record; - } = { - name: 'size-analysis', - version: '0.0.0', - dependencies: {} - }; - - for (const dep of bundleDefinition.dependencies) { - pkgJson.dependencies[dep.packageName] = dep.versionOrTag; - } - - writeFileSync( - `${tmpDir.name}/package.json`, - `${JSON.stringify(pkgJson, null, 2)}\n`, - { encoding: 'utf-8' } - ); - - // install dependencies - await spawn('npm', ['install'], { - cwd: tmpDir.name, - stdio: 'inherit' - }); - - return tmpDir; -} - -async function analyzeBundleWithBundler( - bundleName: string, - entryFileContent: string, - bundler: Exclude, - moduleDirectory?: string, - debugOptions?: DebugOptions -): Promise { - let bundledContent = ''; - - // bundle using bundlers - if (bundler === Bundler.Rollup) { - bundledContent = await bundleWithRollup(entryFileContent, moduleDirectory); - } else { - bundledContent = await bundleWithWebpack(entryFileContent, moduleDirectory); - } - - const minifiedBundle = await minify(bundledContent); - const { size, gzipSize } = calculateContentSize(minifiedBundle); - - const analysisResult: BundleAnalysisResult = { - bundler, - size, - gzipSize - }; - - if (debugOptions) { - const bundleFilePath = `${debugOptions.output}/${bundleName.replace( - / +/g, - '-' - )}.${bundler}.js`; - const minifiedBundleFilePath = `${debugOptions.output}/${bundleName.replace( - / +/g, - '-' - )}.${bundler}.minified.js`; - writeFileSync(bundleFilePath, bundledContent, { encoding: 'utf8' }); - writeFileSync(minifiedBundleFilePath, minifiedBundle, { encoding: 'utf8' }); - - analysisResult.debugInfo = { - pathToBundle: bundleFilePath, - pathToMinifiedBundle: minifiedBundleFilePath, - dependencies: extractAllTopLevelSymbols(bundleFilePath) - }; - } - - return analysisResult; -} - -function createEntryFileContent(bundleDefinition: BundleDefinition): string { - const contentArray = []; - // cache used symbols. Used to avoid symbol collision when multiple modules export symbols with the same name. - const symbolsCache = new Set(); - for (const dep of bundleDefinition.dependencies) { - for (const imp of dep.imports) { - if (typeof imp === 'string') { - contentArray.push( - ...createImportExport(imp, dep.packageName, symbolsCache) - ); - } else { - // submodule imports - for (const subImp of imp.imports) { - contentArray.push( - ...createImportExport( - subImp, - `${dep.packageName}/${imp.path}`, - symbolsCache - ) - ); - } - } - } - } - - return contentArray.join('\n'); -} - -function createImportExport( - symbol: string, - modulePath: string, - symbolsCache: Set -): string[] { - const contentArray = []; - - switch (symbol) { - case SpecialImport.Default: { - const nameToUse = createSymbolName('default_import', symbolsCache); - contentArray.push(`import ${nameToUse} from '${modulePath}';`); - contentArray.push(`console.log(${nameToUse})`); // prevent import from being tree shaken - break; - } - case SpecialImport.Namespace: { - const nameToUse = createSymbolName('namespace', symbolsCache); - contentArray.push(`import * as ${nameToUse} from '${modulePath}';`); - contentArray.push(`console.log(${nameToUse})`); // prevent import from being tree shaken - break; - } - case SpecialImport.Sizeeffect: - contentArray.push(`import '${modulePath}';`); - break; - default: - // named imports - const nameToUse = createSymbolName(symbol, symbolsCache); - - if (nameToUse !== symbol) { - contentArray.push( - `export {${symbol} as ${nameToUse}} from '${modulePath}';` - ); - } else { - contentArray.push(`export {${symbol}} from '${modulePath}';`); - } - } - - return contentArray; -} - -/** - * In case a symbol with the same name is already imported from another module, we need to give this symbol another name - * using "originalname as anothername" syntax, otherwise it returns the original symbol name. - */ -function createSymbolName(symbol: string, symbolsCache: Set): string { - let nameToUse = symbol; - const max = 100; - while (symbolsCache.has(nameToUse)) { - nameToUse = `${symbol}_${Math.floor(Math.random() * max)}`; - } - - symbolsCache.add(nameToUse); - return nameToUse; -} - -interface BundleAnalysis { - name: string; // the bundle name defined in the bundle definition - description: string; - dependencies: BundleDependency[]; - results: BundleAnalysisResult[]; -} - -interface BundleAnalysisResult { - bundler: 'rollup' | 'webpack'; - size: number; - gzipSize: number; - debugInfo?: { - pathToBundle?: string; - pathToMinifiedBundle?: string; - dependencies?: MemberList; - }; -} diff --git a/repo-scripts/size-analysis/bundle-definitions/analytics.json b/repo-scripts/size-analysis/bundle-definitions/analytics.json deleted file mode 100644 index 4f7ae74d700..00000000000 --- a/repo-scripts/size-analysis/bundle-definitions/analytics.json +++ /dev/null @@ -1,32 +0,0 @@ -[ - { - "name": "logEvent", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "analytics", - "imports": [ - "getAnalytics", - "logEvent" - ] - } - ] - } - ] - } -] diff --git a/repo-scripts/size-analysis/bundle-definitions/app-check.json b/repo-scripts/size-analysis/bundle-definitions/app-check.json deleted file mode 100644 index b9b2cada716..00000000000 --- a/repo-scripts/size-analysis/bundle-definitions/app-check.json +++ /dev/null @@ -1,95 +0,0 @@ -[ - { - "name": "ReCaptchaV3Provider", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app-check", - "imports": [ - "initializeAppCheck", - "ReCaptchaV3Provider", - "getToken" - ] - } - ] - } - ] - }, - { - "name": "ReCaptchaEnterpriseProvider", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app-check", - "imports": [ - "initializeAppCheck", - "ReCaptchaEnterpriseProvider", - "getToken" - ] - } - ] - } - ] - }, - { - "name": "CustomProvider", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app-check", - "imports": [ - "initializeAppCheck", - "CustomProvider", - "getToken" - ] - } - ] - } - ] - } -] diff --git a/repo-scripts/size-analysis/bundle-definitions/auth.json b/repo-scripts/size-analysis/bundle-definitions/auth.json deleted file mode 100644 index c777109a7fe..00000000000 --- a/repo-scripts/size-analysis/bundle-definitions/auth.json +++ /dev/null @@ -1,201 +0,0 @@ -[ - { - "name": "EmailAndPassword", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "auth", - "imports": [ - "initializeAuth", - "indexedDBLocalPersistence", - "signInWithEmailAndPassword", - "onAuthStateChanged" - ] - } - ] - } - ] - }, - { - "name": "GooglePopup", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "auth", - "imports": [ - "initializeAuth", - "indexedDBLocalPersistence", - "GoogleAuthProvider", - "signInWithPopup", - "browserPopupRedirectResolver", - "onAuthStateChanged" - ] - } - ] - } - ] - }, - { - "name": "GoogleFBTwitterGitHubPopup", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "auth", - "imports": [ - "getAuth", - "GoogleAuthProvider", - "FacebookAuthProvider", - "TwitterAuthProvider", - "GithubAuthProvider", - "signInWithPopup", - "onAuthStateChanged" - ] - } - ] - } - ] - }, - { - "name": "GoogleRedirect", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "auth", - "imports": [ - "initializeAuth", - "indexedDBLocalPersistence", - "GoogleAuthProvider", - "getRedirectResult", - "browserPopupRedirectResolver", - "signInWithRedirect", - "onAuthStateChanged" - ] - } - ] - } - ] - }, - { - "name": "Phone", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "auth", - "imports": [ - "initializeAuth", - "indexedDBLocalPersistence", - "RecaptchaVerifier", - "signInWithPhoneNumber" - ] - } - ] - } - ] - }, - { - "name": "Anonymous", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "auth", - "imports": [ - "initializeAuth", - "indexedDBLocalPersistence", - "signInAnonymously" - ] - } - ] - } - ] - } -] diff --git a/repo-scripts/size-analysis/bundle-definitions/database.json b/repo-scripts/size-analysis/bundle-definitions/database.json deleted file mode 100644 index b4366ec3dff..00000000000 --- a/repo-scripts/size-analysis/bundle-definitions/database.json +++ /dev/null @@ -1,288 +0,0 @@ -[ - { - "name": "Read data once", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "database", - "imports": [ - "getDatabase", - "ref", - "child", - "get" - ] - } - ] - } - ] - }, - { - "name": "Write data", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "database", - "imports": [ - "getDatabase", - "ref", - "set" - ] - } - ] - } - ] - }, - { - "name": "Save data as transactions", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "database", - "imports": [ - "getDatabase", - "ref", - "runTransaction" - ] - } - ] - } - ] - }, - { - "name": "Append to a list of data", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "database", - "imports": [ - "getDatabase", - "ref", - "push", - "set" - ] - } - ] - } - ] - }, - { - "name": "Listen for child events", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "database", - "imports": [ - "getDatabase", - "ref", - "onChildAdded", - "onChildChanged", - "onChildRemoved" - ] - } - ] - } - ] - }, - { - "name": "Listen for value events", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "database", - "imports": [ - "getDatabase", - "ref", - "onValue" - ] - } - ] - } - ] - }, - { - "name": "Listen for value events + Detach listeners", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "database", - "imports": [ - "getDatabase", - "ref", - "onValue", - "off" - ] - } - ] - } - ] - }, - { - "name": "Sort data", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "database", - "imports": [ - "getDatabase", - "ref", - "query", - "orderByChild" - ] - } - ] - } - ] - }, - { - "name": "Filtering data", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "database", - "imports": [ - "getDatabase", - "ref", - "query", - "limitToLast" - ] - } - ] - } - ] - } -] diff --git a/repo-scripts/size-analysis/bundle-definitions/firestore-lite.json b/repo-scripts/size-analysis/bundle-definitions/firestore-lite.json deleted file mode 100644 index 0313cc7af77..00000000000 --- a/repo-scripts/size-analysis/bundle-definitions/firestore-lite.json +++ /dev/null @@ -1,166 +0,0 @@ -[ - { - "name": "Read data once", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "firestore/lite", - "imports": [ - "getFirestore", - "doc", - "getDoc" - ] - } - ] - } - ] - }, - { - "name": "Write data", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "firestore/lite", - "imports": [ - "getFirestore", - "collection", - "doc", - "setDoc" - ] - } - ] - } - ] - }, - { - "name": "Query", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "firestore/lite", - "imports": [ - "getFirestore", - "collection", - "query", - "where", - "orderBy", - "getDocs" - ] - } - ] - } - ] - }, - { - "name": "Query Cursors", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "firestore/lite", - "imports": [ - "getFirestore", - "collection", - "doc", - "getDocs", - "query", - "orderBy", - "startAt", - "endBefore" - ] - } - ] - } - ] - }, - { - "name": "Transaction", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "firestore/lite", - "imports": [ - "getFirestore", - "doc", - "runTransaction" - ] - } - ] - } - ] - } -] diff --git a/repo-scripts/size-analysis/bundle-definitions/firestore.json b/repo-scripts/size-analysis/bundle-definitions/firestore.json deleted file mode 100644 index f5ddafd167c..00000000000 --- a/repo-scripts/size-analysis/bundle-definitions/firestore.json +++ /dev/null @@ -1,328 +0,0 @@ -[ - { - "name": "Read data once", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "firestore", - "imports": [ - "getFirestore", - "doc", - "getDoc" - ] - } - ] - } - ] - }, - { - "name": "Write data", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "firestore", - "imports": [ - "getFirestore", - "collection", - "doc", - "setDoc" - ] - } - ] - } - ] - }, - { - "name": "Realtime updates", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "firestore", - "imports": [ - "getFirestore", - "doc", - "onSnapshot" - ] - } - ] - } - ] - }, - { - "name": "Query", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "firestore", - "imports": [ - "getFirestore", - "onSnapshot", - "collection", - "query", - "where", - "orderBy", - "getDocs" - ] - } - ] - } - ] - }, - { - "name": "Query Cursors", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "firestore", - "imports": [ - "getFirestore", - "collection", - "doc", - "getDocs", - "query", - "orderBy", - "startAt", - "endBefore" - ] - } - ] - } - ] - }, - { - "name": "Persistence", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "firestore", - "imports": [ - "getFirestore", - "enableMultiTabIndexedDbPersistence" - ] - } - ] - } - ] - }, - { - "name": "Read Write w Persistence", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "firestore", - "imports": [ - "collection", - "doc", - "enableMultiTabIndexedDbPersistence", - "getDoc", - "getFirestore", - "setDoc" - ] - } - ] - } - ] - }, - { - "name": "Transaction", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "firestore", - "imports": [ - "getFirestore", - "doc", - "runTransaction" - ] - } - ] - } - ] - }, - { - "name": "CSI Auto Indexing Enable", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "firestore", - "imports": [ - "deleteAllPersistentCacheIndexes", - "enableIndexedDbPersistence", - "enablePersistentCacheIndexAutoCreation", - "getFirestore", - "getPersistentCacheIndexManager" - ] - } - ] - } - ] - }, - { - "name": "CSI Auto Indexing Disable and Delete", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "firestore", - "imports": [ - "deleteAllPersistentCacheIndexes", - "disablePersistentCacheIndexAutoCreation", - "enableIndexedDbPersistence", - "getFirestore", - "getPersistentCacheIndexManager" - ] - } - ] - } - ] - } -] diff --git a/repo-scripts/size-analysis/bundle-definitions/functions.json b/repo-scripts/size-analysis/bundle-definitions/functions.json deleted file mode 100644 index 6ec4c259dd6..00000000000 --- a/repo-scripts/size-analysis/bundle-definitions/functions.json +++ /dev/null @@ -1,32 +0,0 @@ -[ - { - "name": "call", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "functions", - "imports": [ - "getFunctions", - "httpsCallable" - ] - } - ] - } - ] - } -] diff --git a/repo-scripts/size-analysis/bundle-definitions/messaging.json b/repo-scripts/size-analysis/bundle-definitions/messaging.json deleted file mode 100644 index 03f930d44d7..00000000000 --- a/repo-scripts/size-analysis/bundle-definitions/messaging.json +++ /dev/null @@ -1,33 +0,0 @@ -[ - { - "name": "send + receive", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "messaging", - "imports": [ - "getMessaging", - "getToken", - "onMessage" - ] - } - ] - } - ] - } -] diff --git a/repo-scripts/size-analysis/bundle-definitions/performance.json b/repo-scripts/size-analysis/bundle-definitions/performance.json deleted file mode 100644 index 6803a424540..00000000000 --- a/repo-scripts/size-analysis/bundle-definitions/performance.json +++ /dev/null @@ -1,32 +0,0 @@ -[ - { - "name": "trace", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "performance", - "imports": [ - "getPerformance", - "trace" - ] - } - ] - } - ] - } -] diff --git a/repo-scripts/size-analysis/bundle-definitions/remote-config.json b/repo-scripts/size-analysis/bundle-definitions/remote-config.json deleted file mode 100644 index 9a5f28cf2b6..00000000000 --- a/repo-scripts/size-analysis/bundle-definitions/remote-config.json +++ /dev/null @@ -1,33 +0,0 @@ -[ - { - "name": "getAndFetch", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "remote-config", - "imports": [ - "getRemoteConfig", - "getValue", - "fetchAndActivate" - ] - } - ] - } - ] - } -] diff --git a/repo-scripts/size-analysis/bundle-definitions/storage.json b/repo-scripts/size-analysis/bundle-definitions/storage.json deleted file mode 100644 index 8b847eec197..00000000000 --- a/repo-scripts/size-analysis/bundle-definitions/storage.json +++ /dev/null @@ -1,251 +0,0 @@ -[ - { - "name": "uploadBytes", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "storage", - "imports": [ - "getStorage", - "ref", - "uploadBytes" - ] - } - ] - } - ] - }, - { - "name": "uploadString", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "storage", - "imports": [ - "getStorage", - "ref", - "uploadString" - ] - } - ] - } - ] - }, - { - "name": "uploadBytesResumable", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "storage", - "imports": [ - "getStorage", - "ref", - "uploadBytesResumable" - ] - } - ] - } - ] - }, - { - "name": "getDownloadURL", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "storage", - "imports": [ - "getStorage", - "ref", - "getDownloadURL" - ] - } - ] - } - ] - }, - { - "name": "list + listAll", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "storage", - "imports": [ - "getStorage", - "ref", - "list", - "listAll" - ] - } - ] - } - ] - }, - { - "name": "getMetadata", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "storage", - "imports": [ - "getStorage", - "ref", - "getMetadata" - ] - } - ] - } - ] - }, - { - "name": "updateMetadata", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "storage", - "imports": [ - "getStorage", - "ref", - "updateMetadata" - ] - } - ] - } - ] - }, - { - "name": "getBytes", - "dependencies": [ - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "app", - "imports": [ - "initializeApp" - ] - } - ] - }, - { - "packageName": "firebase", - "versionOrTag": "latest", - "imports": [ - { - "path": "storage", - "imports": [ - "getStorage", - "ref", - "getBytes" - ] - } - ] - } - ] - } -] diff --git a/repo-scripts/size-analysis/bundle/minify.ts b/repo-scripts/size-analysis/bundle/minify.ts deleted file mode 100644 index 60597001a5e..00000000000 --- a/repo-scripts/size-analysis/bundle/minify.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @license - * Copyright 2020 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 * as terser from 'terser'; - -export async function minify(content: string): Promise { - const minified = await terser.minify(content, { - format: { - comments: false - }, - mangle: { toplevel: true }, - compress: false - }); - - return minified.code ?? ''; -} diff --git a/repo-scripts/size-analysis/bundle/rollup.ts b/repo-scripts/size-analysis/bundle/rollup.ts deleted file mode 100644 index 272cd934d08..00000000000 --- a/repo-scripts/size-analysis/bundle/rollup.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * @license - * Copyright 2020 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 * as rollup from 'rollup'; -import resolve, { RollupNodeResolveOptions } from '@rollup/plugin-node-resolve'; -import commonjs from '@rollup/plugin-commonjs'; -// @ts-ignore -import virtual from '@rollup/plugin-virtual'; - -/** - * - * @param fileContent - * @param moduleDirectory - the path to the node_modules folder of the temporary project in npm mode. - * undefined in local mode - */ -export async function bundleWithRollup( - fileContent: string, - moduleDirectory?: string -): Promise { - const resolveOptions: RollupNodeResolveOptions = { - mainFields: ['esm2017', 'module', 'main'] - }; - - if (moduleDirectory) { - resolveOptions.moduleDirectories = [moduleDirectory]; - } - - const bundle = await rollup.rollup({ - input: 'entry', - plugins: [ - virtual({ - entry: fileContent - }), - resolve(resolveOptions), - commonjs() - ] - }); - - const { output } = await bundle.generate({ - format: 'es' - }); - return output[0].code; -} diff --git a/repo-scripts/size-analysis/bundle/webpack.ts b/repo-scripts/size-analysis/bundle/webpack.ts deleted file mode 100644 index 93f8523c37b..00000000000 --- a/repo-scripts/size-analysis/bundle/webpack.ts +++ /dev/null @@ -1,85 +0,0 @@ -/** - * @license - * Copyright 2020 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 webpack from 'webpack'; -// @ts-ignore -import virtualModulesPlugin from 'webpack-virtual-modules'; -import { createFsFromVolume, IFs, Volume } from 'memfs'; -import path from 'path'; -import { projectRoot } from '../util'; - -/** - * - * @param fileContent - * @param moduleDirectory - the path to the node_modules folder of the temporary project in npm mode. - * undefined in local mode - */ -export async function bundleWithWebpack( - fileContent: string, - moduleDirectory?: string -): Promise { - const entryFileName = '/virtual_path_to_in_memory_file/index.js'; - const outputFileName = 'o.js'; - - const resolveConfig: webpack.ResolveOptions = { - mainFields: ['esm2017', 'module', 'main'] - }; - - if (moduleDirectory) { - resolveConfig.modules = [moduleDirectory]; - } else { - // local mode - resolveConfig.modules = [`${projectRoot}/node_modules`]; - } - - const compiler = webpack({ - entry: entryFileName, - output: { - filename: outputFileName - }, - resolve: resolveConfig, - plugins: [ - new virtualModulesPlugin({ - [entryFileName]: fileContent - }) - ], - mode: 'production' - }); - - // use virtual file system for output to avoid I/O - compiler.outputFileSystem = getMemoryFileSystem(); - - return new Promise((res, rej) => { - compiler.run((err, stats) => { - if (err) { - rej(err); - return; - } - - // Hack to get string output without reading the output file using an internal API from webpack - // eslint-disable-next-line @typescript-eslint/no-explicit-any - res((stats!.compilation.assets[outputFileName] as any)['_value']); - }); - }); -} - -function getMemoryFileSystem(): IFs { - const fs = createFsFromVolume(new Volume()); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (fs as any).join = path.join.bind(path); - return fs; -} diff --git a/repo-scripts/size-analysis/cli.ts b/repo-scripts/size-analysis/cli.ts deleted file mode 100644 index e9d6d9653f3..00000000000 --- a/repo-scripts/size-analysis/cli.ts +++ /dev/null @@ -1,92 +0,0 @@ -/** - * @license - * Copyright 2020 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 * as yargs from 'yargs'; -import { run as runBundleAnalysis } from './bundle-analysis'; -import { analyzePackageSize } from './package-analysis'; - -// eslint-disable-next-line no-unused-expressions -yargs - .command( - '$0', - 'Analyze the size of individual exports from packages', - { - inputModule: { - type: 'array', - alias: 'im', - desc: 'The name of the module(s) to be analyzed. example: --inputModule "@firebase/functions" "firebase/auth"' - }, - inputDtsFile: { - type: 'string', - alias: 'if', - desc: 'support for adhoc analysis. requires a path to a d.ts file' - }, - inputBundleFile: { - type: 'string', - alias: 'ib', - desc: 'support for adhoc analysis. requires a path to a bundle file' - }, - output: { - type: 'string', - alias: 'o', - required: true, - desc: 'The location where report(s) will be generated, a directory path if module(s) are analyzed; a file path if ad hoc analysis is to be performed' - } - }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - args => analyzePackageSize(args as any).catch(e => console.log(e)) - ) - .command( - 'bundle', - 'Analyze bundle size', - { - input: { - type: 'string', - alias: 'i', - required: true, - desc: 'Path to the JSON file that describes the bundles to be analyzed' - }, - mode: { - choices: ['npm', 'local'], - alias: 'm', - default: 'npm', - desc: 'Use Firebase packages from npm or the local repo' - }, - bundler: { - choices: ['rollup', 'webpack', 'both'], - alias: 'b', - default: 'rollup', - desc: 'The bundler(s) to be used' - }, - output: { - type: 'string', - alias: 'o', - default: './size-analysis-bundles.json', - desc: 'The output location' - }, - debug: { - type: 'boolean', - alias: 'd', - default: false, - desc: 'debug mode' - } - }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - argv => runBundleAnalysis(argv as any) - ) - .help() - .parseSync(); diff --git a/repo-scripts/size-analysis/package-analysis.ts b/repo-scripts/size-analysis/package-analysis.ts deleted file mode 100644 index e8d179e19fc..00000000000 --- a/repo-scripts/size-analysis/package-analysis.ts +++ /dev/null @@ -1,134 +0,0 @@ -/** - * @license - * Copyright 2020 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 { resolve, basename, dirname } from 'path'; -import { - generateReport, - generateReportForModules, - writeReportToFile, - Report, - ErrorCode, - writeReportToDirectory -} from './analysis-helper'; -import glob from 'glob'; -import * as fs from 'fs'; - -const projectRoot = dirname(resolve(__dirname, '../../package.json')); -/** - * Support Command Line Options - * -- inputModule (optional) : can be left unspecified which results in running analysis on all exp modules. - * can specify one to many module names separated by space. - * eg: --inputModule "@firebase/functions-exp" "firebase/auth-exp" - * - * -- inputDtsFile (optional) : adhoc support. Specify a path to dts file. Must enable -- inputBundleFile if this flag is specified. - * - * -- inputBundleFile (optional): adhoc support. Specify a path to bundle file. Must enable -- inputDtsFile if this flag is specified. - * - * --output (required): output directory or file where reports will be generated. - * specify a directory if module(s) are analyzed - * specify a file path if ad hoc analysis is to be performed - * - */ -interface PackageAnalysisOptions { - inputModule: string[]; - inputDtsFile: string; - inputBundleFile: string; - output: string; -} -/** - * Entry Point of the Tool. - * The function first checks if it's an adhoc run (by checking whether --inputDtsFile and --inputBundle are both enabled) - * The function then checks whether --inputModule flag is specified; Run analysis on all modules if not, run analysis on selected modules if enabled. - * Throw INVALID_FLAG_COMBINATION error if neither case fulfill. - */ -export async function analyzePackageSize( - argv: PackageAnalysisOptions -): Promise { - // check if it's an adhoc run - // adhoc run report can only be redirected to files - if (argv.inputDtsFile && argv.inputBundleFile && argv.output) { - const jsonReport: Report = await generateReport( - 'adhoc', - argv.inputDtsFile, - argv.inputBundleFile - ); - writeReportToFile(jsonReport, resolve(argv.output)); - } else if (!argv.inputDtsFile && !argv.inputBundleFile) { - // retrieve All Module Names - let allModulesLocation = await mapWorkspaceToPackages([ - `${projectRoot}/packages/*` - ]); - allModulesLocation = allModulesLocation.filter(path => { - const pkgJsonPath = `${path}/package.json`; - if (!fs.existsSync(pkgJsonPath)) { - return false; - } - - const json = JSON.parse( - fs.readFileSync(`${path}/package.json`, { encoding: 'utf-8' }) - ); - return ( - json.name.startsWith('@firebase') && - !json.name.includes('-compat') && - !json.name.includes('-types') - ); - }); - if (argv.inputModule) { - allModulesLocation = allModulesLocation.filter(path => { - const json = JSON.parse( - fs.readFileSync(`${path}/package.json`, { encoding: 'utf-8' }) - ); - return argv.inputModule.includes(json.name); - }); - } - let writeFiles: boolean = false; - if (argv.output) { - writeFiles = true; - } - - const reports: Report[] = await generateReportForModules( - allModulesLocation - ); - if (writeFiles) { - for (const report of reports) { - writeReportToDirectory( - report, - `${basename(report.name)}-dependencies.json`, - resolve(argv.output) - ); - } - } - } else { - throw new Error(ErrorCode.INVALID_FLAG_COMBINATION); - } -} - -function mapWorkspaceToPackages(workspaces: string[]): Promise { - return Promise.all>( - workspaces.map>( - workspace => - new Promise(resolve => { - glob(workspace, (err, paths) => { - if (err) { - throw err; - } - resolve(paths); - }); - }) - ) - ).then(paths => paths.reduce((arr, val) => arr.concat(val), [])); -} diff --git a/repo-scripts/size-analysis/package.json b/repo-scripts/size-analysis/package.json deleted file mode 100644 index 5ed11ae4c9b..00000000000 --- a/repo-scripts/size-analysis/package.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "name": "firebase-size-analysis", - "version": "0.1.0", - "private": true, - "description": "A template package for new firebase packages", - "author": "Firebase (https://firebase.google.com/)", - "main": "dist/index.cjs.js", - "esm2017": "dist/index.esm2017.js", - "files": [ - "dist" - ], - "scripts": { - "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", - "test": "run-p --npm-path npm lint test:node", - "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:node", - "pretest:node": "tsc -p test/test-inputs && rollup -c", - "test:node": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha **/*.test.ts --config ../../config/mocharc.node.js --timeout 60000", - "build": "rollup -c" - }, - "dependencies": { - "rollup": "2.79.1", - "@rollup/plugin-commonjs": "21.1.0", - "@rollup/plugin-json": "4.1.0", - "@rollup/plugin-node-resolve": "13.3.0", - "rollup-plugin-replace": "2.2.0", - "rollup-plugin-typescript2": "0.31.2", - "@rollup/plugin-virtual": "2.1.0", - "webpack": "5.76.0", - "@types/webpack": "5.28.5", - "webpack-virtual-modules": "0.5.0", - "child-process-promise": "2.2.1", - "memfs": "3.5.3", - "tmp": "0.2.1", - "typescript": "4.7.4", - "terser": "5.16.1", - "yargs": "17.7.2", - "@firebase/util": "1.9.7", - "gzip-size": "6.0.0", - "glob": "7.2.3" - }, - "license": "Apache-2.0", - "devDependencies": { - "@firebase/logger": "0.4.2", - "@firebase/app": "0.10.7" - }, - "repository": { - "directory": "repo-scripts/size-analysis", - "type": "git", - "url": "git+https://github.com/firebase/firebase-js-sdk.git" - }, - "bugs": { - "url": "https://github.com/firebase/firebase-js-sdk/issues" - }, - "nyc": { - "extension": [ - ".ts" - ], - "reportDir": "./coverage/node" - } -} diff --git a/repo-scripts/size-analysis/rollup.config.js b/repo-scripts/size-analysis/rollup.config.js deleted file mode 100644 index 505596e3d2e..00000000000 --- a/repo-scripts/size-analysis/rollup.config.js +++ /dev/null @@ -1,83 +0,0 @@ -/** - * @license - * Copyright 2019 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 typescriptPlugin from 'rollup-plugin-typescript2'; -import typescript from 'typescript'; -import pkg from './package.json'; -import json from '@rollup/plugin-json'; - -const deps = Object.keys( - Object.assign({}, pkg.peerDependencies, pkg.dependencies) -); - -const nodeInternals = ['fs', 'path', 'util']; - -export default [ - { - input: 'test/test-inputs/subsetExports.ts', - output: [ - { - file: 'test/test-inputs/dist/subsetExportsBundle.js', - format: 'es', - sourcemap: false - } - ], - plugins: [ - typescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - target: 'es2017', - module: 'es2015' - } - } - }), - json({ - preferConst: true - }) - ], - external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) - }, - { - input: 'cli.ts', - output: [ - { - file: 'dist/cli.js', - format: 'cjs', - sourcemap: false - } - ], - plugins: [ - typescriptPlugin({ - typescript, - tsconfigOverride: { - compilerOptions: { - target: 'es2017', - module: 'es2015' - } - } - }), - json({ - preferConst: true - }) - ], - external: id => - [...deps, ...nodeInternals].some( - dep => id === dep || id.startsWith(`${dep}/`) - ) - } -]; diff --git a/repo-scripts/size-analysis/test/size-analysis.test.ts b/repo-scripts/size-analysis/test/size-analysis.test.ts deleted file mode 100644 index d3d32ce1fae..00000000000 --- a/repo-scripts/size-analysis/test/size-analysis.test.ts +++ /dev/null @@ -1,489 +0,0 @@ -/** - * @license - * Copyright 2017 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 { expect } from 'chai'; - -import { - MemberList, - dedup, - mapSymbolToType, - replaceAll, - writeReportToFile, - ErrorCode, - writeReportToDirectory, - extractExternalDependencies, - Report, - extractExports, - extractAllTopLevelSymbols -} from '../analysis-helper'; - -import { - getTestModuleDtsFilePath, - getAssortedImportsJsFilePath, - getSubsetExportsBundleFilePath -} from './utils'; -import * as fs from 'fs'; -import { resolve } from 'path'; - -describe('extractExports', () => { - let testModuleDtsFile: string; - let extractedDeclarations: MemberList; - before(() => { - const start = Date.now(); - testModuleDtsFile = getTestModuleDtsFilePath(); - extractedDeclarations = extractExports(testModuleDtsFile); - console.log('extractExports took ', Date.now() - start); - }); - // export {tar as tarr, tar1 as tarr1} from '..' - it('test export rename', () => { - expect(extractedDeclarations.functions).to.include.members([ - 'tarr', - 'tarr1' - ]); - }); - // function foo() { } - // export { foo as foo2 }; - it('test declare then export', () => { - expect(extractedDeclarations.functions).to.include.members(['foo2']); - expect(extractedDeclarations.classes).to.include.members(['Foo1']); - }); - - it('test basic variable extractions', () => { - expect(extractedDeclarations.variables).to.include.members([ - 'basicVarDeclarationExport', - 'basicVarStatementExport', - 'reExportVarStatementExport' - ]); - }); - it('test re-exported variable extractions from same module - named re-exports', () => { - expect(extractedDeclarations.variables).to.include.members([ - 'basicVarDeclarationExportFar', - 'basicVarStatementExportFar', - 'reExportVarStatementExportFar' - ]); - }); - it('test re-exported variable extractions from same module - * re-exports', () => { - expect(extractedDeclarations.variables).to.include.members([ - 'basicVarDeclarationExportBar', - 'basicVarStatementExportBar', - 'reExportVarStatementExportBar' - ]); - }); - - it('test basic function extractions', () => { - expect(extractedDeclarations.functions).to.include.members([ - 'basicFuncExportNoDependencies', - 'basicFuncExportVarDependencies', - 'basicFuncExportFuncDependencies', - 'basicFuncExportEnumDependencies', - 'basicFuncExternalDependencies' - ]); - }); - it('test basic function de-duplication ', () => { - expect(extractedDeclarations.functions).include.members([ - 'basicUniqueFunc' - ]); - - expect( - extractedDeclarations.functions.filter( - each => each.localeCompare('basicUniqueFunc') === 0 - ).length - ).to.equal(1); - }); - - it('test re-exported function extractions from same module - named re-exports', () => { - expect(extractedDeclarations.functions).to.include.members([ - 'basicFuncExportNoDependenciesFar', - 'basicFuncExportVarDependenciesFar', - 'basicFuncExportFuncDependenciesFar', - 'basicFuncExportEnumDependenciesFar', - 'basicFuncExternalDependenciesFar' - ]); - }); - - it('test re-exported function extractions from same module - * re-exports', () => { - expect(extractedDeclarations.functions).to.include.members([ - 'basicFuncExportNoDependenciesBar', - 'basicFuncExportVarDependenciesBar', - 'basicFuncExportFuncDependenciesBar', - 'basicFuncExportEnumDependenciesBar', - 'basicFuncExternalDependenciesBar' - ]); - }); - - it('test re-exported function de-duplication from same module ', () => { - expect(extractedDeclarations.functions).include.members([ - 'basicUniqueFuncFar' - ]); - - expect( - extractedDeclarations.functions.filter( - each => each.localeCompare('basicUniqueFuncFar') === 0 - ).length - ).to.equal(1); - }); - - it('test basic class extractions', () => { - expect(extractedDeclarations.classes).to.include.members([ - 'BasicClassExport' - ]); - }); - - it('test re-exported class extractions from same module - named re-exports', () => { - expect(extractedDeclarations.classes).to.include.members([ - 'BasicClassExportFar' - ]); - }); - it('test re-exported class extractions from same module - * re-exports', () => { - expect(extractedDeclarations.classes).to.include.members([ - 'BasicClassExportBar' - ]); - }); - - it('test basic enum extractions', () => { - expect(extractedDeclarations.enums).to.include.members(['BasicEnumExport']); - }); - - it('test re-exported enum extractions from same module - named re-exports', () => { - expect(extractedDeclarations.enums).to.include.members([ - 'BasicEnumExportFar' - ]); - }); - it('test re-exported enum extractions from same module - * re-exports', () => { - expect(extractedDeclarations.enums).to.include.members([ - 'BasicEnumExportBar' - ]); - }); - // import {LogLevel as LogLevel1} from '@firebase/logger'; - // export {LogLevel1 as LogLevel2}; - it('test renamed import then renamed export', () => { - expect(extractedDeclarations.enums).to.include.members(['LogLevel2']); - }); - - //import { Logger } from "@firebase/logger"; - // export { Logger as Logger1 }; - it('test import then renamed export', () => { - expect(extractedDeclarations.classes).to.include.members(['Logger1']); - }); - - //import { setLogLevel } from "@firebase/logger"; - // export { setLogLevel }; - it('test import then export', () => { - expect(extractedDeclarations.functions).to.include.members(['setLogLevel']); - }); - - // import * as fs from 'fs' - // export { fs as fs1 }; - it('test namespace export', () => { - expect(extractedDeclarations.unknown).to.include.members(['fs1']); - }); -}); - -describe('extractAllTopLevelSymbols', () => { - let subsetExportsBundleFile: string; - let extractedDeclarations: MemberList; - before(() => { - const start = Date.now(); - subsetExportsBundleFile = getSubsetExportsBundleFilePath(); - extractedDeclarations = extractAllTopLevelSymbols(subsetExportsBundleFile); - console.log( - 'extractDeclarations on js bundle file took ', - Date.now() - start - ); - }); - it('test variable extractions', () => { - const variablesArray = ['aVar']; - variablesArray.sort(); - expect(extractedDeclarations.variables).to.include.members(variablesArray); - }); - - it('test functions extractions', () => { - const functionsArray = [ - 'tar', - 'tar1', - 'basicFuncExportEnumDependencies', - 'd1', - 'd2', - 'd3', - 'basicFuncExportFuncDependenciesBar' - ]; - functionsArray.sort(); - expect(extractedDeclarations.functions).to.have.members(functionsArray); - }); - - it('test enums extractions', () => { - const enumsArray = [ - 'BasicEnumExport', - 'BasicEnumExportBar', - 'BasicEnumExportFar' - ]; - enumsArray.sort(); - expect(extractedDeclarations.variables).to.include.members(enumsArray); - }); - - it('test classes extractions', () => { - const classesArray = ['BasicClassExport']; - classesArray.sort(); - expect(extractedDeclarations.classes).to.have.members(classesArray); - }); -}); - -describe('test dedup helper function', () => { - it('test dedup with non-empty entries', () => { - let memberList: MemberList = { - functions: ['aFunc', 'aFunc', 'bFunc', 'cFunc'], - classes: ['aClass', 'bClass', 'aClass', 'cClass'], - variables: ['aVar', 'bVar', 'cVar', 'aVar'], - enums: ['aEnum', 'bEnum', 'cEnum', 'dEnum'], - unknown: [] - }; - memberList = dedup(memberList); - - expect(memberList.functions).to.have.length(3); - expect(memberList.classes).to.have.length(3); - expect(memberList.variables).to.have.length(3); - expect(memberList.enums).to.have.length(4); - expect( - memberList.functions.filter(each => each.localeCompare('aFunc') === 0) - .length - ).to.equal(1); - expect( - memberList.classes.filter(each => each.localeCompare('aClass') === 0) - .length - ).to.equal(1); - expect( - memberList.variables.filter(each => each.localeCompare('aVar') === 0) - .length - ).to.equal(1); - expect( - memberList.enums.filter(each => each.localeCompare('aEnum') === 0).length - ).to.equal(1); - }); - - it('test dedup with empty entries', () => { - let memberList: MemberList = { - functions: [], - classes: [], - variables: ['aVar', 'bVar', 'cVar', 'aVar'], - enums: [], - unknown: [] - }; - memberList = dedup(memberList); - expect(memberList.functions).to.have.length(0); - expect(memberList.classes).to.have.length(0); - expect(memberList.enums).to.have.length(0); - expect(memberList.variables).to.have.length(3); - - expect( - memberList.variables.filter(each => each.localeCompare('aVar') === 0) - .length - ).to.equal(1); - }); -}); - -describe('test replaceAll helper function', () => { - it('test replaceAll with multiple occurrences of an element', () => { - const memberList: MemberList = { - functions: ['aFunc', 'aFunc', 'bFunc', 'cFunc'], - classes: ['aClass', 'bClass', 'aClass', 'cClass'], - variables: ['aVar', 'bVar', 'cVar', 'aVar'], - enums: ['aEnum', 'bEnum', 'cEnum', 'dEnum'], - unknown: [] - }; - const original: string = 'aFunc'; - const replaceTo: string = 'replacedFunc'; - replaceAll(memberList, original, replaceTo); - expect(memberList.functions).to.not.include.members([original]); - expect(memberList.functions).to.include.members([replaceTo]); - expect(memberList.functions).to.have.length(4); - expect( - memberList.functions.filter(each => each.localeCompare(original) === 0) - .length - ).to.equal(0); - expect( - memberList.functions.filter(each => each.localeCompare(replaceTo) === 0) - .length - ).to.equal(2); - }); - - it('test replaceAll with single occurrence of an element', () => { - const memberList: MemberList = { - functions: ['aFunc', 'aFunc', 'bFunc', 'cFunc'], - classes: ['aClass', 'bClass', 'aClass', 'cClass'], - variables: ['aVar', 'bVar', 'cVar', 'aVar'], - enums: ['aEnum', 'bEnum', 'cEnum', 'dEnum'], - unknown: [] - }; - const replaceTo: string = 'replacedClass'; - const original: string = 'bClass'; - replaceAll(memberList, original, replaceTo); - expect(memberList.classes).to.not.include.members([original]); - expect(memberList.classes).to.include.members([replaceTo]); - expect(memberList.classes).to.have.length(4); - expect( - memberList.classes.filter(each => each.localeCompare(original) === 0) - .length - ).to.equal(0); - expect( - memberList.classes.filter(each => each.localeCompare(replaceTo) === 0) - .length - ).to.equal(1); - }); - - it('test replaceAll with zero occurrence of an element', () => { - const memberList: MemberList = { - functions: ['aFunc', 'aFunc', 'bFunc', 'cFunc'], - classes: ['aClass', 'bClass', 'aClass', 'cClass'], - variables: ['aVar', 'bVar', 'cVar', 'aVar'], - enums: ['aEnum', 'bEnum', 'cEnum', 'dEnum'], - unknown: [] - }; - const replaceTo: string = 'replacedEnum'; - const original: string = 'eEnum'; - replaceAll(memberList, original, replaceTo); - expect(memberList.enums).to.not.include.members([original, replaceTo]); - expect(memberList.enums).to.have.length(4); - expect( - memberList.enums.filter(each => each.localeCompare(original) === 0).length - ).to.equal(0); - expect( - memberList.enums.filter(each => each.localeCompare(replaceTo) === 0) - .length - ).to.equal(0); - }); -}); - -describe('test mapSymbolToType helper function', () => { - it('test if function correctly categorizes symbols that are misplaced', () => { - let memberList: MemberList = { - functions: ['aVar', 'bFunc', 'cFunc'], - classes: ['bClass', 'cClass'], - variables: ['aClass', 'bVar', 'cVar', 'aEnum'], - enums: ['bEnum', 'cEnum', 'dEnum', 'aFunc'], - unknown: [] - }; - - const map: Map = new Map([ - ['aFunc', 'functions'], - ['bFunc', 'functions'], - ['aClass', 'classes'], - ['bClass', 'classes'], - ['aVar', 'variables'], - ['bVar', 'variables'], - ['aEnum', 'enums'] - ]); - - memberList = mapSymbolToType(map, memberList); - - expect(memberList.functions).to.have.members(['aFunc', 'bFunc', 'cFunc']); - expect(memberList.functions).to.not.include.members(['aVar']); - expect(memberList.classes).to.have.members(['aClass', 'bClass', 'cClass']); - expect(memberList.variables).to.not.include.members(['aClass', 'aEnum']); - expect(memberList.variables).to.have.members(['aVar', 'bVar', 'cVar']); - expect(memberList.enums).to.have.members([ - 'aEnum', - 'bEnum', - 'cEnum', - 'dEnum' - ]); - expect(memberList.enums).to.not.include.members(['aFunc']); - - expect(memberList.functions).to.have.length(3); - expect(memberList.classes).to.have.length(3); - expect(memberList.variables).to.have.length(3); - expect(memberList.enums).to.have.length(4); - }); -}); - -describe('test writeReportToFile helper function', () => { - let fileContent: Report; - - before(() => { - fileContent = { - name: 'name', - symbols: [] - }; - }); - it('should throw error when given path exists and points to directory', () => { - const aDir = resolve('./a-dir/a-sub-dir'); - fs.mkdirSync(aDir, { recursive: true }); - expect(() => writeReportToFile(fileContent, aDir)).to.throw( - ErrorCode.OUTPUT_FILE_REQUIRED - ); - }); - - it('should not throw error when given path does not pre-exist', () => { - const aPathToFile = resolve('./a-dir/a-sub-dir/a-file'); - expect(() => writeReportToFile(fileContent, aPathToFile)).to.not.throw(); - fs.unlinkSync(aPathToFile); - }); - after(() => { - fs.rmdirSync('a-dir/a-sub-dir'); - fs.rmdirSync('a-dir', { recursive: true }); - }); -}); - -describe('test writeReportToDirectory helper function', () => { - let fileContent: Report; - - before(() => { - fileContent = { - name: 'name', - symbols: [] - }; - }); - it('should throw error when given path exists and points to a file', () => { - const aDir = resolve('./a-dir/a-sub-dir'); - fs.mkdirSync(aDir, { recursive: true }); - const aFile = `a-file`; - const aPathToFile = `${aDir}/${aFile}`; - fs.writeFileSync(aPathToFile, JSON.stringify(fileContent)); - expect(() => - writeReportToDirectory(fileContent, aFile, aPathToFile) - ).to.throw(ErrorCode.OUTPUT_DIRECTORY_REQUIRED); - }); - - it('should not throw error when given path does not pre-exist', () => { - const aDir = resolve('./a-dir/a-sub-dir'); - const aFile = `a-file`; - expect(() => - writeReportToDirectory(fileContent, aFile, aDir) - ).to.not.throw(); - }); - after(() => { - fs.unlinkSync(`${resolve('./a-dir/a-sub-dir')}/a-file`); - fs.rmdirSync('a-dir/a-sub-dir'); - fs.rmdirSync('a-dir', { recursive: true }); - }); -}); - -describe('test extractExternalDependencies helper function', () => { - it('should correctly extract all symbols listed in import statements', () => { - const assortedImports: string = getAssortedImportsJsFilePath(); - const externals: { [key: string]: string[] } = - extractExternalDependencies(assortedImports); - - expect(externals['./bar']).to.have.members([ - 'basicFuncExternalDependenciesBar', - 'basicFuncExportEnumDependenciesBar', - 'BasicClassExportBar' // extract original name if renamed - ]); - expect(externals['@firebase/logger']).to.be.undefined; - expect(externals['fs']).to.have.members(['*']); // namespace export - // expect(externals['@firebase/app']).to.have.members(['default export']); // default export - }); -}); diff --git a/repo-scripts/size-analysis/test/test-inputs/assortedImports.ts b/repo-scripts/size-analysis/test/test-inputs/assortedImports.ts deleted file mode 100644 index 390936c22c7..00000000000 --- a/repo-scripts/size-analysis/test/test-inputs/assortedImports.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @license - * Copyright 2020 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 '@firebase/logger'; -import * as fs from 'fs'; -import { - basicFuncExportEnumDependenciesBar, - basicFuncExternalDependenciesBar, - BasicClassExportBar as BasicClassExportBarRenamed -} from './bar'; -// import defaultExport from '@firebase/app'; -console.log( - fs, - basicFuncExportEnumDependenciesBar, - basicFuncExternalDependenciesBar, - BasicClassExportBarRenamed - // defaultExport -); diff --git a/repo-scripts/size-analysis/test/test-inputs/bar.ts b/repo-scripts/size-analysis/test/test-inputs/bar.ts deleted file mode 100644 index 534f820d685..00000000000 --- a/repo-scripts/size-analysis/test/test-inputs/bar.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * @license - * Copyright 2020 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 { LogLevel } from '@firebase/logger'; - -export let basicVarDeclarationExportBar: string; -export const basicVarStatementExportBar = 'basicVarStatementExportBar'; -export const reExportVarStatementExportBar = LogLevel; - -export enum BasicEnumExportBar { - DEBUG = 0, - VERBOSE = 1, - INFO = 2, - WARN = 3, - ERROR = 4, - SILENT = 5 -} - -export class BasicClassExportBar {} - -export function basicFuncExportNoDependenciesBar(): string { - return 'basicFuncExportNoDependenciesBar'; -} -export function basicFuncExportVarDependenciesBar(): string { - return basicVarStatementExportBar; -} - -function d1(): string { - return 'd1'; -} -function d2(): string { - return 'd2'; -} -function d3(): string { - return d1() + d2(); -} -export function basicFuncExportFuncDependenciesBar(): string { - return d3(); -} - -export function basicFuncExportEnumDependenciesBar(): BasicEnumExportBar { - return BasicEnumExportBar.DEBUG; -} - -export function basicFuncExternalDependenciesBar(): LogLevel { - return LogLevel.ERROR; -} diff --git a/repo-scripts/size-analysis/test/test-inputs/far.ts b/repo-scripts/size-analysis/test/test-inputs/far.ts deleted file mode 100644 index 7339c51e4e4..00000000000 --- a/repo-scripts/size-analysis/test/test-inputs/far.ts +++ /dev/null @@ -1,77 +0,0 @@ -/** - * @license - * Copyright 2020 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 { LogLevel } from '@firebase/logger'; - -export let basicVarDeclarationExportFar: string; -export const basicVarStatementExportFar = 'basicVarStatementExportFar'; -export const reExportVarStatementExportFar = name; - -export enum BasicEnumExportFar { - DEBUG = 0, - VERBOSE = 1, - INFO = 2, - WARN = 3, - ERROR = 4, - SILENT = 5 -} - -export class BasicClassExportFar {} - -export function basicFuncExportNoDependenciesFar(): string { - return 'basicFuncExportNoDependenciesFar'; -} -export function basicFuncExportVarDependenciesFar(): string { - return basicVarStatementExportFar; -} - -function d1(): string { - return 'd1'; -} -function d2(): string { - return 'd2'; -} -function d3(): string { - return d1() + d2(); -} -export function basicFuncExportFuncDependenciesFar(): string { - return d3(); -} - -export function basicFuncExportEnumDependenciesFar(): BasicEnumExportFar { - return BasicEnumExportFar.DEBUG; -} - -export function basicFuncExternalDependenciesFar(): LogLevel { - return LogLevel.ERROR; -} - -export function basicUniqueFuncFar( - x: Array<{ suit: string; card: number }> -): number; -export function basicUniqueFuncFar(x: number): { suit: string; card: number }; -export function basicUniqueFuncFar( - x: number | Array<{ suit: string; card: number }> -): number | { suit: string; card: number } { - if (typeof x === 'object') { - const pickedCard = Math.floor(Math.random() * x.length); - return pickedCard; - } - // Otherwise just let them pick the card - else { - return { suit: 'a', card: x % 13 }; - } -} diff --git a/repo-scripts/size-analysis/test/test-inputs/index.ts b/repo-scripts/size-analysis/test/test-inputs/index.ts deleted file mode 100644 index f071a54fbd0..00000000000 --- a/repo-scripts/size-analysis/test/test-inputs/index.ts +++ /dev/null @@ -1,117 +0,0 @@ -/** - * @license - * Copyright 2020 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 { LogLevel as LogLevel1, Logger, setLogLevel } from '@firebase/logger'; -import * as fs from 'fs'; -export { fs as fs1 }; -import * as tmp from 'tmp'; -export const aVar: tmp.FileOptions = {}; -// wildcard export -export * from './bar'; -// named export -export { - BasicEnumExportFar, - BasicClassExportFar, - basicFuncExportNoDependenciesFar, - basicFuncExportVarDependenciesFar, - basicFuncExportFuncDependenciesFar, - basicFuncExportEnumDependenciesFar, - basicFuncExternalDependenciesFar, - basicUniqueFuncFar, - basicVarDeclarationExportFar, - basicVarStatementExportFar, - reExportVarStatementExportFar -} from './far'; -export let basicVarDeclarationExport: string; -export const basicVarStatementExport = 'basicVarStatementExport'; -export const reExportVarStatementExport = LogLevel1; - -export enum BasicEnumExport { - DEBUG = 0, - VERBOSE = 1, - INFO = 2, - WARN = 3, - ERROR = 4, - SILENT = 5 -} - -export class BasicClassExport {} - -export function basicFuncExportNoDependencies(): string { - return 'basicFuncExportNoDependencies'; -} -export function basicFuncExportVarDependencies(): string { - return basicVarStatementExport; -} - -function d1(): string { - return 'd1'; -} -function d2(): string { - return 'd2'; -} -function d3(): string { - return d1() + d2(); -} -export function basicFuncExportFuncDependencies(): string { - return d3(); -} - -export function basicFuncExportEnumDependencies(): BasicEnumExport { - return BasicEnumExport.DEBUG; -} - -export function basicFuncExternalDependencies(): LogLevel1 { - return LogLevel1.WARN; -} - -export function basicUniqueFunc( - x: Array<{ suit: string; card: number }> -): number; -export function basicUniqueFunc(x: number): { suit: string; card: number }; -export function basicUniqueFunc( - x: number | Array<{ suit: string; card: number }> -): number | object { - if (typeof x === 'object') { - const pickedCard = Math.floor(Math.random() * x.length); - return pickedCard; - } - // Otherwise just let them pick the card - else { - return { suit: 'a', card: x % 13 }; - } -} - -const apps: Map = new Map(); -export { apps }; - -class Foo {} -export { Foo as Foo1 }; - -function foo(x: string): string { - return x; -} -export { foo as foo2 }; - -export {}; - -export { tar as tarr, tar1 as tarr1 } from './tar'; -// re-export from firebase external module -export { LogLevel1 as LogLevel2 }; - -export { Logger as Logger1 }; - -export { setLogLevel }; diff --git a/repo-scripts/size-analysis/test/test-inputs/subsetExports.ts b/repo-scripts/size-analysis/test/test-inputs/subsetExports.ts deleted file mode 100644 index dac88c80ea2..00000000000 --- a/repo-scripts/size-analysis/test/test-inputs/subsetExports.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ -export { - aVar, - LogLevel2, - tarr1, - basicFuncExportEnumDependencies, - basicFuncExportFuncDependenciesBar, - BasicClassExport -} from './index'; diff --git a/repo-scripts/size-analysis/test/test-inputs/tar.ts b/repo-scripts/size-analysis/test/test-inputs/tar.ts deleted file mode 100644 index d9660e44f3e..00000000000 --- a/repo-scripts/size-analysis/test/test-inputs/tar.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -export function tar(name: string): string { - return name; -} - -export function tar1(name: string): string { - return tar(name); -} diff --git a/repo-scripts/size-analysis/test/test-inputs/tsconfig.json b/repo-scripts/size-analysis/test/test-inputs/tsconfig.json deleted file mode 100644 index 82f14ae86e1..00000000000 --- a/repo-scripts/size-analysis/test/test-inputs/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "compilerOptions": { - "outDir": "dist", - "importHelpers": true, - "module": "es6", - "moduleResolution": "node", - "resolveJsonModule": true, - "target": "es6", - "declaration": true - }, - "exclude": [ - "dist/**/*" - ] - } \ No newline at end of file diff --git a/repo-scripts/size-analysis/test/utils.ts b/repo-scripts/size-analysis/test/utils.ts deleted file mode 100644 index 8af8a36dd58..00000000000 --- a/repo-scripts/size-analysis/test/utils.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -export function getTestModuleDtsFilePath(): string { - return `${__dirname}/test-inputs/dist/index.d.ts`; -} -export function getSubsetExportsBundleFilePath(): string { - return `${__dirname}/test-inputs/dist/subsetExportsBundle.js`; -} -export function getAssortedImportsJsFilePath(): string { - return `${__dirname}/test-inputs/dist/assortedImports.js`; -} diff --git a/repo-scripts/size-analysis/tsconfig.json b/repo-scripts/size-analysis/tsconfig.json deleted file mode 100644 index 326e95a0fa6..00000000000 --- a/repo-scripts/size-analysis/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "compilerOptions": { - "outDir": "dist", - "importHelpers": true, - "module": "commonjs", - "moduleResolution": "node", - "resolveJsonModule": true, - "target": "es2017", - "esModuleInterop": true, - "declaration": true, - "strict": true - }, - "exclude": [ - "dist/**/*" - ] -} \ No newline at end of file diff --git a/repo-scripts/size-analysis/util.ts b/repo-scripts/size-analysis/util.ts deleted file mode 100644 index bd56e98a6e7..00000000000 --- a/repo-scripts/size-analysis/util.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * @license - * Copyright 2020 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 calculateGzipSize from 'gzip-size'; -import { dirname, resolve } from 'path'; - -interface ContentSize { - size: number; - gzipSize: number; -} - -export function calculateContentSize(content: string): ContentSize { - const size = Buffer.byteLength(content, 'utf-8'); - const gzipSize = calculateGzipSize.sync(content); - return { - size, - gzipSize - }; -} - -export const projectRoot = dirname(resolve(__dirname, '../../package.json')); diff --git a/tools/config.js b/tools/config.js deleted file mode 100644 index 7de821095e5..00000000000 --- a/tools/config.js +++ /dev/null @@ -1,103 +0,0 @@ -/** - * @license - * Copyright 2017 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. - */ - -const { argv } = require('yargs'); -const { exec } = require('child-process-promise'); -const { homedir } = require('os'); -const firebaseTools = require('firebase-tools'); -const inquirer = require('inquirer'); -const fs = require('mz/fs'); -const path = require('path'); - -// Command Line Arguments -const userToken = argv.token; -const projectId = argv.projectId; - -let cachedToken; - -try { - const config = require(path.resolve( - homedir(), - '.config/configstore/firebase-tools.json' - )); - cachedToken = config.tokens.refresh_token; -} catch (err) {} - -Promise.resolve(userToken || cachedToken) - // Log in to firebase-tools - .then(async userToken => { - if (userToken) return userToken; - const { - tokens: { refresh_token: freshToken } - } = await firebaseTools.login.ci(); - return freshToken; - }) - // Capture the firebase test project - .then(async token => { - const project = await (async () => { - if (projectId) return projectId; - - const projects = await firebaseTools.projects.list({ token }); - const response = await inquirer.prompt([ - { - type: 'list', - name: 'projectId', - message: 'Which project would you like to use to test?', - choices: projects - .sort(project => - project.name.toLowerCase().includes('jscore') ? -1 : 1 - ) - .map(project => ({ - name: `${project.displayName} (${project.projectId})`, - value: project - })) - } - ]); - - return response.projectId.projectId; - })(); - - // Write config to top-level config directory - await firebaseTools.apps - .sdkconfig('web', undefined, { project, token }) - .then(config => - fs.writeFile( - path.resolve(__dirname, '../config/project.json'), - JSON.stringify(config.sdkConfig, null, 2) - ) - ); - - // npm install the dependencies for functions - await exec('npm install', { - cwd: path.resolve(__dirname, '../config/functions') - }); - - // Deploy database rules - await firebaseTools.deploy({ - project, - token, - cwd: path.resolve(__dirname, '../config') - }); - }) - .then(() => { - console.log('Success! Exiting...'); - process.exit(); - }) - .catch(err => { - console.error(err); - process.exit(1); - }); diff --git a/tools/pretest.js b/tools/pretest.js deleted file mode 100644 index 298455f43f3..00000000000 --- a/tools/pretest.js +++ /dev/null @@ -1,98 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * 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. - */ - -const { resolve } = require('path'); -const { spawn } = require('child-process-promise'); -const chalk = require('chalk'); -const fs = require('mz/fs'); -const semver = require('semver'); - -// CONSTANTS -const root = resolve(__dirname, '..'); -const REQ_YARN_VERSION = '1.0.0'; - -/** - * Potential Problem #1: - * Developer has not yet specified a valid test project - */ -function checkTestConfigExists() { - if (!fs.existsSync(resolve(root, 'config/project.json'))) { - throw chalk` -{red You have not yet specified a Firebase project to use for testing.} - -To create a test project, please visit {underline https://console.firebase.google.com/}. -After doing so, or if you already have a test project, please run the following command -at the root of this package: - -$ npm run test:setup -`; - } -} - -/** - * Potential Problem #2: - * Developer is not using a valid version of `yarn` - */ -async function validateCompatibleYarnVersion() { - const promise = spawn('yarn', ['-v']); - const { childProcess } = promise; - let version = ''; - - childProcess.stdout.on('data', data => { - version += data.toString(); - }); - - await promise; - - if (semver.lt(version, REQ_YARN_VERSION)) { - throw chalk` -{red Your globally installed version of yarn is not compatible} - -We use yarn workspaces to manage this project and your version of yarn is not -compatible. Please visit {underline https://yarnpkg.com/lang/en/docs/install/} -for upgrade/installation instructions -`; - } -} - -/** - * Potential Problem #3: - * Developers yarn setup was misconfigured - */ -async function validateYarnInstall() { - try { - await spawn('yarn', ['check', '--integrity']); - } catch (err) { - throw chalk` -{red Your yarn workspace didn't pass the integrity check} - -To fix the integrity of your test environment please run the following at the -root of this package: - -$ yarn install -`; - } -} - -Promise.resolve() - .then(() => checkTestConfigExists()) - .then(() => validateCompatibleYarnVersion()) - .then(() => validateYarnInstall()) - .catch(err => { - console.error(err); - return process.exit(1); - }); diff --git a/tools/repl.js b/tools/repl.js deleted file mode 100644 index f3c5266810e..00000000000 --- a/tools/repl.js +++ /dev/null @@ -1,61 +0,0 @@ -/** - * @license - * Copyright 2017 Google Inc. - * - * 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. - */ - -const repl = require('repl'); -const firebase = require('../packages/firebase'); - -function clearTerminal() { - return process.stdout.write('\033c'); -} - -function giveContext() { - console.log(`Welcome to the firebase REPL! - -The current firebase build is has been assigned to the \`firebase\` variable. - -Utility Commands Available: - -.clear - Resets the current REPL -.exit - exits the REPL -.help - prints this message -`); -} - -function addFirebaseToContext(repl) { - Object.defineProperty(repl.context, 'firebase', { - configurable: false, - enumerable: true, - value: firebase - }); -} - -clearTerminal(); -giveContext(); - -const replInst = repl.start('> '); -replInst.on('reset', () => { - clearTerminal(); - giveContext(); - addFirebaseToContext(replInst); -}); - -addFirebaseToContext(replInst); - -replInst.defineCommand('help', () => { - giveContext(); - replInst.displayPrompt(); -}); From 50ef920e0d51291e11523cf46d9f070976e58fff Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Tue, 3 Sep 2024 20:13:36 -0700 Subject: [PATCH 35/74] Revert "Fixed appcheck implementation" This reverts commit 994892ce1e91c5536a664850262365f96448e785. --- .changeset/README.md | 8 + .changeset/config.json | 29 + .changeset/late-humans-tan.md | 12 + .changeset/spicy-dragons-pay.md | 5 + .changeset/tender-apes-clap.md | 6 + .opensource/project.json | 20 + .yarn/releases/yarn-1.22.11.cjs | 147406 +++++++++++++++ .yarnrc | 2 + CHANGELOG.md | 5 + LICENSE | 257 + config/.eslintrc.js | 237 + config/api-extractor.json | 365 + config/ci.config.json | 9 + config/database.rules.json | 9 + config/firebase.json | 12 + config/firestore.indexes.json | 3 + config/firestore.rules | 7 + config/functions/index.js | 158 + config/functions/package.json | 13 + config/karma.base.js | 91 + config/karma.saucelabs.js | 237 + config/mocha.browser.opts | 25 + config/mocharc.node.js | 37 + config/tsconfig.base.json | 36 + config/webpack.test.js | 123 + docs-devsite/_toc.yaml | 606 + docs-devsite/analytics.analytics.md | 35 + .../analytics.analyticscalloptions.md | 35 + docs-devsite/analytics.analyticssettings.md | 35 + docs-devsite/analytics.consentsettings.md | 101 + docs-devsite/analytics.controlparams.md | 60 + docs-devsite/analytics.customparams.md | 19 + docs-devsite/analytics.eventparams.md | 316 + docs-devsite/analytics.gtagconfigparams.md | 134 + docs-devsite/analytics.item.md | 278 + docs-devsite/analytics.md | 1024 + docs-devsite/analytics.promotion.md | 65 + docs-devsite/analytics.settingsoptions.md | 46 + docs-devsite/app-check.appcheck.md | 35 + docs-devsite/app-check.appcheckoptions.md | 46 + docs-devsite/app-check.appchecktoken.md | 44 + docs-devsite/app-check.appchecktokenresult.md | 35 + docs-devsite/app-check.customprovider.md | 43 + .../app-check.customprovideroptions.md | 35 + docs-devsite/app-check.md | 201 + .../app-check.recaptchaenterpriseprovider.md | 43 + docs-devsite/app-check.recaptchav3provider.md | 43 + docs-devsite/app.firebaseapp.md | 91 + docs-devsite/app.firebaseappsettings.md | 46 + docs-devsite/app.firebaseoptions.md | 112 + docs-devsite/app.firebaseserverapp.md | 59 + docs-devsite/app.firebaseserverappsettings.md | 59 + docs-devsite/app.md | 371 + docs-devsite/auth.actioncodeinfo.md | 58 + docs-devsite/auth.actioncodesettings.md | 95 + docs-devsite/auth.actioncodeurl.md | 121 + docs-devsite/auth.additionaluserinfo.md | 68 + docs-devsite/auth.applicationverifier.md | 59 + docs-devsite/auth.auth.md | 317 + docs-devsite/auth.authcredential.md | 76 + docs-devsite/auth.autherror.md | 41 + docs-devsite/auth.autherrormap.md | 27 + docs-devsite/auth.authprovider.md | 35 + docs-devsite/auth.authsettings.md | 41 + docs-devsite/auth.config.md | 90 + docs-devsite/auth.confirmationresult.md | 74 + docs-devsite/auth.dependencies.md | 63 + docs-devsite/auth.emailauthcredential.md | 69 + docs-devsite/auth.emailauthprovider.md | 159 + docs-devsite/auth.emulatorconfig.md | 70 + docs-devsite/auth.facebookauthprovider.md | 182 + docs-devsite/auth.githubauthprovider.md | 174 + docs-devsite/auth.googleauthprovider.md | 185 + docs-devsite/auth.idtokenresult.md | 109 + docs-devsite/auth.md | 2148 + docs-devsite/auth.multifactorassertion.md | 37 + docs-devsite/auth.multifactorerror.md | 67 + docs-devsite/auth.multifactorinfo.md | 68 + docs-devsite/auth.multifactorresolver.md | 128 + docs-devsite/auth.multifactorsession.md | 19 + docs-devsite/auth.multifactoruser.md | 161 + docs-devsite/auth.oauthcredential.md | 105 + docs-devsite/auth.oauthcredentialoptions.md | 61 + docs-devsite/auth.oauthprovider.md | 168 + docs-devsite/auth.parsedtoken.md | 83 + docs-devsite/auth.passwordpolicy.md | 75 + docs-devsite/auth.passwordvalidationstatus.md | 112 + docs-devsite/auth.persistence.md | 35 + docs-devsite/auth.phoneauthcredential.md | 63 + docs-devsite/auth.phoneauthprovider.md | 258 + .../auth.phonemultifactorassertion.md | 21 + .../auth.phonemultifactorenrollinfooptions.md | 46 + .../auth.phonemultifactorgenerator.md | 66 + docs-devsite/auth.phonemultifactorinfo.md | 36 + .../auth.phonemultifactorsignininfooptions.md | 61 + .../auth.phonesinglefactorinfooptions.md | 35 + docs-devsite/auth.popupredirectresolver.md | 19 + docs-devsite/auth.reactnativeasyncstorage.md | 89 + docs-devsite/auth.recaptchaparameters.md | 23 + docs-devsite/auth.recaptchaverifier.md | 118 + docs-devsite/auth.samlauthprovider.md | 119 + docs-devsite/auth.totpmultifactorassertion.md | 21 + docs-devsite/auth.totpmultifactorgenerator.md | 112 + docs-devsite/auth.totpmultifactorinfo.md | 21 + docs-devsite/auth.totpsecret.md | 111 + docs-devsite/auth.twitterauthprovider.md | 173 + docs-devsite/auth.user.md | 206 + docs-devsite/auth.usercredential.md | 59 + docs-devsite/auth.userinfo.md | 92 + docs-devsite/auth.usermetadata.md | 46 + .../data-connect.authtokenprovider.md | 61 + .../data-connect.cancellableoperation.md | 43 + docs-devsite/data-connect.connectorconfig.md | 51 + docs-devsite/data-connect.dataconnect.md | 124 + .../data-connect.dataconnectoptions.md | 34 + .../data-connect.dataconnectresult.md | 32 + .../data-connect.dataconnectsubscription.md | 51 + .../data-connect.dataconnecttransport.md | 104 + .../data-connect.firebaseauthprovider.md | 105 + docs-devsite/data-connect.md | 536 + docs-devsite/data-connect.mutationpromise.md | 21 + docs-devsite/data-connect.mutationref.md | 32 + docs-devsite/data-connect.mutationresponse.md | 19 + docs-devsite/data-connect.mutationresult.md | 34 + docs-devsite/data-connect.operationref.md | 58 + docs-devsite/data-connect.opresult.md | 49 + docs-devsite/data-connect.querypromise.md | 21 + docs-devsite/data-connect.queryref.md | 34 + docs-devsite/data-connect.queryresponse.md | 19 + docs-devsite/data-connect.queryresult.md | 43 + docs-devsite/data-connect.refinfo.md | 51 + docs-devsite/data-connect.sender.md | 40 + docs-devsite/data-connect.serializedref.md | 34 + .../data-connect.subscriptionoptions.md | 51 + docs-devsite/data-connect.transportoptions.md | 51 + docs-devsite/database.database.md | 46 + docs-devsite/database.databasereference.md | 68 + docs-devsite/database.datasnapshot.md | 237 + docs-devsite/database.iterateddatasnapshot.md | 34 + docs-devsite/database.listenoptions.md | 35 + docs-devsite/database.md | 1430 + docs-devsite/database.ondisconnect.md | 143 + docs-devsite/database.query.md | 108 + docs-devsite/database.queryconstraint.md | 35 + docs-devsite/database.thenablereference.md | 21 + docs-devsite/database.transactionoptions.md | 35 + docs-devsite/database.transactionresult.md | 66 + docs-devsite/firestore.md | 19 + docs-devsite/firestore_.aggregatefield.md | 46 + .../firestore_.aggregatequerysnapshot.md | 70 + docs-devsite/firestore_.aggregatespec.md | 19 + docs-devsite/firestore_.bytes.md | 138 + .../firestore_.collectionreference.md | 121 + docs-devsite/firestore_.documentchange.md | 68 + docs-devsite/firestore_.documentdata.md | 19 + docs-devsite/firestore_.documentreference.md | 142 + docs-devsite/firestore_.documentsnapshot.md | 146 + ...restore_.experimentallongpollingoptions.md | 43 + docs-devsite/firestore_.fieldpath.md | 72 + docs-devsite/firestore_.fieldvalue.md | 46 + docs-devsite/firestore_.firestore.md | 68 + .../firestore_.firestoredataconverter.md | 264 + docs-devsite/firestore_.firestoreerror.md | 58 + docs-devsite/firestore_.firestoresettings.md | 130 + docs-devsite/firestore_.geopoint.md | 117 + docs-devsite/firestore_.index.md | 60 + docs-devsite/firestore_.indexconfiguration.md | 48 + docs-devsite/firestore_.indexfield.md | 78 + docs-devsite/firestore_.loadbundletask.md | 94 + .../firestore_.loadbundletaskprogress.md | 79 + docs-devsite/firestore_.md | 2731 + .../firestore_.memorycachesettings.md | 35 + .../firestore_.memoryeagergarbagecollector.md | 37 + docs-devsite/firestore_.memorylocalcache.md | 35 + .../firestore_.memorylrugarbagecollector.md | 37 + .../firestore_.persistencesettings.md | 37 + .../firestore_.persistentcacheindexmanager.md | 37 + .../firestore_.persistentcachesettings.md | 50 + .../firestore_.persistentlocalcache.md | 35 + ...firestore_.persistentmultipletabmanager.md | 33 + .../firestore_.persistentsingletabmanager.md | 33 + ...ore_.persistentsingletabmanagersettings.md | 35 + docs-devsite/firestore_.query.md | 125 + ...restore_.querycompositefilterconstraint.md | 35 + docs-devsite/firestore_.queryconstraint.md | 35 + .../firestore_.querydocumentsnapshot.md | 54 + .../firestore_.queryendatconstraint.md | 36 + .../firestore_.queryfieldfilterconstraint.md | 36 + .../firestore_.querylimitconstraint.md | 36 + .../firestore_.queryorderbyconstraint.md | 38 + docs-devsite/firestore_.querysnapshot.md | 128 + .../firestore_.querystartatconstraint.md | 36 + .../firestore_.snapshotlistenoptions.md | 46 + docs-devsite/firestore_.snapshotmetadata.md | 75 + docs-devsite/firestore_.snapshotoptions.md | 41 + docs-devsite/firestore_.timestamp.md | 243 + docs-devsite/firestore_.transaction.md | 190 + docs-devsite/firestore_.transactionoptions.md | 35 + docs-devsite/firestore_.unsubscribe.md | 19 + docs-devsite/firestore_.writebatch.md | 181 + docs-devsite/firestore_lite.aggregatefield.md | 46 + .../firestore_lite.aggregatequerysnapshot.md | 70 + docs-devsite/firestore_lite.aggregatespec.md | 19 + docs-devsite/firestore_lite.bytes.md | 138 + .../firestore_lite.collectionreference.md | 121 + docs-devsite/firestore_lite.documentdata.md | 19 + .../firestore_lite.documentreference.md | 142 + .../firestore_lite.documentsnapshot.md | 125 + docs-devsite/firestore_lite.fieldpath.md | 72 + docs-devsite/firestore_lite.fieldvalue.md | 46 + docs-devsite/firestore_lite.firestore.md | 68 + .../firestore_lite.firestoredataconverter.md | 261 + docs-devsite/firestore_lite.firestoreerror.md | 58 + docs-devsite/firestore_lite.geopoint.md | 117 + docs-devsite/firestore_lite.md | 1773 + docs-devsite/firestore_lite.query.md | 125 + ...ore_lite.querycompositefilterconstraint.md | 35 + .../firestore_lite.queryconstraint.md | 35 + .../firestore_lite.querydocumentsnapshot.md | 45 + .../firestore_lite.queryendatconstraint.md | 36 + ...restore_lite.queryfieldfilterconstraint.md | 36 + .../firestore_lite.querylimitconstraint.md | 36 + .../firestore_lite.queryorderbyconstraint.md | 38 + docs-devsite/firestore_lite.querysnapshot.md | 96 + .../firestore_lite.querystartatconstraint.md | 36 + docs-devsite/firestore_lite.settings.md | 57 + docs-devsite/firestore_lite.timestamp.md | 243 + docs-devsite/firestore_lite.transaction.md | 190 + .../firestore_lite.transactionoptions.md | 35 + docs-devsite/firestore_lite.writebatch.md | 181 + docs-devsite/functions.functions.md | 57 + docs-devsite/functions.functionserror.md | 47 + .../functions.httpscallableoptions.md | 46 + docs-devsite/functions.httpscallableresult.md | 35 + docs-devsite/functions.md | 166 + docs-devsite/index.md | 32 + docs-devsite/installations.installations.md | 35 + docs-devsite/installations.md | 166 + docs-devsite/messaging.md | 19 + docs-devsite/messaging_.fcmoptions.md | 46 + docs-devsite/messaging_.gettokenoptions.md | 46 + docs-devsite/messaging_.md | 149 + docs-devsite/messaging_.messagepayload.md | 92 + docs-devsite/messaging_.messaging.md | 35 + .../messaging_.notificationpayload.md | 68 + docs-devsite/messaging_sw.fcmoptions.md | 46 + docs-devsite/messaging_sw.gettokenoptions.md | 46 + docs-devsite/messaging_sw.md | 122 + docs-devsite/messaging_sw.messagepayload.md | 92 + docs-devsite/messaging_sw.messaging.md | 35 + .../messaging_sw.notificationpayload.md | 68 + .../performance.firebaseperformance.md | 57 + docs-devsite/performance.md | 98 + .../performance.performancesettings.md | 46 + docs-devsite/performance.performancetrace.md | 228 + docs-devsite/remote-config.md | 351 + docs-devsite/remote-config.remoteconfig.md | 81 + .../remote-config.remoteconfigsettings.md | 46 + docs-devsite/remote-config.value.md | 83 + docs-devsite/storage.firebasestorage.md | 58 + docs-devsite/storage.fullmetadata.md | 135 + docs-devsite/storage.listoptions.md | 46 + docs-devsite/storage.listresult.md | 59 + docs-devsite/storage.md | 558 + docs-devsite/storage.settablemetadata.md | 92 + docs-devsite/storage.storageerror.md | 113 + docs-devsite/storage.storageobserver.md | 51 + docs-devsite/storage.storagereference.md | 112 + docs-devsite/storage.uploadmetadata.md | 36 + docs-devsite/storage.uploadresult.md | 46 + docs-devsite/storage.uploadtask.md | 255 + docs-devsite/storage.uploadtasksnapshot.md | 90 + docs-devsite/vertexai-preview.baseparams.md | 42 + docs-devsite/vertexai-preview.chatsession.md | 138 + docs-devsite/vertexai-preview.citation.md | 78 + .../vertexai-preview.citationmetadata.md | 33 + docs-devsite/vertexai-preview.content.md | 42 + .../vertexai-preview.counttokensrequest.md | 33 + .../vertexai-preview.counttokensresponse.md | 46 + .../vertexai-preview.customerrordata.md | 68 + docs-devsite/vertexai-preview.date_2.md | 51 + ...preview.enhancedgeneratecontentresponse.md | 45 + docs-devsite/vertexai-preview.errordetails.md | 66 + docs-devsite/vertexai-preview.filedata.md | 42 + docs-devsite/vertexai-preview.filedatapart.md | 69 + docs-devsite/vertexai-preview.functioncall.md | 42 + .../vertexai-preview.functioncallingconfig.md | 41 + .../vertexai-preview.functioncallpart.md | 60 + .../vertexai-preview.functiondeclaration.md | 57 + ...texai-preview.functiondeclarationschema.md | 70 + ...eview.functiondeclarationschemaproperty.md | 125 + ...rtexai-preview.functiondeclarationstool.md | 35 + .../vertexai-preview.functionresponse.md | 42 + .../vertexai-preview.functionresponsepart.md | 60 + ...rtexai-preview.generatecontentcandidate.md | 87 + ...vertexai-preview.generatecontentrequest.md | 61 + ...ertexai-preview.generatecontentresponse.md | 51 + .../vertexai-preview.generatecontentresult.md | 33 + ...xai-preview.generatecontentstreamresult.md | 42 + .../vertexai-preview.generationconfig.md | 107 + .../vertexai-preview.generativecontentblob.md | 44 + .../vertexai-preview.generativemodel.md | 201 + .../vertexai-preview.groundingattribution.md | 59 + .../vertexai-preview.groundingmetadata.md | 51 + .../vertexai-preview.inlinedatapart.md | 71 + docs-devsite/vertexai-preview.md | 397 + docs-devsite/vertexai-preview.modelparams.md | 61 + .../vertexai-preview.promptfeedback.md | 51 + .../vertexai-preview.requestoptions.md | 46 + ...xai-preview.retrievedcontextattribution.md | 41 + docs-devsite/vertexai-preview.safetyrating.md | 78 + .../vertexai-preview.safetysetting.md | 51 + docs-devsite/vertexai-preview.segment.md | 50 + .../vertexai-preview.startchatparams.md | 61 + docs-devsite/vertexai-preview.textpart.md | 60 + docs-devsite/vertexai-preview.toolconfig.md | 33 + .../vertexai-preview.usagemetadata.md | 51 + docs-devsite/vertexai-preview.vertexai.md | 44 + .../vertexai-preview.vertexaierror.md | 76 + .../vertexai-preview.vertexaioptions.md | 33 + .../vertexai-preview.videometadata.md | 46 + .../vertexai-preview.webattribution.md | 41 + integration/compat-interop/analytics.test.ts | 35 + integration/compat-interop/app.test.ts | 50 + integration/compat-interop/auth.test.ts | 49 + integration/compat-interop/functions.test.ts | 35 + integration/compat-interop/karma.conf.js | 36 + integration/compat-interop/messaging.test.ts | 35 + integration/compat-interop/package.json | 29 + .../compat-interop/performance.test.ts | 60 + .../compat-interop/remote-config.test.ts | 35 + integration/compat-interop/tsconfig.json | 19 + integration/compat-interop/util.ts | 18 + integration/compat-typings/package.json | 15 + integration/compat-typings/tsconfig.json | 11 + integration/compat-typings/typings.ts | 85 + integration/firebase/karma.conf.js | 36 + integration/firebase/package.json | 25 + integration/firebase/test/namespace.test.ts | 38 + .../firebase/test/namespaceDefinition.json | 231 + integration/firebase/test/typings.d.ts | 21 + integration/firebase/test/validator.js | 109 + integration/firebase/tsconfig.json | 19 + integration/firestore/.gitignore | 1 + integration/firestore/README.md | 14 + integration/firestore/firebase_export.ts | 53 + integration/firestore/gulpfile.js | 102 + integration/firestore/karma.conf.js | 36 + integration/firestore/package.json | 36 + integration/firestore/tsconfig.json | 6 + integration/messaging/download-browsers.js | 30 + integration/messaging/manual-test-server.js | 22 + integration/messaging/package.json | 21 + integration/messaging/test/static/app.js | 127 + .../messaging/test/static/constants.js | 35 + .../test/static/default-sw/index.html | 35 + .../test/static/firebase-messaging-sw.js | 18 + integration/messaging/test/static/helpers.js | 52 + integration/messaging/test/static/sw-base.js | 37 + .../test/static/valid-manifest/index.html | 34 + .../test/static/valid-manifest/manifest.json | 3 + .../valid-vapid-key-modern-sw/index.html | 44 + .../static/valid-vapid-key-modern-sw/sw.js | 37 + .../test/static/valid-vapid-key/index.html | 44 + .../test/static/valid-vapid-key/sw.js | 18 + .../messaging/test/test-receive-background.js | 157 + .../messaging/test/test-receive-foreground.js | 177 + .../messaging/test/test-token-delete.js | 82 + .../messaging/test/test-token-update.js | 88 + .../test/test-useDefaultServiceWorker.js | 64 + .../messaging/test/test-useValidManifest.js | 52 + .../test/utils/checkMessageReceived.js | 66 + .../messaging/test/utils/checkSendResponse.js | 22 + .../messaging/test/utils/clearAppForTest.js | 30 + .../test/utils/createPermittedWebDriver.js | 50 + .../messaging/test/utils/deleteToken.js | 29 + .../messaging/test/utils/forwardTime.js | 29 + integration/messaging/test/utils/getErrors.js | 30 + .../utils/getReceivedBackgroundMessages.js | 55 + .../utils/getReceivedForegroundMessages.js | 31 + .../messaging/test/utils/openNewTab.js | 24 + .../messaging/test/utils/retrieveToken.js | 30 + .../messaging/test/utils/sendMessage.js | 43 + .../messaging/test/utils/test-server.js | 74 + .../messaging/test/utils/triggerGetToken.js | 32 + patches/karma-webpack+5.0.0.patch | 23 + renovate.json | 33 + repo-scripts/api-documenter/CHANGELOG.md | 21 + repo-scripts/api-documenter/README.md | 16 + repo-scripts/api-documenter/gulpfile.js | 22 + repo-scripts/api-documenter/package.json | 38 + .../src/cli/ApiDocumenterCommandLine.ts | 45 + .../api-documenter/src/cli/BaseAction.ts | 197 + .../api-documenter/src/cli/MarkdownAction.ts | 71 + .../api-documenter/src/cli/TocAction.ts | 77 + .../src/documenters/DocumenterConfig.ts | 84 + .../src/documenters/IConfigFile.ts | 112 + .../src/documenters/MarkdownDocumenter.ts | 1228 + .../documenters/MarkdownDocumenterHelpers.ts | 449 + repo-scripts/api-documenter/src/index.ts | 44 + .../src/markdown/CustomMarkdownEmitter.ts | 232 + .../src/markdown/MarkdownEmitter.ts | 319 + .../test/CustomMarkdownEmitter.test.ts | 237 + .../CustomMarkdownEmitter.test.ts.snap | 62 + .../src/nodes/CustomDocNodeKind.ts | 89 + .../src/nodes/DocEmphasisSpan.ts | 58 + .../api-documenter/src/nodes/DocHeading.ts | 62 + .../api-documenter/src/nodes/DocNoteBox.ts | 55 + .../api-documenter/src/nodes/DocTable.ts | 101 + .../api-documenter/src/nodes/DocTableCell.ts | 51 + .../api-documenter/src/nodes/DocTableRow.ts | 86 + .../plugin/IApiDocumenterPluginManifest.ts | 96 + .../src/plugin/MarkdownDocumenterAccessor.ts | 55 + .../src/plugin/MarkdownDocumenterFeature.ts | 124 + .../src/plugin/PluginFeature.ts | 85 + .../api-documenter/src/plugin/PluginLoader.ts | 163 + .../src/schemas/api-documenter-template.json | 92 + .../src/schemas/api-documenter.schema.json | 42 + repo-scripts/api-documenter/src/start.ts | 39 + repo-scripts/api-documenter/src/toc.ts | 108 + .../src/utils/IndentedWriter.ts | 243 + .../api-documenter/src/utils/Utilities.ts | 48 + .../src/utils/test/IndentedWriter.test.ts | 99 + .../__snapshots__/IndentedWriter.test.ts.snap | 46 + repo-scripts/api-documenter/tsconfig.json | 12 + repo-scripts/changelog-generator/.eslintrc.js | 26 + repo-scripts/changelog-generator/README.md | 3 + repo-scripts/changelog-generator/index.ts | 120 + repo-scripts/changelog-generator/package.json | 43 + .../changelog-generator/tsconfig.json | 14 + repo-scripts/prune-dts/.run/AllTests.run.xml | 35 + repo-scripts/prune-dts/extract-public-api.ts | 237 + repo-scripts/prune-dts/package.json | 39 + repo-scripts/prune-dts/prune-dts.test.ts | 99 + repo-scripts/prune-dts/prune-dts.ts | 561 + repo-scripts/prune-dts/tests/dom.input.d.ts | 29 + repo-scripts/prune-dts/tests/dom.output.d.ts | 29 + repo-scripts/prune-dts/tests/error.input.d.ts | 23 + .../prune-dts/tests/error.output.d.ts | 21 + .../prune-dts/tests/firestore.input.d.ts | 5779 + .../prune-dts/tests/firestore.output.d.ts | 2078 + .../tests/hide-constructor.input.d.ts | 22 + .../tests/hide-constructor.output.d.ts | 20 + .../inherits-non-exported-members.input.d.ts | 24 + .../inherits-non-exported-members.output.d.ts | 21 + .../tests/keeps-inheritance.input.d.ts | 27 + .../tests/keeps-inheritance.output.d.ts | 24 + ...nly-modifies-non-exported-types.input.d.ts | 25 + ...ly-modifies-non-exported-types.output.d.ts | 24 + .../tests/private-interface.input.d.ts | 24 + .../tests/private-interface.output.d.ts | 20 + .../tests/propagates-comments.input.d.ts | 41 + .../tests/propagates-comments.output.d.ts | 41 + .../tests/propagates-members.input.d.ts | 27 + .../tests/propagates-members.output.d.ts | 24 + .../prunes-non-exported-types.input.d.ts | 22 + .../prunes-non-exported-types.output.d.ts | 19 + .../tests/prunes-underscores.input.d.ts | 25 + .../tests/prunes-underscores.output.d.ts | 20 + .../references-public-interfaces.input.d.ts | 23 + .../references-public-interfaces.output.d.ts | 21 + .../tests/references-public-types.input.d.ts | 28 + .../tests/references-public-types.output.d.ts | 26 + ...esolves-generics-different-name.input.d.ts | 24 + ...solves-generics-different-name.output.d.ts | 21 + ...es-generics-through-inheritance.input.d.ts | 25 + ...s-generics-through-inheritence.output.d.ts | 24 + .../tests/resolves-generics.input.d.ts | 22 + .../tests/resolves-generics.output.d.ts | 20 + .../prune-dts/tests/swaps-generics.input.d.ts | 24 + .../tests/swaps-generics.output.d.ts | 21 + repo-scripts/prune-dts/tsconfig.eslint.json | 4 + repo-scripts/prune-dts/tsconfig.json | 14 + repo-scripts/size-analysis/.eslintrc.js | 26 + repo-scripts/size-analysis/README.md | 117 + repo-scripts/size-analysis/analysis-helper.ts | 670 + .../size-analysis/analyze-all-bundles.ts | 117 + repo-scripts/size-analysis/bundle-analysis.ts | 500 + .../bundle-definitions/analytics.json | 32 + .../bundle-definitions/app-check.json | 95 + .../bundle-definitions/auth.json | 201 + .../bundle-definitions/database.json | 288 + .../bundle-definitions/firestore-lite.json | 166 + .../bundle-definitions/firestore.json | 328 + .../bundle-definitions/functions.json | 32 + .../bundle-definitions/messaging.json | 33 + .../bundle-definitions/performance.json | 32 + .../bundle-definitions/remote-config.json | 33 + .../bundle-definitions/storage.json | 251 + repo-scripts/size-analysis/bundle/minify.ts | 30 + repo-scripts/size-analysis/bundle/rollup.ts | 57 + repo-scripts/size-analysis/bundle/webpack.ts | 85 + repo-scripts/size-analysis/cli.ts | 92 + .../size-analysis/package-analysis.ts | 134 + repo-scripts/size-analysis/package.json | 61 + repo-scripts/size-analysis/rollup.config.js | 83 + .../size-analysis/test/size-analysis.test.ts | 489 + .../test/test-inputs/assortedImports.ts | 32 + .../size-analysis/test/test-inputs/bar.ts | 60 + .../size-analysis/test/test-inputs/far.ts | 77 + .../size-analysis/test/test-inputs/index.ts | 117 + .../test/test-inputs/subsetExports.ts | 24 + .../size-analysis/test/test-inputs/tar.ts | 24 + .../test/test-inputs/tsconfig.json | 14 + repo-scripts/size-analysis/test/utils.ts | 26 + repo-scripts/size-analysis/tsconfig.json | 16 + repo-scripts/size-analysis/util.ts | 34 + tools/config.js | 103 + tools/pretest.js | 98 + tools/repl.js | 61 + 510 files changed, 204975 insertions(+) create mode 100644 .changeset/README.md create mode 100644 .changeset/config.json create mode 100644 .changeset/late-humans-tan.md create mode 100644 .changeset/spicy-dragons-pay.md create mode 100644 .changeset/tender-apes-clap.md create mode 100644 .opensource/project.json create mode 100755 .yarn/releases/yarn-1.22.11.cjs create mode 100644 .yarnrc create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 config/.eslintrc.js create mode 100644 config/api-extractor.json create mode 100644 config/ci.config.json create mode 100644 config/database.rules.json create mode 100644 config/firebase.json create mode 100644 config/firestore.indexes.json create mode 100644 config/firestore.rules create mode 100644 config/functions/index.js create mode 100644 config/functions/package.json create mode 100644 config/karma.base.js create mode 100644 config/karma.saucelabs.js create mode 100644 config/mocha.browser.opts create mode 100644 config/mocharc.node.js create mode 100644 config/tsconfig.base.json create mode 100644 config/webpack.test.js create mode 100644 docs-devsite/_toc.yaml create mode 100644 docs-devsite/analytics.analytics.md create mode 100644 docs-devsite/analytics.analyticscalloptions.md create mode 100644 docs-devsite/analytics.analyticssettings.md create mode 100644 docs-devsite/analytics.consentsettings.md create mode 100644 docs-devsite/analytics.controlparams.md create mode 100644 docs-devsite/analytics.customparams.md create mode 100644 docs-devsite/analytics.eventparams.md create mode 100644 docs-devsite/analytics.gtagconfigparams.md create mode 100644 docs-devsite/analytics.item.md create mode 100644 docs-devsite/analytics.md create mode 100644 docs-devsite/analytics.promotion.md create mode 100644 docs-devsite/analytics.settingsoptions.md create mode 100644 docs-devsite/app-check.appcheck.md create mode 100644 docs-devsite/app-check.appcheckoptions.md create mode 100644 docs-devsite/app-check.appchecktoken.md create mode 100644 docs-devsite/app-check.appchecktokenresult.md create mode 100644 docs-devsite/app-check.customprovider.md create mode 100644 docs-devsite/app-check.customprovideroptions.md create mode 100644 docs-devsite/app-check.md create mode 100644 docs-devsite/app-check.recaptchaenterpriseprovider.md create mode 100644 docs-devsite/app-check.recaptchav3provider.md create mode 100644 docs-devsite/app.firebaseapp.md create mode 100644 docs-devsite/app.firebaseappsettings.md create mode 100644 docs-devsite/app.firebaseoptions.md create mode 100644 docs-devsite/app.firebaseserverapp.md create mode 100644 docs-devsite/app.firebaseserverappsettings.md create mode 100644 docs-devsite/app.md create mode 100644 docs-devsite/auth.actioncodeinfo.md create mode 100644 docs-devsite/auth.actioncodesettings.md create mode 100644 docs-devsite/auth.actioncodeurl.md create mode 100644 docs-devsite/auth.additionaluserinfo.md create mode 100644 docs-devsite/auth.applicationverifier.md create mode 100644 docs-devsite/auth.auth.md create mode 100644 docs-devsite/auth.authcredential.md create mode 100644 docs-devsite/auth.autherror.md create mode 100644 docs-devsite/auth.autherrormap.md create mode 100644 docs-devsite/auth.authprovider.md create mode 100644 docs-devsite/auth.authsettings.md create mode 100644 docs-devsite/auth.config.md create mode 100644 docs-devsite/auth.confirmationresult.md create mode 100644 docs-devsite/auth.dependencies.md create mode 100644 docs-devsite/auth.emailauthcredential.md create mode 100644 docs-devsite/auth.emailauthprovider.md create mode 100644 docs-devsite/auth.emulatorconfig.md create mode 100644 docs-devsite/auth.facebookauthprovider.md create mode 100644 docs-devsite/auth.githubauthprovider.md create mode 100644 docs-devsite/auth.googleauthprovider.md create mode 100644 docs-devsite/auth.idtokenresult.md create mode 100644 docs-devsite/auth.md create mode 100644 docs-devsite/auth.multifactorassertion.md create mode 100644 docs-devsite/auth.multifactorerror.md create mode 100644 docs-devsite/auth.multifactorinfo.md create mode 100644 docs-devsite/auth.multifactorresolver.md create mode 100644 docs-devsite/auth.multifactorsession.md create mode 100644 docs-devsite/auth.multifactoruser.md create mode 100644 docs-devsite/auth.oauthcredential.md create mode 100644 docs-devsite/auth.oauthcredentialoptions.md create mode 100644 docs-devsite/auth.oauthprovider.md create mode 100644 docs-devsite/auth.parsedtoken.md create mode 100644 docs-devsite/auth.passwordpolicy.md create mode 100644 docs-devsite/auth.passwordvalidationstatus.md create mode 100644 docs-devsite/auth.persistence.md create mode 100644 docs-devsite/auth.phoneauthcredential.md create mode 100644 docs-devsite/auth.phoneauthprovider.md create mode 100644 docs-devsite/auth.phonemultifactorassertion.md create mode 100644 docs-devsite/auth.phonemultifactorenrollinfooptions.md create mode 100644 docs-devsite/auth.phonemultifactorgenerator.md create mode 100644 docs-devsite/auth.phonemultifactorinfo.md create mode 100644 docs-devsite/auth.phonemultifactorsignininfooptions.md create mode 100644 docs-devsite/auth.phonesinglefactorinfooptions.md create mode 100644 docs-devsite/auth.popupredirectresolver.md create mode 100644 docs-devsite/auth.reactnativeasyncstorage.md create mode 100644 docs-devsite/auth.recaptchaparameters.md create mode 100644 docs-devsite/auth.recaptchaverifier.md create mode 100644 docs-devsite/auth.samlauthprovider.md create mode 100644 docs-devsite/auth.totpmultifactorassertion.md create mode 100644 docs-devsite/auth.totpmultifactorgenerator.md create mode 100644 docs-devsite/auth.totpmultifactorinfo.md create mode 100644 docs-devsite/auth.totpsecret.md create mode 100644 docs-devsite/auth.twitterauthprovider.md create mode 100644 docs-devsite/auth.user.md create mode 100644 docs-devsite/auth.usercredential.md create mode 100644 docs-devsite/auth.userinfo.md create mode 100644 docs-devsite/auth.usermetadata.md create mode 100644 docs-devsite/data-connect.authtokenprovider.md create mode 100644 docs-devsite/data-connect.cancellableoperation.md create mode 100644 docs-devsite/data-connect.connectorconfig.md create mode 100644 docs-devsite/data-connect.dataconnect.md create mode 100644 docs-devsite/data-connect.dataconnectoptions.md create mode 100644 docs-devsite/data-connect.dataconnectresult.md create mode 100644 docs-devsite/data-connect.dataconnectsubscription.md create mode 100644 docs-devsite/data-connect.dataconnecttransport.md create mode 100644 docs-devsite/data-connect.firebaseauthprovider.md create mode 100644 docs-devsite/data-connect.md create mode 100644 docs-devsite/data-connect.mutationpromise.md create mode 100644 docs-devsite/data-connect.mutationref.md create mode 100644 docs-devsite/data-connect.mutationresponse.md create mode 100644 docs-devsite/data-connect.mutationresult.md create mode 100644 docs-devsite/data-connect.operationref.md create mode 100644 docs-devsite/data-connect.opresult.md create mode 100644 docs-devsite/data-connect.querypromise.md create mode 100644 docs-devsite/data-connect.queryref.md create mode 100644 docs-devsite/data-connect.queryresponse.md create mode 100644 docs-devsite/data-connect.queryresult.md create mode 100644 docs-devsite/data-connect.refinfo.md create mode 100644 docs-devsite/data-connect.sender.md create mode 100644 docs-devsite/data-connect.serializedref.md create mode 100644 docs-devsite/data-connect.subscriptionoptions.md create mode 100644 docs-devsite/data-connect.transportoptions.md create mode 100644 docs-devsite/database.database.md create mode 100644 docs-devsite/database.databasereference.md create mode 100644 docs-devsite/database.datasnapshot.md create mode 100644 docs-devsite/database.iterateddatasnapshot.md create mode 100644 docs-devsite/database.listenoptions.md create mode 100644 docs-devsite/database.md create mode 100644 docs-devsite/database.ondisconnect.md create mode 100644 docs-devsite/database.query.md create mode 100644 docs-devsite/database.queryconstraint.md create mode 100644 docs-devsite/database.thenablereference.md create mode 100644 docs-devsite/database.transactionoptions.md create mode 100644 docs-devsite/database.transactionresult.md create mode 100644 docs-devsite/firestore.md create mode 100644 docs-devsite/firestore_.aggregatefield.md create mode 100644 docs-devsite/firestore_.aggregatequerysnapshot.md create mode 100644 docs-devsite/firestore_.aggregatespec.md create mode 100644 docs-devsite/firestore_.bytes.md create mode 100644 docs-devsite/firestore_.collectionreference.md create mode 100644 docs-devsite/firestore_.documentchange.md create mode 100644 docs-devsite/firestore_.documentdata.md create mode 100644 docs-devsite/firestore_.documentreference.md create mode 100644 docs-devsite/firestore_.documentsnapshot.md create mode 100644 docs-devsite/firestore_.experimentallongpollingoptions.md create mode 100644 docs-devsite/firestore_.fieldpath.md create mode 100644 docs-devsite/firestore_.fieldvalue.md create mode 100644 docs-devsite/firestore_.firestore.md create mode 100644 docs-devsite/firestore_.firestoredataconverter.md create mode 100644 docs-devsite/firestore_.firestoreerror.md create mode 100644 docs-devsite/firestore_.firestoresettings.md create mode 100644 docs-devsite/firestore_.geopoint.md create mode 100644 docs-devsite/firestore_.index.md create mode 100644 docs-devsite/firestore_.indexconfiguration.md create mode 100644 docs-devsite/firestore_.indexfield.md create mode 100644 docs-devsite/firestore_.loadbundletask.md create mode 100644 docs-devsite/firestore_.loadbundletaskprogress.md create mode 100644 docs-devsite/firestore_.md create mode 100644 docs-devsite/firestore_.memorycachesettings.md create mode 100644 docs-devsite/firestore_.memoryeagergarbagecollector.md create mode 100644 docs-devsite/firestore_.memorylocalcache.md create mode 100644 docs-devsite/firestore_.memorylrugarbagecollector.md create mode 100644 docs-devsite/firestore_.persistencesettings.md create mode 100644 docs-devsite/firestore_.persistentcacheindexmanager.md create mode 100644 docs-devsite/firestore_.persistentcachesettings.md create mode 100644 docs-devsite/firestore_.persistentlocalcache.md create mode 100644 docs-devsite/firestore_.persistentmultipletabmanager.md create mode 100644 docs-devsite/firestore_.persistentsingletabmanager.md create mode 100644 docs-devsite/firestore_.persistentsingletabmanagersettings.md create mode 100644 docs-devsite/firestore_.query.md create mode 100644 docs-devsite/firestore_.querycompositefilterconstraint.md create mode 100644 docs-devsite/firestore_.queryconstraint.md create mode 100644 docs-devsite/firestore_.querydocumentsnapshot.md create mode 100644 docs-devsite/firestore_.queryendatconstraint.md create mode 100644 docs-devsite/firestore_.queryfieldfilterconstraint.md create mode 100644 docs-devsite/firestore_.querylimitconstraint.md create mode 100644 docs-devsite/firestore_.queryorderbyconstraint.md create mode 100644 docs-devsite/firestore_.querysnapshot.md create mode 100644 docs-devsite/firestore_.querystartatconstraint.md create mode 100644 docs-devsite/firestore_.snapshotlistenoptions.md create mode 100644 docs-devsite/firestore_.snapshotmetadata.md create mode 100644 docs-devsite/firestore_.snapshotoptions.md create mode 100644 docs-devsite/firestore_.timestamp.md create mode 100644 docs-devsite/firestore_.transaction.md create mode 100644 docs-devsite/firestore_.transactionoptions.md create mode 100644 docs-devsite/firestore_.unsubscribe.md create mode 100644 docs-devsite/firestore_.writebatch.md create mode 100644 docs-devsite/firestore_lite.aggregatefield.md create mode 100644 docs-devsite/firestore_lite.aggregatequerysnapshot.md create mode 100644 docs-devsite/firestore_lite.aggregatespec.md create mode 100644 docs-devsite/firestore_lite.bytes.md create mode 100644 docs-devsite/firestore_lite.collectionreference.md create mode 100644 docs-devsite/firestore_lite.documentdata.md create mode 100644 docs-devsite/firestore_lite.documentreference.md create mode 100644 docs-devsite/firestore_lite.documentsnapshot.md create mode 100644 docs-devsite/firestore_lite.fieldpath.md create mode 100644 docs-devsite/firestore_lite.fieldvalue.md create mode 100644 docs-devsite/firestore_lite.firestore.md create mode 100644 docs-devsite/firestore_lite.firestoredataconverter.md create mode 100644 docs-devsite/firestore_lite.firestoreerror.md create mode 100644 docs-devsite/firestore_lite.geopoint.md create mode 100644 docs-devsite/firestore_lite.md create mode 100644 docs-devsite/firestore_lite.query.md create mode 100644 docs-devsite/firestore_lite.querycompositefilterconstraint.md create mode 100644 docs-devsite/firestore_lite.queryconstraint.md create mode 100644 docs-devsite/firestore_lite.querydocumentsnapshot.md create mode 100644 docs-devsite/firestore_lite.queryendatconstraint.md create mode 100644 docs-devsite/firestore_lite.queryfieldfilterconstraint.md create mode 100644 docs-devsite/firestore_lite.querylimitconstraint.md create mode 100644 docs-devsite/firestore_lite.queryorderbyconstraint.md create mode 100644 docs-devsite/firestore_lite.querysnapshot.md create mode 100644 docs-devsite/firestore_lite.querystartatconstraint.md create mode 100644 docs-devsite/firestore_lite.settings.md create mode 100644 docs-devsite/firestore_lite.timestamp.md create mode 100644 docs-devsite/firestore_lite.transaction.md create mode 100644 docs-devsite/firestore_lite.transactionoptions.md create mode 100644 docs-devsite/firestore_lite.writebatch.md create mode 100644 docs-devsite/functions.functions.md create mode 100644 docs-devsite/functions.functionserror.md create mode 100644 docs-devsite/functions.httpscallableoptions.md create mode 100644 docs-devsite/functions.httpscallableresult.md create mode 100644 docs-devsite/functions.md create mode 100644 docs-devsite/index.md create mode 100644 docs-devsite/installations.installations.md create mode 100644 docs-devsite/installations.md create mode 100644 docs-devsite/messaging.md create mode 100644 docs-devsite/messaging_.fcmoptions.md create mode 100644 docs-devsite/messaging_.gettokenoptions.md create mode 100644 docs-devsite/messaging_.md create mode 100644 docs-devsite/messaging_.messagepayload.md create mode 100644 docs-devsite/messaging_.messaging.md create mode 100644 docs-devsite/messaging_.notificationpayload.md create mode 100644 docs-devsite/messaging_sw.fcmoptions.md create mode 100644 docs-devsite/messaging_sw.gettokenoptions.md create mode 100644 docs-devsite/messaging_sw.md create mode 100644 docs-devsite/messaging_sw.messagepayload.md create mode 100644 docs-devsite/messaging_sw.messaging.md create mode 100644 docs-devsite/messaging_sw.notificationpayload.md create mode 100644 docs-devsite/performance.firebaseperformance.md create mode 100644 docs-devsite/performance.md create mode 100644 docs-devsite/performance.performancesettings.md create mode 100644 docs-devsite/performance.performancetrace.md create mode 100644 docs-devsite/remote-config.md create mode 100644 docs-devsite/remote-config.remoteconfig.md create mode 100644 docs-devsite/remote-config.remoteconfigsettings.md create mode 100644 docs-devsite/remote-config.value.md create mode 100644 docs-devsite/storage.firebasestorage.md create mode 100644 docs-devsite/storage.fullmetadata.md create mode 100644 docs-devsite/storage.listoptions.md create mode 100644 docs-devsite/storage.listresult.md create mode 100644 docs-devsite/storage.md create mode 100644 docs-devsite/storage.settablemetadata.md create mode 100644 docs-devsite/storage.storageerror.md create mode 100644 docs-devsite/storage.storageobserver.md create mode 100644 docs-devsite/storage.storagereference.md create mode 100644 docs-devsite/storage.uploadmetadata.md create mode 100644 docs-devsite/storage.uploadresult.md create mode 100644 docs-devsite/storage.uploadtask.md create mode 100644 docs-devsite/storage.uploadtasksnapshot.md create mode 100644 docs-devsite/vertexai-preview.baseparams.md create mode 100644 docs-devsite/vertexai-preview.chatsession.md create mode 100644 docs-devsite/vertexai-preview.citation.md create mode 100644 docs-devsite/vertexai-preview.citationmetadata.md create mode 100644 docs-devsite/vertexai-preview.content.md create mode 100644 docs-devsite/vertexai-preview.counttokensrequest.md create mode 100644 docs-devsite/vertexai-preview.counttokensresponse.md create mode 100644 docs-devsite/vertexai-preview.customerrordata.md create mode 100644 docs-devsite/vertexai-preview.date_2.md create mode 100644 docs-devsite/vertexai-preview.enhancedgeneratecontentresponse.md create mode 100644 docs-devsite/vertexai-preview.errordetails.md create mode 100644 docs-devsite/vertexai-preview.filedata.md create mode 100644 docs-devsite/vertexai-preview.filedatapart.md create mode 100644 docs-devsite/vertexai-preview.functioncall.md create mode 100644 docs-devsite/vertexai-preview.functioncallingconfig.md create mode 100644 docs-devsite/vertexai-preview.functioncallpart.md create mode 100644 docs-devsite/vertexai-preview.functiondeclaration.md create mode 100644 docs-devsite/vertexai-preview.functiondeclarationschema.md create mode 100644 docs-devsite/vertexai-preview.functiondeclarationschemaproperty.md create mode 100644 docs-devsite/vertexai-preview.functiondeclarationstool.md create mode 100644 docs-devsite/vertexai-preview.functionresponse.md create mode 100644 docs-devsite/vertexai-preview.functionresponsepart.md create mode 100644 docs-devsite/vertexai-preview.generatecontentcandidate.md create mode 100644 docs-devsite/vertexai-preview.generatecontentrequest.md create mode 100644 docs-devsite/vertexai-preview.generatecontentresponse.md create mode 100644 docs-devsite/vertexai-preview.generatecontentresult.md create mode 100644 docs-devsite/vertexai-preview.generatecontentstreamresult.md create mode 100644 docs-devsite/vertexai-preview.generationconfig.md create mode 100644 docs-devsite/vertexai-preview.generativecontentblob.md create mode 100644 docs-devsite/vertexai-preview.generativemodel.md create mode 100644 docs-devsite/vertexai-preview.groundingattribution.md create mode 100644 docs-devsite/vertexai-preview.groundingmetadata.md create mode 100644 docs-devsite/vertexai-preview.inlinedatapart.md create mode 100644 docs-devsite/vertexai-preview.md create mode 100644 docs-devsite/vertexai-preview.modelparams.md create mode 100644 docs-devsite/vertexai-preview.promptfeedback.md create mode 100644 docs-devsite/vertexai-preview.requestoptions.md create mode 100644 docs-devsite/vertexai-preview.retrievedcontextattribution.md create mode 100644 docs-devsite/vertexai-preview.safetyrating.md create mode 100644 docs-devsite/vertexai-preview.safetysetting.md create mode 100644 docs-devsite/vertexai-preview.segment.md create mode 100644 docs-devsite/vertexai-preview.startchatparams.md create mode 100644 docs-devsite/vertexai-preview.textpart.md create mode 100644 docs-devsite/vertexai-preview.toolconfig.md create mode 100644 docs-devsite/vertexai-preview.usagemetadata.md create mode 100644 docs-devsite/vertexai-preview.vertexai.md create mode 100644 docs-devsite/vertexai-preview.vertexaierror.md create mode 100644 docs-devsite/vertexai-preview.vertexaioptions.md create mode 100644 docs-devsite/vertexai-preview.videometadata.md create mode 100644 docs-devsite/vertexai-preview.webattribution.md create mode 100644 integration/compat-interop/analytics.test.ts create mode 100644 integration/compat-interop/app.test.ts create mode 100644 integration/compat-interop/auth.test.ts create mode 100644 integration/compat-interop/functions.test.ts create mode 100644 integration/compat-interop/karma.conf.js create mode 100644 integration/compat-interop/messaging.test.ts create mode 100644 integration/compat-interop/package.json create mode 100644 integration/compat-interop/performance.test.ts create mode 100644 integration/compat-interop/remote-config.test.ts create mode 100644 integration/compat-interop/tsconfig.json create mode 100644 integration/compat-interop/util.ts create mode 100644 integration/compat-typings/package.json create mode 100644 integration/compat-typings/tsconfig.json create mode 100644 integration/compat-typings/typings.ts create mode 100644 integration/firebase/karma.conf.js create mode 100644 integration/firebase/package.json create mode 100644 integration/firebase/test/namespace.test.ts create mode 100644 integration/firebase/test/namespaceDefinition.json create mode 100644 integration/firebase/test/typings.d.ts create mode 100644 integration/firebase/test/validator.js create mode 100644 integration/firebase/tsconfig.json create mode 100644 integration/firestore/.gitignore create mode 100644 integration/firestore/README.md create mode 100644 integration/firestore/firebase_export.ts create mode 100644 integration/firestore/gulpfile.js create mode 100644 integration/firestore/karma.conf.js create mode 100644 integration/firestore/package.json create mode 100644 integration/firestore/tsconfig.json create mode 100644 integration/messaging/download-browsers.js create mode 100644 integration/messaging/manual-test-server.js create mode 100644 integration/messaging/package.json create mode 100644 integration/messaging/test/static/app.js create mode 100644 integration/messaging/test/static/constants.js create mode 100644 integration/messaging/test/static/default-sw/index.html create mode 100644 integration/messaging/test/static/firebase-messaging-sw.js create mode 100644 integration/messaging/test/static/helpers.js create mode 100644 integration/messaging/test/static/sw-base.js create mode 100644 integration/messaging/test/static/valid-manifest/index.html create mode 100644 integration/messaging/test/static/valid-manifest/manifest.json create mode 100644 integration/messaging/test/static/valid-vapid-key-modern-sw/index.html create mode 100644 integration/messaging/test/static/valid-vapid-key-modern-sw/sw.js create mode 100644 integration/messaging/test/static/valid-vapid-key/index.html create mode 100644 integration/messaging/test/static/valid-vapid-key/sw.js create mode 100644 integration/messaging/test/test-receive-background.js create mode 100644 integration/messaging/test/test-receive-foreground.js create mode 100644 integration/messaging/test/test-token-delete.js create mode 100644 integration/messaging/test/test-token-update.js create mode 100644 integration/messaging/test/test-useDefaultServiceWorker.js create mode 100644 integration/messaging/test/test-useValidManifest.js create mode 100644 integration/messaging/test/utils/checkMessageReceived.js create mode 100644 integration/messaging/test/utils/checkSendResponse.js create mode 100644 integration/messaging/test/utils/clearAppForTest.js create mode 100644 integration/messaging/test/utils/createPermittedWebDriver.js create mode 100644 integration/messaging/test/utils/deleteToken.js create mode 100644 integration/messaging/test/utils/forwardTime.js create mode 100644 integration/messaging/test/utils/getErrors.js create mode 100644 integration/messaging/test/utils/getReceivedBackgroundMessages.js create mode 100644 integration/messaging/test/utils/getReceivedForegroundMessages.js create mode 100644 integration/messaging/test/utils/openNewTab.js create mode 100644 integration/messaging/test/utils/retrieveToken.js create mode 100644 integration/messaging/test/utils/sendMessage.js create mode 100644 integration/messaging/test/utils/test-server.js create mode 100644 integration/messaging/test/utils/triggerGetToken.js create mode 100644 patches/karma-webpack+5.0.0.patch create mode 100644 renovate.json create mode 100644 repo-scripts/api-documenter/CHANGELOG.md create mode 100644 repo-scripts/api-documenter/README.md create mode 100644 repo-scripts/api-documenter/gulpfile.js create mode 100644 repo-scripts/api-documenter/package.json create mode 100644 repo-scripts/api-documenter/src/cli/ApiDocumenterCommandLine.ts create mode 100644 repo-scripts/api-documenter/src/cli/BaseAction.ts create mode 100644 repo-scripts/api-documenter/src/cli/MarkdownAction.ts create mode 100644 repo-scripts/api-documenter/src/cli/TocAction.ts create mode 100644 repo-scripts/api-documenter/src/documenters/DocumenterConfig.ts create mode 100644 repo-scripts/api-documenter/src/documenters/IConfigFile.ts create mode 100644 repo-scripts/api-documenter/src/documenters/MarkdownDocumenter.ts create mode 100644 repo-scripts/api-documenter/src/documenters/MarkdownDocumenterHelpers.ts create mode 100644 repo-scripts/api-documenter/src/index.ts create mode 100644 repo-scripts/api-documenter/src/markdown/CustomMarkdownEmitter.ts create mode 100644 repo-scripts/api-documenter/src/markdown/MarkdownEmitter.ts create mode 100644 repo-scripts/api-documenter/src/markdown/test/CustomMarkdownEmitter.test.ts create mode 100644 repo-scripts/api-documenter/src/markdown/test/__snapshots__/CustomMarkdownEmitter.test.ts.snap create mode 100644 repo-scripts/api-documenter/src/nodes/CustomDocNodeKind.ts create mode 100644 repo-scripts/api-documenter/src/nodes/DocEmphasisSpan.ts create mode 100644 repo-scripts/api-documenter/src/nodes/DocHeading.ts create mode 100644 repo-scripts/api-documenter/src/nodes/DocNoteBox.ts create mode 100644 repo-scripts/api-documenter/src/nodes/DocTable.ts create mode 100644 repo-scripts/api-documenter/src/nodes/DocTableCell.ts create mode 100644 repo-scripts/api-documenter/src/nodes/DocTableRow.ts create mode 100644 repo-scripts/api-documenter/src/plugin/IApiDocumenterPluginManifest.ts create mode 100644 repo-scripts/api-documenter/src/plugin/MarkdownDocumenterAccessor.ts create mode 100644 repo-scripts/api-documenter/src/plugin/MarkdownDocumenterFeature.ts create mode 100644 repo-scripts/api-documenter/src/plugin/PluginFeature.ts create mode 100644 repo-scripts/api-documenter/src/plugin/PluginLoader.ts create mode 100644 repo-scripts/api-documenter/src/schemas/api-documenter-template.json create mode 100644 repo-scripts/api-documenter/src/schemas/api-documenter.schema.json create mode 100644 repo-scripts/api-documenter/src/start.ts create mode 100644 repo-scripts/api-documenter/src/toc.ts create mode 100644 repo-scripts/api-documenter/src/utils/IndentedWriter.ts create mode 100644 repo-scripts/api-documenter/src/utils/Utilities.ts create mode 100644 repo-scripts/api-documenter/src/utils/test/IndentedWriter.test.ts create mode 100644 repo-scripts/api-documenter/src/utils/test/__snapshots__/IndentedWriter.test.ts.snap create mode 100644 repo-scripts/api-documenter/tsconfig.json create mode 100644 repo-scripts/changelog-generator/.eslintrc.js create mode 100644 repo-scripts/changelog-generator/README.md create mode 100644 repo-scripts/changelog-generator/index.ts create mode 100644 repo-scripts/changelog-generator/package.json create mode 100644 repo-scripts/changelog-generator/tsconfig.json create mode 100644 repo-scripts/prune-dts/.run/AllTests.run.xml create mode 100644 repo-scripts/prune-dts/extract-public-api.ts create mode 100644 repo-scripts/prune-dts/package.json create mode 100644 repo-scripts/prune-dts/prune-dts.test.ts create mode 100644 repo-scripts/prune-dts/prune-dts.ts create mode 100644 repo-scripts/prune-dts/tests/dom.input.d.ts create mode 100644 repo-scripts/prune-dts/tests/dom.output.d.ts create mode 100644 repo-scripts/prune-dts/tests/error.input.d.ts create mode 100644 repo-scripts/prune-dts/tests/error.output.d.ts create mode 100644 repo-scripts/prune-dts/tests/firestore.input.d.ts create mode 100644 repo-scripts/prune-dts/tests/firestore.output.d.ts create mode 100644 repo-scripts/prune-dts/tests/hide-constructor.input.d.ts create mode 100644 repo-scripts/prune-dts/tests/hide-constructor.output.d.ts create mode 100644 repo-scripts/prune-dts/tests/inherits-non-exported-members.input.d.ts create mode 100644 repo-scripts/prune-dts/tests/inherits-non-exported-members.output.d.ts create mode 100644 repo-scripts/prune-dts/tests/keeps-inheritance.input.d.ts create mode 100644 repo-scripts/prune-dts/tests/keeps-inheritance.output.d.ts create mode 100644 repo-scripts/prune-dts/tests/only-modifies-non-exported-types.input.d.ts create mode 100644 repo-scripts/prune-dts/tests/only-modifies-non-exported-types.output.d.ts create mode 100644 repo-scripts/prune-dts/tests/private-interface.input.d.ts create mode 100644 repo-scripts/prune-dts/tests/private-interface.output.d.ts create mode 100644 repo-scripts/prune-dts/tests/propagates-comments.input.d.ts create mode 100644 repo-scripts/prune-dts/tests/propagates-comments.output.d.ts create mode 100644 repo-scripts/prune-dts/tests/propagates-members.input.d.ts create mode 100644 repo-scripts/prune-dts/tests/propagates-members.output.d.ts create mode 100644 repo-scripts/prune-dts/tests/prunes-non-exported-types.input.d.ts create mode 100644 repo-scripts/prune-dts/tests/prunes-non-exported-types.output.d.ts create mode 100644 repo-scripts/prune-dts/tests/prunes-underscores.input.d.ts create mode 100644 repo-scripts/prune-dts/tests/prunes-underscores.output.d.ts create mode 100644 repo-scripts/prune-dts/tests/references-public-interfaces.input.d.ts create mode 100644 repo-scripts/prune-dts/tests/references-public-interfaces.output.d.ts create mode 100644 repo-scripts/prune-dts/tests/references-public-types.input.d.ts create mode 100644 repo-scripts/prune-dts/tests/references-public-types.output.d.ts create mode 100644 repo-scripts/prune-dts/tests/resolves-generics-different-name.input.d.ts create mode 100644 repo-scripts/prune-dts/tests/resolves-generics-different-name.output.d.ts create mode 100644 repo-scripts/prune-dts/tests/resolves-generics-through-inheritance.input.d.ts create mode 100644 repo-scripts/prune-dts/tests/resolves-generics-through-inheritence.output.d.ts create mode 100644 repo-scripts/prune-dts/tests/resolves-generics.input.d.ts create mode 100644 repo-scripts/prune-dts/tests/resolves-generics.output.d.ts create mode 100644 repo-scripts/prune-dts/tests/swaps-generics.input.d.ts create mode 100644 repo-scripts/prune-dts/tests/swaps-generics.output.d.ts create mode 100644 repo-scripts/prune-dts/tsconfig.eslint.json create mode 100644 repo-scripts/prune-dts/tsconfig.json create mode 100644 repo-scripts/size-analysis/.eslintrc.js create mode 100644 repo-scripts/size-analysis/README.md create mode 100644 repo-scripts/size-analysis/analysis-helper.ts create mode 100644 repo-scripts/size-analysis/analyze-all-bundles.ts create mode 100644 repo-scripts/size-analysis/bundle-analysis.ts create mode 100644 repo-scripts/size-analysis/bundle-definitions/analytics.json create mode 100644 repo-scripts/size-analysis/bundle-definitions/app-check.json create mode 100644 repo-scripts/size-analysis/bundle-definitions/auth.json create mode 100644 repo-scripts/size-analysis/bundle-definitions/database.json create mode 100644 repo-scripts/size-analysis/bundle-definitions/firestore-lite.json create mode 100644 repo-scripts/size-analysis/bundle-definitions/firestore.json create mode 100644 repo-scripts/size-analysis/bundle-definitions/functions.json create mode 100644 repo-scripts/size-analysis/bundle-definitions/messaging.json create mode 100644 repo-scripts/size-analysis/bundle-definitions/performance.json create mode 100644 repo-scripts/size-analysis/bundle-definitions/remote-config.json create mode 100644 repo-scripts/size-analysis/bundle-definitions/storage.json create mode 100644 repo-scripts/size-analysis/bundle/minify.ts create mode 100644 repo-scripts/size-analysis/bundle/rollup.ts create mode 100644 repo-scripts/size-analysis/bundle/webpack.ts create mode 100644 repo-scripts/size-analysis/cli.ts create mode 100644 repo-scripts/size-analysis/package-analysis.ts create mode 100644 repo-scripts/size-analysis/package.json create mode 100644 repo-scripts/size-analysis/rollup.config.js create mode 100644 repo-scripts/size-analysis/test/size-analysis.test.ts create mode 100644 repo-scripts/size-analysis/test/test-inputs/assortedImports.ts create mode 100644 repo-scripts/size-analysis/test/test-inputs/bar.ts create mode 100644 repo-scripts/size-analysis/test/test-inputs/far.ts create mode 100644 repo-scripts/size-analysis/test/test-inputs/index.ts create mode 100644 repo-scripts/size-analysis/test/test-inputs/subsetExports.ts create mode 100644 repo-scripts/size-analysis/test/test-inputs/tar.ts create mode 100644 repo-scripts/size-analysis/test/test-inputs/tsconfig.json create mode 100644 repo-scripts/size-analysis/test/utils.ts create mode 100644 repo-scripts/size-analysis/tsconfig.json create mode 100644 repo-scripts/size-analysis/util.ts create mode 100644 tools/config.js create mode 100644 tools/pretest.js create mode 100644 tools/repl.js diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 00000000000..4f3b76b096b --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,8 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/master/docs/common-questions.md) diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 00000000000..936ef9d7512 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@1.1.0/schema.json", + "changelog": [ + "../repo-scripts/changelog-generator", + { "repo": "firebase/firebase-js-sdk" } + ], + "commit": false, + "linked": [], + "access": "public", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": [ + "firebase-namespace-integration-test", + "firebase-firestore-integration-test", + "firebase-messaging-integration-test", + "firebase-compat-interop-test", + "firebase-compat-typings-test", + "@firebase/changelog-generator", + "firebase-size-analysis", + "@firebase/api-documenter", + "firebase-repo-scripts-prune-dts" + ], + "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": { + "onlyUpdatePeerDependentsWhenOutOfRange": true + }, + "snapshot": { + "useCalculatedVersion": true + } +} diff --git a/.changeset/late-humans-tan.md b/.changeset/late-humans-tan.md new file mode 100644 index 00000000000..bd2b2cbaa0e --- /dev/null +++ b/.changeset/late-humans-tan.md @@ -0,0 +1,12 @@ +--- +'@firebase/firestore-compat': patch +'@firebase/database-compat': patch +'@firebase/auth-compat': patch +'@firebase/app-compat': patch +'@firebase/firestore': patch +'@firebase/database': patch +'firebase': patch +'@firebase/auth': patch +--- + +Fixed typos in documentation and some internal variables and parameters. \ No newline at end of file diff --git a/.changeset/spicy-dragons-pay.md b/.changeset/spicy-dragons-pay.md new file mode 100644 index 00000000000..280406da0f0 --- /dev/null +++ b/.changeset/spicy-dragons-pay.md @@ -0,0 +1,5 @@ +--- +'@firebase/app-compat': patch +--- + +Properly handle the case in app-compat checks where `window` exists but `self` does not. (This occurs in Ionic Stencil's Jest preset.) diff --git a/.changeset/tender-apes-clap.md b/.changeset/tender-apes-clap.md new file mode 100644 index 00000000000..d3629a245b8 --- /dev/null +++ b/.changeset/tender-apes-clap.md @@ -0,0 +1,6 @@ +--- +'@firebase/analytics': patch +'@firebase/app-check': patch +--- + +Revert introduction of safevalues to prevent issues from arising in Browser CommonJS environments due to ES5 incompatibility. For more information, see [GitHub PR #8395](https://github.com/firebase/firebase-js-sdk/pull/8395) diff --git a/.opensource/project.json b/.opensource/project.json new file mode 100644 index 00000000000..09cb26174a2 --- /dev/null +++ b/.opensource/project.json @@ -0,0 +1,20 @@ +{ + "name": "Firebase JavaScript SDK", + "type": "library", + "platforms": [ + "Web" + ], + "content": "README.md", + "pages": { + "packages/rxfire/README.md": "RxFire" + }, + "related": [ + "firebase/quickstart-js" + ], + "tabs": [ + { + "title": "Reference", + "href": "https://firebase.google.com/docs/reference/js/" + } + ] +} diff --git a/.yarn/releases/yarn-1.22.11.cjs b/.yarn/releases/yarn-1.22.11.cjs new file mode 100755 index 00000000000..41236bd841f --- /dev/null +++ b/.yarn/releases/yarn-1.22.11.cjs @@ -0,0 +1,147406 @@ +#!/usr/bin/env node +module.exports = +/******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // identity function for calling harmony imports with the correct context +/******/ __webpack_require__.i = function(value) { return value; }; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { +/******/ configurable: false, +/******/ enumerable: true, +/******/ get: getter +/******/ }); +/******/ } +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 549); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, exports) { + +module.exports = require("path"); + +/***/ }), +/* 1 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +/* harmony export (immutable) */ __webpack_exports__["a"] = __extends; +/* unused harmony export __assign */ +/* unused harmony export __rest */ +/* unused harmony export __decorate */ +/* unused harmony export __param */ +/* unused harmony export __metadata */ +/* unused harmony export __awaiter */ +/* unused harmony export __generator */ +/* unused harmony export __exportStar */ +/* unused harmony export __values */ +/* unused harmony export __read */ +/* unused harmony export __spread */ +/* unused harmony export __await */ +/* unused harmony export __asyncGenerator */ +/* unused harmony export __asyncDelegator */ +/* unused harmony export __asyncValues */ +/* unused harmony export __makeTemplateObject */ +/* unused harmony export __importStar */ +/* unused harmony export __importDefault */ +/*! ***************************************************************************** +Copyright (c) Microsoft Corporation. All rights reserved. +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 + +THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED +WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +MERCHANTABLITY OR NON-INFRINGEMENT. + +See the Apache Version 2.0 License for specific language governing permissions +and limitations under the License. +***************************************************************************** */ +/* global Reflect, Promise */ + +var extendStatics = function(d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); +}; + +function __extends(d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +} + +var __assign = function() { + __assign = Object.assign || function __assign(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; + } + return t; + } + return __assign.apply(this, arguments); +} + +function __rest(s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0) + t[p[i]] = s[p[i]]; + return t; +} + +function __decorate(decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +} + +function __param(paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +} + +function __metadata(metadataKey, metadataValue) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue); +} + +function __awaiter(thisArg, _arguments, P, generator) { + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +} + +function __generator(thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +} + +function __exportStar(m, exports) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +} + +function __values(o) { + var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0; + if (m) return m.call(o); + return { + next: function () { + if (o && i >= o.length) o = void 0; + return { value: o && o[i++], done: !o }; + } + }; +} + +function __read(o, n) { + var m = typeof Symbol === "function" && o[Symbol.iterator]; + if (!m) return o; + var i = m.call(o), r, ar = [], e; + try { + while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); + } + catch (error) { e = { error: error }; } + finally { + try { + if (r && !r.done && (m = i["return"])) m.call(i); + } + finally { if (e) throw e.error; } + } + return ar; +} + +function __spread() { + for (var ar = [], i = 0; i < arguments.length; i++) + ar = ar.concat(__read(arguments[i])); + return ar; +} + +function __await(v) { + return this instanceof __await ? (this.v = v, this) : new __await(v); +} + +function __asyncGenerator(thisArg, _arguments, generator) { + if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); + var g = generator.apply(thisArg, _arguments || []), i, q = []; + return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i; + function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; } + function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } } + function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); } + function fulfill(value) { resume("next", value); } + function reject(value) { resume("throw", value); } + function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); } +} + +function __asyncDelegator(o) { + var i, p; + return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i; + function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v; } : f; } +} + +function __asyncValues(o) { + if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); + var m = o[Symbol.asyncIterator], i; + return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); + function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } + function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } +} + +function __makeTemplateObject(cooked, raw) { + if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } + return cooked; +}; + +function __importStar(mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result.default = mod; + return result; +} + +function __importDefault(mod) { + return (mod && mod.__esModule) ? mod : { default: mod }; +} + + +/***/ }), +/* 2 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +exports.__esModule = true; + +var _promise = __webpack_require__(227); + +var _promise2 = _interopRequireDefault(_promise); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +exports.default = function (fn) { + return function () { + var gen = fn.apply(this, arguments); + return new _promise2.default(function (resolve, reject) { + function step(key, arg) { + try { + var info = gen[key](arg); + var value = info.value; + } catch (error) { + reject(error); + return; + } + + if (info.done) { + resolve(value); + } else { + return _promise2.default.resolve(value).then(function (value) { + step("next", value); + }, function (err) { + step("throw", err); + }); + } + } + + return step("next"); + }); + }; +}; + +/***/ }), +/* 3 */ +/***/ (function(module, exports) { + +module.exports = require("util"); + +/***/ }), +/* 4 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.getFirstSuitableFolder = exports.readFirstAvailableStream = exports.makeTempDir = exports.hardlinksWork = exports.writeFilePreservingEol = exports.getFileSizeOnDisk = exports.walk = exports.symlink = exports.find = exports.readJsonAndFile = exports.readJson = exports.readFileAny = exports.hardlinkBulk = exports.copyBulk = exports.unlink = exports.glob = exports.link = exports.chmod = exports.lstat = exports.exists = exports.mkdirp = exports.stat = exports.access = exports.rename = exports.readdir = exports.realpath = exports.readlink = exports.writeFile = exports.open = exports.readFileBuffer = exports.lockQueue = exports.constants = undefined; + +var _asyncToGenerator2; + +function _load_asyncToGenerator() { + return _asyncToGenerator2 = _interopRequireDefault(__webpack_require__(2)); +} + +let buildActionsForCopy = (() => { + var _ref = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (queue, events, possibleExtraneous, reporter) { + + // + let build = (() => { + var _ref5 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (data) { + const src = data.src, + dest = data.dest, + type = data.type; + + const onFresh = data.onFresh || noop; + const onDone = data.onDone || noop; + + // TODO https://github.com/yarnpkg/yarn/issues/3751 + // related to bundled dependencies handling + if (files.has(dest.toLowerCase())) { + reporter.verbose(`The case-insensitive file ${dest} shouldn't be copied twice in one bulk copy`); + } else { + files.add(dest.toLowerCase()); + } + + if (type === 'symlink') { + yield mkdirp((_path || _load_path()).default.dirname(dest)); + onFresh(); + actions.symlink.push({ + dest, + linkname: src + }); + onDone(); + return; + } + + if (events.ignoreBasenames.indexOf((_path || _load_path()).default.basename(src)) >= 0) { + // ignored file + return; + } + + const srcStat = yield lstat(src); + let srcFiles; + + if (srcStat.isDirectory()) { + srcFiles = yield readdir(src); + } + + let destStat; + try { + // try accessing the destination + destStat = yield lstat(dest); + } catch (e) { + // proceed if destination doesn't exist, otherwise error + if (e.code !== 'ENOENT') { + throw e; + } + } + + // if destination exists + if (destStat) { + const bothSymlinks = srcStat.isSymbolicLink() && destStat.isSymbolicLink(); + const bothFolders = srcStat.isDirectory() && destStat.isDirectory(); + const bothFiles = srcStat.isFile() && destStat.isFile(); + + // EINVAL access errors sometimes happen which shouldn't because node shouldn't be giving + // us modes that aren't valid. investigate this, it's generally safe to proceed. + + /* if (srcStat.mode !== destStat.mode) { + try { + await access(dest, srcStat.mode); + } catch (err) {} + } */ + + if (bothFiles && artifactFiles.has(dest)) { + // this file gets changed during build, likely by a custom install script. Don't bother checking it. + onDone(); + reporter.verbose(reporter.lang('verboseFileSkipArtifact', src)); + return; + } + + if (bothFiles && srcStat.size === destStat.size && (0, (_fsNormalized || _load_fsNormalized()).fileDatesEqual)(srcStat.mtime, destStat.mtime)) { + // we can safely assume this is the same file + onDone(); + reporter.verbose(reporter.lang('verboseFileSkip', src, dest, srcStat.size, +srcStat.mtime)); + return; + } + + if (bothSymlinks) { + const srcReallink = yield readlink(src); + if (srcReallink === (yield readlink(dest))) { + // if both symlinks are the same then we can continue on + onDone(); + reporter.verbose(reporter.lang('verboseFileSkipSymlink', src, dest, srcReallink)); + return; + } + } + + if (bothFolders) { + // mark files that aren't in this folder as possibly extraneous + const destFiles = yield readdir(dest); + invariant(srcFiles, 'src files not initialised'); + + for (var _iterator4 = destFiles, _isArray4 = Array.isArray(_iterator4), _i4 = 0, _iterator4 = _isArray4 ? _iterator4 : _iterator4[Symbol.iterator]();;) { + var _ref6; + + if (_isArray4) { + if (_i4 >= _iterator4.length) break; + _ref6 = _iterator4[_i4++]; + } else { + _i4 = _iterator4.next(); + if (_i4.done) break; + _ref6 = _i4.value; + } + + const file = _ref6; + + if (srcFiles.indexOf(file) < 0) { + const loc = (_path || _load_path()).default.join(dest, file); + possibleExtraneous.add(loc); + + if ((yield lstat(loc)).isDirectory()) { + for (var _iterator5 = yield readdir(loc), _isArray5 = Array.isArray(_iterator5), _i5 = 0, _iterator5 = _isArray5 ? _iterator5 : _iterator5[Symbol.iterator]();;) { + var _ref7; + + if (_isArray5) { + if (_i5 >= _iterator5.length) break; + _ref7 = _iterator5[_i5++]; + } else { + _i5 = _iterator5.next(); + if (_i5.done) break; + _ref7 = _i5.value; + } + + const file = _ref7; + + possibleExtraneous.add((_path || _load_path()).default.join(loc, file)); + } + } + } + } + } + } + + if (destStat && destStat.isSymbolicLink()) { + yield (0, (_fsNormalized || _load_fsNormalized()).unlink)(dest); + destStat = null; + } + + if (srcStat.isSymbolicLink()) { + onFresh(); + const linkname = yield readlink(src); + actions.symlink.push({ + dest, + linkname + }); + onDone(); + } else if (srcStat.isDirectory()) { + if (!destStat) { + reporter.verbose(reporter.lang('verboseFileFolder', dest)); + yield mkdirp(dest); + } + + const destParts = dest.split((_path || _load_path()).default.sep); + while (destParts.length) { + files.add(destParts.join((_path || _load_path()).default.sep).toLowerCase()); + destParts.pop(); + } + + // push all files to queue + invariant(srcFiles, 'src files not initialised'); + let remaining = srcFiles.length; + if (!remaining) { + onDone(); + } + for (var _iterator6 = srcFiles, _isArray6 = Array.isArray(_iterator6), _i6 = 0, _iterator6 = _isArray6 ? _iterator6 : _iterator6[Symbol.iterator]();;) { + var _ref8; + + if (_isArray6) { + if (_i6 >= _iterator6.length) break; + _ref8 = _iterator6[_i6++]; + } else { + _i6 = _iterator6.next(); + if (_i6.done) break; + _ref8 = _i6.value; + } + + const file = _ref8; + + queue.push({ + dest: (_path || _load_path()).default.join(dest, file), + onFresh, + onDone: function (_onDone) { + function onDone() { + return _onDone.apply(this, arguments); + } + + onDone.toString = function () { + return _onDone.toString(); + }; + + return onDone; + }(function () { + if (--remaining === 0) { + onDone(); + } + }), + src: (_path || _load_path()).default.join(src, file) + }); + } + } else if (srcStat.isFile()) { + onFresh(); + actions.file.push({ + src, + dest, + atime: srcStat.atime, + mtime: srcStat.mtime, + mode: srcStat.mode + }); + onDone(); + } else { + throw new Error(`unsure how to copy this: ${src}`); + } + }); + + return function build(_x5) { + return _ref5.apply(this, arguments); + }; + })(); + + const artifactFiles = new Set(events.artifactFiles || []); + const files = new Set(); + + // initialise events + for (var _iterator = queue, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { + var _ref2; + + if (_isArray) { + if (_i >= _iterator.length) break; + _ref2 = _iterator[_i++]; + } else { + _i = _iterator.next(); + if (_i.done) break; + _ref2 = _i.value; + } + + const item = _ref2; + + const onDone = item.onDone; + item.onDone = function () { + events.onProgress(item.dest); + if (onDone) { + onDone(); + } + }; + } + events.onStart(queue.length); + + // start building actions + const actions = { + file: [], + symlink: [], + link: [] + }; + + // custom concurrency logic as we're always executing stacks of CONCURRENT_QUEUE_ITEMS queue items + // at a time due to the requirement to push items onto the queue + while (queue.length) { + const items = queue.splice(0, CONCURRENT_QUEUE_ITEMS); + yield Promise.all(items.map(build)); + } + + // simulate the existence of some files to prevent considering them extraneous + for (var _iterator2 = artifactFiles, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) { + var _ref3; + + if (_isArray2) { + if (_i2 >= _iterator2.length) break; + _ref3 = _iterator2[_i2++]; + } else { + _i2 = _iterator2.next(); + if (_i2.done) break; + _ref3 = _i2.value; + } + + const file = _ref3; + + if (possibleExtraneous.has(file)) { + reporter.verbose(reporter.lang('verboseFilePhantomExtraneous', file)); + possibleExtraneous.delete(file); + } + } + + for (var _iterator3 = possibleExtraneous, _isArray3 = Array.isArray(_iterator3), _i3 = 0, _iterator3 = _isArray3 ? _iterator3 : _iterator3[Symbol.iterator]();;) { + var _ref4; + + if (_isArray3) { + if (_i3 >= _iterator3.length) break; + _ref4 = _iterator3[_i3++]; + } else { + _i3 = _iterator3.next(); + if (_i3.done) break; + _ref4 = _i3.value; + } + + const loc = _ref4; + + if (files.has(loc.toLowerCase())) { + possibleExtraneous.delete(loc); + } + } + + return actions; + }); + + return function buildActionsForCopy(_x, _x2, _x3, _x4) { + return _ref.apply(this, arguments); + }; +})(); + +let buildActionsForHardlink = (() => { + var _ref9 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (queue, events, possibleExtraneous, reporter) { + + // + let build = (() => { + var _ref13 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (data) { + const src = data.src, + dest = data.dest; + + const onFresh = data.onFresh || noop; + const onDone = data.onDone || noop; + if (files.has(dest.toLowerCase())) { + // Fixes issue https://github.com/yarnpkg/yarn/issues/2734 + // When bulk hardlinking we have A -> B structure that we want to hardlink to A1 -> B1, + // package-linker passes that modules A1 and B1 need to be hardlinked, + // the recursive linking algorithm of A1 ends up scheduling files in B1 to be linked twice which will case + // an exception. + onDone(); + return; + } + files.add(dest.toLowerCase()); + + if (events.ignoreBasenames.indexOf((_path || _load_path()).default.basename(src)) >= 0) { + // ignored file + return; + } + + const srcStat = yield lstat(src); + let srcFiles; + + if (srcStat.isDirectory()) { + srcFiles = yield readdir(src); + } + + const destExists = yield exists(dest); + if (destExists) { + const destStat = yield lstat(dest); + + const bothSymlinks = srcStat.isSymbolicLink() && destStat.isSymbolicLink(); + const bothFolders = srcStat.isDirectory() && destStat.isDirectory(); + const bothFiles = srcStat.isFile() && destStat.isFile(); + + if (srcStat.mode !== destStat.mode) { + try { + yield access(dest, srcStat.mode); + } catch (err) { + // EINVAL access errors sometimes happen which shouldn't because node shouldn't be giving + // us modes that aren't valid. investigate this, it's generally safe to proceed. + reporter.verbose(err); + } + } + + if (bothFiles && artifactFiles.has(dest)) { + // this file gets changed during build, likely by a custom install script. Don't bother checking it. + onDone(); + reporter.verbose(reporter.lang('verboseFileSkipArtifact', src)); + return; + } + + // correct hardlink + if (bothFiles && srcStat.ino !== null && srcStat.ino === destStat.ino) { + onDone(); + reporter.verbose(reporter.lang('verboseFileSkip', src, dest, srcStat.ino)); + return; + } + + if (bothSymlinks) { + const srcReallink = yield readlink(src); + if (srcReallink === (yield readlink(dest))) { + // if both symlinks are the same then we can continue on + onDone(); + reporter.verbose(reporter.lang('verboseFileSkipSymlink', src, dest, srcReallink)); + return; + } + } + + if (bothFolders) { + // mark files that aren't in this folder as possibly extraneous + const destFiles = yield readdir(dest); + invariant(srcFiles, 'src files not initialised'); + + for (var _iterator10 = destFiles, _isArray10 = Array.isArray(_iterator10), _i10 = 0, _iterator10 = _isArray10 ? _iterator10 : _iterator10[Symbol.iterator]();;) { + var _ref14; + + if (_isArray10) { + if (_i10 >= _iterator10.length) break; + _ref14 = _iterator10[_i10++]; + } else { + _i10 = _iterator10.next(); + if (_i10.done) break; + _ref14 = _i10.value; + } + + const file = _ref14; + + if (srcFiles.indexOf(file) < 0) { + const loc = (_path || _load_path()).default.join(dest, file); + possibleExtraneous.add(loc); + + if ((yield lstat(loc)).isDirectory()) { + for (var _iterator11 = yield readdir(loc), _isArray11 = Array.isArray(_iterator11), _i11 = 0, _iterator11 = _isArray11 ? _iterator11 : _iterator11[Symbol.iterator]();;) { + var _ref15; + + if (_isArray11) { + if (_i11 >= _iterator11.length) break; + _ref15 = _iterator11[_i11++]; + } else { + _i11 = _iterator11.next(); + if (_i11.done) break; + _ref15 = _i11.value; + } + + const file = _ref15; + + possibleExtraneous.add((_path || _load_path()).default.join(loc, file)); + } + } + } + } + } + } + + if (srcStat.isSymbolicLink()) { + onFresh(); + const linkname = yield readlink(src); + actions.symlink.push({ + dest, + linkname + }); + onDone(); + } else if (srcStat.isDirectory()) { + reporter.verbose(reporter.lang('verboseFileFolder', dest)); + yield mkdirp(dest); + + const destParts = dest.split((_path || _load_path()).default.sep); + while (destParts.length) { + files.add(destParts.join((_path || _load_path()).default.sep).toLowerCase()); + destParts.pop(); + } + + // push all files to queue + invariant(srcFiles, 'src files not initialised'); + let remaining = srcFiles.length; + if (!remaining) { + onDone(); + } + for (var _iterator12 = srcFiles, _isArray12 = Array.isArray(_iterator12), _i12 = 0, _iterator12 = _isArray12 ? _iterator12 : _iterator12[Symbol.iterator]();;) { + var _ref16; + + if (_isArray12) { + if (_i12 >= _iterator12.length) break; + _ref16 = _iterator12[_i12++]; + } else { + _i12 = _iterator12.next(); + if (_i12.done) break; + _ref16 = _i12.value; + } + + const file = _ref16; + + queue.push({ + onFresh, + src: (_path || _load_path()).default.join(src, file), + dest: (_path || _load_path()).default.join(dest, file), + onDone: function (_onDone2) { + function onDone() { + return _onDone2.apply(this, arguments); + } + + onDone.toString = function () { + return _onDone2.toString(); + }; + + return onDone; + }(function () { + if (--remaining === 0) { + onDone(); + } + }) + }); + } + } else if (srcStat.isFile()) { + onFresh(); + actions.link.push({ + src, + dest, + removeDest: destExists + }); + onDone(); + } else { + throw new Error(`unsure how to copy this: ${src}`); + } + }); + + return function build(_x10) { + return _ref13.apply(this, arguments); + }; + })(); + + const artifactFiles = new Set(events.artifactFiles || []); + const files = new Set(); + + // initialise events + for (var _iterator7 = queue, _isArray7 = Array.isArray(_iterator7), _i7 = 0, _iterator7 = _isArray7 ? _iterator7 : _iterator7[Symbol.iterator]();;) { + var _ref10; + + if (_isArray7) { + if (_i7 >= _iterator7.length) break; + _ref10 = _iterator7[_i7++]; + } else { + _i7 = _iterator7.next(); + if (_i7.done) break; + _ref10 = _i7.value; + } + + const item = _ref10; + + const onDone = item.onDone || noop; + item.onDone = function () { + events.onProgress(item.dest); + onDone(); + }; + } + events.onStart(queue.length); + + // start building actions + const actions = { + file: [], + symlink: [], + link: [] + }; + + // custom concurrency logic as we're always executing stacks of CONCURRENT_QUEUE_ITEMS queue items + // at a time due to the requirement to push items onto the queue + while (queue.length) { + const items = queue.splice(0, CONCURRENT_QUEUE_ITEMS); + yield Promise.all(items.map(build)); + } + + // simulate the existence of some files to prevent considering them extraneous + for (var _iterator8 = artifactFiles, _isArray8 = Array.isArray(_iterator8), _i8 = 0, _iterator8 = _isArray8 ? _iterator8 : _iterator8[Symbol.iterator]();;) { + var _ref11; + + if (_isArray8) { + if (_i8 >= _iterator8.length) break; + _ref11 = _iterator8[_i8++]; + } else { + _i8 = _iterator8.next(); + if (_i8.done) break; + _ref11 = _i8.value; + } + + const file = _ref11; + + if (possibleExtraneous.has(file)) { + reporter.verbose(reporter.lang('verboseFilePhantomExtraneous', file)); + possibleExtraneous.delete(file); + } + } + + for (var _iterator9 = possibleExtraneous, _isArray9 = Array.isArray(_iterator9), _i9 = 0, _iterator9 = _isArray9 ? _iterator9 : _iterator9[Symbol.iterator]();;) { + var _ref12; + + if (_isArray9) { + if (_i9 >= _iterator9.length) break; + _ref12 = _iterator9[_i9++]; + } else { + _i9 = _iterator9.next(); + if (_i9.done) break; + _ref12 = _i9.value; + } + + const loc = _ref12; + + if (files.has(loc.toLowerCase())) { + possibleExtraneous.delete(loc); + } + } + + return actions; + }); + + return function buildActionsForHardlink(_x6, _x7, _x8, _x9) { + return _ref9.apply(this, arguments); + }; +})(); + +let copyBulk = exports.copyBulk = (() => { + var _ref17 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (queue, reporter, _events) { + const events = { + onStart: _events && _events.onStart || noop, + onProgress: _events && _events.onProgress || noop, + possibleExtraneous: _events ? _events.possibleExtraneous : new Set(), + ignoreBasenames: _events && _events.ignoreBasenames || [], + artifactFiles: _events && _events.artifactFiles || [] + }; + + const actions = yield buildActionsForCopy(queue, events, events.possibleExtraneous, reporter); + events.onStart(actions.file.length + actions.symlink.length + actions.link.length); + + const fileActions = actions.file; + + const currentlyWriting = new Map(); + + yield (_promise || _load_promise()).queue(fileActions, (() => { + var _ref18 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (data) { + let writePromise; + while (writePromise = currentlyWriting.get(data.dest)) { + yield writePromise; + } + + reporter.verbose(reporter.lang('verboseFileCopy', data.src, data.dest)); + const copier = (0, (_fsNormalized || _load_fsNormalized()).copyFile)(data, function () { + return currentlyWriting.delete(data.dest); + }); + currentlyWriting.set(data.dest, copier); + events.onProgress(data.dest); + return copier; + }); + + return function (_x14) { + return _ref18.apply(this, arguments); + }; + })(), CONCURRENT_QUEUE_ITEMS); + + // we need to copy symlinks last as they could reference files we were copying + const symlinkActions = actions.symlink; + yield (_promise || _load_promise()).queue(symlinkActions, function (data) { + const linkname = (_path || _load_path()).default.resolve((_path || _load_path()).default.dirname(data.dest), data.linkname); + reporter.verbose(reporter.lang('verboseFileSymlink', data.dest, linkname)); + return symlink(linkname, data.dest); + }); + }); + + return function copyBulk(_x11, _x12, _x13) { + return _ref17.apply(this, arguments); + }; +})(); + +let hardlinkBulk = exports.hardlinkBulk = (() => { + var _ref19 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (queue, reporter, _events) { + const events = { + onStart: _events && _events.onStart || noop, + onProgress: _events && _events.onProgress || noop, + possibleExtraneous: _events ? _events.possibleExtraneous : new Set(), + artifactFiles: _events && _events.artifactFiles || [], + ignoreBasenames: [] + }; + + const actions = yield buildActionsForHardlink(queue, events, events.possibleExtraneous, reporter); + events.onStart(actions.file.length + actions.symlink.length + actions.link.length); + + const fileActions = actions.link; + + yield (_promise || _load_promise()).queue(fileActions, (() => { + var _ref20 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (data) { + reporter.verbose(reporter.lang('verboseFileLink', data.src, data.dest)); + if (data.removeDest) { + yield (0, (_fsNormalized || _load_fsNormalized()).unlink)(data.dest); + } + yield link(data.src, data.dest); + }); + + return function (_x18) { + return _ref20.apply(this, arguments); + }; + })(), CONCURRENT_QUEUE_ITEMS); + + // we need to copy symlinks last as they could reference files we were copying + const symlinkActions = actions.symlink; + yield (_promise || _load_promise()).queue(symlinkActions, function (data) { + const linkname = (_path || _load_path()).default.resolve((_path || _load_path()).default.dirname(data.dest), data.linkname); + reporter.verbose(reporter.lang('verboseFileSymlink', data.dest, linkname)); + return symlink(linkname, data.dest); + }); + }); + + return function hardlinkBulk(_x15, _x16, _x17) { + return _ref19.apply(this, arguments); + }; +})(); + +let readFileAny = exports.readFileAny = (() => { + var _ref21 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (files) { + for (var _iterator13 = files, _isArray13 = Array.isArray(_iterator13), _i13 = 0, _iterator13 = _isArray13 ? _iterator13 : _iterator13[Symbol.iterator]();;) { + var _ref22; + + if (_isArray13) { + if (_i13 >= _iterator13.length) break; + _ref22 = _iterator13[_i13++]; + } else { + _i13 = _iterator13.next(); + if (_i13.done) break; + _ref22 = _i13.value; + } + + const file = _ref22; + + if (yield exists(file)) { + return readFile(file); + } + } + return null; + }); + + return function readFileAny(_x19) { + return _ref21.apply(this, arguments); + }; +})(); + +let readJson = exports.readJson = (() => { + var _ref23 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (loc) { + return (yield readJsonAndFile(loc)).object; + }); + + return function readJson(_x20) { + return _ref23.apply(this, arguments); + }; +})(); + +let readJsonAndFile = exports.readJsonAndFile = (() => { + var _ref24 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (loc) { + const file = yield readFile(loc); + try { + return { + object: (0, (_map || _load_map()).default)(JSON.parse(stripBOM(file))), + content: file + }; + } catch (err) { + err.message = `${loc}: ${err.message}`; + throw err; + } + }); + + return function readJsonAndFile(_x21) { + return _ref24.apply(this, arguments); + }; +})(); + +let find = exports.find = (() => { + var _ref25 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (filename, dir) { + const parts = dir.split((_path || _load_path()).default.sep); + + while (parts.length) { + const loc = parts.concat(filename).join((_path || _load_path()).default.sep); + + if (yield exists(loc)) { + return loc; + } else { + parts.pop(); + } + } + + return false; + }); + + return function find(_x22, _x23) { + return _ref25.apply(this, arguments); + }; +})(); + +let symlink = exports.symlink = (() => { + var _ref26 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (src, dest) { + if (process.platform !== 'win32') { + // use relative paths otherwise which will be retained if the directory is moved + src = (_path || _load_path()).default.relative((_path || _load_path()).default.dirname(dest), src); + // When path.relative returns an empty string for the current directory, we should instead use + // '.', which is a valid fs.symlink target. + src = src || '.'; + } + + try { + const stats = yield lstat(dest); + if (stats.isSymbolicLink()) { + const resolved = dest; + if (resolved === src) { + return; + } + } + } catch (err) { + if (err.code !== 'ENOENT') { + throw err; + } + } + + // We use rimraf for unlink which never throws an ENOENT on missing target + yield (0, (_fsNormalized || _load_fsNormalized()).unlink)(dest); + + if (process.platform === 'win32') { + // use directory junctions if possible on win32, this requires absolute paths + yield fsSymlink(src, dest, 'junction'); + } else { + yield fsSymlink(src, dest); + } + }); + + return function symlink(_x24, _x25) { + return _ref26.apply(this, arguments); + }; +})(); + +let walk = exports.walk = (() => { + var _ref27 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (dir, relativeDir, ignoreBasenames = new Set()) { + let files = []; + + let filenames = yield readdir(dir); + if (ignoreBasenames.size) { + filenames = filenames.filter(function (name) { + return !ignoreBasenames.has(name); + }); + } + + for (var _iterator14 = filenames, _isArray14 = Array.isArray(_iterator14), _i14 = 0, _iterator14 = _isArray14 ? _iterator14 : _iterator14[Symbol.iterator]();;) { + var _ref28; + + if (_isArray14) { + if (_i14 >= _iterator14.length) break; + _ref28 = _iterator14[_i14++]; + } else { + _i14 = _iterator14.next(); + if (_i14.done) break; + _ref28 = _i14.value; + } + + const name = _ref28; + + const relative = relativeDir ? (_path || _load_path()).default.join(relativeDir, name) : name; + const loc = (_path || _load_path()).default.join(dir, name); + const stat = yield lstat(loc); + + files.push({ + relative, + basename: name, + absolute: loc, + mtime: +stat.mtime + }); + + if (stat.isDirectory()) { + files = files.concat((yield walk(loc, relative, ignoreBasenames))); + } + } + + return files; + }); + + return function walk(_x26, _x27) { + return _ref27.apply(this, arguments); + }; +})(); + +let getFileSizeOnDisk = exports.getFileSizeOnDisk = (() => { + var _ref29 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (loc) { + const stat = yield lstat(loc); + const size = stat.size, + blockSize = stat.blksize; + + + return Math.ceil(size / blockSize) * blockSize; + }); + + return function getFileSizeOnDisk(_x28) { + return _ref29.apply(this, arguments); + }; +})(); + +let getEolFromFile = (() => { + var _ref30 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (path) { + if (!(yield exists(path))) { + return undefined; + } + + const buffer = yield readFileBuffer(path); + + for (let i = 0; i < buffer.length; ++i) { + if (buffer[i] === cr) { + return '\r\n'; + } + if (buffer[i] === lf) { + return '\n'; + } + } + return undefined; + }); + + return function getEolFromFile(_x29) { + return _ref30.apply(this, arguments); + }; +})(); + +let writeFilePreservingEol = exports.writeFilePreservingEol = (() => { + var _ref31 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (path, data) { + const eol = (yield getEolFromFile(path)) || (_os || _load_os()).default.EOL; + if (eol !== '\n') { + data = data.replace(/\n/g, eol); + } + yield writeFile(path, data); + }); + + return function writeFilePreservingEol(_x30, _x31) { + return _ref31.apply(this, arguments); + }; +})(); + +let hardlinksWork = exports.hardlinksWork = (() => { + var _ref32 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (dir) { + const filename = 'test-file' + Math.random(); + const file = (_path || _load_path()).default.join(dir, filename); + const fileLink = (_path || _load_path()).default.join(dir, filename + '-link'); + try { + yield writeFile(file, 'test'); + yield link(file, fileLink); + } catch (err) { + return false; + } finally { + yield (0, (_fsNormalized || _load_fsNormalized()).unlink)(file); + yield (0, (_fsNormalized || _load_fsNormalized()).unlink)(fileLink); + } + return true; + }); + + return function hardlinksWork(_x32) { + return _ref32.apply(this, arguments); + }; +})(); + +// not a strict polyfill for Node's fs.mkdtemp + + +let makeTempDir = exports.makeTempDir = (() => { + var _ref33 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (prefix) { + const dir = (_path || _load_path()).default.join((_os || _load_os()).default.tmpdir(), `yarn-${prefix || ''}-${Date.now()}-${Math.random()}`); + yield (0, (_fsNormalized || _load_fsNormalized()).unlink)(dir); + yield mkdirp(dir); + return dir; + }); + + return function makeTempDir(_x33) { + return _ref33.apply(this, arguments); + }; +})(); + +let readFirstAvailableStream = exports.readFirstAvailableStream = (() => { + var _ref34 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (paths) { + for (var _iterator15 = paths, _isArray15 = Array.isArray(_iterator15), _i15 = 0, _iterator15 = _isArray15 ? _iterator15 : _iterator15[Symbol.iterator]();;) { + var _ref35; + + if (_isArray15) { + if (_i15 >= _iterator15.length) break; + _ref35 = _iterator15[_i15++]; + } else { + _i15 = _iterator15.next(); + if (_i15.done) break; + _ref35 = _i15.value; + } + + const path = _ref35; + + try { + const fd = yield open(path, 'r'); + return (_fs || _load_fs()).default.createReadStream(path, { fd }); + } catch (err) { + // Try the next one + } + } + return null; + }); + + return function readFirstAvailableStream(_x34) { + return _ref34.apply(this, arguments); + }; +})(); + +let getFirstSuitableFolder = exports.getFirstSuitableFolder = (() => { + var _ref36 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (paths, mode = constants.W_OK | constants.X_OK) { + const result = { + skipped: [], + folder: null + }; + + for (var _iterator16 = paths, _isArray16 = Array.isArray(_iterator16), _i16 = 0, _iterator16 = _isArray16 ? _iterator16 : _iterator16[Symbol.iterator]();;) { + var _ref37; + + if (_isArray16) { + if (_i16 >= _iterator16.length) break; + _ref37 = _iterator16[_i16++]; + } else { + _i16 = _iterator16.next(); + if (_i16.done) break; + _ref37 = _i16.value; + } + + const folder = _ref37; + + try { + yield mkdirp(folder); + yield access(folder, mode); + + result.folder = folder; + + return result; + } catch (error) { + result.skipped.push({ + error, + folder + }); + } + } + return result; + }); + + return function getFirstSuitableFolder(_x35) { + return _ref36.apply(this, arguments); + }; +})(); + +exports.copy = copy; +exports.readFile = readFile; +exports.readFileRaw = readFileRaw; +exports.normalizeOS = normalizeOS; + +var _fs; + +function _load_fs() { + return _fs = _interopRequireDefault(__webpack_require__(5)); +} + +var _glob; + +function _load_glob() { + return _glob = _interopRequireDefault(__webpack_require__(99)); +} + +var _os; + +function _load_os() { + return _os = _interopRequireDefault(__webpack_require__(46)); +} + +var _path; + +function _load_path() { + return _path = _interopRequireDefault(__webpack_require__(0)); +} + +var _blockingQueue; + +function _load_blockingQueue() { + return _blockingQueue = _interopRequireDefault(__webpack_require__(110)); +} + +var _promise; + +function _load_promise() { + return _promise = _interopRequireWildcard(__webpack_require__(50)); +} + +var _promise2; + +function _load_promise2() { + return _promise2 = __webpack_require__(50); +} + +var _map; + +function _load_map() { + return _map = _interopRequireDefault(__webpack_require__(29)); +} + +var _fsNormalized; + +function _load_fsNormalized() { + return _fsNormalized = __webpack_require__(218); +} + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const constants = exports.constants = typeof (_fs || _load_fs()).default.constants !== 'undefined' ? (_fs || _load_fs()).default.constants : { + R_OK: (_fs || _load_fs()).default.R_OK, + W_OK: (_fs || _load_fs()).default.W_OK, + X_OK: (_fs || _load_fs()).default.X_OK +}; + +const lockQueue = exports.lockQueue = new (_blockingQueue || _load_blockingQueue()).default('fs lock'); + +const readFileBuffer = exports.readFileBuffer = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.readFile); +const open = exports.open = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.open); +const writeFile = exports.writeFile = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.writeFile); +const readlink = exports.readlink = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.readlink); +const realpath = exports.realpath = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.realpath); +const readdir = exports.readdir = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.readdir); +const rename = exports.rename = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.rename); +const access = exports.access = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.access); +const stat = exports.stat = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.stat); +const mkdirp = exports.mkdirp = (0, (_promise2 || _load_promise2()).promisify)(__webpack_require__(145)); +const exists = exports.exists = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.exists, true); +const lstat = exports.lstat = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.lstat); +const chmod = exports.chmod = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.chmod); +const link = exports.link = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.link); +const glob = exports.glob = (0, (_promise2 || _load_promise2()).promisify)((_glob || _load_glob()).default); +exports.unlink = (_fsNormalized || _load_fsNormalized()).unlink; + +// fs.copyFile uses the native file copying instructions on the system, performing much better +// than any JS-based solution and consumes fewer resources. Repeated testing to fine tune the +// concurrency level revealed 128 as the sweet spot on a quad-core, 16 CPU Intel system with SSD. + +const CONCURRENT_QUEUE_ITEMS = (_fs || _load_fs()).default.copyFile ? 128 : 4; + +const fsSymlink = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.symlink); +const invariant = __webpack_require__(9); +const stripBOM = __webpack_require__(160); + +const noop = () => {}; + +function copy(src, dest, reporter) { + return copyBulk([{ src, dest }], reporter); +} + +function _readFile(loc, encoding) { + return new Promise((resolve, reject) => { + (_fs || _load_fs()).default.readFile(loc, encoding, function (err, content) { + if (err) { + reject(err); + } else { + resolve(content); + } + }); + }); +} + +function readFile(loc) { + return _readFile(loc, 'utf8').then(normalizeOS); +} + +function readFileRaw(loc) { + return _readFile(loc, 'binary'); +} + +function normalizeOS(body) { + return body.replace(/\r\n/g, '\n'); +} + +const cr = '\r'.charCodeAt(0); +const lf = '\n'.charCodeAt(0); + +/***/ }), +/* 5 */ +/***/ (function(module, exports) { + +module.exports = require("fs"); + +/***/ }), +/* 6 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +class MessageError extends Error { + constructor(msg, code) { + super(msg); + this.code = code; + } + +} + +exports.MessageError = MessageError; +class ProcessSpawnError extends MessageError { + constructor(msg, code, process) { + super(msg, code); + this.process = process; + } + +} + +exports.ProcessSpawnError = ProcessSpawnError; +class SecurityError extends MessageError {} + +exports.SecurityError = SecurityError; +class ProcessTermError extends MessageError {} + +exports.ProcessTermError = ProcessTermError; +class ResponseError extends Error { + constructor(msg, responseCode) { + super(msg); + this.responseCode = responseCode; + } + +} + +exports.ResponseError = ResponseError; +class OneTimePasswordError extends Error {} +exports.OneTimePasswordError = OneTimePasswordError; + +/***/ }), +/* 7 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return Subscriber; }); +/* unused harmony export SafeSubscriber */ +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_tslib__ = __webpack_require__(1); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__util_isFunction__ = __webpack_require__(154); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__Observer__ = __webpack_require__(420); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__Subscription__ = __webpack_require__(25); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__internal_symbol_rxSubscriber__ = __webpack_require__(321); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__config__ = __webpack_require__(185); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_6__util_hostReportError__ = __webpack_require__(323); +/** PURE_IMPORTS_START tslib,_util_isFunction,_Observer,_Subscription,_internal_symbol_rxSubscriber,_config,_util_hostReportError PURE_IMPORTS_END */ + + + + + + + +var Subscriber = /*@__PURE__*/ (function (_super) { + __WEBPACK_IMPORTED_MODULE_0_tslib__["a" /* __extends */](Subscriber, _super); + function Subscriber(destinationOrNext, error, complete) { + var _this = _super.call(this) || this; + _this.syncErrorValue = null; + _this.syncErrorThrown = false; + _this.syncErrorThrowable = false; + _this.isStopped = false; + _this._parentSubscription = null; + switch (arguments.length) { + case 0: + _this.destination = __WEBPACK_IMPORTED_MODULE_2__Observer__["a" /* empty */]; + break; + case 1: + if (!destinationOrNext) { + _this.destination = __WEBPACK_IMPORTED_MODULE_2__Observer__["a" /* empty */]; + break; + } + if (typeof destinationOrNext === 'object') { + if (destinationOrNext instanceof Subscriber) { + _this.syncErrorThrowable = destinationOrNext.syncErrorThrowable; + _this.destination = destinationOrNext; + destinationOrNext.add(_this); + } + else { + _this.syncErrorThrowable = true; + _this.destination = new SafeSubscriber(_this, destinationOrNext); + } + break; + } + default: + _this.syncErrorThrowable = true; + _this.destination = new SafeSubscriber(_this, destinationOrNext, error, complete); + break; + } + return _this; + } + Subscriber.prototype[__WEBPACK_IMPORTED_MODULE_4__internal_symbol_rxSubscriber__["a" /* rxSubscriber */]] = function () { return this; }; + Subscriber.create = function (next, error, complete) { + var subscriber = new Subscriber(next, error, complete); + subscriber.syncErrorThrowable = false; + return subscriber; + }; + Subscriber.prototype.next = function (value) { + if (!this.isStopped) { + this._next(value); + } + }; + Subscriber.prototype.error = function (err) { + if (!this.isStopped) { + this.isStopped = true; + this._error(err); + } + }; + Subscriber.prototype.complete = function () { + if (!this.isStopped) { + this.isStopped = true; + this._complete(); + } + }; + Subscriber.prototype.unsubscribe = function () { + if (this.closed) { + return; + } + this.isStopped = true; + _super.prototype.unsubscribe.call(this); + }; + Subscriber.prototype._next = function (value) { + this.destination.next(value); + }; + Subscriber.prototype._error = function (err) { + this.destination.error(err); + this.unsubscribe(); + }; + Subscriber.prototype._complete = function () { + this.destination.complete(); + this.unsubscribe(); + }; + Subscriber.prototype._unsubscribeAndRecycle = function () { + var _a = this, _parent = _a._parent, _parents = _a._parents; + this._parent = null; + this._parents = null; + this.unsubscribe(); + this.closed = false; + this.isStopped = false; + this._parent = _parent; + this._parents = _parents; + this._parentSubscription = null; + return this; + }; + return Subscriber; +}(__WEBPACK_IMPORTED_MODULE_3__Subscription__["a" /* Subscription */])); + +var SafeSubscriber = /*@__PURE__*/ (function (_super) { + __WEBPACK_IMPORTED_MODULE_0_tslib__["a" /* __extends */](SafeSubscriber, _super); + function SafeSubscriber(_parentSubscriber, observerOrNext, error, complete) { + var _this = _super.call(this) || this; + _this._parentSubscriber = _parentSubscriber; + var next; + var context = _this; + if (__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_1__util_isFunction__["a" /* isFunction */])(observerOrNext)) { + next = observerOrNext; + } + else if (observerOrNext) { + next = observerOrNext.next; + error = observerOrNext.error; + complete = observerOrNext.complete; + if (observerOrNext !== __WEBPACK_IMPORTED_MODULE_2__Observer__["a" /* empty */]) { + context = Object.create(observerOrNext); + if (__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_1__util_isFunction__["a" /* isFunction */])(context.unsubscribe)) { + _this.add(context.unsubscribe.bind(context)); + } + context.unsubscribe = _this.unsubscribe.bind(_this); + } + } + _this._context = context; + _this._next = next; + _this._error = error; + _this._complete = complete; + return _this; + } + SafeSubscriber.prototype.next = function (value) { + if (!this.isStopped && this._next) { + var _parentSubscriber = this._parentSubscriber; + if (!__WEBPACK_IMPORTED_MODULE_5__config__["a" /* config */].useDeprecatedSynchronousErrorHandling || !_parentSubscriber.syncErrorThrowable) { + this.__tryOrUnsub(this._next, value); + } + else if (this.__tryOrSetError(_parentSubscriber, this._next, value)) { + this.unsubscribe(); + } + } + }; + SafeSubscriber.prototype.error = function (err) { + if (!this.isStopped) { + var _parentSubscriber = this._parentSubscriber; + var useDeprecatedSynchronousErrorHandling = __WEBPACK_IMPORTED_MODULE_5__config__["a" /* config */].useDeprecatedSynchronousErrorHandling; + if (this._error) { + if (!useDeprecatedSynchronousErrorHandling || !_parentSubscriber.syncErrorThrowable) { + this.__tryOrUnsub(this._error, err); + this.unsubscribe(); + } + else { + this.__tryOrSetError(_parentSubscriber, this._error, err); + this.unsubscribe(); + } + } + else if (!_parentSubscriber.syncErrorThrowable) { + this.unsubscribe(); + if (useDeprecatedSynchronousErrorHandling) { + throw err; + } + __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_6__util_hostReportError__["a" /* hostReportError */])(err); + } + else { + if (useDeprecatedSynchronousErrorHandling) { + _parentSubscriber.syncErrorValue = err; + _parentSubscriber.syncErrorThrown = true; + } + else { + __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_6__util_hostReportError__["a" /* hostReportError */])(err); + } + this.unsubscribe(); + } + } + }; + SafeSubscriber.prototype.complete = function () { + var _this = this; + if (!this.isStopped) { + var _parentSubscriber = this._parentSubscriber; + if (this._complete) { + var wrappedComplete = function () { return _this._complete.call(_this._context); }; + if (!__WEBPACK_IMPORTED_MODULE_5__config__["a" /* config */].useDeprecatedSynchronousErrorHandling || !_parentSubscriber.syncErrorThrowable) { + this.__tryOrUnsub(wrappedComplete); + this.unsubscribe(); + } + else { + this.__tryOrSetError(_parentSubscriber, wrappedComplete); + this.unsubscribe(); + } + } + else { + this.unsubscribe(); + } + } + }; + SafeSubscriber.prototype.__tryOrUnsub = function (fn, value) { + try { + fn.call(this._context, value); + } + catch (err) { + this.unsubscribe(); + if (__WEBPACK_IMPORTED_MODULE_5__config__["a" /* config */].useDeprecatedSynchronousErrorHandling) { + throw err; + } + else { + __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_6__util_hostReportError__["a" /* hostReportError */])(err); + } + } + }; + SafeSubscriber.prototype.__tryOrSetError = function (parent, fn, value) { + if (!__WEBPACK_IMPORTED_MODULE_5__config__["a" /* config */].useDeprecatedSynchronousErrorHandling) { + throw new Error('bad call'); + } + try { + fn.call(this._context, value); + } + catch (err) { + if (__WEBPACK_IMPORTED_MODULE_5__config__["a" /* config */].useDeprecatedSynchronousErrorHandling) { + parent.syncErrorValue = err; + parent.syncErrorThrown = true; + return true; + } + else { + __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_6__util_hostReportError__["a" /* hostReportError */])(err); + return true; + } + } + return false; + }; + SafeSubscriber.prototype._unsubscribe = function () { + var _parentSubscriber = this._parentSubscriber; + this._context = null; + this._parentSubscriber = null; + _parentSubscriber.unsubscribe(); + }; + return SafeSubscriber; +}(Subscriber)); + +//# sourceMappingURL=Subscriber.js.map + + +/***/ }), +/* 8 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.getPathKey = getPathKey; +const os = __webpack_require__(46); +const path = __webpack_require__(0); +const userHome = __webpack_require__(67).default; + +var _require = __webpack_require__(225); + +const getCacheDir = _require.getCacheDir, + getConfigDir = _require.getConfigDir, + getDataDir = _require.getDataDir; + +const isWebpackBundle = __webpack_require__(278); + +const DEPENDENCY_TYPES = exports.DEPENDENCY_TYPES = ['devDependencies', 'dependencies', 'optionalDependencies', 'peerDependencies']; +const OWNED_DEPENDENCY_TYPES = exports.OWNED_DEPENDENCY_TYPES = ['devDependencies', 'dependencies', 'optionalDependencies']; + +const RESOLUTIONS = exports.RESOLUTIONS = 'resolutions'; +const MANIFEST_FIELDS = exports.MANIFEST_FIELDS = [RESOLUTIONS, ...DEPENDENCY_TYPES]; + +const SUPPORTED_NODE_VERSIONS = exports.SUPPORTED_NODE_VERSIONS = '^4.8.0 || ^5.7.0 || ^6.2.2 || >=8.0.0'; + +const YARN_REGISTRY = exports.YARN_REGISTRY = 'https://registry.yarnpkg.com'; +const NPM_REGISTRY_RE = exports.NPM_REGISTRY_RE = /https?:\/\/registry\.npmjs\.org/g; + +const YARN_DOCS = exports.YARN_DOCS = 'https://yarnpkg.com/en/docs/cli/'; +const YARN_INSTALLER_SH = exports.YARN_INSTALLER_SH = 'https://yarnpkg.com/install.sh'; +const YARN_INSTALLER_MSI = exports.YARN_INSTALLER_MSI = 'https://yarnpkg.com/latest.msi'; + +const SELF_UPDATE_VERSION_URL = exports.SELF_UPDATE_VERSION_URL = 'https://yarnpkg.com/latest-version'; + +// cache version, bump whenever we make backwards incompatible changes +const CACHE_VERSION = exports.CACHE_VERSION = 6; + +// lockfile version, bump whenever we make backwards incompatible changes +const LOCKFILE_VERSION = exports.LOCKFILE_VERSION = 1; + +// max amount of network requests to perform concurrently +const NETWORK_CONCURRENCY = exports.NETWORK_CONCURRENCY = 8; + +// HTTP timeout used when downloading packages +const NETWORK_TIMEOUT = exports.NETWORK_TIMEOUT = 30 * 1000; // in milliseconds + +// max amount of child processes to execute concurrently +const CHILD_CONCURRENCY = exports.CHILD_CONCURRENCY = 5; + +const REQUIRED_PACKAGE_KEYS = exports.REQUIRED_PACKAGE_KEYS = ['name', 'version', '_uid']; + +function getPreferredCacheDirectories() { + const preferredCacheDirectories = [getCacheDir()]; + + if (process.getuid) { + // $FlowFixMe: process.getuid exists, dammit + preferredCacheDirectories.push(path.join(os.tmpdir(), `.yarn-cache-${process.getuid()}`)); + } + + preferredCacheDirectories.push(path.join(os.tmpdir(), `.yarn-cache`)); + + return preferredCacheDirectories; +} + +const PREFERRED_MODULE_CACHE_DIRECTORIES = exports.PREFERRED_MODULE_CACHE_DIRECTORIES = getPreferredCacheDirectories(); +const CONFIG_DIRECTORY = exports.CONFIG_DIRECTORY = getConfigDir(); +const DATA_DIRECTORY = exports.DATA_DIRECTORY = getDataDir(); +const LINK_REGISTRY_DIRECTORY = exports.LINK_REGISTRY_DIRECTORY = path.join(DATA_DIRECTORY, 'link'); +const GLOBAL_MODULE_DIRECTORY = exports.GLOBAL_MODULE_DIRECTORY = path.join(DATA_DIRECTORY, 'global'); + +const NODE_BIN_PATH = exports.NODE_BIN_PATH = process.execPath; +const YARN_BIN_PATH = exports.YARN_BIN_PATH = getYarnBinPath(); + +// Webpack needs to be configured with node.__dirname/__filename = false +function getYarnBinPath() { + if (isWebpackBundle) { + return __filename; + } else { + return path.join(__dirname, '..', 'bin', 'yarn.js'); + } +} + +const NODE_MODULES_FOLDER = exports.NODE_MODULES_FOLDER = 'node_modules'; +const NODE_PACKAGE_JSON = exports.NODE_PACKAGE_JSON = 'package.json'; + +const PNP_FILENAME = exports.PNP_FILENAME = '.pnp.js'; + +const POSIX_GLOBAL_PREFIX = exports.POSIX_GLOBAL_PREFIX = `${process.env.DESTDIR || ''}/usr/local`; +const FALLBACK_GLOBAL_PREFIX = exports.FALLBACK_GLOBAL_PREFIX = path.join(userHome, '.yarn'); + +const META_FOLDER = exports.META_FOLDER = '.yarn-meta'; +const INTEGRITY_FILENAME = exports.INTEGRITY_FILENAME = '.yarn-integrity'; +const LOCKFILE_FILENAME = exports.LOCKFILE_FILENAME = 'yarn.lock'; +const METADATA_FILENAME = exports.METADATA_FILENAME = '.yarn-metadata.json'; +const TARBALL_FILENAME = exports.TARBALL_FILENAME = '.yarn-tarball.tgz'; +const CLEAN_FILENAME = exports.CLEAN_FILENAME = '.yarnclean'; + +const NPM_LOCK_FILENAME = exports.NPM_LOCK_FILENAME = 'package-lock.json'; +const NPM_SHRINKWRAP_FILENAME = exports.NPM_SHRINKWRAP_FILENAME = 'npm-shrinkwrap.json'; + +const DEFAULT_INDENT = exports.DEFAULT_INDENT = ' '; +const SINGLE_INSTANCE_PORT = exports.SINGLE_INSTANCE_PORT = 31997; +const SINGLE_INSTANCE_FILENAME = exports.SINGLE_INSTANCE_FILENAME = '.yarn-single-instance'; + +const ENV_PATH_KEY = exports.ENV_PATH_KEY = getPathKey(process.platform, process.env); + +function getPathKey(platform, env) { + let pathKey = 'PATH'; + + // windows calls its path "Path" usually, but this is not guaranteed. + if (platform === 'win32') { + pathKey = 'Path'; + + for (const key in env) { + if (key.toLowerCase() === 'path') { + pathKey = key; + } + } + } + + return pathKey; +} + +const VERSION_COLOR_SCHEME = exports.VERSION_COLOR_SCHEME = { + major: 'red', + premajor: 'red', + minor: 'yellow', + preminor: 'yellow', + patch: 'green', + prepatch: 'green', + prerelease: 'red', + unchanged: 'white', + unknown: 'red' +}; + +/***/ }), +/* 9 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + + + +/** + * Use invariant() to assert state which your program assumes to be true. + * + * Provide sprintf-style format (only %s is supported) and arguments + * to provide information about what broke and what you were + * expecting. + * + * The invariant message will be stripped in production, but the invariant + * will remain to ensure logic does not differ in production. + */ + +var NODE_ENV = process.env.NODE_ENV; + +var invariant = function(condition, format, a, b, c, d, e, f) { + if (NODE_ENV !== 'production') { + if (format === undefined) { + throw new Error('invariant requires an error message argument'); + } + } + + if (!condition) { + var error; + if (format === undefined) { + error = new Error( + 'Minified exception occurred; use the non-minified dev environment ' + + 'for the full error message and additional helpful warnings.' + ); + } else { + var args = [a, b, c, d, e, f]; + var argIndex = 0; + error = new Error( + format.replace(/%s/g, function() { return args[argIndex++]; }) + ); + error.name = 'Invariant Violation'; + } + + error.framesToPop = 1; // we don't care about invariant's own frame + throw error; + } +}; + +module.exports = invariant; + + +/***/ }), +/* 10 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var YAMLException = __webpack_require__(54); + +var TYPE_CONSTRUCTOR_OPTIONS = [ + 'kind', + 'resolve', + 'construct', + 'instanceOf', + 'predicate', + 'represent', + 'defaultStyle', + 'styleAliases' +]; + +var YAML_NODE_KINDS = [ + 'scalar', + 'sequence', + 'mapping' +]; + +function compileStyleAliases(map) { + var result = {}; + + if (map !== null) { + Object.keys(map).forEach(function (style) { + map[style].forEach(function (alias) { + result[String(alias)] = style; + }); + }); + } + + return result; +} + +function Type(tag, options) { + options = options || {}; + + Object.keys(options).forEach(function (name) { + if (TYPE_CONSTRUCTOR_OPTIONS.indexOf(name) === -1) { + throw new YAMLException('Unknown option "' + name + '" is met in definition of "' + tag + '" YAML type.'); + } + }); + + // TODO: Add tag format check. + this.tag = tag; + this.kind = options['kind'] || null; + this.resolve = options['resolve'] || function () { return true; }; + this.construct = options['construct'] || function (data) { return data; }; + this.instanceOf = options['instanceOf'] || null; + this.predicate = options['predicate'] || null; + this.represent = options['represent'] || null; + this.defaultStyle = options['defaultStyle'] || null; + this.styleAliases = compileStyleAliases(options['styleAliases'] || null); + + if (YAML_NODE_KINDS.indexOf(this.kind) === -1) { + throw new YAMLException('Unknown kind "' + this.kind + '" is specified for "' + tag + '" YAML type.'); + } +} + +module.exports = Type; + + +/***/ }), +/* 11 */ +/***/ (function(module, exports) { + +module.exports = require("crypto"); + +/***/ }), +/* 12 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return Observable; }); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__util_canReportError__ = __webpack_require__(322); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__util_toSubscriber__ = __webpack_require__(932); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__internal_symbol_observable__ = __webpack_require__(117); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__util_pipe__ = __webpack_require__(324); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__config__ = __webpack_require__(185); +/** PURE_IMPORTS_START _util_canReportError,_util_toSubscriber,_internal_symbol_observable,_util_pipe,_config PURE_IMPORTS_END */ + + + + + +var Observable = /*@__PURE__*/ (function () { + function Observable(subscribe) { + this._isScalar = false; + if (subscribe) { + this._subscribe = subscribe; + } + } + Observable.prototype.lift = function (operator) { + var observable = new Observable(); + observable.source = this; + observable.operator = operator; + return observable; + }; + Observable.prototype.subscribe = function (observerOrNext, error, complete) { + var operator = this.operator; + var sink = __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_1__util_toSubscriber__["a" /* toSubscriber */])(observerOrNext, error, complete); + if (operator) { + operator.call(sink, this.source); + } + else { + sink.add(this.source || (__WEBPACK_IMPORTED_MODULE_4__config__["a" /* config */].useDeprecatedSynchronousErrorHandling && !sink.syncErrorThrowable) ? + this._subscribe(sink) : + this._trySubscribe(sink)); + } + if (__WEBPACK_IMPORTED_MODULE_4__config__["a" /* config */].useDeprecatedSynchronousErrorHandling) { + if (sink.syncErrorThrowable) { + sink.syncErrorThrowable = false; + if (sink.syncErrorThrown) { + throw sink.syncErrorValue; + } + } + } + return sink; + }; + Observable.prototype._trySubscribe = function (sink) { + try { + return this._subscribe(sink); + } + catch (err) { + if (__WEBPACK_IMPORTED_MODULE_4__config__["a" /* config */].useDeprecatedSynchronousErrorHandling) { + sink.syncErrorThrown = true; + sink.syncErrorValue = err; + } + if (__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__util_canReportError__["a" /* canReportError */])(sink)) { + sink.error(err); + } + else { + console.warn(err); + } + } + }; + Observable.prototype.forEach = function (next, promiseCtor) { + var _this = this; + promiseCtor = getPromiseCtor(promiseCtor); + return new promiseCtor(function (resolve, reject) { + var subscription; + subscription = _this.subscribe(function (value) { + try { + next(value); + } + catch (err) { + reject(err); + if (subscription) { + subscription.unsubscribe(); + } + } + }, reject, resolve); + }); + }; + Observable.prototype._subscribe = function (subscriber) { + var source = this.source; + return source && source.subscribe(subscriber); + }; + Observable.prototype[__WEBPACK_IMPORTED_MODULE_2__internal_symbol_observable__["a" /* observable */]] = function () { + return this; + }; + Observable.prototype.pipe = function () { + var operations = []; + for (var _i = 0; _i < arguments.length; _i++) { + operations[_i] = arguments[_i]; + } + if (operations.length === 0) { + return this; + } + return __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_3__util_pipe__["b" /* pipeFromArray */])(operations)(this); + }; + Observable.prototype.toPromise = function (promiseCtor) { + var _this = this; + promiseCtor = getPromiseCtor(promiseCtor); + return new promiseCtor(function (resolve, reject) { + var value; + _this.subscribe(function (x) { return value = x; }, function (err) { return reject(err); }, function () { return resolve(value); }); + }); + }; + Observable.create = function (subscribe) { + return new Observable(subscribe); + }; + return Observable; +}()); + +function getPromiseCtor(promiseCtor) { + if (!promiseCtor) { + promiseCtor = __WEBPACK_IMPORTED_MODULE_4__config__["a" /* config */].Promise || Promise; + } + if (!promiseCtor) { + throw new Error('no Promise impl found'); + } + return promiseCtor; +} +//# sourceMappingURL=Observable.js.map + + +/***/ }), +/* 13 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return OuterSubscriber; }); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_tslib__ = __webpack_require__(1); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__Subscriber__ = __webpack_require__(7); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + + +var OuterSubscriber = /*@__PURE__*/ (function (_super) { + __WEBPACK_IMPORTED_MODULE_0_tslib__["a" /* __extends */](OuterSubscriber, _super); + function OuterSubscriber() { + return _super !== null && _super.apply(this, arguments) || this; + } + OuterSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { + this.destination.next(innerValue); + }; + OuterSubscriber.prototype.notifyError = function (error, innerSub) { + this.destination.error(error); + }; + OuterSubscriber.prototype.notifyComplete = function (innerSub) { + this.destination.complete(); + }; + return OuterSubscriber; +}(__WEBPACK_IMPORTED_MODULE_1__Subscriber__["a" /* Subscriber */])); + +//# sourceMappingURL=OuterSubscriber.js.map + + +/***/ }), +/* 14 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +/* harmony export (immutable) */ __webpack_exports__["a"] = subscribeToResult; +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__InnerSubscriber__ = __webpack_require__(84); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__subscribeTo__ = __webpack_require__(446); +/** PURE_IMPORTS_START _InnerSubscriber,_subscribeTo PURE_IMPORTS_END */ + + +function subscribeToResult(outerSubscriber, result, outerValue, outerIndex, destination) { + if (destination === void 0) { + destination = new __WEBPACK_IMPORTED_MODULE_0__InnerSubscriber__["a" /* InnerSubscriber */](outerSubscriber, outerValue, outerIndex); + } + if (destination.closed) { + return; + } + return __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_1__subscribeTo__["a" /* subscribeTo */])(result)(destination); +} +//# sourceMappingURL=subscribeToResult.js.map + + +/***/ }), +/* 15 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/* eslint-disable node/no-deprecated-api */ + + + +var buffer = __webpack_require__(64) +var Buffer = buffer.Buffer + +var safer = {} + +var key + +for (key in buffer) { + if (!buffer.hasOwnProperty(key)) continue + if (key === 'SlowBuffer' || key === 'Buffer') continue + safer[key] = buffer[key] +} + +var Safer = safer.Buffer = {} +for (key in Buffer) { + if (!Buffer.hasOwnProperty(key)) continue + if (key === 'allocUnsafe' || key === 'allocUnsafeSlow') continue + Safer[key] = Buffer[key] +} + +safer.Buffer.prototype = Buffer.prototype + +if (!Safer.from || Safer.from === Uint8Array.from) { + Safer.from = function (value, encodingOrOffset, length) { + if (typeof value === 'number') { + throw new TypeError('The "value" argument must not be of type number. Received type ' + typeof value) + } + if (value && typeof value.length === 'undefined') { + throw new TypeError('The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type ' + typeof value) + } + return Buffer(value, encodingOrOffset, length) + } +} + +if (!Safer.alloc) { + Safer.alloc = function (size, fill, encoding) { + if (typeof size !== 'number') { + throw new TypeError('The "size" argument must be of type number. Received type ' + typeof size) + } + if (size < 0 || size >= 2 * (1 << 30)) { + throw new RangeError('The value "' + size + '" is invalid for option "size"') + } + var buf = Buffer(size) + if (!fill || fill.length === 0) { + buf.fill(0) + } else if (typeof encoding === 'string') { + buf.fill(fill, encoding) + } else { + buf.fill(fill) + } + return buf + } +} + +if (!safer.kStringMaxLength) { + try { + safer.kStringMaxLength = process.binding('buffer').kStringMaxLength + } catch (e) { + // we can't determine kStringMaxLength in environments where process.binding + // is unsupported, so let's not set it + } +} + +if (!safer.constants) { + safer.constants = { + MAX_LENGTH: safer.kMaxLength + } + if (safer.kStringMaxLength) { + safer.constants.MAX_STRING_LENGTH = safer.kStringMaxLength + } +} + +module.exports = safer + + +/***/ }), +/* 16 */ +/***/ (function(module, exports, __webpack_require__) { + +// Copyright (c) 2012, Mark Cavage. All rights reserved. +// Copyright 2015 Joyent, Inc. + +var assert = __webpack_require__(28); +var Stream = __webpack_require__(23).Stream; +var util = __webpack_require__(3); + + +///--- Globals + +/* JSSTYLED */ +var UUID_REGEXP = /^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$/; + + +///--- Internal + +function _capitalize(str) { + return (str.charAt(0).toUpperCase() + str.slice(1)); +} + +function _toss(name, expected, oper, arg, actual) { + throw new assert.AssertionError({ + message: util.format('%s (%s) is required', name, expected), + actual: (actual === undefined) ? typeof (arg) : actual(arg), + expected: expected, + operator: oper || '===', + stackStartFunction: _toss.caller + }); +} + +function _getClass(arg) { + return (Object.prototype.toString.call(arg).slice(8, -1)); +} + +function noop() { + // Why even bother with asserts? +} + + +///--- Exports + +var types = { + bool: { + check: function (arg) { return typeof (arg) === 'boolean'; } + }, + func: { + check: function (arg) { return typeof (arg) === 'function'; } + }, + string: { + check: function (arg) { return typeof (arg) === 'string'; } + }, + object: { + check: function (arg) { + return typeof (arg) === 'object' && arg !== null; + } + }, + number: { + check: function (arg) { + return typeof (arg) === 'number' && !isNaN(arg); + } + }, + finite: { + check: function (arg) { + return typeof (arg) === 'number' && !isNaN(arg) && isFinite(arg); + } + }, + buffer: { + check: function (arg) { return Buffer.isBuffer(arg); }, + operator: 'Buffer.isBuffer' + }, + array: { + check: function (arg) { return Array.isArray(arg); }, + operator: 'Array.isArray' + }, + stream: { + check: function (arg) { return arg instanceof Stream; }, + operator: 'instanceof', + actual: _getClass + }, + date: { + check: function (arg) { return arg instanceof Date; }, + operator: 'instanceof', + actual: _getClass + }, + regexp: { + check: function (arg) { return arg instanceof RegExp; }, + operator: 'instanceof', + actual: _getClass + }, + uuid: { + check: function (arg) { + return typeof (arg) === 'string' && UUID_REGEXP.test(arg); + }, + operator: 'isUUID' + } +}; + +function _setExports(ndebug) { + var keys = Object.keys(types); + var out; + + /* re-export standard assert */ + if (process.env.NODE_NDEBUG) { + out = noop; + } else { + out = function (arg, msg) { + if (!arg) { + _toss(msg, 'true', arg); + } + }; + } + + /* standard checks */ + keys.forEach(function (k) { + if (ndebug) { + out[k] = noop; + return; + } + var type = types[k]; + out[k] = function (arg, msg) { + if (!type.check(arg)) { + _toss(msg, k, type.operator, arg, type.actual); + } + }; + }); + + /* optional checks */ + keys.forEach(function (k) { + var name = 'optional' + _capitalize(k); + if (ndebug) { + out[name] = noop; + return; + } + var type = types[k]; + out[name] = function (arg, msg) { + if (arg === undefined || arg === null) { + return; + } + if (!type.check(arg)) { + _toss(msg, k, type.operator, arg, type.actual); + } + }; + }); + + /* arrayOf checks */ + keys.forEach(function (k) { + var name = 'arrayOf' + _capitalize(k); + if (ndebug) { + out[name] = noop; + return; + } + var type = types[k]; + var expected = '[' + k + ']'; + out[name] = function (arg, msg) { + if (!Array.isArray(arg)) { + _toss(msg, expected, type.operator, arg, type.actual); + } + var i; + for (i = 0; i < arg.length; i++) { + if (!type.check(arg[i])) { + _toss(msg, expected, type.operator, arg, type.actual); + } + } + }; + }); + + /* optionalArrayOf checks */ + keys.forEach(function (k) { + var name = 'optionalArrayOf' + _capitalize(k); + if (ndebug) { + out[name] = noop; + return; + } + var type = types[k]; + var expected = '[' + k + ']'; + out[name] = function (arg, msg) { + if (arg === undefined || arg === null) { + return; + } + if (!Array.isArray(arg)) { + _toss(msg, expected, type.operator, arg, type.actual); + } + var i; + for (i = 0; i < arg.length; i++) { + if (!type.check(arg[i])) { + _toss(msg, expected, type.operator, arg, type.actual); + } + } + }; + }); + + /* re-export built-in assertions */ + Object.keys(assert).forEach(function (k) { + if (k === 'AssertionError') { + out[k] = assert[k]; + return; + } + if (ndebug) { + out[k] = noop; + return; + } + out[k] = assert[k]; + }); + + /* export ourselves (for unit tests _only_) */ + out._setExports = _setExports; + + return out; +} + +module.exports = _setExports(process.env.NODE_NDEBUG); + + +/***/ }), +/* 17 */ +/***/ (function(module, exports) { + +// https://github.com/zloirock/core-js/issues/86#issuecomment-115759028 +var global = module.exports = typeof window != 'undefined' && window.Math == Math + ? window : typeof self != 'undefined' && self.Math == Math ? self + // eslint-disable-next-line no-new-func + : Function('return this')(); +if (typeof __g == 'number') __g = global; // eslint-disable-line no-undef + + +/***/ }), +/* 18 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.sortAlpha = sortAlpha; +exports.sortOptionsByFlags = sortOptionsByFlags; +exports.entries = entries; +exports.removePrefix = removePrefix; +exports.removeSuffix = removeSuffix; +exports.addSuffix = addSuffix; +exports.hyphenate = hyphenate; +exports.camelCase = camelCase; +exports.compareSortedArrays = compareSortedArrays; +exports.sleep = sleep; +const _camelCase = __webpack_require__(230); + +function sortAlpha(a, b) { + // sort alphabetically in a deterministic way + const shortLen = Math.min(a.length, b.length); + for (let i = 0; i < shortLen; i++) { + const aChar = a.charCodeAt(i); + const bChar = b.charCodeAt(i); + if (aChar !== bChar) { + return aChar - bChar; + } + } + return a.length - b.length; +} + +function sortOptionsByFlags(a, b) { + const aOpt = a.flags.replace(/-/g, ''); + const bOpt = b.flags.replace(/-/g, ''); + return sortAlpha(aOpt, bOpt); +} + +function entries(obj) { + const entries = []; + if (obj) { + for (const key in obj) { + entries.push([key, obj[key]]); + } + } + return entries; +} + +function removePrefix(pattern, prefix) { + if (pattern.startsWith(prefix)) { + pattern = pattern.slice(prefix.length); + } + + return pattern; +} + +function removeSuffix(pattern, suffix) { + if (pattern.endsWith(suffix)) { + return pattern.slice(0, -suffix.length); + } + + return pattern; +} + +function addSuffix(pattern, suffix) { + if (!pattern.endsWith(suffix)) { + return pattern + suffix; + } + + return pattern; +} + +function hyphenate(str) { + return str.replace(/[A-Z]/g, match => { + return '-' + match.charAt(0).toLowerCase(); + }); +} + +function camelCase(str) { + if (/[A-Z]/.test(str)) { + return null; + } else { + return _camelCase(str); + } +} + +function compareSortedArrays(array1, array2) { + if (array1.length !== array2.length) { + return false; + } + for (let i = 0, len = array1.length; i < len; i++) { + if (array1[i] !== array2[i]) { + return false; + } + } + return true; +} + +function sleep(ms) { + return new Promise(resolve => { + setTimeout(resolve, ms); + }); +} + +/***/ }), +/* 19 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.stringify = exports.parse = undefined; + +var _asyncToGenerator2; + +function _load_asyncToGenerator() { + return _asyncToGenerator2 = _interopRequireDefault(__webpack_require__(2)); +} + +var _parse; + +function _load_parse() { + return _parse = __webpack_require__(105); +} + +Object.defineProperty(exports, 'parse', { + enumerable: true, + get: function get() { + return _interopRequireDefault(_parse || _load_parse()).default; + } +}); + +var _stringify; + +function _load_stringify() { + return _stringify = __webpack_require__(199); +} + +Object.defineProperty(exports, 'stringify', { + enumerable: true, + get: function get() { + return _interopRequireDefault(_stringify || _load_stringify()).default; + } +}); +exports.implodeEntry = implodeEntry; +exports.explodeEntry = explodeEntry; + +var _misc; + +function _load_misc() { + return _misc = __webpack_require__(18); +} + +var _normalizePattern; + +function _load_normalizePattern() { + return _normalizePattern = __webpack_require__(37); +} + +var _parse2; + +function _load_parse2() { + return _parse2 = _interopRequireDefault(__webpack_require__(105)); +} + +var _constants; + +function _load_constants() { + return _constants = __webpack_require__(8); +} + +var _fs; + +function _load_fs() { + return _fs = _interopRequireWildcard(__webpack_require__(4)); +} + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const invariant = __webpack_require__(9); + +const path = __webpack_require__(0); +const ssri = __webpack_require__(65); + +function getName(pattern) { + return (0, (_normalizePattern || _load_normalizePattern()).normalizePattern)(pattern).name; +} + +function blankObjectUndefined(obj) { + return obj && Object.keys(obj).length ? obj : undefined; +} + +function keyForRemote(remote) { + return remote.resolved || (remote.reference && remote.hash ? `${remote.reference}#${remote.hash}` : null); +} + +function serializeIntegrity(integrity) { + // We need this because `Integrity.toString()` does not use sorting to ensure a stable string output + // See https://git.io/vx2Hy + return integrity.toString().split(' ').sort().join(' '); +} + +function implodeEntry(pattern, obj) { + const inferredName = getName(pattern); + const integrity = obj.integrity ? serializeIntegrity(obj.integrity) : ''; + const imploded = { + name: inferredName === obj.name ? undefined : obj.name, + version: obj.version, + uid: obj.uid === obj.version ? undefined : obj.uid, + resolved: obj.resolved, + registry: obj.registry === 'npm' ? undefined : obj.registry, + dependencies: blankObjectUndefined(obj.dependencies), + optionalDependencies: blankObjectUndefined(obj.optionalDependencies), + permissions: blankObjectUndefined(obj.permissions), + prebuiltVariants: blankObjectUndefined(obj.prebuiltVariants) + }; + if (integrity) { + imploded.integrity = integrity; + } + return imploded; +} + +function explodeEntry(pattern, obj) { + obj.optionalDependencies = obj.optionalDependencies || {}; + obj.dependencies = obj.dependencies || {}; + obj.uid = obj.uid || obj.version; + obj.permissions = obj.permissions || {}; + obj.registry = obj.registry || 'npm'; + obj.name = obj.name || getName(pattern); + const integrity = obj.integrity; + if (integrity && integrity.isIntegrity) { + obj.integrity = ssri.parse(integrity); + } + return obj; +} + +class Lockfile { + constructor({ cache, source, parseResultType } = {}) { + this.source = source || ''; + this.cache = cache; + this.parseResultType = parseResultType; + } + + // source string if the `cache` was parsed + + + // if true, we're parsing an old yarn file and need to update integrity fields + hasEntriesExistWithoutIntegrity() { + if (!this.cache) { + return false; + } + + for (const key in this.cache) { + // $FlowFixMe - `this.cache` is clearly defined at this point + if (!/^.*@(file:|http)/.test(key) && this.cache[key] && !this.cache[key].integrity) { + return true; + } + } + + return false; + } + + static fromDirectory(dir, reporter) { + return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { + // read the manifest in this directory + const lockfileLoc = path.join(dir, (_constants || _load_constants()).LOCKFILE_FILENAME); + + let lockfile; + let rawLockfile = ''; + let parseResult; + + if (yield (_fs || _load_fs()).exists(lockfileLoc)) { + rawLockfile = yield (_fs || _load_fs()).readFile(lockfileLoc); + parseResult = (0, (_parse2 || _load_parse2()).default)(rawLockfile, lockfileLoc); + + if (reporter) { + if (parseResult.type === 'merge') { + reporter.info(reporter.lang('lockfileMerged')); + } else if (parseResult.type === 'conflict') { + reporter.warn(reporter.lang('lockfileConflict')); + } + } + + lockfile = parseResult.object; + } else if (reporter) { + reporter.info(reporter.lang('noLockfileFound')); + } + + if (lockfile && lockfile.__metadata) { + const lockfilev2 = lockfile; + lockfile = {}; + } + + return new Lockfile({ cache: lockfile, source: rawLockfile, parseResultType: parseResult && parseResult.type }); + })(); + } + + getLocked(pattern) { + const cache = this.cache; + if (!cache) { + return undefined; + } + + const shrunk = pattern in cache && cache[pattern]; + + if (typeof shrunk === 'string') { + return this.getLocked(shrunk); + } else if (shrunk) { + explodeEntry(pattern, shrunk); + return shrunk; + } + + return undefined; + } + + removePattern(pattern) { + const cache = this.cache; + if (!cache) { + return; + } + delete cache[pattern]; + } + + getLockfile(patterns) { + const lockfile = {}; + const seen = new Map(); + + // order by name so that lockfile manifest is assigned to the first dependency with this manifest + // the others that have the same remoteKey will just refer to the first + // ordering allows for consistency in lockfile when it is serialized + const sortedPatternsKeys = Object.keys(patterns).sort((_misc || _load_misc()).sortAlpha); + + for (var _iterator = sortedPatternsKeys, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { + var _ref; + + if (_isArray) { + if (_i >= _iterator.length) break; + _ref = _iterator[_i++]; + } else { + _i = _iterator.next(); + if (_i.done) break; + _ref = _i.value; + } + + const pattern = _ref; + + const pkg = patterns[pattern]; + const remote = pkg._remote, + ref = pkg._reference; + + invariant(ref, 'Package is missing a reference'); + invariant(remote, 'Package is missing a remote'); + + const remoteKey = keyForRemote(remote); + const seenPattern = remoteKey && seen.get(remoteKey); + if (seenPattern) { + // no point in duplicating it + lockfile[pattern] = seenPattern; + + // if we're relying on our name being inferred and two of the patterns have + // different inferred names then we need to set it + if (!seenPattern.name && getName(pattern) !== pkg.name) { + seenPattern.name = pkg.name; + } + continue; + } + const obj = implodeEntry(pattern, { + name: pkg.name, + version: pkg.version, + uid: pkg._uid, + resolved: remote.resolved, + integrity: remote.integrity, + registry: remote.registry, + dependencies: pkg.dependencies, + peerDependencies: pkg.peerDependencies, + optionalDependencies: pkg.optionalDependencies, + permissions: ref.permissions, + prebuiltVariants: pkg.prebuiltVariants + }); + + lockfile[pattern] = obj; + + if (remoteKey) { + seen.set(remoteKey, obj); + } + } + + return lockfile; + } +} +exports.default = Lockfile; + +/***/ }), +/* 20 */ +/***/ (function(module, exports, __webpack_require__) { + +var store = __webpack_require__(133)('wks'); +var uid = __webpack_require__(137); +var Symbol = __webpack_require__(17).Symbol; +var USE_SYMBOL = typeof Symbol == 'function'; + +var $exports = module.exports = function (name) { + return store[name] || (store[name] = + USE_SYMBOL && Symbol[name] || (USE_SYMBOL ? Symbol : uid)('Symbol.' + name)); +}; + +$exports.store = store; + + +/***/ }), +/* 21 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +exports.__esModule = true; + +var _assign = __webpack_require__(591); + +var _assign2 = _interopRequireDefault(_assign); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +exports.default = _assign2.default || function (target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i]; + + for (var key in source) { + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } + } + + return target; +}; + +/***/ }), +/* 22 */ +/***/ (function(module, exports) { + +exports = module.exports = SemVer; + +// The debug function is excluded entirely from the minified version. +/* nomin */ var debug; +/* nomin */ if (typeof process === 'object' && + /* nomin */ process.env && + /* nomin */ process.env.NODE_DEBUG && + /* nomin */ /\bsemver\b/i.test(process.env.NODE_DEBUG)) + /* nomin */ debug = function() { + /* nomin */ var args = Array.prototype.slice.call(arguments, 0); + /* nomin */ args.unshift('SEMVER'); + /* nomin */ console.log.apply(console, args); + /* nomin */ }; +/* nomin */ else + /* nomin */ debug = function() {}; + +// Note: this is the semver.org version of the spec that it implements +// Not necessarily the package version of this code. +exports.SEMVER_SPEC_VERSION = '2.0.0'; + +var MAX_LENGTH = 256; +var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; + +// Max safe segment length for coercion. +var MAX_SAFE_COMPONENT_LENGTH = 16; + +// The actual regexps go on exports.re +var re = exports.re = []; +var src = exports.src = []; +var R = 0; + +// The following Regular Expressions can be used for tokenizing, +// validating, and parsing SemVer version strings. + +// ## Numeric Identifier +// A single `0`, or a non-zero digit followed by zero or more digits. + +var NUMERICIDENTIFIER = R++; +src[NUMERICIDENTIFIER] = '0|[1-9]\\d*'; +var NUMERICIDENTIFIERLOOSE = R++; +src[NUMERICIDENTIFIERLOOSE] = '[0-9]+'; + + +// ## Non-numeric Identifier +// Zero or more digits, followed by a letter or hyphen, and then zero or +// more letters, digits, or hyphens. + +var NONNUMERICIDENTIFIER = R++; +src[NONNUMERICIDENTIFIER] = '\\d*[a-zA-Z-][a-zA-Z0-9-]*'; + + +// ## Main Version +// Three dot-separated numeric identifiers. + +var MAINVERSION = R++; +src[MAINVERSION] = '(' + src[NUMERICIDENTIFIER] + ')\\.' + + '(' + src[NUMERICIDENTIFIER] + ')\\.' + + '(' + src[NUMERICIDENTIFIER] + ')'; + +var MAINVERSIONLOOSE = R++; +src[MAINVERSIONLOOSE] = '(' + src[NUMERICIDENTIFIERLOOSE] + ')\\.' + + '(' + src[NUMERICIDENTIFIERLOOSE] + ')\\.' + + '(' + src[NUMERICIDENTIFIERLOOSE] + ')'; + +// ## Pre-release Version Identifier +// A numeric identifier, or a non-numeric identifier. + +var PRERELEASEIDENTIFIER = R++; +src[PRERELEASEIDENTIFIER] = '(?:' + src[NUMERICIDENTIFIER] + + '|' + src[NONNUMERICIDENTIFIER] + ')'; + +var PRERELEASEIDENTIFIERLOOSE = R++; +src[PRERELEASEIDENTIFIERLOOSE] = '(?:' + src[NUMERICIDENTIFIERLOOSE] + + '|' + src[NONNUMERICIDENTIFIER] + ')'; + + +// ## Pre-release Version +// Hyphen, followed by one or more dot-separated pre-release version +// identifiers. + +var PRERELEASE = R++; +src[PRERELEASE] = '(?:-(' + src[PRERELEASEIDENTIFIER] + + '(?:\\.' + src[PRERELEASEIDENTIFIER] + ')*))'; + +var PRERELEASELOOSE = R++; +src[PRERELEASELOOSE] = '(?:-?(' + src[PRERELEASEIDENTIFIERLOOSE] + + '(?:\\.' + src[PRERELEASEIDENTIFIERLOOSE] + ')*))'; + +// ## Build Metadata Identifier +// Any combination of digits, letters, or hyphens. + +var BUILDIDENTIFIER = R++; +src[BUILDIDENTIFIER] = '[0-9A-Za-z-]+'; + +// ## Build Metadata +// Plus sign, followed by one or more period-separated build metadata +// identifiers. + +var BUILD = R++; +src[BUILD] = '(?:\\+(' + src[BUILDIDENTIFIER] + + '(?:\\.' + src[BUILDIDENTIFIER] + ')*))'; + + +// ## Full Version String +// A main version, followed optionally by a pre-release version and +// build metadata. + +// Note that the only major, minor, patch, and pre-release sections of +// the version string are capturing groups. The build metadata is not a +// capturing group, because it should not ever be used in version +// comparison. + +var FULL = R++; +var FULLPLAIN = 'v?' + src[MAINVERSION] + + src[PRERELEASE] + '?' + + src[BUILD] + '?'; + +src[FULL] = '^' + FULLPLAIN + '$'; + +// like full, but allows v1.2.3 and =1.2.3, which people do sometimes. +// also, 1.0.0alpha1 (prerelease without the hyphen) which is pretty +// common in the npm registry. +var LOOSEPLAIN = '[v=\\s]*' + src[MAINVERSIONLOOSE] + + src[PRERELEASELOOSE] + '?' + + src[BUILD] + '?'; + +var LOOSE = R++; +src[LOOSE] = '^' + LOOSEPLAIN + '$'; + +var GTLT = R++; +src[GTLT] = '((?:<|>)?=?)'; + +// Something like "2.*" or "1.2.x". +// Note that "x.x" is a valid xRange identifer, meaning "any version" +// Only the first item is strictly required. +var XRANGEIDENTIFIERLOOSE = R++; +src[XRANGEIDENTIFIERLOOSE] = src[NUMERICIDENTIFIERLOOSE] + '|x|X|\\*'; +var XRANGEIDENTIFIER = R++; +src[XRANGEIDENTIFIER] = src[NUMERICIDENTIFIER] + '|x|X|\\*'; + +var XRANGEPLAIN = R++; +src[XRANGEPLAIN] = '[v=\\s]*(' + src[XRANGEIDENTIFIER] + ')' + + '(?:\\.(' + src[XRANGEIDENTIFIER] + ')' + + '(?:\\.(' + src[XRANGEIDENTIFIER] + ')' + + '(?:' + src[PRERELEASE] + ')?' + + src[BUILD] + '?' + + ')?)?'; + +var XRANGEPLAINLOOSE = R++; +src[XRANGEPLAINLOOSE] = '[v=\\s]*(' + src[XRANGEIDENTIFIERLOOSE] + ')' + + '(?:\\.(' + src[XRANGEIDENTIFIERLOOSE] + ')' + + '(?:\\.(' + src[XRANGEIDENTIFIERLOOSE] + ')' + + '(?:' + src[PRERELEASELOOSE] + ')?' + + src[BUILD] + '?' + + ')?)?'; + +var XRANGE = R++; +src[XRANGE] = '^' + src[GTLT] + '\\s*' + src[XRANGEPLAIN] + '$'; +var XRANGELOOSE = R++; +src[XRANGELOOSE] = '^' + src[GTLT] + '\\s*' + src[XRANGEPLAINLOOSE] + '$'; + +// Coercion. +// Extract anything that could conceivably be a part of a valid semver +var COERCE = R++; +src[COERCE] = '(?:^|[^\\d])' + + '(\\d{1,' + MAX_SAFE_COMPONENT_LENGTH + '})' + + '(?:\\.(\\d{1,' + MAX_SAFE_COMPONENT_LENGTH + '}))?' + + '(?:\\.(\\d{1,' + MAX_SAFE_COMPONENT_LENGTH + '}))?' + + '(?:$|[^\\d])'; + +// Tilde ranges. +// Meaning is "reasonably at or greater than" +var LONETILDE = R++; +src[LONETILDE] = '(?:~>?)'; + +var TILDETRIM = R++; +src[TILDETRIM] = '(\\s*)' + src[LONETILDE] + '\\s+'; +re[TILDETRIM] = new RegExp(src[TILDETRIM], 'g'); +var tildeTrimReplace = '$1~'; + +var TILDE = R++; +src[TILDE] = '^' + src[LONETILDE] + src[XRANGEPLAIN] + '$'; +var TILDELOOSE = R++; +src[TILDELOOSE] = '^' + src[LONETILDE] + src[XRANGEPLAINLOOSE] + '$'; + +// Caret ranges. +// Meaning is "at least and backwards compatible with" +var LONECARET = R++; +src[LONECARET] = '(?:\\^)'; + +var CARETTRIM = R++; +src[CARETTRIM] = '(\\s*)' + src[LONECARET] + '\\s+'; +re[CARETTRIM] = new RegExp(src[CARETTRIM], 'g'); +var caretTrimReplace = '$1^'; + +var CARET = R++; +src[CARET] = '^' + src[LONECARET] + src[XRANGEPLAIN] + '$'; +var CARETLOOSE = R++; +src[CARETLOOSE] = '^' + src[LONECARET] + src[XRANGEPLAINLOOSE] + '$'; + +// A simple gt/lt/eq thing, or just "" to indicate "any version" +var COMPARATORLOOSE = R++; +src[COMPARATORLOOSE] = '^' + src[GTLT] + '\\s*(' + LOOSEPLAIN + ')$|^$'; +var COMPARATOR = R++; +src[COMPARATOR] = '^' + src[GTLT] + '\\s*(' + FULLPLAIN + ')$|^$'; + + +// An expression to strip any whitespace between the gtlt and the thing +// it modifies, so that `> 1.2.3` ==> `>1.2.3` +var COMPARATORTRIM = R++; +src[COMPARATORTRIM] = '(\\s*)' + src[GTLT] + + '\\s*(' + LOOSEPLAIN + '|' + src[XRANGEPLAIN] + ')'; + +// this one has to use the /g flag +re[COMPARATORTRIM] = new RegExp(src[COMPARATORTRIM], 'g'); +var comparatorTrimReplace = '$1$2$3'; + + +// Something like `1.2.3 - 1.2.4` +// Note that these all use the loose form, because they'll be +// checked against either the strict or loose comparator form +// later. +var HYPHENRANGE = R++; +src[HYPHENRANGE] = '^\\s*(' + src[XRANGEPLAIN] + ')' + + '\\s+-\\s+' + + '(' + src[XRANGEPLAIN] + ')' + + '\\s*$'; + +var HYPHENRANGELOOSE = R++; +src[HYPHENRANGELOOSE] = '^\\s*(' + src[XRANGEPLAINLOOSE] + ')' + + '\\s+-\\s+' + + '(' + src[XRANGEPLAINLOOSE] + ')' + + '\\s*$'; + +// Star ranges basically just allow anything at all. +var STAR = R++; +src[STAR] = '(<|>)?=?\\s*\\*'; + +// Compile to actual regexp objects. +// All are flag-free, unless they were created above with a flag. +for (var i = 0; i < R; i++) { + debug(i, src[i]); + if (!re[i]) + re[i] = new RegExp(src[i]); +} + +exports.parse = parse; +function parse(version, loose) { + if (version instanceof SemVer) + return version; + + if (typeof version !== 'string') + return null; + + if (version.length > MAX_LENGTH) + return null; + + var r = loose ? re[LOOSE] : re[FULL]; + if (!r.test(version)) + return null; + + try { + return new SemVer(version, loose); + } catch (er) { + return null; + } +} + +exports.valid = valid; +function valid(version, loose) { + var v = parse(version, loose); + return v ? v.version : null; +} + + +exports.clean = clean; +function clean(version, loose) { + var s = parse(version.trim().replace(/^[=v]+/, ''), loose); + return s ? s.version : null; +} + +exports.SemVer = SemVer; + +function SemVer(version, loose) { + if (version instanceof SemVer) { + if (version.loose === loose) + return version; + else + version = version.version; + } else if (typeof version !== 'string') { + throw new TypeError('Invalid Version: ' + version); + } + + if (version.length > MAX_LENGTH) + throw new TypeError('version is longer than ' + MAX_LENGTH + ' characters') + + if (!(this instanceof SemVer)) + return new SemVer(version, loose); + + debug('SemVer', version, loose); + this.loose = loose; + var m = version.trim().match(loose ? re[LOOSE] : re[FULL]); + + if (!m) + throw new TypeError('Invalid Version: ' + version); + + this.raw = version; + + // these are actually numbers + this.major = +m[1]; + this.minor = +m[2]; + this.patch = +m[3]; + + if (this.major > MAX_SAFE_INTEGER || this.major < 0) + throw new TypeError('Invalid major version') + + if (this.minor > MAX_SAFE_INTEGER || this.minor < 0) + throw new TypeError('Invalid minor version') + + if (this.patch > MAX_SAFE_INTEGER || this.patch < 0) + throw new TypeError('Invalid patch version') + + // numberify any prerelease numeric ids + if (!m[4]) + this.prerelease = []; + else + this.prerelease = m[4].split('.').map(function(id) { + if (/^[0-9]+$/.test(id)) { + var num = +id; + if (num >= 0 && num < MAX_SAFE_INTEGER) + return num; + } + return id; + }); + + this.build = m[5] ? m[5].split('.') : []; + this.format(); +} + +SemVer.prototype.format = function() { + this.version = this.major + '.' + this.minor + '.' + this.patch; + if (this.prerelease.length) + this.version += '-' + this.prerelease.join('.'); + return this.version; +}; + +SemVer.prototype.toString = function() { + return this.version; +}; + +SemVer.prototype.compare = function(other) { + debug('SemVer.compare', this.version, this.loose, other); + if (!(other instanceof SemVer)) + other = new SemVer(other, this.loose); + + return this.compareMain(other) || this.comparePre(other); +}; + +SemVer.prototype.compareMain = function(other) { + if (!(other instanceof SemVer)) + other = new SemVer(other, this.loose); + + return compareIdentifiers(this.major, other.major) || + compareIdentifiers(this.minor, other.minor) || + compareIdentifiers(this.patch, other.patch); +}; + +SemVer.prototype.comparePre = function(other) { + if (!(other instanceof SemVer)) + other = new SemVer(other, this.loose); + + // NOT having a prerelease is > having one + if (this.prerelease.length && !other.prerelease.length) + return -1; + else if (!this.prerelease.length && other.prerelease.length) + return 1; + else if (!this.prerelease.length && !other.prerelease.length) + return 0; + + var i = 0; + do { + var a = this.prerelease[i]; + var b = other.prerelease[i]; + debug('prerelease compare', i, a, b); + if (a === undefined && b === undefined) + return 0; + else if (b === undefined) + return 1; + else if (a === undefined) + return -1; + else if (a === b) + continue; + else + return compareIdentifiers(a, b); + } while (++i); +}; + +// preminor will bump the version up to the next minor release, and immediately +// down to pre-release. premajor and prepatch work the same way. +SemVer.prototype.inc = function(release, identifier) { + switch (release) { + case 'premajor': + this.prerelease.length = 0; + this.patch = 0; + this.minor = 0; + this.major++; + this.inc('pre', identifier); + break; + case 'preminor': + this.prerelease.length = 0; + this.patch = 0; + this.minor++; + this.inc('pre', identifier); + break; + case 'prepatch': + // If this is already a prerelease, it will bump to the next version + // drop any prereleases that might already exist, since they are not + // relevant at this point. + this.prerelease.length = 0; + this.inc('patch', identifier); + this.inc('pre', identifier); + break; + // If the input is a non-prerelease version, this acts the same as + // prepatch. + case 'prerelease': + if (this.prerelease.length === 0) + this.inc('patch', identifier); + this.inc('pre', identifier); + break; + + case 'major': + // If this is a pre-major version, bump up to the same major version. + // Otherwise increment major. + // 1.0.0-5 bumps to 1.0.0 + // 1.1.0 bumps to 2.0.0 + if (this.minor !== 0 || this.patch !== 0 || this.prerelease.length === 0) + this.major++; + this.minor = 0; + this.patch = 0; + this.prerelease = []; + break; + case 'minor': + // If this is a pre-minor version, bump up to the same minor version. + // Otherwise increment minor. + // 1.2.0-5 bumps to 1.2.0 + // 1.2.1 bumps to 1.3.0 + if (this.patch !== 0 || this.prerelease.length === 0) + this.minor++; + this.patch = 0; + this.prerelease = []; + break; + case 'patch': + // If this is not a pre-release version, it will increment the patch. + // If it is a pre-release it will bump up to the same patch version. + // 1.2.0-5 patches to 1.2.0 + // 1.2.0 patches to 1.2.1 + if (this.prerelease.length === 0) + this.patch++; + this.prerelease = []; + break; + // This probably shouldn't be used publicly. + // 1.0.0 "pre" would become 1.0.0-0 which is the wrong direction. + case 'pre': + if (this.prerelease.length === 0) + this.prerelease = [0]; + else { + var i = this.prerelease.length; + while (--i >= 0) { + if (typeof this.prerelease[i] === 'number') { + this.prerelease[i]++; + i = -2; + } + } + if (i === -1) // didn't increment anything + this.prerelease.push(0); + } + if (identifier) { + // 1.2.0-beta.1 bumps to 1.2.0-beta.2, + // 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0 + if (this.prerelease[0] === identifier) { + if (isNaN(this.prerelease[1])) + this.prerelease = [identifier, 0]; + } else + this.prerelease = [identifier, 0]; + } + break; + + default: + throw new Error('invalid increment argument: ' + release); + } + this.format(); + this.raw = this.version; + return this; +}; + +exports.inc = inc; +function inc(version, release, loose, identifier) { + if (typeof(loose) === 'string') { + identifier = loose; + loose = undefined; + } + + try { + return new SemVer(version, loose).inc(release, identifier).version; + } catch (er) { + return null; + } +} + +exports.diff = diff; +function diff(version1, version2) { + if (eq(version1, version2)) { + return null; + } else { + var v1 = parse(version1); + var v2 = parse(version2); + if (v1.prerelease.length || v2.prerelease.length) { + for (var key in v1) { + if (key === 'major' || key === 'minor' || key === 'patch') { + if (v1[key] !== v2[key]) { + return 'pre'+key; + } + } + } + return 'prerelease'; + } + for (var key in v1) { + if (key === 'major' || key === 'minor' || key === 'patch') { + if (v1[key] !== v2[key]) { + return key; + } + } + } + } +} + +exports.compareIdentifiers = compareIdentifiers; + +var numeric = /^[0-9]+$/; +function compareIdentifiers(a, b) { + var anum = numeric.test(a); + var bnum = numeric.test(b); + + if (anum && bnum) { + a = +a; + b = +b; + } + + return (anum && !bnum) ? -1 : + (bnum && !anum) ? 1 : + a < b ? -1 : + a > b ? 1 : + 0; +} + +exports.rcompareIdentifiers = rcompareIdentifiers; +function rcompareIdentifiers(a, b) { + return compareIdentifiers(b, a); +} + +exports.major = major; +function major(a, loose) { + return new SemVer(a, loose).major; +} + +exports.minor = minor; +function minor(a, loose) { + return new SemVer(a, loose).minor; +} + +exports.patch = patch; +function patch(a, loose) { + return new SemVer(a, loose).patch; +} + +exports.compare = compare; +function compare(a, b, loose) { + return new SemVer(a, loose).compare(new SemVer(b, loose)); +} + +exports.compareLoose = compareLoose; +function compareLoose(a, b) { + return compare(a, b, true); +} + +exports.rcompare = rcompare; +function rcompare(a, b, loose) { + return compare(b, a, loose); +} + +exports.sort = sort; +function sort(list, loose) { + return list.sort(function(a, b) { + return exports.compare(a, b, loose); + }); +} + +exports.rsort = rsort; +function rsort(list, loose) { + return list.sort(function(a, b) { + return exports.rcompare(a, b, loose); + }); +} + +exports.gt = gt; +function gt(a, b, loose) { + return compare(a, b, loose) > 0; +} + +exports.lt = lt; +function lt(a, b, loose) { + return compare(a, b, loose) < 0; +} + +exports.eq = eq; +function eq(a, b, loose) { + return compare(a, b, loose) === 0; +} + +exports.neq = neq; +function neq(a, b, loose) { + return compare(a, b, loose) !== 0; +} + +exports.gte = gte; +function gte(a, b, loose) { + return compare(a, b, loose) >= 0; +} + +exports.lte = lte; +function lte(a, b, loose) { + return compare(a, b, loose) <= 0; +} + +exports.cmp = cmp; +function cmp(a, op, b, loose) { + var ret; + switch (op) { + case '===': + if (typeof a === 'object') a = a.version; + if (typeof b === 'object') b = b.version; + ret = a === b; + break; + case '!==': + if (typeof a === 'object') a = a.version; + if (typeof b === 'object') b = b.version; + ret = a !== b; + break; + case '': case '=': case '==': ret = eq(a, b, loose); break; + case '!=': ret = neq(a, b, loose); break; + case '>': ret = gt(a, b, loose); break; + case '>=': ret = gte(a, b, loose); break; + case '<': ret = lt(a, b, loose); break; + case '<=': ret = lte(a, b, loose); break; + default: throw new TypeError('Invalid operator: ' + op); + } + return ret; +} + +exports.Comparator = Comparator; +function Comparator(comp, loose) { + if (comp instanceof Comparator) { + if (comp.loose === loose) + return comp; + else + comp = comp.value; + } + + if (!(this instanceof Comparator)) + return new Comparator(comp, loose); + + debug('comparator', comp, loose); + this.loose = loose; + this.parse(comp); + + if (this.semver === ANY) + this.value = ''; + else + this.value = this.operator + this.semver.version; + + debug('comp', this); +} + +var ANY = {}; +Comparator.prototype.parse = function(comp) { + var r = this.loose ? re[COMPARATORLOOSE] : re[COMPARATOR]; + var m = comp.match(r); + + if (!m) + throw new TypeError('Invalid comparator: ' + comp); + + this.operator = m[1]; + if (this.operator === '=') + this.operator = ''; + + // if it literally is just '>' or '' then allow anything. + if (!m[2]) + this.semver = ANY; + else + this.semver = new SemVer(m[2], this.loose); +}; + +Comparator.prototype.toString = function() { + return this.value; +}; + +Comparator.prototype.test = function(version) { + debug('Comparator.test', version, this.loose); + + if (this.semver === ANY) + return true; + + if (typeof version === 'string') + version = new SemVer(version, this.loose); + + return cmp(version, this.operator, this.semver, this.loose); +}; + +Comparator.prototype.intersects = function(comp, loose) { + if (!(comp instanceof Comparator)) { + throw new TypeError('a Comparator is required'); + } + + var rangeTmp; + + if (this.operator === '') { + rangeTmp = new Range(comp.value, loose); + return satisfies(this.value, rangeTmp, loose); + } else if (comp.operator === '') { + rangeTmp = new Range(this.value, loose); + return satisfies(comp.semver, rangeTmp, loose); + } + + var sameDirectionIncreasing = + (this.operator === '>=' || this.operator === '>') && + (comp.operator === '>=' || comp.operator === '>'); + var sameDirectionDecreasing = + (this.operator === '<=' || this.operator === '<') && + (comp.operator === '<=' || comp.operator === '<'); + var sameSemVer = this.semver.version === comp.semver.version; + var differentDirectionsInclusive = + (this.operator === '>=' || this.operator === '<=') && + (comp.operator === '>=' || comp.operator === '<='); + var oppositeDirectionsLessThan = + cmp(this.semver, '<', comp.semver, loose) && + ((this.operator === '>=' || this.operator === '>') && + (comp.operator === '<=' || comp.operator === '<')); + var oppositeDirectionsGreaterThan = + cmp(this.semver, '>', comp.semver, loose) && + ((this.operator === '<=' || this.operator === '<') && + (comp.operator === '>=' || comp.operator === '>')); + + return sameDirectionIncreasing || sameDirectionDecreasing || + (sameSemVer && differentDirectionsInclusive) || + oppositeDirectionsLessThan || oppositeDirectionsGreaterThan; +}; + + +exports.Range = Range; +function Range(range, loose) { + if (range instanceof Range) { + if (range.loose === loose) { + return range; + } else { + return new Range(range.raw, loose); + } + } + + if (range instanceof Comparator) { + return new Range(range.value, loose); + } + + if (!(this instanceof Range)) + return new Range(range, loose); + + this.loose = loose; + + // First, split based on boolean or || + this.raw = range; + this.set = range.split(/\s*\|\|\s*/).map(function(range) { + return this.parseRange(range.trim()); + }, this).filter(function(c) { + // throw out any that are not relevant for whatever reason + return c.length; + }); + + if (!this.set.length) { + throw new TypeError('Invalid SemVer Range: ' + range); + } + + this.format(); +} + +Range.prototype.format = function() { + this.range = this.set.map(function(comps) { + return comps.join(' ').trim(); + }).join('||').trim(); + return this.range; +}; + +Range.prototype.toString = function() { + return this.range; +}; + +Range.prototype.parseRange = function(range) { + var loose = this.loose; + range = range.trim(); + debug('range', range, loose); + // `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4` + var hr = loose ? re[HYPHENRANGELOOSE] : re[HYPHENRANGE]; + range = range.replace(hr, hyphenReplace); + debug('hyphen replace', range); + // `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5` + range = range.replace(re[COMPARATORTRIM], comparatorTrimReplace); + debug('comparator trim', range, re[COMPARATORTRIM]); + + // `~ 1.2.3` => `~1.2.3` + range = range.replace(re[TILDETRIM], tildeTrimReplace); + + // `^ 1.2.3` => `^1.2.3` + range = range.replace(re[CARETTRIM], caretTrimReplace); + + // normalize spaces + range = range.split(/\s+/).join(' '); + + // At this point, the range is completely trimmed and + // ready to be split into comparators. + + var compRe = loose ? re[COMPARATORLOOSE] : re[COMPARATOR]; + var set = range.split(' ').map(function(comp) { + return parseComparator(comp, loose); + }).join(' ').split(/\s+/); + if (this.loose) { + // in loose mode, throw out any that are not valid comparators + set = set.filter(function(comp) { + return !!comp.match(compRe); + }); + } + set = set.map(function(comp) { + return new Comparator(comp, loose); + }); + + return set; +}; + +Range.prototype.intersects = function(range, loose) { + if (!(range instanceof Range)) { + throw new TypeError('a Range is required'); + } + + return this.set.some(function(thisComparators) { + return thisComparators.every(function(thisComparator) { + return range.set.some(function(rangeComparators) { + return rangeComparators.every(function(rangeComparator) { + return thisComparator.intersects(rangeComparator, loose); + }); + }); + }); + }); +}; + +// Mostly just for testing and legacy API reasons +exports.toComparators = toComparators; +function toComparators(range, loose) { + return new Range(range, loose).set.map(function(comp) { + return comp.map(function(c) { + return c.value; + }).join(' ').trim().split(' '); + }); +} + +// comprised of xranges, tildes, stars, and gtlt's at this point. +// already replaced the hyphen ranges +// turn into a set of JUST comparators. +function parseComparator(comp, loose) { + debug('comp', comp); + comp = replaceCarets(comp, loose); + debug('caret', comp); + comp = replaceTildes(comp, loose); + debug('tildes', comp); + comp = replaceXRanges(comp, loose); + debug('xrange', comp); + comp = replaceStars(comp, loose); + debug('stars', comp); + return comp; +} + +function isX(id) { + return !id || id.toLowerCase() === 'x' || id === '*'; +} + +// ~, ~> --> * (any, kinda silly) +// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0 +// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0 +// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0 +// ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0 +// ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0 +function replaceTildes(comp, loose) { + return comp.trim().split(/\s+/).map(function(comp) { + return replaceTilde(comp, loose); + }).join(' '); +} + +function replaceTilde(comp, loose) { + var r = loose ? re[TILDELOOSE] : re[TILDE]; + return comp.replace(r, function(_, M, m, p, pr) { + debug('tilde', comp, _, M, m, p, pr); + var ret; + + if (isX(M)) + ret = ''; + else if (isX(m)) + ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0'; + else if (isX(p)) + // ~1.2 == >=1.2.0 <1.3.0 + ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0'; + else if (pr) { + debug('replaceTilde pr', pr); + if (pr.charAt(0) !== '-') + pr = '-' + pr; + ret = '>=' + M + '.' + m + '.' + p + pr + + ' <' + M + '.' + (+m + 1) + '.0'; + } else + // ~1.2.3 == >=1.2.3 <1.3.0 + ret = '>=' + M + '.' + m + '.' + p + + ' <' + M + '.' + (+m + 1) + '.0'; + + debug('tilde return', ret); + return ret; + }); +} + +// ^ --> * (any, kinda silly) +// ^2, ^2.x, ^2.x.x --> >=2.0.0 <3.0.0 +// ^2.0, ^2.0.x --> >=2.0.0 <3.0.0 +// ^1.2, ^1.2.x --> >=1.2.0 <2.0.0 +// ^1.2.3 --> >=1.2.3 <2.0.0 +// ^1.2.0 --> >=1.2.0 <2.0.0 +function replaceCarets(comp, loose) { + return comp.trim().split(/\s+/).map(function(comp) { + return replaceCaret(comp, loose); + }).join(' '); +} + +function replaceCaret(comp, loose) { + debug('caret', comp, loose); + var r = loose ? re[CARETLOOSE] : re[CARET]; + return comp.replace(r, function(_, M, m, p, pr) { + debug('caret', comp, _, M, m, p, pr); + var ret; + + if (isX(M)) + ret = ''; + else if (isX(m)) + ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0'; + else if (isX(p)) { + if (M === '0') + ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0'; + else + ret = '>=' + M + '.' + m + '.0 <' + (+M + 1) + '.0.0'; + } else if (pr) { + debug('replaceCaret pr', pr); + if (pr.charAt(0) !== '-') + pr = '-' + pr; + if (M === '0') { + if (m === '0') + ret = '>=' + M + '.' + m + '.' + p + pr + + ' <' + M + '.' + m + '.' + (+p + 1); + else + ret = '>=' + M + '.' + m + '.' + p + pr + + ' <' + M + '.' + (+m + 1) + '.0'; + } else + ret = '>=' + M + '.' + m + '.' + p + pr + + ' <' + (+M + 1) + '.0.0'; + } else { + debug('no pr'); + if (M === '0') { + if (m === '0') + ret = '>=' + M + '.' + m + '.' + p + + ' <' + M + '.' + m + '.' + (+p + 1); + else + ret = '>=' + M + '.' + m + '.' + p + + ' <' + M + '.' + (+m + 1) + '.0'; + } else + ret = '>=' + M + '.' + m + '.' + p + + ' <' + (+M + 1) + '.0.0'; + } + + debug('caret return', ret); + return ret; + }); +} + +function replaceXRanges(comp, loose) { + debug('replaceXRanges', comp, loose); + return comp.split(/\s+/).map(function(comp) { + return replaceXRange(comp, loose); + }).join(' '); +} + +function replaceXRange(comp, loose) { + comp = comp.trim(); + var r = loose ? re[XRANGELOOSE] : re[XRANGE]; + return comp.replace(r, function(ret, gtlt, M, m, p, pr) { + debug('xRange', comp, ret, gtlt, M, m, p, pr); + var xM = isX(M); + var xm = xM || isX(m); + var xp = xm || isX(p); + var anyX = xp; + + if (gtlt === '=' && anyX) + gtlt = ''; + + if (xM) { + if (gtlt === '>' || gtlt === '<') { + // nothing is allowed + ret = '<0.0.0'; + } else { + // nothing is forbidden + ret = '*'; + } + } else if (gtlt && anyX) { + // replace X with 0 + if (xm) + m = 0; + if (xp) + p = 0; + + if (gtlt === '>') { + // >1 => >=2.0.0 + // >1.2 => >=1.3.0 + // >1.2.3 => >= 1.2.4 + gtlt = '>='; + if (xm) { + M = +M + 1; + m = 0; + p = 0; + } else if (xp) { + m = +m + 1; + p = 0; + } + } else if (gtlt === '<=') { + // <=0.7.x is actually <0.8.0, since any 0.7.x should + // pass. Similarly, <=7.x is actually <8.0.0, etc. + gtlt = '<'; + if (xm) + M = +M + 1; + else + m = +m + 1; + } + + ret = gtlt + M + '.' + m + '.' + p; + } else if (xm) { + ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0'; + } else if (xp) { + ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0'; + } + + debug('xRange return', ret); + + return ret; + }); +} + +// Because * is AND-ed with everything else in the comparator, +// and '' means "any version", just remove the *s entirely. +function replaceStars(comp, loose) { + debug('replaceStars', comp, loose); + // Looseness is ignored here. star is always as loose as it gets! + return comp.trim().replace(re[STAR], ''); +} + +// This function is passed to string.replace(re[HYPHENRANGE]) +// M, m, patch, prerelease, build +// 1.2 - 3.4.5 => >=1.2.0 <=3.4.5 +// 1.2.3 - 3.4 => >=1.2.0 <3.5.0 Any 3.4.x will do +// 1.2 - 3.4 => >=1.2.0 <3.5.0 +function hyphenReplace($0, + from, fM, fm, fp, fpr, fb, + to, tM, tm, tp, tpr, tb) { + + if (isX(fM)) + from = ''; + else if (isX(fm)) + from = '>=' + fM + '.0.0'; + else if (isX(fp)) + from = '>=' + fM + '.' + fm + '.0'; + else + from = '>=' + from; + + if (isX(tM)) + to = ''; + else if (isX(tm)) + to = '<' + (+tM + 1) + '.0.0'; + else if (isX(tp)) + to = '<' + tM + '.' + (+tm + 1) + '.0'; + else if (tpr) + to = '<=' + tM + '.' + tm + '.' + tp + '-' + tpr; + else + to = '<=' + to; + + return (from + ' ' + to).trim(); +} + + +// if ANY of the sets match ALL of its comparators, then pass +Range.prototype.test = function(version) { + if (!version) + return false; + + if (typeof version === 'string') + version = new SemVer(version, this.loose); + + for (var i = 0; i < this.set.length; i++) { + if (testSet(this.set[i], version)) + return true; + } + return false; +}; + +function testSet(set, version) { + for (var i = 0; i < set.length; i++) { + if (!set[i].test(version)) + return false; + } + + if (version.prerelease.length) { + // Find the set of versions that are allowed to have prereleases + // For example, ^1.2.3-pr.1 desugars to >=1.2.3-pr.1 <2.0.0 + // That should allow `1.2.3-pr.2` to pass. + // However, `1.2.4-alpha.notready` should NOT be allowed, + // even though it's within the range set by the comparators. + for (var i = 0; i < set.length; i++) { + debug(set[i].semver); + if (set[i].semver === ANY) + continue; + + if (set[i].semver.prerelease.length > 0) { + var allowed = set[i].semver; + if (allowed.major === version.major && + allowed.minor === version.minor && + allowed.patch === version.patch) + return true; + } + } + + // Version has a -pre, but it's not one of the ones we like. + return false; + } + + return true; +} + +exports.satisfies = satisfies; +function satisfies(version, range, loose) { + try { + range = new Range(range, loose); + } catch (er) { + return false; + } + return range.test(version); +} + +exports.maxSatisfying = maxSatisfying; +function maxSatisfying(versions, range, loose) { + var max = null; + var maxSV = null; + try { + var rangeObj = new Range(range, loose); + } catch (er) { + return null; + } + versions.forEach(function (v) { + if (rangeObj.test(v)) { // satisfies(v, range, loose) + if (!max || maxSV.compare(v) === -1) { // compare(max, v, true) + max = v; + maxSV = new SemVer(max, loose); + } + } + }) + return max; +} + +exports.minSatisfying = minSatisfying; +function minSatisfying(versions, range, loose) { + var min = null; + var minSV = null; + try { + var rangeObj = new Range(range, loose); + } catch (er) { + return null; + } + versions.forEach(function (v) { + if (rangeObj.test(v)) { // satisfies(v, range, loose) + if (!min || minSV.compare(v) === 1) { // compare(min, v, true) + min = v; + minSV = new SemVer(min, loose); + } + } + }) + return min; +} + +exports.validRange = validRange; +function validRange(range, loose) { + try { + // Return '*' instead of '' so that truthiness works. + // This will throw if it's invalid anyway + return new Range(range, loose).range || '*'; + } catch (er) { + return null; + } +} + +// Determine if version is less than all the versions possible in the range +exports.ltr = ltr; +function ltr(version, range, loose) { + return outside(version, range, '<', loose); +} + +// Determine if version is greater than all the versions possible in the range. +exports.gtr = gtr; +function gtr(version, range, loose) { + return outside(version, range, '>', loose); +} + +exports.outside = outside; +function outside(version, range, hilo, loose) { + version = new SemVer(version, loose); + range = new Range(range, loose); + + var gtfn, ltefn, ltfn, comp, ecomp; + switch (hilo) { + case '>': + gtfn = gt; + ltefn = lte; + ltfn = lt; + comp = '>'; + ecomp = '>='; + break; + case '<': + gtfn = lt; + ltefn = gte; + ltfn = gt; + comp = '<'; + ecomp = '<='; + break; + default: + throw new TypeError('Must provide a hilo val of "<" or ">"'); + } + + // If it satisifes the range it is not outside + if (satisfies(version, range, loose)) { + return false; + } + + // From now on, variable terms are as if we're in "gtr" mode. + // but note that everything is flipped for the "ltr" function. + + for (var i = 0; i < range.set.length; ++i) { + var comparators = range.set[i]; + + var high = null; + var low = null; + + comparators.forEach(function(comparator) { + if (comparator.semver === ANY) { + comparator = new Comparator('>=0.0.0') + } + high = high || comparator; + low = low || comparator; + if (gtfn(comparator.semver, high.semver, loose)) { + high = comparator; + } else if (ltfn(comparator.semver, low.semver, loose)) { + low = comparator; + } + }); + + // If the edge version comparator has a operator then our version + // isn't outside it + if (high.operator === comp || high.operator === ecomp) { + return false; + } + + // If the lowest version comparator has an operator and our version + // is less than it then it isn't higher than the range + if ((!low.operator || low.operator === comp) && + ltefn(version, low.semver)) { + return false; + } else if (low.operator === ecomp && ltfn(version, low.semver)) { + return false; + } + } + return true; +} + +exports.prerelease = prerelease; +function prerelease(version, loose) { + var parsed = parse(version, loose); + return (parsed && parsed.prerelease.length) ? parsed.prerelease : null; +} + +exports.intersects = intersects; +function intersects(r1, r2, loose) { + r1 = new Range(r1, loose) + r2 = new Range(r2, loose) + return r1.intersects(r2) +} + +exports.coerce = coerce; +function coerce(version) { + if (version instanceof SemVer) + return version; + + if (typeof version !== 'string') + return null; + + var match = version.match(re[COERCE]); + + if (match == null) + return null; + + return parse((match[1] || '0') + '.' + (match[2] || '0') + '.' + (match[3] || '0')); +} + + +/***/ }), +/* 23 */ +/***/ (function(module, exports) { + +module.exports = require("stream"); + +/***/ }), +/* 24 */ +/***/ (function(module, exports) { + +module.exports = require("url"); + +/***/ }), +/* 25 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return Subscription; }); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__util_isArray__ = __webpack_require__(41); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__util_isObject__ = __webpack_require__(444); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__util_isFunction__ = __webpack_require__(154); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__util_tryCatch__ = __webpack_require__(56); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__util_errorObject__ = __webpack_require__(48); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__util_UnsubscriptionError__ = __webpack_require__(441); +/** PURE_IMPORTS_START _util_isArray,_util_isObject,_util_isFunction,_util_tryCatch,_util_errorObject,_util_UnsubscriptionError PURE_IMPORTS_END */ + + + + + + +var Subscription = /*@__PURE__*/ (function () { + function Subscription(unsubscribe) { + this.closed = false; + this._parent = null; + this._parents = null; + this._subscriptions = null; + if (unsubscribe) { + this._unsubscribe = unsubscribe; + } + } + Subscription.prototype.unsubscribe = function () { + var hasErrors = false; + var errors; + if (this.closed) { + return; + } + var _a = this, _parent = _a._parent, _parents = _a._parents, _unsubscribe = _a._unsubscribe, _subscriptions = _a._subscriptions; + this.closed = true; + this._parent = null; + this._parents = null; + this._subscriptions = null; + var index = -1; + var len = _parents ? _parents.length : 0; + while (_parent) { + _parent.remove(this); + _parent = ++index < len && _parents[index] || null; + } + if (__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_2__util_isFunction__["a" /* isFunction */])(_unsubscribe)) { + var trial = __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_3__util_tryCatch__["a" /* tryCatch */])(_unsubscribe).call(this); + if (trial === __WEBPACK_IMPORTED_MODULE_4__util_errorObject__["a" /* errorObject */]) { + hasErrors = true; + errors = errors || (__WEBPACK_IMPORTED_MODULE_4__util_errorObject__["a" /* errorObject */].e instanceof __WEBPACK_IMPORTED_MODULE_5__util_UnsubscriptionError__["a" /* UnsubscriptionError */] ? + flattenUnsubscriptionErrors(__WEBPACK_IMPORTED_MODULE_4__util_errorObject__["a" /* errorObject */].e.errors) : [__WEBPACK_IMPORTED_MODULE_4__util_errorObject__["a" /* errorObject */].e]); + } + } + if (__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__util_isArray__["a" /* isArray */])(_subscriptions)) { + index = -1; + len = _subscriptions.length; + while (++index < len) { + var sub = _subscriptions[index]; + if (__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_1__util_isObject__["a" /* isObject */])(sub)) { + var trial = __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_3__util_tryCatch__["a" /* tryCatch */])(sub.unsubscribe).call(sub); + if (trial === __WEBPACK_IMPORTED_MODULE_4__util_errorObject__["a" /* errorObject */]) { + hasErrors = true; + errors = errors || []; + var err = __WEBPACK_IMPORTED_MODULE_4__util_errorObject__["a" /* errorObject */].e; + if (err instanceof __WEBPACK_IMPORTED_MODULE_5__util_UnsubscriptionError__["a" /* UnsubscriptionError */]) { + errors = errors.concat(flattenUnsubscriptionErrors(err.errors)); + } + else { + errors.push(err); + } + } + } + } + } + if (hasErrors) { + throw new __WEBPACK_IMPORTED_MODULE_5__util_UnsubscriptionError__["a" /* UnsubscriptionError */](errors); + } + }; + Subscription.prototype.add = function (teardown) { + if (!teardown || (teardown === Subscription.EMPTY)) { + return Subscription.EMPTY; + } + if (teardown === this) { + return this; + } + var subscription = teardown; + switch (typeof teardown) { + case 'function': + subscription = new Subscription(teardown); + case 'object': + if (subscription.closed || typeof subscription.unsubscribe !== 'function') { + return subscription; + } + else if (this.closed) { + subscription.unsubscribe(); + return subscription; + } + else if (typeof subscription._addParent !== 'function') { + var tmp = subscription; + subscription = new Subscription(); + subscription._subscriptions = [tmp]; + } + break; + default: + throw new Error('unrecognized teardown ' + teardown + ' added to Subscription.'); + } + var subscriptions = this._subscriptions || (this._subscriptions = []); + subscriptions.push(subscription); + subscription._addParent(this); + return subscription; + }; + Subscription.prototype.remove = function (subscription) { + var subscriptions = this._subscriptions; + if (subscriptions) { + var subscriptionIndex = subscriptions.indexOf(subscription); + if (subscriptionIndex !== -1) { + subscriptions.splice(subscriptionIndex, 1); + } + } + }; + Subscription.prototype._addParent = function (parent) { + var _a = this, _parent = _a._parent, _parents = _a._parents; + if (!_parent || _parent === parent) { + this._parent = parent; + } + else if (!_parents) { + this._parents = [parent]; + } + else if (_parents.indexOf(parent) === -1) { + _parents.push(parent); + } + }; + Subscription.EMPTY = (function (empty) { + empty.closed = true; + return empty; + }(new Subscription())); + return Subscription; +}()); + +function flattenUnsubscriptionErrors(errors) { + return errors.reduce(function (errs, err) { return errs.concat((err instanceof __WEBPACK_IMPORTED_MODULE_5__util_UnsubscriptionError__["a" /* UnsubscriptionError */]) ? err.errors : err); }, []); +} +//# sourceMappingURL=Subscription.js.map + + +/***/ }), +/* 26 */ +/***/ (function(module, exports, __webpack_require__) { + +// Copyright 2015 Joyent, Inc. + +module.exports = { + bufferSplit: bufferSplit, + addRSAMissing: addRSAMissing, + calculateDSAPublic: calculateDSAPublic, + calculateED25519Public: calculateED25519Public, + calculateX25519Public: calculateX25519Public, + mpNormalize: mpNormalize, + mpDenormalize: mpDenormalize, + ecNormalize: ecNormalize, + countZeros: countZeros, + assertCompatible: assertCompatible, + isCompatible: isCompatible, + opensslKeyDeriv: opensslKeyDeriv, + opensshCipherInfo: opensshCipherInfo, + publicFromPrivateECDSA: publicFromPrivateECDSA, + zeroPadToLength: zeroPadToLength, + writeBitString: writeBitString, + readBitString: readBitString +}; + +var assert = __webpack_require__(16); +var Buffer = __webpack_require__(15).Buffer; +var PrivateKey = __webpack_require__(33); +var Key = __webpack_require__(27); +var crypto = __webpack_require__(11); +var algs = __webpack_require__(32); +var asn1 = __webpack_require__(66); + +var ec, jsbn; +var nacl; + +var MAX_CLASS_DEPTH = 3; + +function isCompatible(obj, klass, needVer) { + if (obj === null || typeof (obj) !== 'object') + return (false); + if (needVer === undefined) + needVer = klass.prototype._sshpkApiVersion; + if (obj instanceof klass && + klass.prototype._sshpkApiVersion[0] == needVer[0]) + return (true); + var proto = Object.getPrototypeOf(obj); + var depth = 0; + while (proto.constructor.name !== klass.name) { + proto = Object.getPrototypeOf(proto); + if (!proto || ++depth > MAX_CLASS_DEPTH) + return (false); + } + if (proto.constructor.name !== klass.name) + return (false); + var ver = proto._sshpkApiVersion; + if (ver === undefined) + ver = klass._oldVersionDetect(obj); + if (ver[0] != needVer[0] || ver[1] < needVer[1]) + return (false); + return (true); +} + +function assertCompatible(obj, klass, needVer, name) { + if (name === undefined) + name = 'object'; + assert.ok(obj, name + ' must not be null'); + assert.object(obj, name + ' must be an object'); + if (needVer === undefined) + needVer = klass.prototype._sshpkApiVersion; + if (obj instanceof klass && + klass.prototype._sshpkApiVersion[0] == needVer[0]) + return; + var proto = Object.getPrototypeOf(obj); + var depth = 0; + while (proto.constructor.name !== klass.name) { + proto = Object.getPrototypeOf(proto); + assert.ok(proto && ++depth <= MAX_CLASS_DEPTH, + name + ' must be a ' + klass.name + ' instance'); + } + assert.strictEqual(proto.constructor.name, klass.name, + name + ' must be a ' + klass.name + ' instance'); + var ver = proto._sshpkApiVersion; + if (ver === undefined) + ver = klass._oldVersionDetect(obj); + assert.ok(ver[0] == needVer[0] && ver[1] >= needVer[1], + name + ' must be compatible with ' + klass.name + ' klass ' + + 'version ' + needVer[0] + '.' + needVer[1]); +} + +var CIPHER_LEN = { + 'des-ede3-cbc': { key: 7, iv: 8 }, + 'aes-128-cbc': { key: 16, iv: 16 } +}; +var PKCS5_SALT_LEN = 8; + +function opensslKeyDeriv(cipher, salt, passphrase, count) { + assert.buffer(salt, 'salt'); + assert.buffer(passphrase, 'passphrase'); + assert.number(count, 'iteration count'); + + var clen = CIPHER_LEN[cipher]; + assert.object(clen, 'supported cipher'); + + salt = salt.slice(0, PKCS5_SALT_LEN); + + var D, D_prev, bufs; + var material = Buffer.alloc(0); + while (material.length < clen.key + clen.iv) { + bufs = []; + if (D_prev) + bufs.push(D_prev); + bufs.push(passphrase); + bufs.push(salt); + D = Buffer.concat(bufs); + for (var j = 0; j < count; ++j) + D = crypto.createHash('md5').update(D).digest(); + material = Buffer.concat([material, D]); + D_prev = D; + } + + return ({ + key: material.slice(0, clen.key), + iv: material.slice(clen.key, clen.key + clen.iv) + }); +} + +/* Count leading zero bits on a buffer */ +function countZeros(buf) { + var o = 0, obit = 8; + while (o < buf.length) { + var mask = (1 << obit); + if ((buf[o] & mask) === mask) + break; + obit--; + if (obit < 0) { + o++; + obit = 8; + } + } + return (o*8 + (8 - obit) - 1); +} + +function bufferSplit(buf, chr) { + assert.buffer(buf); + assert.string(chr); + + var parts = []; + var lastPart = 0; + var matches = 0; + for (var i = 0; i < buf.length; ++i) { + if (buf[i] === chr.charCodeAt(matches)) + ++matches; + else if (buf[i] === chr.charCodeAt(0)) + matches = 1; + else + matches = 0; + + if (matches >= chr.length) { + var newPart = i + 1; + parts.push(buf.slice(lastPart, newPart - matches)); + lastPart = newPart; + matches = 0; + } + } + if (lastPart <= buf.length) + parts.push(buf.slice(lastPart, buf.length)); + + return (parts); +} + +function ecNormalize(buf, addZero) { + assert.buffer(buf); + if (buf[0] === 0x00 && buf[1] === 0x04) { + if (addZero) + return (buf); + return (buf.slice(1)); + } else if (buf[0] === 0x04) { + if (!addZero) + return (buf); + } else { + while (buf[0] === 0x00) + buf = buf.slice(1); + if (buf[0] === 0x02 || buf[0] === 0x03) + throw (new Error('Compressed elliptic curve points ' + + 'are not supported')); + if (buf[0] !== 0x04) + throw (new Error('Not a valid elliptic curve point')); + if (!addZero) + return (buf); + } + var b = Buffer.alloc(buf.length + 1); + b[0] = 0x0; + buf.copy(b, 1); + return (b); +} + +function readBitString(der, tag) { + if (tag === undefined) + tag = asn1.Ber.BitString; + var buf = der.readString(tag, true); + assert.strictEqual(buf[0], 0x00, 'bit strings with unused bits are ' + + 'not supported (0x' + buf[0].toString(16) + ')'); + return (buf.slice(1)); +} + +function writeBitString(der, buf, tag) { + if (tag === undefined) + tag = asn1.Ber.BitString; + var b = Buffer.alloc(buf.length + 1); + b[0] = 0x00; + buf.copy(b, 1); + der.writeBuffer(b, tag); +} + +function mpNormalize(buf) { + assert.buffer(buf); + while (buf.length > 1 && buf[0] === 0x00 && (buf[1] & 0x80) === 0x00) + buf = buf.slice(1); + if ((buf[0] & 0x80) === 0x80) { + var b = Buffer.alloc(buf.length + 1); + b[0] = 0x00; + buf.copy(b, 1); + buf = b; + } + return (buf); +} + +function mpDenormalize(buf) { + assert.buffer(buf); + while (buf.length > 1 && buf[0] === 0x00) + buf = buf.slice(1); + return (buf); +} + +function zeroPadToLength(buf, len) { + assert.buffer(buf); + assert.number(len); + while (buf.length > len) { + assert.equal(buf[0], 0x00); + buf = buf.slice(1); + } + while (buf.length < len) { + var b = Buffer.alloc(buf.length + 1); + b[0] = 0x00; + buf.copy(b, 1); + buf = b; + } + return (buf); +} + +function bigintToMpBuf(bigint) { + var buf = Buffer.from(bigint.toByteArray()); + buf = mpNormalize(buf); + return (buf); +} + +function calculateDSAPublic(g, p, x) { + assert.buffer(g); + assert.buffer(p); + assert.buffer(x); + try { + var bigInt = __webpack_require__(81).BigInteger; + } catch (e) { + throw (new Error('To load a PKCS#8 format DSA private key, ' + + 'the node jsbn library is required.')); + } + g = new bigInt(g); + p = new bigInt(p); + x = new bigInt(x); + var y = g.modPow(x, p); + var ybuf = bigintToMpBuf(y); + return (ybuf); +} + +function calculateED25519Public(k) { + assert.buffer(k); + + if (nacl === undefined) + nacl = __webpack_require__(76); + + var kp = nacl.sign.keyPair.fromSeed(new Uint8Array(k)); + return (Buffer.from(kp.publicKey)); +} + +function calculateX25519Public(k) { + assert.buffer(k); + + if (nacl === undefined) + nacl = __webpack_require__(76); + + var kp = nacl.box.keyPair.fromSeed(new Uint8Array(k)); + return (Buffer.from(kp.publicKey)); +} + +function addRSAMissing(key) { + assert.object(key); + assertCompatible(key, PrivateKey, [1, 1]); + try { + var bigInt = __webpack_require__(81).BigInteger; + } catch (e) { + throw (new Error('To write a PEM private key from ' + + 'this source, the node jsbn lib is required.')); + } + + var d = new bigInt(key.part.d.data); + var buf; + + if (!key.part.dmodp) { + var p = new bigInt(key.part.p.data); + var dmodp = d.mod(p.subtract(1)); + + buf = bigintToMpBuf(dmodp); + key.part.dmodp = {name: 'dmodp', data: buf}; + key.parts.push(key.part.dmodp); + } + if (!key.part.dmodq) { + var q = new bigInt(key.part.q.data); + var dmodq = d.mod(q.subtract(1)); + + buf = bigintToMpBuf(dmodq); + key.part.dmodq = {name: 'dmodq', data: buf}; + key.parts.push(key.part.dmodq); + } +} + +function publicFromPrivateECDSA(curveName, priv) { + assert.string(curveName, 'curveName'); + assert.buffer(priv); + if (ec === undefined) + ec = __webpack_require__(139); + if (jsbn === undefined) + jsbn = __webpack_require__(81).BigInteger; + var params = algs.curves[curveName]; + var p = new jsbn(params.p); + var a = new jsbn(params.a); + var b = new jsbn(params.b); + var curve = new ec.ECCurveFp(p, a, b); + var G = curve.decodePointHex(params.G.toString('hex')); + + var d = new jsbn(mpNormalize(priv)); + var pub = G.multiply(d); + pub = Buffer.from(curve.encodePointHex(pub), 'hex'); + + var parts = []; + parts.push({name: 'curve', data: Buffer.from(curveName)}); + parts.push({name: 'Q', data: pub}); + + var key = new Key({type: 'ecdsa', curve: curve, parts: parts}); + return (key); +} + +function opensshCipherInfo(cipher) { + var inf = {}; + switch (cipher) { + case '3des-cbc': + inf.keySize = 24; + inf.blockSize = 8; + inf.opensslName = 'des-ede3-cbc'; + break; + case 'blowfish-cbc': + inf.keySize = 16; + inf.blockSize = 8; + inf.opensslName = 'bf-cbc'; + break; + case 'aes128-cbc': + case 'aes128-ctr': + case 'aes128-gcm@openssh.com': + inf.keySize = 16; + inf.blockSize = 16; + inf.opensslName = 'aes-128-' + cipher.slice(7, 10); + break; + case 'aes192-cbc': + case 'aes192-ctr': + case 'aes192-gcm@openssh.com': + inf.keySize = 24; + inf.blockSize = 16; + inf.opensslName = 'aes-192-' + cipher.slice(7, 10); + break; + case 'aes256-cbc': + case 'aes256-ctr': + case 'aes256-gcm@openssh.com': + inf.keySize = 32; + inf.blockSize = 16; + inf.opensslName = 'aes-256-' + cipher.slice(7, 10); + break; + default: + throw (new Error( + 'Unsupported openssl cipher "' + cipher + '"')); + } + return (inf); +} + + +/***/ }), +/* 27 */ +/***/ (function(module, exports, __webpack_require__) { + +// Copyright 2017 Joyent, Inc. + +module.exports = Key; + +var assert = __webpack_require__(16); +var algs = __webpack_require__(32); +var crypto = __webpack_require__(11); +var Fingerprint = __webpack_require__(156); +var Signature = __webpack_require__(75); +var DiffieHellman = __webpack_require__(325).DiffieHellman; +var errs = __webpack_require__(74); +var utils = __webpack_require__(26); +var PrivateKey = __webpack_require__(33); +var edCompat; + +try { + edCompat = __webpack_require__(454); +} catch (e) { + /* Just continue through, and bail out if we try to use it. */ +} + +var InvalidAlgorithmError = errs.InvalidAlgorithmError; +var KeyParseError = errs.KeyParseError; + +var formats = {}; +formats['auto'] = __webpack_require__(455); +formats['pem'] = __webpack_require__(86); +formats['pkcs1'] = __webpack_require__(327); +formats['pkcs8'] = __webpack_require__(157); +formats['rfc4253'] = __webpack_require__(103); +formats['ssh'] = __webpack_require__(456); +formats['ssh-private'] = __webpack_require__(192); +formats['openssh'] = formats['ssh-private']; +formats['dnssec'] = __webpack_require__(326); + +function Key(opts) { + assert.object(opts, 'options'); + assert.arrayOfObject(opts.parts, 'options.parts'); + assert.string(opts.type, 'options.type'); + assert.optionalString(opts.comment, 'options.comment'); + + var algInfo = algs.info[opts.type]; + if (typeof (algInfo) !== 'object') + throw (new InvalidAlgorithmError(opts.type)); + + var partLookup = {}; + for (var i = 0; i < opts.parts.length; ++i) { + var part = opts.parts[i]; + partLookup[part.name] = part; + } + + this.type = opts.type; + this.parts = opts.parts; + this.part = partLookup; + this.comment = undefined; + this.source = opts.source; + + /* for speeding up hashing/fingerprint operations */ + this._rfc4253Cache = opts._rfc4253Cache; + this._hashCache = {}; + + var sz; + this.curve = undefined; + if (this.type === 'ecdsa') { + var curve = this.part.curve.data.toString(); + this.curve = curve; + sz = algs.curves[curve].size; + } else if (this.type === 'ed25519' || this.type === 'curve25519') { + sz = 256; + this.curve = 'curve25519'; + } else { + var szPart = this.part[algInfo.sizePart]; + sz = szPart.data.length; + sz = sz * 8 - utils.countZeros(szPart.data); + } + this.size = sz; +} + +Key.formats = formats; + +Key.prototype.toBuffer = function (format, options) { + if (format === undefined) + format = 'ssh'; + assert.string(format, 'format'); + assert.object(formats[format], 'formats[format]'); + assert.optionalObject(options, 'options'); + + if (format === 'rfc4253') { + if (this._rfc4253Cache === undefined) + this._rfc4253Cache = formats['rfc4253'].write(this); + return (this._rfc4253Cache); + } + + return (formats[format].write(this, options)); +}; + +Key.prototype.toString = function (format, options) { + return (this.toBuffer(format, options).toString()); +}; + +Key.prototype.hash = function (algo) { + assert.string(algo, 'algorithm'); + algo = algo.toLowerCase(); + if (algs.hashAlgs[algo] === undefined) + throw (new InvalidAlgorithmError(algo)); + + if (this._hashCache[algo]) + return (this._hashCache[algo]); + var hash = crypto.createHash(algo). + update(this.toBuffer('rfc4253')).digest(); + this._hashCache[algo] = hash; + return (hash); +}; + +Key.prototype.fingerprint = function (algo) { + if (algo === undefined) + algo = 'sha256'; + assert.string(algo, 'algorithm'); + var opts = { + type: 'key', + hash: this.hash(algo), + algorithm: algo + }; + return (new Fingerprint(opts)); +}; + +Key.prototype.defaultHashAlgorithm = function () { + var hashAlgo = 'sha1'; + if (this.type === 'rsa') + hashAlgo = 'sha256'; + if (this.type === 'dsa' && this.size > 1024) + hashAlgo = 'sha256'; + if (this.type === 'ed25519') + hashAlgo = 'sha512'; + if (this.type === 'ecdsa') { + if (this.size <= 256) + hashAlgo = 'sha256'; + else if (this.size <= 384) + hashAlgo = 'sha384'; + else + hashAlgo = 'sha512'; + } + return (hashAlgo); +}; + +Key.prototype.createVerify = function (hashAlgo) { + if (hashAlgo === undefined) + hashAlgo = this.defaultHashAlgorithm(); + assert.string(hashAlgo, 'hash algorithm'); + + /* ED25519 is not supported by OpenSSL, use a javascript impl. */ + if (this.type === 'ed25519' && edCompat !== undefined) + return (new edCompat.Verifier(this, hashAlgo)); + if (this.type === 'curve25519') + throw (new Error('Curve25519 keys are not suitable for ' + + 'signing or verification')); + + var v, nm, err; + try { + nm = hashAlgo.toUpperCase(); + v = crypto.createVerify(nm); + } catch (e) { + err = e; + } + if (v === undefined || (err instanceof Error && + err.message.match(/Unknown message digest/))) { + nm = 'RSA-'; + nm += hashAlgo.toUpperCase(); + v = crypto.createVerify(nm); + } + assert.ok(v, 'failed to create verifier'); + var oldVerify = v.verify.bind(v); + var key = this.toBuffer('pkcs8'); + var curve = this.curve; + var self = this; + v.verify = function (signature, fmt) { + if (Signature.isSignature(signature, [2, 0])) { + if (signature.type !== self.type) + return (false); + if (signature.hashAlgorithm && + signature.hashAlgorithm !== hashAlgo) + return (false); + if (signature.curve && self.type === 'ecdsa' && + signature.curve !== curve) + return (false); + return (oldVerify(key, signature.toBuffer('asn1'))); + + } else if (typeof (signature) === 'string' || + Buffer.isBuffer(signature)) { + return (oldVerify(key, signature, fmt)); + + /* + * Avoid doing this on valid arguments, walking the prototype + * chain can be quite slow. + */ + } else if (Signature.isSignature(signature, [1, 0])) { + throw (new Error('signature was created by too old ' + + 'a version of sshpk and cannot be verified')); + + } else { + throw (new TypeError('signature must be a string, ' + + 'Buffer, or Signature object')); + } + }; + return (v); +}; + +Key.prototype.createDiffieHellman = function () { + if (this.type === 'rsa') + throw (new Error('RSA keys do not support Diffie-Hellman')); + + return (new DiffieHellman(this)); +}; +Key.prototype.createDH = Key.prototype.createDiffieHellman; + +Key.parse = function (data, format, options) { + if (typeof (data) !== 'string') + assert.buffer(data, 'data'); + if (format === undefined) + format = 'auto'; + assert.string(format, 'format'); + if (typeof (options) === 'string') + options = { filename: options }; + assert.optionalObject(options, 'options'); + if (options === undefined) + options = {}; + assert.optionalString(options.filename, 'options.filename'); + if (options.filename === undefined) + options.filename = '(unnamed)'; + + assert.object(formats[format], 'formats[format]'); + + try { + var k = formats[format].read(data, options); + if (k instanceof PrivateKey) + k = k.toPublic(); + if (!k.comment) + k.comment = options.filename; + return (k); + } catch (e) { + if (e.name === 'KeyEncryptedError') + throw (e); + throw (new KeyParseError(options.filename, format, e)); + } +}; + +Key.isKey = function (obj, ver) { + return (utils.isCompatible(obj, Key, ver)); +}; + +/* + * API versions for Key: + * [1,0] -- initial ver, may take Signature for createVerify or may not + * [1,1] -- added pkcs1, pkcs8 formats + * [1,2] -- added auto, ssh-private, openssh formats + * [1,3] -- added defaultHashAlgorithm + * [1,4] -- added ed support, createDH + * [1,5] -- first explicitly tagged version + * [1,6] -- changed ed25519 part names + */ +Key.prototype._sshpkApiVersion = [1, 6]; + +Key._oldVersionDetect = function (obj) { + assert.func(obj.toBuffer); + assert.func(obj.fingerprint); + if (obj.createDH) + return ([1, 4]); + if (obj.defaultHashAlgorithm) + return ([1, 3]); + if (obj.formats['auto']) + return ([1, 2]); + if (obj.formats['pkcs1']) + return ([1, 1]); + return ([1, 0]); +}; + + +/***/ }), +/* 28 */ +/***/ (function(module, exports) { + +module.exports = require("assert"); + +/***/ }), +/* 29 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = nullify; +function nullify(obj = {}) { + if (Array.isArray(obj)) { + for (var _iterator = obj, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { + var _ref; + + if (_isArray) { + if (_i >= _iterator.length) break; + _ref = _iterator[_i++]; + } else { + _i = _iterator.next(); + if (_i.done) break; + _ref = _i.value; + } + + const item = _ref; + + nullify(item); + } + } else if (obj !== null && typeof obj === 'object' || typeof obj === 'function') { + Object.setPrototypeOf(obj, null); + + // for..in can only be applied to 'object', not 'function' + if (typeof obj === 'object') { + for (const key in obj) { + nullify(obj[key]); + } + } + } + + return obj; +} + +/***/ }), +/* 30 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const escapeStringRegexp = __webpack_require__(388); +const ansiStyles = __webpack_require__(506); +const stdoutColor = __webpack_require__(598).stdout; + +const template = __webpack_require__(599); + +const isSimpleWindowsTerm = process.platform === 'win32' && !(process.env.TERM || '').toLowerCase().startsWith('xterm'); + +// `supportsColor.level` → `ansiStyles.color[name]` mapping +const levelMapping = ['ansi', 'ansi', 'ansi256', 'ansi16m']; + +// `color-convert` models to exclude from the Chalk API due to conflicts and such +const skipModels = new Set(['gray']); + +const styles = Object.create(null); + +function applyOptions(obj, options) { + options = options || {}; + + // Detect level if not set manually + const scLevel = stdoutColor ? stdoutColor.level : 0; + obj.level = options.level === undefined ? scLevel : options.level; + obj.enabled = 'enabled' in options ? options.enabled : obj.level > 0; +} + +function Chalk(options) { + // We check for this.template here since calling `chalk.constructor()` + // by itself will have a `this` of a previously constructed chalk object + if (!this || !(this instanceof Chalk) || this.template) { + const chalk = {}; + applyOptions(chalk, options); + + chalk.template = function () { + const args = [].slice.call(arguments); + return chalkTag.apply(null, [chalk.template].concat(args)); + }; + + Object.setPrototypeOf(chalk, Chalk.prototype); + Object.setPrototypeOf(chalk.template, chalk); + + chalk.template.constructor = Chalk; + + return chalk.template; + } + + applyOptions(this, options); +} + +// Use bright blue on Windows as the normal blue color is illegible +if (isSimpleWindowsTerm) { + ansiStyles.blue.open = '\u001B[94m'; +} + +for (const key of Object.keys(ansiStyles)) { + ansiStyles[key].closeRe = new RegExp(escapeStringRegexp(ansiStyles[key].close), 'g'); + + styles[key] = { + get() { + const codes = ansiStyles[key]; + return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, key); + } + }; +} + +styles.visible = { + get() { + return build.call(this, this._styles || [], true, 'visible'); + } +}; + +ansiStyles.color.closeRe = new RegExp(escapeStringRegexp(ansiStyles.color.close), 'g'); +for (const model of Object.keys(ansiStyles.color.ansi)) { + if (skipModels.has(model)) { + continue; + } + + styles[model] = { + get() { + const level = this.level; + return function () { + const open = ansiStyles.color[levelMapping[level]][model].apply(null, arguments); + const codes = { + open, + close: ansiStyles.color.close, + closeRe: ansiStyles.color.closeRe + }; + return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, model); + }; + } + }; +} + +ansiStyles.bgColor.closeRe = new RegExp(escapeStringRegexp(ansiStyles.bgColor.close), 'g'); +for (const model of Object.keys(ansiStyles.bgColor.ansi)) { + if (skipModels.has(model)) { + continue; + } + + const bgModel = 'bg' + model[0].toUpperCase() + model.slice(1); + styles[bgModel] = { + get() { + const level = this.level; + return function () { + const open = ansiStyles.bgColor[levelMapping[level]][model].apply(null, arguments); + const codes = { + open, + close: ansiStyles.bgColor.close, + closeRe: ansiStyles.bgColor.closeRe + }; + return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, model); + }; + } + }; +} + +const proto = Object.defineProperties(() => {}, styles); + +function build(_styles, _empty, key) { + const builder = function () { + return applyStyle.apply(builder, arguments); + }; + + builder._styles = _styles; + builder._empty = _empty; + + const self = this; + + Object.defineProperty(builder, 'level', { + enumerable: true, + get() { + return self.level; + }, + set(level) { + self.level = level; + } + }); + + Object.defineProperty(builder, 'enabled', { + enumerable: true, + get() { + return self.enabled; + }, + set(enabled) { + self.enabled = enabled; + } + }); + + // See below for fix regarding invisible grey/dim combination on Windows + builder.hasGrey = this.hasGrey || key === 'gray' || key === 'grey'; + + // `__proto__` is used because we must return a function, but there is + // no way to create a function with a different prototype + builder.__proto__ = proto; // eslint-disable-line no-proto + + return builder; +} + +function applyStyle() { + // Support varags, but simply cast to string in case there's only one arg + const args = arguments; + const argsLen = args.length; + let str = String(arguments[0]); + + if (argsLen === 0) { + return ''; + } + + if (argsLen > 1) { + // Don't slice `arguments`, it prevents V8 optimizations + for (let a = 1; a < argsLen; a++) { + str += ' ' + args[a]; + } + } + + if (!this.enabled || this.level <= 0 || !str) { + return this._empty ? '' : str; + } + + // Turns out that on Windows dimmed gray text becomes invisible in cmd.exe, + // see https://github.com/chalk/chalk/issues/58 + // If we're on Windows and we're dealing with a gray color, temporarily make 'dim' a noop. + const originalDim = ansiStyles.dim.open; + if (isSimpleWindowsTerm && this.hasGrey) { + ansiStyles.dim.open = ''; + } + + for (const code of this._styles.slice().reverse()) { + // Replace any instances already present with a re-opening code + // otherwise only the part of the string until said closing code + // will be colored, and the rest will simply be 'plain'. + str = code.open + str.replace(code.closeRe, code.open) + code.close; + + // Close the styling before a linebreak and reopen + // after next line to fix a bleed issue on macOS + // https://github.com/chalk/chalk/pull/92 + str = str.replace(/\r?\n/g, `${code.close}$&${code.open}`); + } + + // Reset the original `dim` if we changed it to work around the Windows dimmed gray issue + ansiStyles.dim.open = originalDim; + + return str; +} + +function chalkTag(chalk, strings) { + if (!Array.isArray(strings)) { + // If chalk() was called by itself or with a string, + // return the string itself as a string. + return [].slice.call(arguments, 1).join(' '); + } + + const args = [].slice.call(arguments, 2); + const parts = [strings.raw[0]]; + + for (let i = 1; i < strings.length; i++) { + parts.push(String(args[i - 1]).replace(/[{}\\]/g, '\\$&')); + parts.push(String(strings.raw[i])); + } + + return template(chalk, parts.join('')); +} + +Object.defineProperties(Chalk.prototype, styles); + +module.exports = Chalk(); // eslint-disable-line new-cap +module.exports.supportsColor = stdoutColor; +module.exports.default = module.exports; // For TypeScript + + +/***/ }), +/* 31 */ +/***/ (function(module, exports) { + +var core = module.exports = { version: '2.5.7' }; +if (typeof __e == 'number') __e = core; // eslint-disable-line no-undef + + +/***/ }), +/* 32 */ +/***/ (function(module, exports, __webpack_require__) { + +// Copyright 2015 Joyent, Inc. + +var Buffer = __webpack_require__(15).Buffer; + +var algInfo = { + 'dsa': { + parts: ['p', 'q', 'g', 'y'], + sizePart: 'p' + }, + 'rsa': { + parts: ['e', 'n'], + sizePart: 'n' + }, + 'ecdsa': { + parts: ['curve', 'Q'], + sizePart: 'Q' + }, + 'ed25519': { + parts: ['A'], + sizePart: 'A' + } +}; +algInfo['curve25519'] = algInfo['ed25519']; + +var algPrivInfo = { + 'dsa': { + parts: ['p', 'q', 'g', 'y', 'x'] + }, + 'rsa': { + parts: ['n', 'e', 'd', 'iqmp', 'p', 'q'] + }, + 'ecdsa': { + parts: ['curve', 'Q', 'd'] + }, + 'ed25519': { + parts: ['A', 'k'] + } +}; +algPrivInfo['curve25519'] = algPrivInfo['ed25519']; + +var hashAlgs = { + 'md5': true, + 'sha1': true, + 'sha256': true, + 'sha384': true, + 'sha512': true +}; + +/* + * Taken from + * http://csrc.nist.gov/groups/ST/toolkit/documents/dss/NISTReCur.pdf + */ +var curves = { + 'nistp256': { + size: 256, + pkcs8oid: '1.2.840.10045.3.1.7', + p: Buffer.from(('00' + + 'ffffffff 00000001 00000000 00000000' + + '00000000 ffffffff ffffffff ffffffff'). + replace(/ /g, ''), 'hex'), + a: Buffer.from(('00' + + 'FFFFFFFF 00000001 00000000 00000000' + + '00000000 FFFFFFFF FFFFFFFF FFFFFFFC'). + replace(/ /g, ''), 'hex'), + b: Buffer.from(( + '5ac635d8 aa3a93e7 b3ebbd55 769886bc' + + '651d06b0 cc53b0f6 3bce3c3e 27d2604b'). + replace(/ /g, ''), 'hex'), + s: Buffer.from(('00' + + 'c49d3608 86e70493 6a6678e1 139d26b7' + + '819f7e90'). + replace(/ /g, ''), 'hex'), + n: Buffer.from(('00' + + 'ffffffff 00000000 ffffffff ffffffff' + + 'bce6faad a7179e84 f3b9cac2 fc632551'). + replace(/ /g, ''), 'hex'), + G: Buffer.from(('04' + + '6b17d1f2 e12c4247 f8bce6e5 63a440f2' + + '77037d81 2deb33a0 f4a13945 d898c296' + + '4fe342e2 fe1a7f9b 8ee7eb4a 7c0f9e16' + + '2bce3357 6b315ece cbb64068 37bf51f5'). + replace(/ /g, ''), 'hex') + }, + 'nistp384': { + size: 384, + pkcs8oid: '1.3.132.0.34', + p: Buffer.from(('00' + + 'ffffffff ffffffff ffffffff ffffffff' + + 'ffffffff ffffffff ffffffff fffffffe' + + 'ffffffff 00000000 00000000 ffffffff'). + replace(/ /g, ''), 'hex'), + a: Buffer.from(('00' + + 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF' + + 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE' + + 'FFFFFFFF 00000000 00000000 FFFFFFFC'). + replace(/ /g, ''), 'hex'), + b: Buffer.from(( + 'b3312fa7 e23ee7e4 988e056b e3f82d19' + + '181d9c6e fe814112 0314088f 5013875a' + + 'c656398d 8a2ed19d 2a85c8ed d3ec2aef'). + replace(/ /g, ''), 'hex'), + s: Buffer.from(('00' + + 'a335926a a319a27a 1d00896a 6773a482' + + '7acdac73'). + replace(/ /g, ''), 'hex'), + n: Buffer.from(('00' + + 'ffffffff ffffffff ffffffff ffffffff' + + 'ffffffff ffffffff c7634d81 f4372ddf' + + '581a0db2 48b0a77a ecec196a ccc52973'). + replace(/ /g, ''), 'hex'), + G: Buffer.from(('04' + + 'aa87ca22 be8b0537 8eb1c71e f320ad74' + + '6e1d3b62 8ba79b98 59f741e0 82542a38' + + '5502f25d bf55296c 3a545e38 72760ab7' + + '3617de4a 96262c6f 5d9e98bf 9292dc29' + + 'f8f41dbd 289a147c e9da3113 b5f0b8c0' + + '0a60b1ce 1d7e819d 7a431d7c 90ea0e5f'). + replace(/ /g, ''), 'hex') + }, + 'nistp521': { + size: 521, + pkcs8oid: '1.3.132.0.35', + p: Buffer.from(( + '01ffffff ffffffff ffffffff ffffffff' + + 'ffffffff ffffffff ffffffff ffffffff' + + 'ffffffff ffffffff ffffffff ffffffff' + + 'ffffffff ffffffff ffffffff ffffffff' + + 'ffff').replace(/ /g, ''), 'hex'), + a: Buffer.from(('01FF' + + 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF' + + 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF' + + 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF' + + 'FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFC'). + replace(/ /g, ''), 'hex'), + b: Buffer.from(('51' + + '953eb961 8e1c9a1f 929a21a0 b68540ee' + + 'a2da725b 99b315f3 b8b48991 8ef109e1' + + '56193951 ec7e937b 1652c0bd 3bb1bf07' + + '3573df88 3d2c34f1 ef451fd4 6b503f00'). + replace(/ /g, ''), 'hex'), + s: Buffer.from(('00' + + 'd09e8800 291cb853 96cc6717 393284aa' + + 'a0da64ba').replace(/ /g, ''), 'hex'), + n: Buffer.from(('01ff' + + 'ffffffff ffffffff ffffffff ffffffff' + + 'ffffffff ffffffff ffffffff fffffffa' + + '51868783 bf2f966b 7fcc0148 f709a5d0' + + '3bb5c9b8 899c47ae bb6fb71e 91386409'). + replace(/ /g, ''), 'hex'), + G: Buffer.from(('04' + + '00c6 858e06b7 0404e9cd 9e3ecb66 2395b442' + + '9c648139 053fb521 f828af60 6b4d3dba' + + 'a14b5e77 efe75928 fe1dc127 a2ffa8de' + + '3348b3c1 856a429b f97e7e31 c2e5bd66' + + '0118 39296a78 9a3bc004 5c8a5fb4 2c7d1bd9' + + '98f54449 579b4468 17afbd17 273e662c' + + '97ee7299 5ef42640 c550b901 3fad0761' + + '353c7086 a272c240 88be9476 9fd16650'). + replace(/ /g, ''), 'hex') + } +}; + +module.exports = { + info: algInfo, + privInfo: algPrivInfo, + hashAlgs: hashAlgs, + curves: curves +}; + + +/***/ }), +/* 33 */ +/***/ (function(module, exports, __webpack_require__) { + +// Copyright 2017 Joyent, Inc. + +module.exports = PrivateKey; + +var assert = __webpack_require__(16); +var Buffer = __webpack_require__(15).Buffer; +var algs = __webpack_require__(32); +var crypto = __webpack_require__(11); +var Fingerprint = __webpack_require__(156); +var Signature = __webpack_require__(75); +var errs = __webpack_require__(74); +var util = __webpack_require__(3); +var utils = __webpack_require__(26); +var dhe = __webpack_require__(325); +var generateECDSA = dhe.generateECDSA; +var generateED25519 = dhe.generateED25519; +var edCompat; +var nacl; + +try { + edCompat = __webpack_require__(454); +} catch (e) { + /* Just continue through, and bail out if we try to use it. */ +} + +var Key = __webpack_require__(27); + +var InvalidAlgorithmError = errs.InvalidAlgorithmError; +var KeyParseError = errs.KeyParseError; +var KeyEncryptedError = errs.KeyEncryptedError; + +var formats = {}; +formats['auto'] = __webpack_require__(455); +formats['pem'] = __webpack_require__(86); +formats['pkcs1'] = __webpack_require__(327); +formats['pkcs8'] = __webpack_require__(157); +formats['rfc4253'] = __webpack_require__(103); +formats['ssh-private'] = __webpack_require__(192); +formats['openssh'] = formats['ssh-private']; +formats['ssh'] = formats['ssh-private']; +formats['dnssec'] = __webpack_require__(326); + +function PrivateKey(opts) { + assert.object(opts, 'options'); + Key.call(this, opts); + + this._pubCache = undefined; +} +util.inherits(PrivateKey, Key); + +PrivateKey.formats = formats; + +PrivateKey.prototype.toBuffer = function (format, options) { + if (format === undefined) + format = 'pkcs1'; + assert.string(format, 'format'); + assert.object(formats[format], 'formats[format]'); + assert.optionalObject(options, 'options'); + + return (formats[format].write(this, options)); +}; + +PrivateKey.prototype.hash = function (algo) { + return (this.toPublic().hash(algo)); +}; + +PrivateKey.prototype.toPublic = function () { + if (this._pubCache) + return (this._pubCache); + + var algInfo = algs.info[this.type]; + var pubParts = []; + for (var i = 0; i < algInfo.parts.length; ++i) { + var p = algInfo.parts[i]; + pubParts.push(this.part[p]); + } + + this._pubCache = new Key({ + type: this.type, + source: this, + parts: pubParts + }); + if (this.comment) + this._pubCache.comment = this.comment; + return (this._pubCache); +}; + +PrivateKey.prototype.derive = function (newType) { + assert.string(newType, 'type'); + var priv, pub, pair; + + if (this.type === 'ed25519' && newType === 'curve25519') { + if (nacl === undefined) + nacl = __webpack_require__(76); + + priv = this.part.k.data; + if (priv[0] === 0x00) + priv = priv.slice(1); + + pair = nacl.box.keyPair.fromSecretKey(new Uint8Array(priv)); + pub = Buffer.from(pair.publicKey); + + return (new PrivateKey({ + type: 'curve25519', + parts: [ + { name: 'A', data: utils.mpNormalize(pub) }, + { name: 'k', data: utils.mpNormalize(priv) } + ] + })); + } else if (this.type === 'curve25519' && newType === 'ed25519') { + if (nacl === undefined) + nacl = __webpack_require__(76); + + priv = this.part.k.data; + if (priv[0] === 0x00) + priv = priv.slice(1); + + pair = nacl.sign.keyPair.fromSeed(new Uint8Array(priv)); + pub = Buffer.from(pair.publicKey); + + return (new PrivateKey({ + type: 'ed25519', + parts: [ + { name: 'A', data: utils.mpNormalize(pub) }, + { name: 'k', data: utils.mpNormalize(priv) } + ] + })); + } + throw (new Error('Key derivation not supported from ' + this.type + + ' to ' + newType)); +}; + +PrivateKey.prototype.createVerify = function (hashAlgo) { + return (this.toPublic().createVerify(hashAlgo)); +}; + +PrivateKey.prototype.createSign = function (hashAlgo) { + if (hashAlgo === undefined) + hashAlgo = this.defaultHashAlgorithm(); + assert.string(hashAlgo, 'hash algorithm'); + + /* ED25519 is not supported by OpenSSL, use a javascript impl. */ + if (this.type === 'ed25519' && edCompat !== undefined) + return (new edCompat.Signer(this, hashAlgo)); + if (this.type === 'curve25519') + throw (new Error('Curve25519 keys are not suitable for ' + + 'signing or verification')); + + var v, nm, err; + try { + nm = hashAlgo.toUpperCase(); + v = crypto.createSign(nm); + } catch (e) { + err = e; + } + if (v === undefined || (err instanceof Error && + err.message.match(/Unknown message digest/))) { + nm = 'RSA-'; + nm += hashAlgo.toUpperCase(); + v = crypto.createSign(nm); + } + assert.ok(v, 'failed to create verifier'); + var oldSign = v.sign.bind(v); + var key = this.toBuffer('pkcs1'); + var type = this.type; + var curve = this.curve; + v.sign = function () { + var sig = oldSign(key); + if (typeof (sig) === 'string') + sig = Buffer.from(sig, 'binary'); + sig = Signature.parse(sig, type, 'asn1'); + sig.hashAlgorithm = hashAlgo; + sig.curve = curve; + return (sig); + }; + return (v); +}; + +PrivateKey.parse = function (data, format, options) { + if (typeof (data) !== 'string') + assert.buffer(data, 'data'); + if (format === undefined) + format = 'auto'; + assert.string(format, 'format'); + if (typeof (options) === 'string') + options = { filename: options }; + assert.optionalObject(options, 'options'); + if (options === undefined) + options = {}; + assert.optionalString(options.filename, 'options.filename'); + if (options.filename === undefined) + options.filename = '(unnamed)'; + + assert.object(formats[format], 'formats[format]'); + + try { + var k = formats[format].read(data, options); + assert.ok(k instanceof PrivateKey, 'key is not a private key'); + if (!k.comment) + k.comment = options.filename; + return (k); + } catch (e) { + if (e.name === 'KeyEncryptedError') + throw (e); + throw (new KeyParseError(options.filename, format, e)); + } +}; + +PrivateKey.isPrivateKey = function (obj, ver) { + return (utils.isCompatible(obj, PrivateKey, ver)); +}; + +PrivateKey.generate = function (type, options) { + if (options === undefined) + options = {}; + assert.object(options, 'options'); + + switch (type) { + case 'ecdsa': + if (options.curve === undefined) + options.curve = 'nistp256'; + assert.string(options.curve, 'options.curve'); + return (generateECDSA(options.curve)); + case 'ed25519': + return (generateED25519()); + default: + throw (new Error('Key generation not supported with key ' + + 'type "' + type + '"')); + } +}; + +/* + * API versions for PrivateKey: + * [1,0] -- initial ver + * [1,1] -- added auto, pkcs[18], openssh/ssh-private formats + * [1,2] -- added defaultHashAlgorithm + * [1,3] -- added derive, ed, createDH + * [1,4] -- first tagged version + * [1,5] -- changed ed25519 part names and format + */ +PrivateKey.prototype._sshpkApiVersion = [1, 5]; + +PrivateKey._oldVersionDetect = function (obj) { + assert.func(obj.toPublic); + assert.func(obj.createSign); + if (obj.derive) + return ([1, 3]); + if (obj.defaultHashAlgorithm) + return ([1, 2]); + if (obj.formats['auto']) + return ([1, 1]); + return ([1, 0]); +}; + + +/***/ }), +/* 34 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.wrapLifecycle = exports.run = exports.install = exports.Install = undefined; + +var _extends2; + +function _load_extends() { + return _extends2 = _interopRequireDefault(__webpack_require__(21)); +} + +var _asyncToGenerator2; + +function _load_asyncToGenerator() { + return _asyncToGenerator2 = _interopRequireDefault(__webpack_require__(2)); +} + +let install = exports.install = (() => { + var _ref29 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (config, reporter, flags, lockfile) { + yield wrapLifecycle(config, flags, (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { + const install = new Install(flags, config, reporter, lockfile); + yield install.init(); + })); + }); + + return function install(_x7, _x8, _x9, _x10) { + return _ref29.apply(this, arguments); + }; +})(); + +let run = exports.run = (() => { + var _ref31 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (config, reporter, flags, args) { + let lockfile; + let error = 'installCommandRenamed'; + if (flags.lockfile === false) { + lockfile = new (_lockfile || _load_lockfile()).default(); + } else { + lockfile = yield (_lockfile || _load_lockfile()).default.fromDirectory(config.lockfileFolder, reporter); + } + + if (args.length) { + const exampleArgs = args.slice(); + + if (flags.saveDev) { + exampleArgs.push('--dev'); + } + if (flags.savePeer) { + exampleArgs.push('--peer'); + } + if (flags.saveOptional) { + exampleArgs.push('--optional'); + } + if (flags.saveExact) { + exampleArgs.push('--exact'); + } + if (flags.saveTilde) { + exampleArgs.push('--tilde'); + } + let command = 'add'; + if (flags.global) { + error = 'globalFlagRemoved'; + command = 'global add'; + } + throw new (_errors || _load_errors()).MessageError(reporter.lang(error, `yarn ${command} ${exampleArgs.join(' ')}`)); + } + + yield install(config, reporter, flags, lockfile); + }); + + return function run(_x11, _x12, _x13, _x14) { + return _ref31.apply(this, arguments); + }; +})(); + +let wrapLifecycle = exports.wrapLifecycle = (() => { + var _ref32 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (config, flags, factory) { + yield config.executeLifecycleScript('preinstall'); + + yield factory(); + + // npm behaviour, seems kinda funky but yay compatibility + yield config.executeLifecycleScript('install'); + yield config.executeLifecycleScript('postinstall'); + + if (!config.production) { + if (!config.disablePrepublish) { + yield config.executeLifecycleScript('prepublish'); + } + yield config.executeLifecycleScript('prepare'); + } + }); + + return function wrapLifecycle(_x15, _x16, _x17) { + return _ref32.apply(this, arguments); + }; +})(); + +exports.hasWrapper = hasWrapper; +exports.setFlags = setFlags; + +var _objectPath; + +function _load_objectPath() { + return _objectPath = _interopRequireDefault(__webpack_require__(304)); +} + +var _hooks; + +function _load_hooks() { + return _hooks = __webpack_require__(374); +} + +var _index; + +function _load_index() { + return _index = _interopRequireDefault(__webpack_require__(220)); +} + +var _errors; + +function _load_errors() { + return _errors = __webpack_require__(6); +} + +var _integrityChecker; + +function _load_integrityChecker() { + return _integrityChecker = _interopRequireDefault(__webpack_require__(208)); +} + +var _lockfile; + +function _load_lockfile() { + return _lockfile = _interopRequireDefault(__webpack_require__(19)); +} + +var _lockfile2; + +function _load_lockfile2() { + return _lockfile2 = __webpack_require__(19); +} + +var _packageFetcher; + +function _load_packageFetcher() { + return _packageFetcher = _interopRequireWildcard(__webpack_require__(210)); +} + +var _packageInstallScripts; + +function _load_packageInstallScripts() { + return _packageInstallScripts = _interopRequireDefault(__webpack_require__(557)); +} + +var _packageCompatibility; + +function _load_packageCompatibility() { + return _packageCompatibility = _interopRequireWildcard(__webpack_require__(209)); +} + +var _packageResolver; + +function _load_packageResolver() { + return _packageResolver = _interopRequireDefault(__webpack_require__(366)); +} + +var _packageLinker; + +function _load_packageLinker() { + return _packageLinker = _interopRequireDefault(__webpack_require__(211)); +} + +var _index2; + +function _load_index2() { + return _index2 = __webpack_require__(57); +} + +var _index3; + +function _load_index3() { + return _index3 = __webpack_require__(78); +} + +var _autoclean; + +function _load_autoclean() { + return _autoclean = __webpack_require__(354); +} + +var _constants; + +function _load_constants() { + return _constants = _interopRequireWildcard(__webpack_require__(8)); +} + +var _normalizePattern; + +function _load_normalizePattern() { + return _normalizePattern = __webpack_require__(37); +} + +var _fs; + +function _load_fs() { + return _fs = _interopRequireWildcard(__webpack_require__(4)); +} + +var _map; + +function _load_map() { + return _map = _interopRequireDefault(__webpack_require__(29)); +} + +var _yarnVersion; + +function _load_yarnVersion() { + return _yarnVersion = __webpack_require__(120); +} + +var _generatePnpMap; + +function _load_generatePnpMap() { + return _generatePnpMap = __webpack_require__(579); +} + +var _workspaceLayout; + +function _load_workspaceLayout() { + return _workspaceLayout = _interopRequireDefault(__webpack_require__(90)); +} + +var _resolutionMap; + +function _load_resolutionMap() { + return _resolutionMap = _interopRequireDefault(__webpack_require__(214)); +} + +var _guessName; + +function _load_guessName() { + return _guessName = _interopRequireDefault(__webpack_require__(169)); +} + +var _audit; + +function _load_audit() { + return _audit = _interopRequireDefault(__webpack_require__(353)); +} + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const deepEqual = __webpack_require__(631); + +const emoji = __webpack_require__(302); +const invariant = __webpack_require__(9); +const path = __webpack_require__(0); +const semver = __webpack_require__(22); +const uuid = __webpack_require__(119); +const ssri = __webpack_require__(65); + +const ONE_DAY = 1000 * 60 * 60 * 24; + +/** + * Try and detect the installation method for Yarn and provide a command to update it with. + */ + +function getUpdateCommand(installationMethod) { + if (installationMethod === 'tar') { + return `curl --compressed -o- -L ${(_constants || _load_constants()).YARN_INSTALLER_SH} | bash`; + } + + if (installationMethod === 'homebrew') { + return 'brew upgrade yarn'; + } + + if (installationMethod === 'deb') { + return 'sudo apt-get update && sudo apt-get install yarn'; + } + + if (installationMethod === 'rpm') { + return 'sudo yum install yarn'; + } + + if (installationMethod === 'npm') { + return 'npm install --global yarn'; + } + + if (installationMethod === 'chocolatey') { + return 'choco upgrade yarn'; + } + + if (installationMethod === 'apk') { + return 'apk update && apk add -u yarn'; + } + + if (installationMethod === 'portage') { + return 'sudo emerge --sync && sudo emerge -au sys-apps/yarn'; + } + + return null; +} + +function getUpdateInstaller(installationMethod) { + // Windows + if (installationMethod === 'msi') { + return (_constants || _load_constants()).YARN_INSTALLER_MSI; + } + + return null; +} + +function normalizeFlags(config, rawFlags) { + const flags = { + // install + har: !!rawFlags.har, + ignorePlatform: !!rawFlags.ignorePlatform, + ignoreEngines: !!rawFlags.ignoreEngines, + ignoreScripts: !!rawFlags.ignoreScripts, + ignoreOptional: !!rawFlags.ignoreOptional, + force: !!rawFlags.force, + flat: !!rawFlags.flat, + lockfile: rawFlags.lockfile !== false, + pureLockfile: !!rawFlags.pureLockfile, + updateChecksums: !!rawFlags.updateChecksums, + skipIntegrityCheck: !!rawFlags.skipIntegrityCheck, + frozenLockfile: !!rawFlags.frozenLockfile, + linkDuplicates: !!rawFlags.linkDuplicates, + checkFiles: !!rawFlags.checkFiles, + audit: !!rawFlags.audit, + + // add + peer: !!rawFlags.peer, + dev: !!rawFlags.dev, + optional: !!rawFlags.optional, + exact: !!rawFlags.exact, + tilde: !!rawFlags.tilde, + ignoreWorkspaceRootCheck: !!rawFlags.ignoreWorkspaceRootCheck, + + // outdated, update-interactive + includeWorkspaceDeps: !!rawFlags.includeWorkspaceDeps, + + // add, remove, update + workspaceRootIsCwd: rawFlags.workspaceRootIsCwd !== false + }; + + if (config.getOption('ignore-scripts')) { + flags.ignoreScripts = true; + } + + if (config.getOption('ignore-platform')) { + flags.ignorePlatform = true; + } + + if (config.getOption('ignore-engines')) { + flags.ignoreEngines = true; + } + + if (config.getOption('ignore-optional')) { + flags.ignoreOptional = true; + } + + if (config.getOption('force')) { + flags.force = true; + } + + return flags; +} + +class Install { + constructor(flags, config, reporter, lockfile) { + this.rootManifestRegistries = []; + this.rootPatternsToOrigin = (0, (_map || _load_map()).default)(); + this.lockfile = lockfile; + this.reporter = reporter; + this.config = config; + this.flags = normalizeFlags(config, flags); + this.resolutions = (0, (_map || _load_map()).default)(); // Legacy resolutions field used for flat install mode + this.resolutionMap = new (_resolutionMap || _load_resolutionMap()).default(config); // Selective resolutions for nested dependencies + this.resolver = new (_packageResolver || _load_packageResolver()).default(config, lockfile, this.resolutionMap); + this.integrityChecker = new (_integrityChecker || _load_integrityChecker()).default(config); + this.linker = new (_packageLinker || _load_packageLinker()).default(config, this.resolver); + this.scripts = new (_packageInstallScripts || _load_packageInstallScripts()).default(config, this.resolver, this.flags.force); + } + + /** + * Create a list of dependency requests from the current directories manifests. + */ + + fetchRequestFromCwd(excludePatterns = [], ignoreUnusedPatterns = false) { + var _this = this; + + return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { + const patterns = []; + const deps = []; + let resolutionDeps = []; + const manifest = {}; + + const ignorePatterns = []; + const usedPatterns = []; + let workspaceLayout; + + // some commands should always run in the context of the entire workspace + const cwd = _this.flags.includeWorkspaceDeps || _this.flags.workspaceRootIsCwd ? _this.config.lockfileFolder : _this.config.cwd; + + // non-workspaces are always root, otherwise check for workspace root + const cwdIsRoot = !_this.config.workspaceRootFolder || _this.config.lockfileFolder === cwd; + + // exclude package names that are in install args + const excludeNames = []; + for (var _iterator = excludePatterns, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { + var _ref; + + if (_isArray) { + if (_i >= _iterator.length) break; + _ref = _iterator[_i++]; + } else { + _i = _iterator.next(); + if (_i.done) break; + _ref = _i.value; + } + + const pattern = _ref; + + if ((0, (_index3 || _load_index3()).getExoticResolver)(pattern)) { + excludeNames.push((0, (_guessName || _load_guessName()).default)(pattern)); + } else { + // extract the name + const parts = (0, (_normalizePattern || _load_normalizePattern()).normalizePattern)(pattern); + excludeNames.push(parts.name); + } + } + + const stripExcluded = function stripExcluded(manifest) { + for (var _iterator2 = excludeNames, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) { + var _ref2; + + if (_isArray2) { + if (_i2 >= _iterator2.length) break; + _ref2 = _iterator2[_i2++]; + } else { + _i2 = _iterator2.next(); + if (_i2.done) break; + _ref2 = _i2.value; + } + + const exclude = _ref2; + + if (manifest.dependencies && manifest.dependencies[exclude]) { + delete manifest.dependencies[exclude]; + } + if (manifest.devDependencies && manifest.devDependencies[exclude]) { + delete manifest.devDependencies[exclude]; + } + if (manifest.optionalDependencies && manifest.optionalDependencies[exclude]) { + delete manifest.optionalDependencies[exclude]; + } + } + }; + + for (var _iterator3 = Object.keys((_index2 || _load_index2()).registries), _isArray3 = Array.isArray(_iterator3), _i3 = 0, _iterator3 = _isArray3 ? _iterator3 : _iterator3[Symbol.iterator]();;) { + var _ref3; + + if (_isArray3) { + if (_i3 >= _iterator3.length) break; + _ref3 = _iterator3[_i3++]; + } else { + _i3 = _iterator3.next(); + if (_i3.done) break; + _ref3 = _i3.value; + } + + const registry = _ref3; + + const filename = (_index2 || _load_index2()).registries[registry].filename; + + const loc = path.join(cwd, filename); + if (!(yield (_fs || _load_fs()).exists(loc))) { + continue; + } + + _this.rootManifestRegistries.push(registry); + + const projectManifestJson = yield _this.config.readJson(loc); + yield (0, (_index || _load_index()).default)(projectManifestJson, cwd, _this.config, cwdIsRoot); + + Object.assign(_this.resolutions, projectManifestJson.resolutions); + Object.assign(manifest, projectManifestJson); + + _this.resolutionMap.init(_this.resolutions); + for (var _iterator4 = Object.keys(_this.resolutionMap.resolutionsByPackage), _isArray4 = Array.isArray(_iterator4), _i4 = 0, _iterator4 = _isArray4 ? _iterator4 : _iterator4[Symbol.iterator]();;) { + var _ref4; + + if (_isArray4) { + if (_i4 >= _iterator4.length) break; + _ref4 = _iterator4[_i4++]; + } else { + _i4 = _iterator4.next(); + if (_i4.done) break; + _ref4 = _i4.value; + } + + const packageName = _ref4; + + const optional = (_objectPath || _load_objectPath()).default.has(manifest.optionalDependencies, packageName) && _this.flags.ignoreOptional; + for (var _iterator8 = _this.resolutionMap.resolutionsByPackage[packageName], _isArray8 = Array.isArray(_iterator8), _i8 = 0, _iterator8 = _isArray8 ? _iterator8 : _iterator8[Symbol.iterator]();;) { + var _ref9; + + if (_isArray8) { + if (_i8 >= _iterator8.length) break; + _ref9 = _iterator8[_i8++]; + } else { + _i8 = _iterator8.next(); + if (_i8.done) break; + _ref9 = _i8.value; + } + + const _ref8 = _ref9; + const pattern = _ref8.pattern; + + resolutionDeps = [...resolutionDeps, { registry, pattern, optional, hint: 'resolution' }]; + } + } + + const pushDeps = function pushDeps(depType, manifest, { hint, optional }, isUsed) { + if (ignoreUnusedPatterns && !isUsed) { + return; + } + // We only take unused dependencies into consideration to get deterministic hoisting. + // Since flat mode doesn't care about hoisting and everything is top level and specified then we can safely + // leave these out. + if (_this.flags.flat && !isUsed) { + return; + } + const depMap = manifest[depType]; + for (const name in depMap) { + if (excludeNames.indexOf(name) >= 0) { + continue; + } + + let pattern = name; + if (!_this.lockfile.getLocked(pattern)) { + // when we use --save we save the dependency to the lockfile with just the name rather than the + // version combo + pattern += '@' + depMap[name]; + } + + // normalization made sure packages are mentioned only once + if (isUsed) { + usedPatterns.push(pattern); + } else { + ignorePatterns.push(pattern); + } + + _this.rootPatternsToOrigin[pattern] = depType; + patterns.push(pattern); + deps.push({ pattern, registry, hint, optional, workspaceName: manifest.name, workspaceLoc: manifest._loc }); + } + }; + + if (cwdIsRoot) { + pushDeps('dependencies', projectManifestJson, { hint: null, optional: false }, true); + pushDeps('devDependencies', projectManifestJson, { hint: 'dev', optional: false }, !_this.config.production); + pushDeps('optionalDependencies', projectManifestJson, { hint: 'optional', optional: true }, true); + } + + if (_this.config.workspaceRootFolder) { + const workspaceLoc = cwdIsRoot ? loc : path.join(_this.config.lockfileFolder, filename); + const workspacesRoot = path.dirname(workspaceLoc); + + let workspaceManifestJson = projectManifestJson; + if (!cwdIsRoot) { + // the manifest we read before was a child workspace, so get the root + workspaceManifestJson = yield _this.config.readJson(workspaceLoc); + yield (0, (_index || _load_index()).default)(workspaceManifestJson, workspacesRoot, _this.config, true); + } + + const workspaces = yield _this.config.resolveWorkspaces(workspacesRoot, workspaceManifestJson); + workspaceLayout = new (_workspaceLayout || _load_workspaceLayout()).default(workspaces, _this.config); + + // add virtual manifest that depends on all workspaces, this way package hoisters and resolvers will work fine + const workspaceDependencies = (0, (_extends2 || _load_extends()).default)({}, workspaceManifestJson.dependencies); + for (var _iterator5 = Object.keys(workspaces), _isArray5 = Array.isArray(_iterator5), _i5 = 0, _iterator5 = _isArray5 ? _iterator5 : _iterator5[Symbol.iterator]();;) { + var _ref5; + + if (_isArray5) { + if (_i5 >= _iterator5.length) break; + _ref5 = _iterator5[_i5++]; + } else { + _i5 = _iterator5.next(); + if (_i5.done) break; + _ref5 = _i5.value; + } + + const workspaceName = _ref5; + + const workspaceManifest = workspaces[workspaceName].manifest; + workspaceDependencies[workspaceName] = workspaceManifest.version; + + // include dependencies from all workspaces + if (_this.flags.includeWorkspaceDeps) { + pushDeps('dependencies', workspaceManifest, { hint: null, optional: false }, true); + pushDeps('devDependencies', workspaceManifest, { hint: 'dev', optional: false }, !_this.config.production); + pushDeps('optionalDependencies', workspaceManifest, { hint: 'optional', optional: true }, true); + } + } + const virtualDependencyManifest = { + _uid: '', + name: `workspace-aggregator-${uuid.v4()}`, + version: '1.0.0', + _registry: 'npm', + _loc: workspacesRoot, + dependencies: workspaceDependencies, + devDependencies: (0, (_extends2 || _load_extends()).default)({}, workspaceManifestJson.devDependencies), + optionalDependencies: (0, (_extends2 || _load_extends()).default)({}, workspaceManifestJson.optionalDependencies), + private: workspaceManifestJson.private, + workspaces: workspaceManifestJson.workspaces + }; + workspaceLayout.virtualManifestName = virtualDependencyManifest.name; + const virtualDep = {}; + virtualDep[virtualDependencyManifest.name] = virtualDependencyManifest.version; + workspaces[virtualDependencyManifest.name] = { loc: workspacesRoot, manifest: virtualDependencyManifest }; + + // ensure dependencies that should be excluded are stripped from the correct manifest + stripExcluded(cwdIsRoot ? virtualDependencyManifest : workspaces[projectManifestJson.name].manifest); + + pushDeps('workspaces', { workspaces: virtualDep }, { hint: 'workspaces', optional: false }, true); + + const implicitWorkspaceDependencies = (0, (_extends2 || _load_extends()).default)({}, workspaceDependencies); + + for (var _iterator6 = (_constants || _load_constants()).OWNED_DEPENDENCY_TYPES, _isArray6 = Array.isArray(_iterator6), _i6 = 0, _iterator6 = _isArray6 ? _iterator6 : _iterator6[Symbol.iterator]();;) { + var _ref6; + + if (_isArray6) { + if (_i6 >= _iterator6.length) break; + _ref6 = _iterator6[_i6++]; + } else { + _i6 = _iterator6.next(); + if (_i6.done) break; + _ref6 = _i6.value; + } + + const type = _ref6; + + for (var _iterator7 = Object.keys(projectManifestJson[type] || {}), _isArray7 = Array.isArray(_iterator7), _i7 = 0, _iterator7 = _isArray7 ? _iterator7 : _iterator7[Symbol.iterator]();;) { + var _ref7; + + if (_isArray7) { + if (_i7 >= _iterator7.length) break; + _ref7 = _iterator7[_i7++]; + } else { + _i7 = _iterator7.next(); + if (_i7.done) break; + _ref7 = _i7.value; + } + + const dependencyName = _ref7; + + delete implicitWorkspaceDependencies[dependencyName]; + } + } + + pushDeps('dependencies', { dependencies: implicitWorkspaceDependencies }, { hint: 'workspaces', optional: false }, true); + } + + break; + } + + // inherit root flat flag + if (manifest.flat) { + _this.flags.flat = true; + } + + return { + requests: [...resolutionDeps, ...deps], + patterns, + manifest, + usedPatterns, + ignorePatterns, + workspaceLayout + }; + })(); + } + + /** + * TODO description + */ + + prepareRequests(requests) { + return requests; + } + + preparePatterns(patterns) { + return patterns; + } + preparePatternsForLinking(patterns, cwdManifest, cwdIsRoot) { + return patterns; + } + + prepareManifests() { + var _this2 = this; + + return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { + const manifests = yield _this2.config.getRootManifests(); + return manifests; + })(); + } + + bailout(patterns, workspaceLayout) { + var _this3 = this; + + return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { + // We don't want to skip the audit - it could yield important errors + if (_this3.flags.audit) { + return false; + } + // PNP is so fast that the integrity check isn't pertinent + if (_this3.config.plugnplayEnabled) { + return false; + } + if (_this3.flags.skipIntegrityCheck || _this3.flags.force) { + return false; + } + const lockfileCache = _this3.lockfile.cache; + if (!lockfileCache) { + return false; + } + const lockfileClean = _this3.lockfile.parseResultType === 'success'; + const match = yield _this3.integrityChecker.check(patterns, lockfileCache, _this3.flags, workspaceLayout); + if (_this3.flags.frozenLockfile && (!lockfileClean || match.missingPatterns.length > 0)) { + throw new (_errors || _load_errors()).MessageError(_this3.reporter.lang('frozenLockfileError')); + } + + const haveLockfile = yield (_fs || _load_fs()).exists(path.join(_this3.config.lockfileFolder, (_constants || _load_constants()).LOCKFILE_FILENAME)); + + const lockfileIntegrityPresent = !_this3.lockfile.hasEntriesExistWithoutIntegrity(); + const integrityBailout = lockfileIntegrityPresent || !_this3.config.autoAddIntegrity; + + if (match.integrityMatches && haveLockfile && lockfileClean && integrityBailout) { + _this3.reporter.success(_this3.reporter.lang('upToDate')); + return true; + } + + if (match.integrityFileMissing && haveLockfile) { + // Integrity file missing, force script installations + _this3.scripts.setForce(true); + return false; + } + + if (match.hardRefreshRequired) { + // e.g. node version doesn't match, force script installations + _this3.scripts.setForce(true); + return false; + } + + if (!patterns.length && !match.integrityFileMissing) { + _this3.reporter.success(_this3.reporter.lang('nothingToInstall')); + yield _this3.createEmptyManifestFolders(); + yield _this3.saveLockfileAndIntegrity(patterns, workspaceLayout); + return true; + } + + return false; + })(); + } + + /** + * Produce empty folders for all used root manifests. + */ + + createEmptyManifestFolders() { + var _this4 = this; + + return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { + if (_this4.config.modulesFolder) { + // already created + return; + } + + for (var _iterator9 = _this4.rootManifestRegistries, _isArray9 = Array.isArray(_iterator9), _i9 = 0, _iterator9 = _isArray9 ? _iterator9 : _iterator9[Symbol.iterator]();;) { + var _ref10; + + if (_isArray9) { + if (_i9 >= _iterator9.length) break; + _ref10 = _iterator9[_i9++]; + } else { + _i9 = _iterator9.next(); + if (_i9.done) break; + _ref10 = _i9.value; + } + + const registryName = _ref10; + const folder = _this4.config.registries[registryName].folder; + + yield (_fs || _load_fs()).mkdirp(path.join(_this4.config.lockfileFolder, folder)); + } + })(); + } + + /** + * TODO description + */ + + markIgnored(patterns) { + for (var _iterator10 = patterns, _isArray10 = Array.isArray(_iterator10), _i10 = 0, _iterator10 = _isArray10 ? _iterator10 : _iterator10[Symbol.iterator]();;) { + var _ref11; + + if (_isArray10) { + if (_i10 >= _iterator10.length) break; + _ref11 = _iterator10[_i10++]; + } else { + _i10 = _iterator10.next(); + if (_i10.done) break; + _ref11 = _i10.value; + } + + const pattern = _ref11; + + const manifest = this.resolver.getStrictResolvedPattern(pattern); + const ref = manifest._reference; + invariant(ref, 'expected package reference'); + + // just mark the package as ignored. if the package is used by a required package, the hoister + // will take care of that. + ref.ignore = true; + } + } + + /** + * helper method that gets only recent manifests + * used by global.ls command + */ + getFlattenedDeps() { + var _this5 = this; + + return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { + var _ref12 = yield _this5.fetchRequestFromCwd(); + + const depRequests = _ref12.requests, + rawPatterns = _ref12.patterns; + + + yield _this5.resolver.init(depRequests, {}); + + const manifests = yield (_packageFetcher || _load_packageFetcher()).fetch(_this5.resolver.getManifests(), _this5.config); + _this5.resolver.updateManifests(manifests); + + return _this5.flatten(rawPatterns); + })(); + } + + /** + * TODO description + */ + + init() { + var _this6 = this; + + return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { + _this6.checkUpdate(); + + // warn if we have a shrinkwrap + if (yield (_fs || _load_fs()).exists(path.join(_this6.config.lockfileFolder, (_constants || _load_constants()).NPM_SHRINKWRAP_FILENAME))) { + _this6.reporter.warn(_this6.reporter.lang('shrinkwrapWarning')); + } + + // warn if we have an npm lockfile + if (yield (_fs || _load_fs()).exists(path.join(_this6.config.lockfileFolder, (_constants || _load_constants()).NPM_LOCK_FILENAME))) { + _this6.reporter.warn(_this6.reporter.lang('npmLockfileWarning')); + } + + if (_this6.config.plugnplayEnabled) { + _this6.reporter.info(_this6.reporter.lang('plugnplaySuggestV2L1')); + _this6.reporter.info(_this6.reporter.lang('plugnplaySuggestV2L2')); + } + + let flattenedTopLevelPatterns = []; + const steps = []; + + var _ref13 = yield _this6.fetchRequestFromCwd(); + + const depRequests = _ref13.requests, + rawPatterns = _ref13.patterns, + ignorePatterns = _ref13.ignorePatterns, + workspaceLayout = _ref13.workspaceLayout, + manifest = _ref13.manifest; + + let topLevelPatterns = []; + + const artifacts = yield _this6.integrityChecker.getArtifacts(); + if (artifacts) { + _this6.linker.setArtifacts(artifacts); + _this6.scripts.setArtifacts(artifacts); + } + + if ((_packageCompatibility || _load_packageCompatibility()).shouldCheck(manifest, _this6.flags)) { + steps.push((() => { + var _ref14 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (curr, total) { + _this6.reporter.step(curr, total, _this6.reporter.lang('checkingManifest'), emoji.get('mag')); + yield _this6.checkCompatibility(); + }); + + return function (_x, _x2) { + return _ref14.apply(this, arguments); + }; + })()); + } + + const audit = new (_audit || _load_audit()).default(_this6.config, _this6.reporter, { groups: (_constants || _load_constants()).OWNED_DEPENDENCY_TYPES }); + let auditFoundProblems = false; + + steps.push(function (curr, total) { + return (0, (_hooks || _load_hooks()).callThroughHook)('resolveStep', (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { + _this6.reporter.step(curr, total, _this6.reporter.lang('resolvingPackages'), emoji.get('mag')); + yield _this6.resolver.init(_this6.prepareRequests(depRequests), { + isFlat: _this6.flags.flat, + isFrozen: _this6.flags.frozenLockfile, + workspaceLayout + }); + topLevelPatterns = _this6.preparePatterns(rawPatterns); + flattenedTopLevelPatterns = yield _this6.flatten(topLevelPatterns); + return { bailout: !_this6.flags.audit && (yield _this6.bailout(topLevelPatterns, workspaceLayout)) }; + })); + }); + + if (_this6.flags.audit) { + steps.push(function (curr, total) { + return (0, (_hooks || _load_hooks()).callThroughHook)('auditStep', (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { + _this6.reporter.step(curr, total, _this6.reporter.lang('auditRunning'), emoji.get('mag')); + if (_this6.flags.offline) { + _this6.reporter.warn(_this6.reporter.lang('auditOffline')); + return { bailout: false }; + } + const preparedManifests = yield _this6.prepareManifests(); + // $FlowFixMe - Flow considers `m` in the map operation to be "mixed", so does not recognize `m.object` + const mergedManifest = Object.assign({}, ...Object.values(preparedManifests).map(function (m) { + return m.object; + })); + const auditVulnerabilityCounts = yield audit.performAudit(mergedManifest, _this6.lockfile, _this6.resolver, _this6.linker, topLevelPatterns); + auditFoundProblems = auditVulnerabilityCounts.info || auditVulnerabilityCounts.low || auditVulnerabilityCounts.moderate || auditVulnerabilityCounts.high || auditVulnerabilityCounts.critical; + return { bailout: yield _this6.bailout(topLevelPatterns, workspaceLayout) }; + })); + }); + } + + steps.push(function (curr, total) { + return (0, (_hooks || _load_hooks()).callThroughHook)('fetchStep', (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { + _this6.markIgnored(ignorePatterns); + _this6.reporter.step(curr, total, _this6.reporter.lang('fetchingPackages'), emoji.get('truck')); + const manifests = yield (_packageFetcher || _load_packageFetcher()).fetch(_this6.resolver.getManifests(), _this6.config); + _this6.resolver.updateManifests(manifests); + yield (_packageCompatibility || _load_packageCompatibility()).check(_this6.resolver.getManifests(), _this6.config, _this6.flags.ignoreEngines); + })); + }); + + steps.push(function (curr, total) { + return (0, (_hooks || _load_hooks()).callThroughHook)('linkStep', (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { + // remove integrity hash to make this operation atomic + yield _this6.integrityChecker.removeIntegrityFile(); + _this6.reporter.step(curr, total, _this6.reporter.lang('linkingDependencies'), emoji.get('link')); + flattenedTopLevelPatterns = _this6.preparePatternsForLinking(flattenedTopLevelPatterns, manifest, _this6.config.lockfileFolder === _this6.config.cwd); + yield _this6.linker.init(flattenedTopLevelPatterns, workspaceLayout, { + linkDuplicates: _this6.flags.linkDuplicates, + ignoreOptional: _this6.flags.ignoreOptional + }); + })); + }); + + if (_this6.config.plugnplayEnabled) { + steps.push(function (curr, total) { + return (0, (_hooks || _load_hooks()).callThroughHook)('pnpStep', (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { + const pnpPath = `${_this6.config.lockfileFolder}/${(_constants || _load_constants()).PNP_FILENAME}`; + + const code = yield (0, (_generatePnpMap || _load_generatePnpMap()).generatePnpMap)(_this6.config, flattenedTopLevelPatterns, { + resolver: _this6.resolver, + reporter: _this6.reporter, + targetPath: pnpPath, + workspaceLayout + }); + + try { + const file = yield (_fs || _load_fs()).readFile(pnpPath); + if (file === code) { + return; + } + } catch (error) {} + + yield (_fs || _load_fs()).writeFile(pnpPath, code); + yield (_fs || _load_fs()).chmod(pnpPath, 0o755); + })); + }); + } + + steps.push(function (curr, total) { + return (0, (_hooks || _load_hooks()).callThroughHook)('buildStep', (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { + _this6.reporter.step(curr, total, _this6.flags.force ? _this6.reporter.lang('rebuildingPackages') : _this6.reporter.lang('buildingFreshPackages'), emoji.get('hammer')); + + if (_this6.config.ignoreScripts) { + _this6.reporter.warn(_this6.reporter.lang('ignoredScripts')); + } else { + yield _this6.scripts.init(flattenedTopLevelPatterns); + } + })); + }); + + if (_this6.flags.har) { + steps.push((() => { + var _ref21 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (curr, total) { + const formattedDate = new Date().toISOString().replace(/:/g, '-'); + const filename = `yarn-install_${formattedDate}.har`; + _this6.reporter.step(curr, total, _this6.reporter.lang('savingHar', filename), emoji.get('black_circle_for_record')); + yield _this6.config.requestManager.saveHar(filename); + }); + + return function (_x3, _x4) { + return _ref21.apply(this, arguments); + }; + })()); + } + + if (yield _this6.shouldClean()) { + steps.push((() => { + var _ref22 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (curr, total) { + _this6.reporter.step(curr, total, _this6.reporter.lang('cleaningModules'), emoji.get('recycle')); + yield (0, (_autoclean || _load_autoclean()).clean)(_this6.config, _this6.reporter); + }); + + return function (_x5, _x6) { + return _ref22.apply(this, arguments); + }; + })()); + } + + let currentStep = 0; + for (var _iterator11 = steps, _isArray11 = Array.isArray(_iterator11), _i11 = 0, _iterator11 = _isArray11 ? _iterator11 : _iterator11[Symbol.iterator]();;) { + var _ref23; + + if (_isArray11) { + if (_i11 >= _iterator11.length) break; + _ref23 = _iterator11[_i11++]; + } else { + _i11 = _iterator11.next(); + if (_i11.done) break; + _ref23 = _i11.value; + } + + const step = _ref23; + + const stepResult = yield step(++currentStep, steps.length); + if (stepResult && stepResult.bailout) { + if (_this6.flags.audit) { + audit.summary(); + } + if (auditFoundProblems) { + _this6.reporter.warn(_this6.reporter.lang('auditRunAuditForDetails')); + } + _this6.maybeOutputUpdate(); + return flattenedTopLevelPatterns; + } + } + + // fin! + if (_this6.flags.audit) { + audit.summary(); + } + if (auditFoundProblems) { + _this6.reporter.warn(_this6.reporter.lang('auditRunAuditForDetails')); + } + yield _this6.saveLockfileAndIntegrity(topLevelPatterns, workspaceLayout); + yield _this6.persistChanges(); + _this6.maybeOutputUpdate(); + _this6.config.requestManager.clearCache(); + return flattenedTopLevelPatterns; + })(); + } + + checkCompatibility() { + var _this7 = this; + + return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { + var _ref24 = yield _this7.fetchRequestFromCwd(); + + const manifest = _ref24.manifest; + + yield (_packageCompatibility || _load_packageCompatibility()).checkOne(manifest, _this7.config, _this7.flags.ignoreEngines); + })(); + } + + persistChanges() { + var _this8 = this; + + return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { + // get all the different registry manifests in this folder + const manifests = yield _this8.config.getRootManifests(); + + if (yield _this8.applyChanges(manifests)) { + yield _this8.config.saveRootManifests(manifests); + } + })(); + } + + applyChanges(manifests) { + let hasChanged = false; + + if (this.config.plugnplayPersist) { + const object = manifests.npm.object; + + + if (typeof object.installConfig !== 'object') { + object.installConfig = {}; + } + + if (this.config.plugnplayEnabled && object.installConfig.pnp !== true) { + object.installConfig.pnp = true; + hasChanged = true; + } else if (!this.config.plugnplayEnabled && typeof object.installConfig.pnp !== 'undefined') { + delete object.installConfig.pnp; + hasChanged = true; + } + + if (Object.keys(object.installConfig).length === 0) { + delete object.installConfig; + } + } + + return Promise.resolve(hasChanged); + } + + /** + * Check if we should run the cleaning step. + */ + + shouldClean() { + return (_fs || _load_fs()).exists(path.join(this.config.lockfileFolder, (_constants || _load_constants()).CLEAN_FILENAME)); + } + + /** + * TODO + */ + + flatten(patterns) { + var _this9 = this; + + return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { + if (!_this9.flags.flat) { + return patterns; + } + + const flattenedPatterns = []; + + for (var _iterator12 = _this9.resolver.getAllDependencyNamesByLevelOrder(patterns), _isArray12 = Array.isArray(_iterator12), _i12 = 0, _iterator12 = _isArray12 ? _iterator12 : _iterator12[Symbol.iterator]();;) { + var _ref25; + + if (_isArray12) { + if (_i12 >= _iterator12.length) break; + _ref25 = _iterator12[_i12++]; + } else { + _i12 = _iterator12.next(); + if (_i12.done) break; + _ref25 = _i12.value; + } + + const name = _ref25; + + const infos = _this9.resolver.getAllInfoForPackageName(name).filter(function (manifest) { + const ref = manifest._reference; + invariant(ref, 'expected package reference'); + return !ref.ignore; + }); + + if (infos.length === 0) { + continue; + } + + if (infos.length === 1) { + // single version of this package + // take out a single pattern as multiple patterns may have resolved to this package + flattenedPatterns.push(_this9.resolver.patternsByPackage[name][0]); + continue; + } + + const options = infos.map(function (info) { + const ref = info._reference; + invariant(ref, 'expected reference'); + return { + // TODO `and is required by {PARENT}`, + name: _this9.reporter.lang('manualVersionResolutionOption', ref.patterns.join(', '), info.version), + + value: info.version + }; + }); + const versions = infos.map(function (info) { + return info.version; + }); + let version; + + const resolutionVersion = _this9.resolutions[name]; + if (resolutionVersion && versions.indexOf(resolutionVersion) >= 0) { + // use json `resolution` version + version = resolutionVersion; + } else { + version = yield _this9.reporter.select(_this9.reporter.lang('manualVersionResolution', name), _this9.reporter.lang('answer'), options); + _this9.resolutions[name] = version; + } + + flattenedPatterns.push(_this9.resolver.collapseAllVersionsOfPackage(name, version)); + } + + // save resolutions to their appropriate root manifest + if (Object.keys(_this9.resolutions).length) { + const manifests = yield _this9.config.getRootManifests(); + + for (const name in _this9.resolutions) { + const version = _this9.resolutions[name]; + + const patterns = _this9.resolver.patternsByPackage[name]; + if (!patterns) { + continue; + } + + let manifest; + for (var _iterator13 = patterns, _isArray13 = Array.isArray(_iterator13), _i13 = 0, _iterator13 = _isArray13 ? _iterator13 : _iterator13[Symbol.iterator]();;) { + var _ref26; + + if (_isArray13) { + if (_i13 >= _iterator13.length) break; + _ref26 = _iterator13[_i13++]; + } else { + _i13 = _iterator13.next(); + if (_i13.done) break; + _ref26 = _i13.value; + } + + const pattern = _ref26; + + manifest = _this9.resolver.getResolvedPattern(pattern); + if (manifest) { + break; + } + } + invariant(manifest, 'expected manifest'); + + const ref = manifest._reference; + invariant(ref, 'expected reference'); + + const object = manifests[ref.registry].object; + object.resolutions = object.resolutions || {}; + object.resolutions[name] = version; + } + + yield _this9.config.saveRootManifests(manifests); + } + + return flattenedPatterns; + })(); + } + + /** + * Remove offline tarballs that are no longer required + */ + + pruneOfflineMirror(lockfile) { + var _this10 = this; + + return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { + const mirror = _this10.config.getOfflineMirrorPath(); + if (!mirror) { + return; + } + + const requiredTarballs = new Set(); + for (const dependency in lockfile) { + const resolved = lockfile[dependency].resolved; + if (resolved) { + const basename = path.basename(resolved.split('#')[0]); + if (dependency[0] === '@' && basename[0] !== '@') { + requiredTarballs.add(`${dependency.split('/')[0]}-${basename}`); + } + requiredTarballs.add(basename); + } + } + + const mirrorFiles = yield (_fs || _load_fs()).walk(mirror); + for (var _iterator14 = mirrorFiles, _isArray14 = Array.isArray(_iterator14), _i14 = 0, _iterator14 = _isArray14 ? _iterator14 : _iterator14[Symbol.iterator]();;) { + var _ref27; + + if (_isArray14) { + if (_i14 >= _iterator14.length) break; + _ref27 = _iterator14[_i14++]; + } else { + _i14 = _iterator14.next(); + if (_i14.done) break; + _ref27 = _i14.value; + } + + const file = _ref27; + + const isTarball = path.extname(file.basename) === '.tgz'; + // if using experimental-pack-script-packages-in-mirror flag, don't unlink prebuilt packages + const hasPrebuiltPackage = file.relative.startsWith('prebuilt/'); + if (isTarball && !hasPrebuiltPackage && !requiredTarballs.has(file.basename)) { + yield (_fs || _load_fs()).unlink(file.absolute); + } + } + })(); + } + + /** + * Save updated integrity and lockfiles. + */ + + saveLockfileAndIntegrity(patterns, workspaceLayout) { + var _this11 = this; + + return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { + const resolvedPatterns = {}; + Object.keys(_this11.resolver.patterns).forEach(function (pattern) { + if (!workspaceLayout || !workspaceLayout.getManifestByPattern(pattern)) { + resolvedPatterns[pattern] = _this11.resolver.patterns[pattern]; + } + }); + + // TODO this code is duplicated in a few places, need a common way to filter out workspace patterns from lockfile + patterns = patterns.filter(function (p) { + return !workspaceLayout || !workspaceLayout.getManifestByPattern(p); + }); + + const lockfileBasedOnResolver = _this11.lockfile.getLockfile(resolvedPatterns); + + if (_this11.config.pruneOfflineMirror) { + yield _this11.pruneOfflineMirror(lockfileBasedOnResolver); + } + + // write integrity hash + if (!_this11.config.plugnplayEnabled) { + yield _this11.integrityChecker.save(patterns, lockfileBasedOnResolver, _this11.flags, workspaceLayout, _this11.scripts.getArtifacts()); + } + + // --no-lockfile or --pure-lockfile or --frozen-lockfile + if (_this11.flags.lockfile === false || _this11.flags.pureLockfile || _this11.flags.frozenLockfile) { + return; + } + + const lockFileHasAllPatterns = patterns.every(function (p) { + return _this11.lockfile.getLocked(p); + }); + const lockfilePatternsMatch = Object.keys(_this11.lockfile.cache || {}).every(function (p) { + return lockfileBasedOnResolver[p]; + }); + const resolverPatternsAreSameAsInLockfile = Object.keys(lockfileBasedOnResolver).every(function (pattern) { + const manifest = _this11.lockfile.getLocked(pattern); + return manifest && manifest.resolved === lockfileBasedOnResolver[pattern].resolved && deepEqual(manifest.prebuiltVariants, lockfileBasedOnResolver[pattern].prebuiltVariants); + }); + const integrityPatternsAreSameAsInLockfile = Object.keys(lockfileBasedOnResolver).every(function (pattern) { + const existingIntegrityInfo = lockfileBasedOnResolver[pattern].integrity; + if (!existingIntegrityInfo) { + // if this entry does not have an integrity, no need to re-write the lockfile because of it + return true; + } + const manifest = _this11.lockfile.getLocked(pattern); + if (manifest && manifest.integrity) { + const manifestIntegrity = ssri.stringify(manifest.integrity); + return manifestIntegrity === existingIntegrityInfo; + } + return false; + }); + + // remove command is followed by install with force, lockfile will be rewritten in any case then + if (!_this11.flags.force && _this11.lockfile.parseResultType === 'success' && lockFileHasAllPatterns && lockfilePatternsMatch && resolverPatternsAreSameAsInLockfile && integrityPatternsAreSameAsInLockfile && patterns.length) { + return; + } + + // build lockfile location + const loc = path.join(_this11.config.lockfileFolder, (_constants || _load_constants()).LOCKFILE_FILENAME); + + // write lockfile + const lockSource = (0, (_lockfile2 || _load_lockfile2()).stringify)(lockfileBasedOnResolver, false, _this11.config.enableLockfileVersions); + yield (_fs || _load_fs()).writeFilePreservingEol(loc, lockSource); + + _this11._logSuccessSaveLockfile(); + })(); + } + + _logSuccessSaveLockfile() { + this.reporter.success(this.reporter.lang('savedLockfile')); + } + + /** + * Load the dependency graph of the current install. Only does package resolving and wont write to the cwd. + */ + hydrate(ignoreUnusedPatterns) { + var _this12 = this; + + return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { + const request = yield _this12.fetchRequestFromCwd([], ignoreUnusedPatterns); + const depRequests = request.requests, + rawPatterns = request.patterns, + ignorePatterns = request.ignorePatterns, + workspaceLayout = request.workspaceLayout; + + + yield _this12.resolver.init(depRequests, { + isFlat: _this12.flags.flat, + isFrozen: _this12.flags.frozenLockfile, + workspaceLayout + }); + yield _this12.flatten(rawPatterns); + _this12.markIgnored(ignorePatterns); + + // fetch packages, should hit cache most of the time + const manifests = yield (_packageFetcher || _load_packageFetcher()).fetch(_this12.resolver.getManifests(), _this12.config); + _this12.resolver.updateManifests(manifests); + yield (_packageCompatibility || _load_packageCompatibility()).check(_this12.resolver.getManifests(), _this12.config, _this12.flags.ignoreEngines); + + // expand minimal manifests + for (var _iterator15 = _this12.resolver.getManifests(), _isArray15 = Array.isArray(_iterator15), _i15 = 0, _iterator15 = _isArray15 ? _iterator15 : _iterator15[Symbol.iterator]();;) { + var _ref28; + + if (_isArray15) { + if (_i15 >= _iterator15.length) break; + _ref28 = _iterator15[_i15++]; + } else { + _i15 = _iterator15.next(); + if (_i15.done) break; + _ref28 = _i15.value; + } + + const manifest = _ref28; + + const ref = manifest._reference; + invariant(ref, 'expected reference'); + const type = ref.remote.type; + // link specifier won't ever hit cache + + let loc = ''; + if (type === 'link') { + continue; + } else if (type === 'workspace') { + if (!ref.remote.reference) { + continue; + } + loc = ref.remote.reference; + } else { + loc = _this12.config.generateModuleCachePath(ref); + } + const newPkg = yield _this12.config.readManifest(loc); + yield _this12.resolver.updateManifest(ref, newPkg); + } + + return request; + })(); + } + + /** + * Check for updates every day and output a nag message if there's a newer version. + */ + + checkUpdate() { + if (this.config.nonInteractive) { + // don't show upgrade dialog on CI or non-TTY terminals + return; + } + + // don't check if disabled + if (this.config.getOption('disable-self-update-check')) { + return; + } + + // only check for updates once a day + const lastUpdateCheck = Number(this.config.getOption('lastUpdateCheck')) || 0; + if (lastUpdateCheck && Date.now() - lastUpdateCheck < ONE_DAY) { + return; + } + + // don't bug for updates on tagged releases + if ((_yarnVersion || _load_yarnVersion()).version.indexOf('-') >= 0) { + return; + } + + this._checkUpdate().catch(() => { + // swallow errors + }); + } + + _checkUpdate() { + var _this13 = this; + + return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { + let latestVersion = yield _this13.config.requestManager.request({ + url: (_constants || _load_constants()).SELF_UPDATE_VERSION_URL + }); + invariant(typeof latestVersion === 'string', 'expected string'); + latestVersion = latestVersion.trim(); + if (!semver.valid(latestVersion)) { + return; + } + + // ensure we only check for updates periodically + _this13.config.registries.yarn.saveHomeConfig({ + lastUpdateCheck: Date.now() + }); + + if (semver.gt(latestVersion, (_yarnVersion || _load_yarnVersion()).version)) { + const installationMethod = yield (0, (_yarnVersion || _load_yarnVersion()).getInstallationMethod)(); + _this13.maybeOutputUpdate = function () { + _this13.reporter.warn(_this13.reporter.lang('yarnOutdated', latestVersion, (_yarnVersion || _load_yarnVersion()).version)); + + const command = getUpdateCommand(installationMethod); + if (command) { + _this13.reporter.info(_this13.reporter.lang('yarnOutdatedCommand')); + _this13.reporter.command(command); + } else { + const installer = getUpdateInstaller(installationMethod); + if (installer) { + _this13.reporter.info(_this13.reporter.lang('yarnOutdatedInstaller', installer)); + } + } + }; + } + })(); + } + + /** + * Method to override with a possible upgrade message. + */ + + maybeOutputUpdate() {} +} + +exports.Install = Install; +function hasWrapper(commander, args) { + return true; +} + +function setFlags(commander) { + commander.description('Yarn install is used to install all dependencies for a project.'); + commander.usage('install [flags]'); + commander.option('-A, --audit', 'Run vulnerability audit on installed packages'); + commander.option('-g, --global', 'DEPRECATED'); + commander.option('-S, --save', 'DEPRECATED - save package to your `dependencies`'); + commander.option('-D, --save-dev', 'DEPRECATED - save package to your `devDependencies`'); + commander.option('-P, --save-peer', 'DEPRECATED - save package to your `peerDependencies`'); + commander.option('-O, --save-optional', 'DEPRECATED - save package to your `optionalDependencies`'); + commander.option('-E, --save-exact', 'DEPRECATED'); + commander.option('-T, --save-tilde', 'DEPRECATED'); +} + +/***/ }), +/* 35 */ +/***/ (function(module, exports, __webpack_require__) { + +var isObject = __webpack_require__(52); +module.exports = function (it) { + if (!isObject(it)) throw TypeError(it + ' is not an object!'); + return it; +}; + + +/***/ }), +/* 36 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return SubjectSubscriber; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return Subject; }); +/* unused harmony export AnonymousSubject */ +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_tslib__ = __webpack_require__(1); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__Observable__ = __webpack_require__(12); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__Subscriber__ = __webpack_require__(7); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__Subscription__ = __webpack_require__(25); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__util_ObjectUnsubscribedError__ = __webpack_require__(189); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__SubjectSubscription__ = __webpack_require__(422); +/* harmony import */ var __WEBPACK_IMPORTED_MODULE_6__internal_symbol_rxSubscriber__ = __webpack_require__(321); +/** PURE_IMPORTS_START tslib,_Observable,_Subscriber,_Subscription,_util_ObjectUnsubscribedError,_SubjectSubscription,_internal_symbol_rxSubscriber PURE_IMPORTS_END */ + + + + + + + +var SubjectSubscriber = /*@__PURE__*/ (function (_super) { + __WEBPACK_IMPORTED_MODULE_0_tslib__["a" /* __extends */](SubjectSubscriber, _super); + function SubjectSubscriber(destination) { + var _this = _super.call(this, destination) || this; + _this.destination = destination; + return _this; + } + return SubjectSubscriber; +}(__WEBPACK_IMPORTED_MODULE_2__Subscriber__["a" /* Subscriber */])); + +var Subject = /*@__PURE__*/ (function (_super) { + __WEBPACK_IMPORTED_MODULE_0_tslib__["a" /* __extends */](Subject, _super); + function Subject() { + var _this = _super.call(this) || this; + _this.observers = []; + _this.closed = false; + _this.isStopped = false; + _this.hasError = false; + _this.thrownError = null; + return _this; + } + Subject.prototype[__WEBPACK_IMPORTED_MODULE_6__internal_symbol_rxSubscriber__["a" /* rxSubscriber */]] = function () { + return new SubjectSubscriber(this); + }; + Subject.prototype.lift = function (operator) { + var subject = new AnonymousSubject(this, this); + subject.operator = operator; + return subject; + }; + Subject.prototype.next = function (value) { + if (this.closed) { + throw new __WEBPACK_IMPORTED_MODULE_4__util_ObjectUnsubscribedError__["a" /* ObjectUnsubscribedError */](); + } + if (!this.isStopped) { + var observers = this.observers; + var len = observers.length; + var copy = observers.slice(); + for (var i = 0; i < len; i++) { + copy[i].next(value); + } + } + }; + Subject.prototype.error = function (err) { + if (this.closed) { + throw new __WEBPACK_IMPORTED_MODULE_4__util_ObjectUnsubscribedError__["a" /* ObjectUnsubscribedError */](); + } + this.hasError = true; + this.thrownError = err; + this.isStopped = true; + var observers = this.observers; + var len = observers.length; + var copy = observers.slice(); + for (var i = 0; i < len; i++) { + copy[i].error(err); + } + this.observers.length = 0; + }; + Subject.prototype.complete = function () { + if (this.closed) { + throw new __WEBPACK_IMPORTED_MODULE_4__util_ObjectUnsubscribedError__["a" /* ObjectUnsubscribedError */](); + } + this.isStopped = true; + var observers = this.observers; + var len = observers.length; + var copy = observers.slice(); + for (var i = 0; i < len; i++) { + copy[i].complete(); + } + this.observers.length = 0; + }; + Subject.prototype.unsubscribe = function () { + this.isStopped = true; + this.closed = true; + this.observers = null; + }; + Subject.prototype._trySubscribe = function (subscriber) { + if (this.closed) { + throw new __WEBPACK_IMPORTED_MODULE_4__util_ObjectUnsubscribedError__["a" /* ObjectUnsubscribedError */](); + } + else { + return _super.prototype._trySubscribe.call(this, subscriber); + } + }; + Subject.prototype._subscribe = function (subscriber) { + if (this.closed) { + throw new __WEBPACK_IMPORTED_MODULE_4__util_ObjectUnsubscribedError__["a" /* ObjectUnsubscribedError */](); + } + else if (this.hasError) { + subscriber.error(this.thrownError); + return __WEBPACK_IMPORTED_MODULE_3__Subscription__["a" /* Subscription */].EMPTY; + } + else if (this.isStopped) { + subscriber.complete(); + return __WEBPACK_IMPORTED_MODULE_3__Subscription__["a" /* Subscription */].EMPTY; + } + else { + this.observers.push(subscriber); + return new __WEBPACK_IMPORTED_MODULE_5__SubjectSubscription__["a" /* SubjectSubscription */](this, subscriber); + } + }; + Subject.prototype.asObservable = function () { + var observable = new __WEBPACK_IMPORTED_MODULE_1__Observable__["a" /* Observable */](); + observable.source = this; + return observable; + }; + Subject.create = function (destination, source) { + return new AnonymousSubject(destination, source); + }; + return Subject; +}(__WEBPACK_IMPORTED_MODULE_1__Observable__["a" /* Observable */])); + +var AnonymousSubject = /*@__PURE__*/ (function (_super) { + __WEBPACK_IMPORTED_MODULE_0_tslib__["a" /* __extends */](AnonymousSubject, _super); + function AnonymousSubject(destination, source) { + var _this = _super.call(this) || this; + _this.destination = destination; + _this.source = source; + return _this; + } + AnonymousSubject.prototype.next = function (value) { + var destination = this.destination; + if (destination && destination.next) { + destination.next(value); + } + }; + AnonymousSubject.prototype.error = function (err) { + var destination = this.destination; + if (destination && destination.error) { + this.destination.error(err); + } + }; + AnonymousSubject.prototype.complete = function () { + var destination = this.destination; + if (destination && destination.complete) { + this.destination.complete(); + } + }; + AnonymousSubject.prototype._subscribe = function (subscriber) { + var source = this.source; + if (source) { + return this.source.subscribe(subscriber); + } + else { + return __WEBPACK_IMPORTED_MODULE_3__Subscription__["a" /* Subscription */].EMPTY; + } + }; + return AnonymousSubject; +}(Subject)); + +//# sourceMappingURL=Subject.js.map + + +/***/ }), +/* 37 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.normalizePattern = normalizePattern; + +/** + * Explode and normalize a pattern into its name and range. + */ + +function normalizePattern(pattern) { + let hasVersion = false; + let range = 'latest'; + let name = pattern; + + // if we're a scope then remove the @ and add it back later + let isScoped = false; + if (name[0] === '@') { + isScoped = true; + name = name.slice(1); + } + + // take first part as the name + const parts = name.split('@'); + if (parts.length > 1) { + name = parts.shift(); + range = parts.join('@'); + + if (range) { + hasVersion = true; + } else { + range = '*'; + } + } + + // add back @ scope suffix + if (isScoped) { + name = `@${name}`; + } + + return { name, range, hasVersion }; +} + +/***/ }), +/* 38 */ +/***/ (function(module, exports, __webpack_require__) { + +/* WEBPACK VAR INJECTION */(function(module) {var __WEBPACK_AMD_DEFINE_RESULT__;/** + * @license + * Lodash + * Copyright JS Foundation and other contributors + * Released under MIT license + * Based on Underscore.js 1.8.3 + * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + */ +;(function() { + + /** Used as a safe reference for `undefined` in pre-ES5 environments. */ + var undefined; + + /** Used as the semantic version number. */ + var VERSION = '4.17.10'; + + /** Used as the size to enable large array optimizations. */ + var LARGE_ARRAY_SIZE = 200; + + /** Error message constants. */ + var CORE_ERROR_TEXT = 'Unsupported core-js use. Try https://npms.io/search?q=ponyfill.', + FUNC_ERROR_TEXT = 'Expected a function'; + + /** Used to stand-in for `undefined` hash values. */ + var HASH_UNDEFINED = '__lodash_hash_undefined__'; + + /** Used as the maximum memoize cache size. */ + var MAX_MEMOIZE_SIZE = 500; + + /** Used as the internal argument placeholder. */ + var PLACEHOLDER = '__lodash_placeholder__'; + + /** Used to compose bitmasks for cloning. */ + var CLONE_DEEP_FLAG = 1, + CLONE_FLAT_FLAG = 2, + CLONE_SYMBOLS_FLAG = 4; + + /** Used to compose bitmasks for value comparisons. */ + var COMPARE_PARTIAL_FLAG = 1, + COMPARE_UNORDERED_FLAG = 2; + + /** Used to compose bitmasks for function metadata. */ + var WRAP_BIND_FLAG = 1, + WRAP_BIND_KEY_FLAG = 2, + WRAP_CURRY_BOUND_FLAG = 4, + WRAP_CURRY_FLAG = 8, + WRAP_CURRY_RIGHT_FLAG = 16, + WRAP_PARTIAL_FLAG = 32, + WRAP_PARTIAL_RIGHT_FLAG = 64, + WRAP_ARY_FLAG = 128, + WRAP_REARG_FLAG = 256, + WRAP_FLIP_FLAG = 512; + + /** Used as default options for `_.truncate`. */ + var DEFAULT_TRUNC_LENGTH = 30, + DEFAULT_TRUNC_OMISSION = '...'; + + /** Used to detect hot functions by number of calls within a span of milliseconds. */ + var HOT_COUNT = 800, + HOT_SPAN = 16; + + /** Used to indicate the type of lazy iteratees. */ + var LAZY_FILTER_FLAG = 1, + LAZY_MAP_FLAG = 2, + LAZY_WHILE_FLAG = 3; + + /** Used as references for various `Number` constants. */ + var INFINITY = 1 / 0, + MAX_SAFE_INTEGER = 9007199254740991, + MAX_INTEGER = 1.7976931348623157e+308, + NAN = 0 / 0; + + /** Used as references for the maximum length and index of an array. */ + var MAX_ARRAY_LENGTH = 4294967295, + MAX_ARRAY_INDEX = MAX_ARRAY_LENGTH - 1, + HALF_MAX_ARRAY_LENGTH = MAX_ARRAY_LENGTH >>> 1; + + /** Used to associate wrap methods with their bit flags. */ + var wrapFlags = [ + ['ary', WRAP_ARY_FLAG], + ['bind', WRAP_BIND_FLAG], + ['bindKey', WRAP_BIND_KEY_FLAG], + ['curry', WRAP_CURRY_FLAG], + ['curryRight', WRAP_CURRY_RIGHT_FLAG], + ['flip', WRAP_FLIP_FLAG], + ['partial', WRAP_PARTIAL_FLAG], + ['partialRight', WRAP_PARTIAL_RIGHT_FLAG], + ['rearg', WRAP_REARG_FLAG] + ]; + + /** `Object#toString` result references. */ + var argsTag = '[object Arguments]', + arrayTag = '[object Array]', + asyncTag = '[object AsyncFunction]', + boolTag = '[object Boolean]', + dateTag = '[object Date]', + domExcTag = '[object DOMException]', + errorTag = '[object Error]', + funcTag = '[object Function]', + genTag = '[object GeneratorFunction]', + mapTag = '[object Map]', + numberTag = '[object Number]', + nullTag = '[object Null]', + objectTag = '[object Object]', + promiseTag = '[object Promise]', + proxyTag = '[object Proxy]', + regexpTag = '[object RegExp]', + setTag = '[object Set]', + stringTag = '[object String]', + symbolTag = '[object Symbol]', + undefinedTag = '[object Undefined]', + weakMapTag = '[object WeakMap]', + weakSetTag = '[object WeakSet]'; + + var arrayBufferTag = '[object ArrayBuffer]', + dataViewTag = '[object DataView]', + float32Tag = '[object Float32Array]', + float64Tag = '[object Float64Array]', + int8Tag = '[object Int8Array]', + int16Tag = '[object Int16Array]', + int32Tag = '[object Int32Array]', + uint8Tag = '[object Uint8Array]', + uint8ClampedTag = '[object Uint8ClampedArray]', + uint16Tag = '[object Uint16Array]', + uint32Tag = '[object Uint32Array]'; + + /** Used to match empty string literals in compiled template source. */ + var reEmptyStringLeading = /\b__p \+= '';/g, + reEmptyStringMiddle = /\b(__p \+=) '' \+/g, + reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g; + + /** Used to match HTML entities and HTML characters. */ + var reEscapedHtml = /&(?:amp|lt|gt|quot|#39);/g, + reUnescapedHtml = /[&<>"']/g, + reHasEscapedHtml = RegExp(reEscapedHtml.source), + reHasUnescapedHtml = RegExp(reUnescapedHtml.source); + + /** Used to match template delimiters. */ + var reEscape = /<%-([\s\S]+?)%>/g, + reEvaluate = /<%([\s\S]+?)%>/g, + reInterpolate = /<%=([\s\S]+?)%>/g; + + /** Used to match property names within property paths. */ + var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/, + reIsPlainProp = /^\w*$/, + rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g; + + /** + * Used to match `RegExp` + * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns). + */ + var reRegExpChar = /[\\^$.*+?()[\]{}|]/g, + reHasRegExpChar = RegExp(reRegExpChar.source); + + /** Used to match leading and trailing whitespace. */ + var reTrim = /^\s+|\s+$/g, + reTrimStart = /^\s+/, + reTrimEnd = /\s+$/; + + /** Used to match wrap detail comments. */ + var reWrapComment = /\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/, + reWrapDetails = /\{\n\/\* \[wrapped with (.+)\] \*/, + reSplitDetails = /,? & /; + + /** Used to match words composed of alphanumeric characters. */ + var reAsciiWord = /[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g; + + /** Used to match backslashes in property paths. */ + var reEscapeChar = /\\(\\)?/g; + + /** + * Used to match + * [ES template delimiters](http://ecma-international.org/ecma-262/7.0/#sec-template-literal-lexical-components). + */ + var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g; + + /** Used to match `RegExp` flags from their coerced string values. */ + var reFlags = /\w*$/; + + /** Used to detect bad signed hexadecimal string values. */ + var reIsBadHex = /^[-+]0x[0-9a-f]+$/i; + + /** Used to detect binary string values. */ + var reIsBinary = /^0b[01]+$/i; + + /** Used to detect host constructors (Safari). */ + var reIsHostCtor = /^\[object .+?Constructor\]$/; + + /** Used to detect octal string values. */ + var reIsOctal = /^0o[0-7]+$/i; + + /** Used to detect unsigned integer values. */ + var reIsUint = /^(?:0|[1-9]\d*)$/; + + /** Used to match Latin Unicode letters (excluding mathematical operators). */ + var reLatin = /[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g; + + /** Used to ensure capturing order of template delimiters. */ + var reNoMatch = /($^)/; + + /** Used to match unescaped characters in compiled string literals. */ + var reUnescapedString = /['\n\r\u2028\u2029\\]/g; + + /** Used to compose unicode character classes. */ + var rsAstralRange = '\\ud800-\\udfff', + rsComboMarksRange = '\\u0300-\\u036f', + reComboHalfMarksRange = '\\ufe20-\\ufe2f', + rsComboSymbolsRange = '\\u20d0-\\u20ff', + rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange, + rsDingbatRange = '\\u2700-\\u27bf', + rsLowerRange = 'a-z\\xdf-\\xf6\\xf8-\\xff', + rsMathOpRange = '\\xac\\xb1\\xd7\\xf7', + rsNonCharRange = '\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf', + rsPunctuationRange = '\\u2000-\\u206f', + rsSpaceRange = ' \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000', + rsUpperRange = 'A-Z\\xc0-\\xd6\\xd8-\\xde', + rsVarRange = '\\ufe0e\\ufe0f', + rsBreakRange = rsMathOpRange + rsNonCharRange + rsPunctuationRange + rsSpaceRange; + + /** Used to compose unicode capture groups. */ + var rsApos = "['\u2019]", + rsAstral = '[' + rsAstralRange + ']', + rsBreak = '[' + rsBreakRange + ']', + rsCombo = '[' + rsComboRange + ']', + rsDigits = '\\d+', + rsDingbat = '[' + rsDingbatRange + ']', + rsLower = '[' + rsLowerRange + ']', + rsMisc = '[^' + rsAstralRange + rsBreakRange + rsDigits + rsDingbatRange + rsLowerRange + rsUpperRange + ']', + rsFitz = '\\ud83c[\\udffb-\\udfff]', + rsModifier = '(?:' + rsCombo + '|' + rsFitz + ')', + rsNonAstral = '[^' + rsAstralRange + ']', + rsRegional = '(?:\\ud83c[\\udde6-\\uddff]){2}', + rsSurrPair = '[\\ud800-\\udbff][\\udc00-\\udfff]', + rsUpper = '[' + rsUpperRange + ']', + rsZWJ = '\\u200d'; + + /** Used to compose unicode regexes. */ + var rsMiscLower = '(?:' + rsLower + '|' + rsMisc + ')', + rsMiscUpper = '(?:' + rsUpper + '|' + rsMisc + ')', + rsOptContrLower = '(?:' + rsApos + '(?:d|ll|m|re|s|t|ve))?', + rsOptContrUpper = '(?:' + rsApos + '(?:D|LL|M|RE|S|T|VE))?', + reOptMod = rsModifier + '?', + rsOptVar = '[' + rsVarRange + ']?', + rsOptJoin = '(?:' + rsZWJ + '(?:' + [rsNonAstral, rsRegional, rsSurrPair].join('|') + ')' + rsOptVar + reOptMod + ')*', + rsOrdLower = '\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])', + rsOrdUpper = '\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])', + rsSeq = rsOptVar + reOptMod + rsOptJoin, + rsEmoji = '(?:' + [rsDingbat, rsRegional, rsSurrPair].join('|') + ')' + rsSeq, + rsSymbol = '(?:' + [rsNonAstral + rsCombo + '?', rsCombo, rsRegional, rsSurrPair, rsAstral].join('|') + ')'; + + /** Used to match apostrophes. */ + var reApos = RegExp(rsApos, 'g'); + + /** + * Used to match [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks) and + * [combining diacritical marks for symbols](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks_for_Symbols). + */ + var reComboMark = RegExp(rsCombo, 'g'); + + /** Used to match [string symbols](https://mathiasbynens.be/notes/javascript-unicode). */ + var reUnicode = RegExp(rsFitz + '(?=' + rsFitz + ')|' + rsSymbol + rsSeq, 'g'); + + /** Used to match complex or compound words. */ + var reUnicodeWord = RegExp([ + rsUpper + '?' + rsLower + '+' + rsOptContrLower + '(?=' + [rsBreak, rsUpper, '$'].join('|') + ')', + rsMiscUpper + '+' + rsOptContrUpper + '(?=' + [rsBreak, rsUpper + rsMiscLower, '$'].join('|') + ')', + rsUpper + '?' + rsMiscLower + '+' + rsOptContrLower, + rsUpper + '+' + rsOptContrUpper, + rsOrdUpper, + rsOrdLower, + rsDigits, + rsEmoji + ].join('|'), 'g'); + + /** Used to detect strings with [zero-width joiners or code points from the astral planes](http://eev.ee/blog/2015/09/12/dark-corners-of-unicode/). */ + var reHasUnicode = RegExp('[' + rsZWJ + rsAstralRange + rsComboRange + rsVarRange + ']'); + + /** Used to detect strings that need a more robust regexp to match words. */ + var reHasUnicodeWord = /[a-z][A-Z]|[A-Z]{2,}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/; + + /** Used to assign default `context` object properties. */ + var contextProps = [ + 'Array', 'Buffer', 'DataView', 'Date', 'Error', 'Float32Array', 'Float64Array', + 'Function', 'Int8Array', 'Int16Array', 'Int32Array', 'Map', 'Math', 'Object', + 'Promise', 'RegExp', 'Set', 'String', 'Symbol', 'TypeError', 'Uint8Array', + 'Uint8ClampedArray', 'Uint16Array', 'Uint32Array', 'WeakMap', + '_', 'clearTimeout', 'isFinite', 'parseInt', 'setTimeout' + ]; + + /** Used to make template sourceURLs easier to identify. */ + var templateCounter = -1; + + /** Used to identify `toStringTag` values of typed arrays. */ + var typedArrayTags = {}; + typedArrayTags[float32Tag] = typedArrayTags[float64Tag] = + typedArrayTags[int8Tag] = typedArrayTags[int16Tag] = + typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] = + typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] = + typedArrayTags[uint32Tag] = true; + typedArrayTags[argsTag] = typedArrayTags[arrayTag] = + typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] = + typedArrayTags[dataViewTag] = typedArrayTags[dateTag] = + typedArrayTags[errorTag] = typedArrayTags[funcTag] = + typedArrayTags[mapTag] = typedArrayTags[numberTag] = + typedArrayTags[objectTag] = typedArrayTags[regexpTag] = + typedArrayTags[setTag] = typedArrayTags[stringTag] = + typedArrayTags[weakMapTag] = false; + + /** Used to identify `toStringTag` values supported by `_.clone`. */ + var cloneableTags = {}; + cloneableTags[argsTag] = cloneableTags[arrayTag] = + cloneableTags[arrayBufferTag] = cloneableTags[dataViewTag] = + cloneableTags[boolTag] = cloneableTags[dateTag] = + cloneableTags[float32Tag] = cloneableTags[float64Tag] = + cloneableTags[int8Tag] = cloneableTags[int16Tag] = + cloneableTags[int32Tag] = cloneableTags[mapTag] = + cloneableTags[numberTag] = cloneableTags[objectTag] = + cloneableTags[regexpTag] = cloneableTags[setTag] = + cloneableTags[stringTag] = cloneableTags[symbolTag] = + cloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] = + cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true; + cloneableTags[errorTag] = cloneableTags[funcTag] = + cloneableTags[weakMapTag] = false; + + /** Used to map Latin Unicode letters to basic Latin letters. */ + var deburredLetters = { + // Latin-1 Supplement block. + '\xc0': 'A', '\xc1': 'A', '\xc2': 'A', '\xc3': 'A', '\xc4': 'A', '\xc5': 'A', + '\xe0': 'a', '\xe1': 'a', '\xe2': 'a', '\xe3': 'a', '\xe4': 'a', '\xe5': 'a', + '\xc7': 'C', '\xe7': 'c', + '\xd0': 'D', '\xf0': 'd', + '\xc8': 'E', '\xc9': 'E', '\xca': 'E', '\xcb': 'E', + '\xe8': 'e', '\xe9': 'e', '\xea': 'e', '\xeb': 'e', + '\xcc': 'I', '\xcd': 'I', '\xce': 'I', '\xcf': 'I', + '\xec': 'i', '\xed': 'i', '\xee': 'i', '\xef': 'i', + '\xd1': 'N', '\xf1': 'n', + '\xd2': 'O', '\xd3': 'O', '\xd4': 'O', '\xd5': 'O', '\xd6': 'O', '\xd8': 'O', + '\xf2': 'o', '\xf3': 'o', '\xf4': 'o', '\xf5': 'o', '\xf6': 'o', '\xf8': 'o', + '\xd9': 'U', '\xda': 'U', '\xdb': 'U', '\xdc': 'U', + '\xf9': 'u', '\xfa': 'u', '\xfb': 'u', '\xfc': 'u', + '\xdd': 'Y', '\xfd': 'y', '\xff': 'y', + '\xc6': 'Ae', '\xe6': 'ae', + '\xde': 'Th', '\xfe': 'th', + '\xdf': 'ss', + // Latin Extended-A block. + '\u0100': 'A', '\u0102': 'A', '\u0104': 'A', + '\u0101': 'a', '\u0103': 'a', '\u0105': 'a', + '\u0106': 'C', '\u0108': 'C', '\u010a': 'C', '\u010c': 'C', + '\u0107': 'c', '\u0109': 'c', '\u010b': 'c', '\u010d': 'c', + '\u010e': 'D', '\u0110': 'D', '\u010f': 'd', '\u0111': 'd', + '\u0112': 'E', '\u0114': 'E', '\u0116': 'E', '\u0118': 'E', '\u011a': 'E', + '\u0113': 'e', '\u0115': 'e', '\u0117': 'e', '\u0119': 'e', '\u011b': 'e', + '\u011c': 'G', '\u011e': 'G', '\u0120': 'G', '\u0122': 'G', + '\u011d': 'g', '\u011f': 'g', '\u0121': 'g', '\u0123': 'g', + '\u0124': 'H', '\u0126': 'H', '\u0125': 'h', '\u0127': 'h', + '\u0128': 'I', '\u012a': 'I', '\u012c': 'I', '\u012e': 'I', '\u0130': 'I', + '\u0129': 'i', '\u012b': 'i', '\u012d': 'i', '\u012f': 'i', '\u0131': 'i', + '\u0134': 'J', '\u0135': 'j', + '\u0136': 'K', '\u0137': 'k', '\u0138': 'k', + '\u0139': 'L', '\u013b': 'L', '\u013d': 'L', '\u013f': 'L', '\u0141': 'L', + '\u013a': 'l', '\u013c': 'l', '\u013e': 'l', '\u0140': 'l', '\u0142': 'l', + '\u0143': 'N', '\u0145': 'N', '\u0147': 'N', '\u014a': 'N', + '\u0144': 'n', '\u0146': 'n', '\u0148': 'n', '\u014b': 'n', + '\u014c': 'O', '\u014e': 'O', '\u0150': 'O', + '\u014d': 'o', '\u014f': 'o', '\u0151': 'o', + '\u0154': 'R', '\u0156': 'R', '\u0158': 'R', + '\u0155': 'r', '\u0157': 'r', '\u0159': 'r', + '\u015a': 'S', '\u015c': 'S', '\u015e': 'S', '\u0160': 'S', + '\u015b': 's', '\u015d': 's', '\u015f': 's', '\u0161': 's', + '\u0162': 'T', '\u0164': 'T', '\u0166': 'T', + '\u0163': 't', '\u0165': 't', '\u0167': 't', + '\u0168': 'U', '\u016a': 'U', '\u016c': 'U', '\u016e': 'U', '\u0170': 'U', '\u0172': 'U', + '\u0169': 'u', '\u016b': 'u', '\u016d': 'u', '\u016f': 'u', '\u0171': 'u', '\u0173': 'u', + '\u0174': 'W', '\u0175': 'w', + '\u0176': 'Y', '\u0177': 'y', '\u0178': 'Y', + '\u0179': 'Z', '\u017b': 'Z', '\u017d': 'Z', + '\u017a': 'z', '\u017c': 'z', '\u017e': 'z', + '\u0132': 'IJ', '\u0133': 'ij', + '\u0152': 'Oe', '\u0153': 'oe', + '\u0149': "'n", '\u017f': 's' + }; + + /** Used to map characters to HTML entities. */ + var htmlEscapes = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + }; + + /** Used to map HTML entities to characters. */ + var htmlUnescapes = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + ''': "'" + }; + + /** Used to escape characters for inclusion in compiled string literals. */ + var stringEscapes = { + '\\': '\\', + "'": "'", + '\n': 'n', + '\r': 'r', + '\u2028': 'u2028', + '\u2029': 'u2029' + }; + + /** Built-in method references without a dependency on `root`. */ + var freeParseFloat = parseFloat, + freeParseInt = parseInt; + + /** Detect free variable `global` from Node.js. */ + var freeGlobal = typeof global == 'object' && global && global.Object === Object && global; + + /** Detect free variable `self`. */ + var freeSelf = typeof self == 'object' && self && self.Object === Object && self; + + /** Used as a reference to the global object. */ + var root = freeGlobal || freeSelf || Function('return this')(); + + /** Detect free variable `exports`. */ + var freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports; + + /** Detect free variable `module`. */ + var freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module; + + /** Detect the popular CommonJS extension `module.exports`. */ + var moduleExports = freeModule && freeModule.exports === freeExports; + + /** Detect free variable `process` from Node.js. */ + var freeProcess = moduleExports && freeGlobal.process; + + /** Used to access faster Node.js helpers. */ + var nodeUtil = (function() { + try { + // Use `util.types` for Node.js 10+. + var types = freeModule && freeModule.require && freeModule.require('util').types; + + if (types) { + return types; + } + + // Legacy `process.binding('util')` for Node.js < 10. + return freeProcess && freeProcess.binding && freeProcess.binding('util'); + } catch (e) {} + }()); + + /* Node.js helper references. */ + var nodeIsArrayBuffer = nodeUtil && nodeUtil.isArrayBuffer, + nodeIsDate = nodeUtil && nodeUtil.isDate, + nodeIsMap = nodeUtil && nodeUtil.isMap, + nodeIsRegExp = nodeUtil && nodeUtil.isRegExp, + nodeIsSet = nodeUtil && nodeUtil.isSet, + nodeIsTypedArray = nodeUtil && nodeUtil.isTypedArray; + + /*--------------------------------------------------------------------------*/ + + /** + * A faster alternative to `Function#apply`, this function invokes `func` + * with the `this` binding of `thisArg` and the arguments of `args`. + * + * @private + * @param {Function} func The function to invoke. + * @param {*} thisArg The `this` binding of `func`. + * @param {Array} args The arguments to invoke `func` with. + * @returns {*} Returns the result of `func`. + */ + function apply(func, thisArg, args) { + switch (args.length) { + case 0: return func.call(thisArg); + case 1: return func.call(thisArg, args[0]); + case 2: return func.call(thisArg, args[0], args[1]); + case 3: return func.call(thisArg, args[0], args[1], args[2]); + } + return func.apply(thisArg, args); + } + + /** + * A specialized version of `baseAggregator` for arrays. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} setter The function to set `accumulator` values. + * @param {Function} iteratee The iteratee to transform keys. + * @param {Object} accumulator The initial aggregated object. + * @returns {Function} Returns `accumulator`. + */ + function arrayAggregator(array, setter, iteratee, accumulator) { + var index = -1, + length = array == null ? 0 : array.length; + + while (++index < length) { + var value = array[index]; + setter(accumulator, value, iteratee(value), array); + } + return accumulator; + } + + /** + * A specialized version of `_.forEach` for arrays without support for + * iteratee shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns `array`. + */ + function arrayEach(array, iteratee) { + var index = -1, + length = array == null ? 0 : array.length; + + while (++index < length) { + if (iteratee(array[index], index, array) === false) { + break; + } + } + return array; + } + + /** + * A specialized version of `_.forEachRight` for arrays without support for + * iteratee shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns `array`. + */ + function arrayEachRight(array, iteratee) { + var length = array == null ? 0 : array.length; + + while (length--) { + if (iteratee(array[length], length, array) === false) { + break; + } + } + return array; + } + + /** + * A specialized version of `_.every` for arrays without support for + * iteratee shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} predicate The function invoked per iteration. + * @returns {boolean} Returns `true` if all elements pass the predicate check, + * else `false`. + */ + function arrayEvery(array, predicate) { + var index = -1, + length = array == null ? 0 : array.length; + + while (++index < length) { + if (!predicate(array[index], index, array)) { + return false; + } + } + return true; + } + + /** + * A specialized version of `_.filter` for arrays without support for + * iteratee shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} predicate The function invoked per iteration. + * @returns {Array} Returns the new filtered array. + */ + function arrayFilter(array, predicate) { + var index = -1, + length = array == null ? 0 : array.length, + resIndex = 0, + result = []; + + while (++index < length) { + var value = array[index]; + if (predicate(value, index, array)) { + result[resIndex++] = value; + } + } + return result; + } + + /** + * A specialized version of `_.includes` for arrays without support for + * specifying an index to search from. + * + * @private + * @param {Array} [array] The array to inspect. + * @param {*} target The value to search for. + * @returns {boolean} Returns `true` if `target` is found, else `false`. + */ + function arrayIncludes(array, value) { + var length = array == null ? 0 : array.length; + return !!length && baseIndexOf(array, value, 0) > -1; + } + + /** + * This function is like `arrayIncludes` except that it accepts a comparator. + * + * @private + * @param {Array} [array] The array to inspect. + * @param {*} target The value to search for. + * @param {Function} comparator The comparator invoked per element. + * @returns {boolean} Returns `true` if `target` is found, else `false`. + */ + function arrayIncludesWith(array, value, comparator) { + var index = -1, + length = array == null ? 0 : array.length; + + while (++index < length) { + if (comparator(value, array[index])) { + return true; + } + } + return false; + } + + /** + * A specialized version of `_.map` for arrays without support for iteratee + * shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns the new mapped array. + */ + function arrayMap(array, iteratee) { + var index = -1, + length = array == null ? 0 : array.length, + result = Array(length); + + while (++index < length) { + result[index] = iteratee(array[index], index, array); + } + return result; + } + + /** + * Appends the elements of `values` to `array`. + * + * @private + * @param {Array} array The array to modify. + * @param {Array} values The values to append. + * @returns {Array} Returns `array`. + */ + function arrayPush(array, values) { + var index = -1, + length = values.length, + offset = array.length; + + while (++index < length) { + array[offset + index] = values[index]; + } + return array; + } + + /** + * A specialized version of `_.reduce` for arrays without support for + * iteratee shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @param {*} [accumulator] The initial value. + * @param {boolean} [initAccum] Specify using the first element of `array` as + * the initial value. + * @returns {*} Returns the accumulated value. + */ + function arrayReduce(array, iteratee, accumulator, initAccum) { + var index = -1, + length = array == null ? 0 : array.length; + + if (initAccum && length) { + accumulator = array[++index]; + } + while (++index < length) { + accumulator = iteratee(accumulator, array[index], index, array); + } + return accumulator; + } + + /** + * A specialized version of `_.reduceRight` for arrays without support for + * iteratee shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @param {*} [accumulator] The initial value. + * @param {boolean} [initAccum] Specify using the last element of `array` as + * the initial value. + * @returns {*} Returns the accumulated value. + */ + function arrayReduceRight(array, iteratee, accumulator, initAccum) { + var length = array == null ? 0 : array.length; + if (initAccum && length) { + accumulator = array[--length]; + } + while (length--) { + accumulator = iteratee(accumulator, array[length], length, array); + } + return accumulator; + } + + /** + * A specialized version of `_.some` for arrays without support for iteratee + * shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} predicate The function invoked per iteration. + * @returns {boolean} Returns `true` if any element passes the predicate check, + * else `false`. + */ + function arraySome(array, predicate) { + var index = -1, + length = array == null ? 0 : array.length; + + while (++index < length) { + if (predicate(array[index], index, array)) { + return true; + } + } + return false; + } + + /** + * Gets the size of an ASCII `string`. + * + * @private + * @param {string} string The string inspect. + * @returns {number} Returns the string size. + */ + var asciiSize = baseProperty('length'); + + /** + * Converts an ASCII `string` to an array. + * + * @private + * @param {string} string The string to convert. + * @returns {Array} Returns the converted array. + */ + function asciiToArray(string) { + return string.split(''); + } + + /** + * Splits an ASCII `string` into an array of its words. + * + * @private + * @param {string} The string to inspect. + * @returns {Array} Returns the words of `string`. + */ + function asciiWords(string) { + return string.match(reAsciiWord) || []; + } + + /** + * The base implementation of methods like `_.findKey` and `_.findLastKey`, + * without support for iteratee shorthands, which iterates over `collection` + * using `eachFunc`. + * + * @private + * @param {Array|Object} collection The collection to inspect. + * @param {Function} predicate The function invoked per iteration. + * @param {Function} eachFunc The function to iterate over `collection`. + * @returns {*} Returns the found element or its key, else `undefined`. + */ + function baseFindKey(collection, predicate, eachFunc) { + var result; + eachFunc(collection, function(value, key, collection) { + if (predicate(value, key, collection)) { + result = key; + return false; + } + }); + return result; + } + + /** + * The base implementation of `_.findIndex` and `_.findLastIndex` without + * support for iteratee shorthands. + * + * @private + * @param {Array} array The array to inspect. + * @param {Function} predicate The function invoked per iteration. + * @param {number} fromIndex The index to search from. + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {number} Returns the index of the matched value, else `-1`. + */ + function baseFindIndex(array, predicate, fromIndex, fromRight) { + var length = array.length, + index = fromIndex + (fromRight ? 1 : -1); + + while ((fromRight ? index-- : ++index < length)) { + if (predicate(array[index], index, array)) { + return index; + } + } + return -1; + } + + /** + * The base implementation of `_.indexOf` without `fromIndex` bounds checks. + * + * @private + * @param {Array} array The array to inspect. + * @param {*} value The value to search for. + * @param {number} fromIndex The index to search from. + * @returns {number} Returns the index of the matched value, else `-1`. + */ + function baseIndexOf(array, value, fromIndex) { + return value === value + ? strictIndexOf(array, value, fromIndex) + : baseFindIndex(array, baseIsNaN, fromIndex); + } + + /** + * This function is like `baseIndexOf` except that it accepts a comparator. + * + * @private + * @param {Array} array The array to inspect. + * @param {*} value The value to search for. + * @param {number} fromIndex The index to search from. + * @param {Function} comparator The comparator invoked per element. + * @returns {number} Returns the index of the matched value, else `-1`. + */ + function baseIndexOfWith(array, value, fromIndex, comparator) { + var index = fromIndex - 1, + length = array.length; + + while (++index < length) { + if (comparator(array[index], value)) { + return index; + } + } + return -1; + } + + /** + * The base implementation of `_.isNaN` without support for number objects. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`. + */ + function baseIsNaN(value) { + return value !== value; + } + + /** + * The base implementation of `_.mean` and `_.meanBy` without support for + * iteratee shorthands. + * + * @private + * @param {Array} array The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {number} Returns the mean. + */ + function baseMean(array, iteratee) { + var length = array == null ? 0 : array.length; + return length ? (baseSum(array, iteratee) / length) : NAN; + } + + /** + * The base implementation of `_.property` without support for deep paths. + * + * @private + * @param {string} key The key of the property to get. + * @returns {Function} Returns the new accessor function. + */ + function baseProperty(key) { + return function(object) { + return object == null ? undefined : object[key]; + }; + } + + /** + * The base implementation of `_.propertyOf` without support for deep paths. + * + * @private + * @param {Object} object The object to query. + * @returns {Function} Returns the new accessor function. + */ + function basePropertyOf(object) { + return function(key) { + return object == null ? undefined : object[key]; + }; + } + + /** + * The base implementation of `_.reduce` and `_.reduceRight`, without support + * for iteratee shorthands, which iterates over `collection` using `eachFunc`. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @param {*} accumulator The initial value. + * @param {boolean} initAccum Specify using the first or last element of + * `collection` as the initial value. + * @param {Function} eachFunc The function to iterate over `collection`. + * @returns {*} Returns the accumulated value. + */ + function baseReduce(collection, iteratee, accumulator, initAccum, eachFunc) { + eachFunc(collection, function(value, index, collection) { + accumulator = initAccum + ? (initAccum = false, value) + : iteratee(accumulator, value, index, collection); + }); + return accumulator; + } + + /** + * The base implementation of `_.sortBy` which uses `comparer` to define the + * sort order of `array` and replaces criteria objects with their corresponding + * values. + * + * @private + * @param {Array} array The array to sort. + * @param {Function} comparer The function to define sort order. + * @returns {Array} Returns `array`. + */ + function baseSortBy(array, comparer) { + var length = array.length; + + array.sort(comparer); + while (length--) { + array[length] = array[length].value; + } + return array; + } + + /** + * The base implementation of `_.sum` and `_.sumBy` without support for + * iteratee shorthands. + * + * @private + * @param {Array} array The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {number} Returns the sum. + */ + function baseSum(array, iteratee) { + var result, + index = -1, + length = array.length; + + while (++index < length) { + var current = iteratee(array[index]); + if (current !== undefined) { + result = result === undefined ? current : (result + current); + } + } + return result; + } + + /** + * The base implementation of `_.times` without support for iteratee shorthands + * or max array length checks. + * + * @private + * @param {number} n The number of times to invoke `iteratee`. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns the array of results. + */ + function baseTimes(n, iteratee) { + var index = -1, + result = Array(n); + + while (++index < n) { + result[index] = iteratee(index); + } + return result; + } + + /** + * The base implementation of `_.toPairs` and `_.toPairsIn` which creates an array + * of key-value pairs for `object` corresponding to the property names of `props`. + * + * @private + * @param {Object} object The object to query. + * @param {Array} props The property names to get values for. + * @returns {Object} Returns the key-value pairs. + */ + function baseToPairs(object, props) { + return arrayMap(props, function(key) { + return [key, object[key]]; + }); + } + + /** + * The base implementation of `_.unary` without support for storing metadata. + * + * @private + * @param {Function} func The function to cap arguments for. + * @returns {Function} Returns the new capped function. + */ + function baseUnary(func) { + return function(value) { + return func(value); + }; + } + + /** + * The base implementation of `_.values` and `_.valuesIn` which creates an + * array of `object` property values corresponding to the property names + * of `props`. + * + * @private + * @param {Object} object The object to query. + * @param {Array} props The property names to get values for. + * @returns {Object} Returns the array of property values. + */ + function baseValues(object, props) { + return arrayMap(props, function(key) { + return object[key]; + }); + } + + /** + * Checks if a `cache` value for `key` exists. + * + * @private + * @param {Object} cache The cache to query. + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ + function cacheHas(cache, key) { + return cache.has(key); + } + + /** + * Used by `_.trim` and `_.trimStart` to get the index of the first string symbol + * that is not found in the character symbols. + * + * @private + * @param {Array} strSymbols The string symbols to inspect. + * @param {Array} chrSymbols The character symbols to find. + * @returns {number} Returns the index of the first unmatched string symbol. + */ + function charsStartIndex(strSymbols, chrSymbols) { + var index = -1, + length = strSymbols.length; + + while (++index < length && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {} + return index; + } + + /** + * Used by `_.trim` and `_.trimEnd` to get the index of the last string symbol + * that is not found in the character symbols. + * + * @private + * @param {Array} strSymbols The string symbols to inspect. + * @param {Array} chrSymbols The character symbols to find. + * @returns {number} Returns the index of the last unmatched string symbol. + */ + function charsEndIndex(strSymbols, chrSymbols) { + var index = strSymbols.length; + + while (index-- && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {} + return index; + } + + /** + * Gets the number of `placeholder` occurrences in `array`. + * + * @private + * @param {Array} array The array to inspect. + * @param {*} placeholder The placeholder to search for. + * @returns {number} Returns the placeholder count. + */ + function countHolders(array, placeholder) { + var length = array.length, + result = 0; + + while (length--) { + if (array[length] === placeholder) { + ++result; + } + } + return result; + } + + /** + * Used by `_.deburr` to convert Latin-1 Supplement and Latin Extended-A + * letters to basic Latin letters. + * + * @private + * @param {string} letter The matched letter to deburr. + * @returns {string} Returns the deburred letter. + */ + var deburrLetter = basePropertyOf(deburredLetters); + + /** + * Used by `_.escape` to convert characters to HTML entities. + * + * @private + * @param {string} chr The matched character to escape. + * @returns {string} Returns the escaped character. + */ + var escapeHtmlChar = basePropertyOf(htmlEscapes); + + /** + * Used by `_.template` to escape characters for inclusion in compiled string literals. + * + * @private + * @param {string} chr The matched character to escape. + * @returns {string} Returns the escaped character. + */ + function escapeStringChar(chr) { + return '\\' + stringEscapes[chr]; + } + + /** + * Gets the value at `key` of `object`. + * + * @private + * @param {Object} [object] The object to query. + * @param {string} key The key of the property to get. + * @returns {*} Returns the property value. + */ + function getValue(object, key) { + return object == null ? undefined : object[key]; + } + + /** + * Checks if `string` contains Unicode symbols. + * + * @private + * @param {string} string The string to inspect. + * @returns {boolean} Returns `true` if a symbol is found, else `false`. + */ + function hasUnicode(string) { + return reHasUnicode.test(string); + } + + /** + * Checks if `string` contains a word composed of Unicode symbols. + * + * @private + * @param {string} string The string to inspect. + * @returns {boolean} Returns `true` if a word is found, else `false`. + */ + function hasUnicodeWord(string) { + return reHasUnicodeWord.test(string); + } + + /** + * Converts `iterator` to an array. + * + * @private + * @param {Object} iterator The iterator to convert. + * @returns {Array} Returns the converted array. + */ + function iteratorToArray(iterator) { + var data, + result = []; + + while (!(data = iterator.next()).done) { + result.push(data.value); + } + return result; + } + + /** + * Converts `map` to its key-value pairs. + * + * @private + * @param {Object} map The map to convert. + * @returns {Array} Returns the key-value pairs. + */ + function mapToArray(map) { + var index = -1, + result = Array(map.size); + + map.forEach(function(value, key) { + result[++index] = [key, value]; + }); + return result; + } + + /** + * Creates a unary function that invokes `func` with its argument transformed. + * + * @private + * @param {Function} func The function to wrap. + * @param {Function} transform The argument transform. + * @returns {Function} Returns the new function. + */ + function overArg(func, transform) { + return function(arg) { + return func(transform(arg)); + }; + } + + /** + * Replaces all `placeholder` elements in `array` with an internal placeholder + * and returns an array of their indexes. + * + * @private + * @param {Array} array The array to modify. + * @param {*} placeholder The placeholder to replace. + * @returns {Array} Returns the new array of placeholder indexes. + */ + function replaceHolders(array, placeholder) { + var index = -1, + length = array.length, + resIndex = 0, + result = []; + + while (++index < length) { + var value = array[index]; + if (value === placeholder || value === PLACEHOLDER) { + array[index] = PLACEHOLDER; + result[resIndex++] = index; + } + } + return result; + } + + /** + * Gets the value at `key`, unless `key` is "__proto__". + * + * @private + * @param {Object} object The object to query. + * @param {string} key The key of the property to get. + * @returns {*} Returns the property value. + */ + function safeGet(object, key) { + return key == '__proto__' + ? undefined + : object[key]; + } + + /** + * Converts `set` to an array of its values. + * + * @private + * @param {Object} set The set to convert. + * @returns {Array} Returns the values. + */ + function setToArray(set) { + var index = -1, + result = Array(set.size); + + set.forEach(function(value) { + result[++index] = value; + }); + return result; + } + + /** + * Converts `set` to its value-value pairs. + * + * @private + * @param {Object} set The set to convert. + * @returns {Array} Returns the value-value pairs. + */ + function setToPairs(set) { + var index = -1, + result = Array(set.size); + + set.forEach(function(value) { + result[++index] = [value, value]; + }); + return result; + } + + /** + * A specialized version of `_.indexOf` which performs strict equality + * comparisons of values, i.e. `===`. + * + * @private + * @param {Array} array The array to inspect. + * @param {*} value The value to search for. + * @param {number} fromIndex The index to search from. + * @returns {number} Returns the index of the matched value, else `-1`. + */ + function strictIndexOf(array, value, fromIndex) { + var index = fromIndex - 1, + length = array.length; + + while (++index < length) { + if (array[index] === value) { + return index; + } + } + return -1; + } + + /** + * A specialized version of `_.lastIndexOf` which performs strict equality + * comparisons of values, i.e. `===`. + * + * @private + * @param {Array} array The array to inspect. + * @param {*} value The value to search for. + * @param {number} fromIndex The index to search from. + * @returns {number} Returns the index of the matched value, else `-1`. + */ + function strictLastIndexOf(array, value, fromIndex) { + var index = fromIndex + 1; + while (index--) { + if (array[index] === value) { + return index; + } + } + return index; + } + + /** + * Gets the number of symbols in `string`. + * + * @private + * @param {string} string The string to inspect. + * @returns {number} Returns the string size. + */ + function stringSize(string) { + return hasUnicode(string) + ? unicodeSize(string) + : asciiSize(string); + } + + /** + * Converts `string` to an array. + * + * @private + * @param {string} string The string to convert. + * @returns {Array} Returns the converted array. + */ + function stringToArray(string) { + return hasUnicode(string) + ? unicodeToArray(string) + : asciiToArray(string); + } + + /** + * Used by `_.unescape` to convert HTML entities to characters. + * + * @private + * @param {string} chr The matched character to unescape. + * @returns {string} Returns the unescaped character. + */ + var unescapeHtmlChar = basePropertyOf(htmlUnescapes); + + /** + * Gets the size of a Unicode `string`. + * + * @private + * @param {string} string The string inspect. + * @returns {number} Returns the string size. + */ + function unicodeSize(string) { + var result = reUnicode.lastIndex = 0; + while (reUnicode.test(string)) { + ++result; + } + return result; + } + + /** + * Converts a Unicode `string` to an array. + * + * @private + * @param {string} string The string to convert. + * @returns {Array} Returns the converted array. + */ + function unicodeToArray(string) { + return string.match(reUnicode) || []; + } + + /** + * Splits a Unicode `string` into an array of its words. + * + * @private + * @param {string} The string to inspect. + * @returns {Array} Returns the words of `string`. + */ + function unicodeWords(string) { + return string.match(reUnicodeWord) || []; + } + + /*--------------------------------------------------------------------------*/ + + /** + * Create a new pristine `lodash` function using the `context` object. + * + * @static + * @memberOf _ + * @since 1.1.0 + * @category Util + * @param {Object} [context=root] The context object. + * @returns {Function} Returns a new `lodash` function. + * @example + * + * _.mixin({ 'foo': _.constant('foo') }); + * + * var lodash = _.runInContext(); + * lodash.mixin({ 'bar': lodash.constant('bar') }); + * + * _.isFunction(_.foo); + * // => true + * _.isFunction(_.bar); + * // => false + * + * lodash.isFunction(lodash.foo); + * // => false + * lodash.isFunction(lodash.bar); + * // => true + * + * // Create a suped-up `defer` in Node.js. + * var defer = _.runInContext({ 'setTimeout': setImmediate }).defer; + */ + var runInContext = (function runInContext(context) { + context = context == null ? root : _.defaults(root.Object(), context, _.pick(root, contextProps)); + + /** Built-in constructor references. */ + var Array = context.Array, + Date = context.Date, + Error = context.Error, + Function = context.Function, + Math = context.Math, + Object = context.Object, + RegExp = context.RegExp, + String = context.String, + TypeError = context.TypeError; + + /** Used for built-in method references. */ + var arrayProto = Array.prototype, + funcProto = Function.prototype, + objectProto = Object.prototype; + + /** Used to detect overreaching core-js shims. */ + var coreJsData = context['__core-js_shared__']; + + /** Used to resolve the decompiled source of functions. */ + var funcToString = funcProto.toString; + + /** Used to check objects for own properties. */ + var hasOwnProperty = objectProto.hasOwnProperty; + + /** Used to generate unique IDs. */ + var idCounter = 0; + + /** Used to detect methods masquerading as native. */ + var maskSrcKey = (function() { + var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || ''); + return uid ? ('Symbol(src)_1.' + uid) : ''; + }()); + + /** + * Used to resolve the + * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) + * of values. + */ + var nativeObjectToString = objectProto.toString; + + /** Used to infer the `Object` constructor. */ + var objectCtorString = funcToString.call(Object); + + /** Used to restore the original `_` reference in `_.noConflict`. */ + var oldDash = root._; + + /** Used to detect if a method is native. */ + var reIsNative = RegExp('^' + + funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&') + .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$' + ); + + /** Built-in value references. */ + var Buffer = moduleExports ? context.Buffer : undefined, + Symbol = context.Symbol, + Uint8Array = context.Uint8Array, + allocUnsafe = Buffer ? Buffer.allocUnsafe : undefined, + getPrototype = overArg(Object.getPrototypeOf, Object), + objectCreate = Object.create, + propertyIsEnumerable = objectProto.propertyIsEnumerable, + splice = arrayProto.splice, + spreadableSymbol = Symbol ? Symbol.isConcatSpreadable : undefined, + symIterator = Symbol ? Symbol.iterator : undefined, + symToStringTag = Symbol ? Symbol.toStringTag : undefined; + + var defineProperty = (function() { + try { + var func = getNative(Object, 'defineProperty'); + func({}, '', {}); + return func; + } catch (e) {} + }()); + + /** Mocked built-ins. */ + var ctxClearTimeout = context.clearTimeout !== root.clearTimeout && context.clearTimeout, + ctxNow = Date && Date.now !== root.Date.now && Date.now, + ctxSetTimeout = context.setTimeout !== root.setTimeout && context.setTimeout; + + /* Built-in method references for those with the same name as other `lodash` methods. */ + var nativeCeil = Math.ceil, + nativeFloor = Math.floor, + nativeGetSymbols = Object.getOwnPropertySymbols, + nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined, + nativeIsFinite = context.isFinite, + nativeJoin = arrayProto.join, + nativeKeys = overArg(Object.keys, Object), + nativeMax = Math.max, + nativeMin = Math.min, + nativeNow = Date.now, + nativeParseInt = context.parseInt, + nativeRandom = Math.random, + nativeReverse = arrayProto.reverse; + + /* Built-in method references that are verified to be native. */ + var DataView = getNative(context, 'DataView'), + Map = getNative(context, 'Map'), + Promise = getNative(context, 'Promise'), + Set = getNative(context, 'Set'), + WeakMap = getNative(context, 'WeakMap'), + nativeCreate = getNative(Object, 'create'); + + /** Used to store function metadata. */ + var metaMap = WeakMap && new WeakMap; + + /** Used to lookup unminified function names. */ + var realNames = {}; + + /** Used to detect maps, sets, and weakmaps. */ + var dataViewCtorString = toSource(DataView), + mapCtorString = toSource(Map), + promiseCtorString = toSource(Promise), + setCtorString = toSource(Set), + weakMapCtorString = toSource(WeakMap); + + /** Used to convert symbols to primitives and strings. */ + var symbolProto = Symbol ? Symbol.prototype : undefined, + symbolValueOf = symbolProto ? symbolProto.valueOf : undefined, + symbolToString = symbolProto ? symbolProto.toString : undefined; + + /*------------------------------------------------------------------------*/ + + /** + * Creates a `lodash` object which wraps `value` to enable implicit method + * chain sequences. Methods that operate on and return arrays, collections, + * and functions can be chained together. Methods that retrieve a single value + * or may return a primitive value will automatically end the chain sequence + * and return the unwrapped value. Otherwise, the value must be unwrapped + * with `_#value`. + * + * Explicit chain sequences, which must be unwrapped with `_#value`, may be + * enabled using `_.chain`. + * + * The execution of chained methods is lazy, that is, it's deferred until + * `_#value` is implicitly or explicitly called. + * + * Lazy evaluation allows several methods to support shortcut fusion. + * Shortcut fusion is an optimization to merge iteratee calls; this avoids + * the creation of intermediate arrays and can greatly reduce the number of + * iteratee executions. Sections of a chain sequence qualify for shortcut + * fusion if the section is applied to an array and iteratees accept only + * one argument. The heuristic for whether a section qualifies for shortcut + * fusion is subject to change. + * + * Chaining is supported in custom builds as long as the `_#value` method is + * directly or indirectly included in the build. + * + * In addition to lodash methods, wrappers have `Array` and `String` methods. + * + * The wrapper `Array` methods are: + * `concat`, `join`, `pop`, `push`, `shift`, `sort`, `splice`, and `unshift` + * + * The wrapper `String` methods are: + * `replace` and `split` + * + * The wrapper methods that support shortcut fusion are: + * `at`, `compact`, `drop`, `dropRight`, `dropWhile`, `filter`, `find`, + * `findLast`, `head`, `initial`, `last`, `map`, `reject`, `reverse`, `slice`, + * `tail`, `take`, `takeRight`, `takeRightWhile`, `takeWhile`, and `toArray` + * + * The chainable wrapper methods are: + * `after`, `ary`, `assign`, `assignIn`, `assignInWith`, `assignWith`, `at`, + * `before`, `bind`, `bindAll`, `bindKey`, `castArray`, `chain`, `chunk`, + * `commit`, `compact`, `concat`, `conforms`, `constant`, `countBy`, `create`, + * `curry`, `debounce`, `defaults`, `defaultsDeep`, `defer`, `delay`, + * `difference`, `differenceBy`, `differenceWith`, `drop`, `dropRight`, + * `dropRightWhile`, `dropWhile`, `extend`, `extendWith`, `fill`, `filter`, + * `flatMap`, `flatMapDeep`, `flatMapDepth`, `flatten`, `flattenDeep`, + * `flattenDepth`, `flip`, `flow`, `flowRight`, `fromPairs`, `functions`, + * `functionsIn`, `groupBy`, `initial`, `intersection`, `intersectionBy`, + * `intersectionWith`, `invert`, `invertBy`, `invokeMap`, `iteratee`, `keyBy`, + * `keys`, `keysIn`, `map`, `mapKeys`, `mapValues`, `matches`, `matchesProperty`, + * `memoize`, `merge`, `mergeWith`, `method`, `methodOf`, `mixin`, `negate`, + * `nthArg`, `omit`, `omitBy`, `once`, `orderBy`, `over`, `overArgs`, + * `overEvery`, `overSome`, `partial`, `partialRight`, `partition`, `pick`, + * `pickBy`, `plant`, `property`, `propertyOf`, `pull`, `pullAll`, `pullAllBy`, + * `pullAllWith`, `pullAt`, `push`, `range`, `rangeRight`, `rearg`, `reject`, + * `remove`, `rest`, `reverse`, `sampleSize`, `set`, `setWith`, `shuffle`, + * `slice`, `sort`, `sortBy`, `splice`, `spread`, `tail`, `take`, `takeRight`, + * `takeRightWhile`, `takeWhile`, `tap`, `throttle`, `thru`, `toArray`, + * `toPairs`, `toPairsIn`, `toPath`, `toPlainObject`, `transform`, `unary`, + * `union`, `unionBy`, `unionWith`, `uniq`, `uniqBy`, `uniqWith`, `unset`, + * `unshift`, `unzip`, `unzipWith`, `update`, `updateWith`, `values`, + * `valuesIn`, `without`, `wrap`, `xor`, `xorBy`, `xorWith`, `zip`, + * `zipObject`, `zipObjectDeep`, and `zipWith` + * + * The wrapper methods that are **not** chainable by default are: + * `add`, `attempt`, `camelCase`, `capitalize`, `ceil`, `clamp`, `clone`, + * `cloneDeep`, `cloneDeepWith`, `cloneWith`, `conformsTo`, `deburr`, + * `defaultTo`, `divide`, `each`, `eachRight`, `endsWith`, `eq`, `escape`, + * `escapeRegExp`, `every`, `find`, `findIndex`, `findKey`, `findLast`, + * `findLastIndex`, `findLastKey`, `first`, `floor`, `forEach`, `forEachRight`, + * `forIn`, `forInRight`, `forOwn`, `forOwnRight`, `get`, `gt`, `gte`, `has`, + * `hasIn`, `head`, `identity`, `includes`, `indexOf`, `inRange`, `invoke`, + * `isArguments`, `isArray`, `isArrayBuffer`, `isArrayLike`, `isArrayLikeObject`, + * `isBoolean`, `isBuffer`, `isDate`, `isElement`, `isEmpty`, `isEqual`, + * `isEqualWith`, `isError`, `isFinite`, `isFunction`, `isInteger`, `isLength`, + * `isMap`, `isMatch`, `isMatchWith`, `isNaN`, `isNative`, `isNil`, `isNull`, + * `isNumber`, `isObject`, `isObjectLike`, `isPlainObject`, `isRegExp`, + * `isSafeInteger`, `isSet`, `isString`, `isUndefined`, `isTypedArray`, + * `isWeakMap`, `isWeakSet`, `join`, `kebabCase`, `last`, `lastIndexOf`, + * `lowerCase`, `lowerFirst`, `lt`, `lte`, `max`, `maxBy`, `mean`, `meanBy`, + * `min`, `minBy`, `multiply`, `noConflict`, `noop`, `now`, `nth`, `pad`, + * `padEnd`, `padStart`, `parseInt`, `pop`, `random`, `reduce`, `reduceRight`, + * `repeat`, `result`, `round`, `runInContext`, `sample`, `shift`, `size`, + * `snakeCase`, `some`, `sortedIndex`, `sortedIndexBy`, `sortedLastIndex`, + * `sortedLastIndexBy`, `startCase`, `startsWith`, `stubArray`, `stubFalse`, + * `stubObject`, `stubString`, `stubTrue`, `subtract`, `sum`, `sumBy`, + * `template`, `times`, `toFinite`, `toInteger`, `toJSON`, `toLength`, + * `toLower`, `toNumber`, `toSafeInteger`, `toString`, `toUpper`, `trim`, + * `trimEnd`, `trimStart`, `truncate`, `unescape`, `uniqueId`, `upperCase`, + * `upperFirst`, `value`, and `words` + * + * @name _ + * @constructor + * @category Seq + * @param {*} value The value to wrap in a `lodash` instance. + * @returns {Object} Returns the new `lodash` wrapper instance. + * @example + * + * function square(n) { + * return n * n; + * } + * + * var wrapped = _([1, 2, 3]); + * + * // Returns an unwrapped value. + * wrapped.reduce(_.add); + * // => 6 + * + * // Returns a wrapped value. + * var squares = wrapped.map(square); + * + * _.isArray(squares); + * // => false + * + * _.isArray(squares.value()); + * // => true + */ + function lodash(value) { + if (isObjectLike(value) && !isArray(value) && !(value instanceof LazyWrapper)) { + if (value instanceof LodashWrapper) { + return value; + } + if (hasOwnProperty.call(value, '__wrapped__')) { + return wrapperClone(value); + } + } + return new LodashWrapper(value); + } + + /** + * The base implementation of `_.create` without support for assigning + * properties to the created object. + * + * @private + * @param {Object} proto The object to inherit from. + * @returns {Object} Returns the new object. + */ + var baseCreate = (function() { + function object() {} + return function(proto) { + if (!isObject(proto)) { + return {}; + } + if (objectCreate) { + return objectCreate(proto); + } + object.prototype = proto; + var result = new object; + object.prototype = undefined; + return result; + }; + }()); + + /** + * The function whose prototype chain sequence wrappers inherit from. + * + * @private + */ + function baseLodash() { + // No operation performed. + } + + /** + * The base constructor for creating `lodash` wrapper objects. + * + * @private + * @param {*} value The value to wrap. + * @param {boolean} [chainAll] Enable explicit method chain sequences. + */ + function LodashWrapper(value, chainAll) { + this.__wrapped__ = value; + this.__actions__ = []; + this.__chain__ = !!chainAll; + this.__index__ = 0; + this.__values__ = undefined; + } + + /** + * By default, the template delimiters used by lodash are like those in + * embedded Ruby (ERB) as well as ES2015 template strings. Change the + * following template settings to use alternative delimiters. + * + * @static + * @memberOf _ + * @type {Object} + */ + lodash.templateSettings = { + + /** + * Used to detect `data` property values to be HTML-escaped. + * + * @memberOf _.templateSettings + * @type {RegExp} + */ + 'escape': reEscape, + + /** + * Used to detect code to be evaluated. + * + * @memberOf _.templateSettings + * @type {RegExp} + */ + 'evaluate': reEvaluate, + + /** + * Used to detect `data` property values to inject. + * + * @memberOf _.templateSettings + * @type {RegExp} + */ + 'interpolate': reInterpolate, + + /** + * Used to reference the data object in the template text. + * + * @memberOf _.templateSettings + * @type {string} + */ + 'variable': '', + + /** + * Used to import variables into the compiled template. + * + * @memberOf _.templateSettings + * @type {Object} + */ + 'imports': { + + /** + * A reference to the `lodash` function. + * + * @memberOf _.templateSettings.imports + * @type {Function} + */ + '_': lodash + } + }; + + // Ensure wrappers are instances of `baseLodash`. + lodash.prototype = baseLodash.prototype; + lodash.prototype.constructor = lodash; + + LodashWrapper.prototype = baseCreate(baseLodash.prototype); + LodashWrapper.prototype.constructor = LodashWrapper; + + /*------------------------------------------------------------------------*/ + + /** + * Creates a lazy wrapper object which wraps `value` to enable lazy evaluation. + * + * @private + * @constructor + * @param {*} value The value to wrap. + */ + function LazyWrapper(value) { + this.__wrapped__ = value; + this.__actions__ = []; + this.__dir__ = 1; + this.__filtered__ = false; + this.__iteratees__ = []; + this.__takeCount__ = MAX_ARRAY_LENGTH; + this.__views__ = []; + } + + /** + * Creates a clone of the lazy wrapper object. + * + * @private + * @name clone + * @memberOf LazyWrapper + * @returns {Object} Returns the cloned `LazyWrapper` object. + */ + function lazyClone() { + var result = new LazyWrapper(this.__wrapped__); + result.__actions__ = copyArray(this.__actions__); + result.__dir__ = this.__dir__; + result.__filtered__ = this.__filtered__; + result.__iteratees__ = copyArray(this.__iteratees__); + result.__takeCount__ = this.__takeCount__; + result.__views__ = copyArray(this.__views__); + return result; + } + + /** + * Reverses the direction of lazy iteration. + * + * @private + * @name reverse + * @memberOf LazyWrapper + * @returns {Object} Returns the new reversed `LazyWrapper` object. + */ + function lazyReverse() { + if (this.__filtered__) { + var result = new LazyWrapper(this); + result.__dir__ = -1; + result.__filtered__ = true; + } else { + result = this.clone(); + result.__dir__ *= -1; + } + return result; + } + + /** + * Extracts the unwrapped value from its lazy wrapper. + * + * @private + * @name value + * @memberOf LazyWrapper + * @returns {*} Returns the unwrapped value. + */ + function lazyValue() { + var array = this.__wrapped__.value(), + dir = this.__dir__, + isArr = isArray(array), + isRight = dir < 0, + arrLength = isArr ? array.length : 0, + view = getView(0, arrLength, this.__views__), + start = view.start, + end = view.end, + length = end - start, + index = isRight ? end : (start - 1), + iteratees = this.__iteratees__, + iterLength = iteratees.length, + resIndex = 0, + takeCount = nativeMin(length, this.__takeCount__); + + if (!isArr || (!isRight && arrLength == length && takeCount == length)) { + return baseWrapperValue(array, this.__actions__); + } + var result = []; + + outer: + while (length-- && resIndex < takeCount) { + index += dir; + + var iterIndex = -1, + value = array[index]; + + while (++iterIndex < iterLength) { + var data = iteratees[iterIndex], + iteratee = data.iteratee, + type = data.type, + computed = iteratee(value); + + if (type == LAZY_MAP_FLAG) { + value = computed; + } else if (!computed) { + if (type == LAZY_FILTER_FLAG) { + continue outer; + } else { + break outer; + } + } + } + result[resIndex++] = value; + } + return result; + } + + // Ensure `LazyWrapper` is an instance of `baseLodash`. + LazyWrapper.prototype = baseCreate(baseLodash.prototype); + LazyWrapper.prototype.constructor = LazyWrapper; + + /*------------------------------------------------------------------------*/ + + /** + * Creates a hash object. + * + * @private + * @constructor + * @param {Array} [entries] The key-value pairs to cache. + */ + function Hash(entries) { + var index = -1, + length = entries == null ? 0 : entries.length; + + this.clear(); + while (++index < length) { + var entry = entries[index]; + this.set(entry[0], entry[1]); + } + } + + /** + * Removes all key-value entries from the hash. + * + * @private + * @name clear + * @memberOf Hash + */ + function hashClear() { + this.__data__ = nativeCreate ? nativeCreate(null) : {}; + this.size = 0; + } + + /** + * Removes `key` and its value from the hash. + * + * @private + * @name delete + * @memberOf Hash + * @param {Object} hash The hash to modify. + * @param {string} key The key of the value to remove. + * @returns {boolean} Returns `true` if the entry was removed, else `false`. + */ + function hashDelete(key) { + var result = this.has(key) && delete this.__data__[key]; + this.size -= result ? 1 : 0; + return result; + } + + /** + * Gets the hash value for `key`. + * + * @private + * @name get + * @memberOf Hash + * @param {string} key The key of the value to get. + * @returns {*} Returns the entry value. + */ + function hashGet(key) { + var data = this.__data__; + if (nativeCreate) { + var result = data[key]; + return result === HASH_UNDEFINED ? undefined : result; + } + return hasOwnProperty.call(data, key) ? data[key] : undefined; + } + + /** + * Checks if a hash value for `key` exists. + * + * @private + * @name has + * @memberOf Hash + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ + function hashHas(key) { + var data = this.__data__; + return nativeCreate ? (data[key] !== undefined) : hasOwnProperty.call(data, key); + } + + /** + * Sets the hash `key` to `value`. + * + * @private + * @name set + * @memberOf Hash + * @param {string} key The key of the value to set. + * @param {*} value The value to set. + * @returns {Object} Returns the hash instance. + */ + function hashSet(key, value) { + var data = this.__data__; + this.size += this.has(key) ? 0 : 1; + data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value; + return this; + } + + // Add methods to `Hash`. + Hash.prototype.clear = hashClear; + Hash.prototype['delete'] = hashDelete; + Hash.prototype.get = hashGet; + Hash.prototype.has = hashHas; + Hash.prototype.set = hashSet; + + /*------------------------------------------------------------------------*/ + + /** + * Creates an list cache object. + * + * @private + * @constructor + * @param {Array} [entries] The key-value pairs to cache. + */ + function ListCache(entries) { + var index = -1, + length = entries == null ? 0 : entries.length; + + this.clear(); + while (++index < length) { + var entry = entries[index]; + this.set(entry[0], entry[1]); + } + } + + /** + * Removes all key-value entries from the list cache. + * + * @private + * @name clear + * @memberOf ListCache + */ + function listCacheClear() { + this.__data__ = []; + this.size = 0; + } + + /** + * Removes `key` and its value from the list cache. + * + * @private + * @name delete + * @memberOf ListCache + * @param {string} key The key of the value to remove. + * @returns {boolean} Returns `true` if the entry was removed, else `false`. + */ + function listCacheDelete(key) { + var data = this.__data__, + index = assocIndexOf(data, key); + + if (index < 0) { + return false; + } + var lastIndex = data.length - 1; + if (index == lastIndex) { + data.pop(); + } else { + splice.call(data, index, 1); + } + --this.size; + return true; + } + + /** + * Gets the list cache value for `key`. + * + * @private + * @name get + * @memberOf ListCache + * @param {string} key The key of the value to get. + * @returns {*} Returns the entry value. + */ + function listCacheGet(key) { + var data = this.__data__, + index = assocIndexOf(data, key); + + return index < 0 ? undefined : data[index][1]; + } + + /** + * Checks if a list cache value for `key` exists. + * + * @private + * @name has + * @memberOf ListCache + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ + function listCacheHas(key) { + return assocIndexOf(this.__data__, key) > -1; + } + + /** + * Sets the list cache `key` to `value`. + * + * @private + * @name set + * @memberOf ListCache + * @param {string} key The key of the value to set. + * @param {*} value The value to set. + * @returns {Object} Returns the list cache instance. + */ + function listCacheSet(key, value) { + var data = this.__data__, + index = assocIndexOf(data, key); + + if (index < 0) { + ++this.size; + data.push([key, value]); + } else { + data[index][1] = value; + } + return this; + } + + // Add methods to `ListCache`. + ListCache.prototype.clear = listCacheClear; + ListCache.prototype['delete'] = listCacheDelete; + ListCache.prototype.get = listCacheGet; + ListCache.prototype.has = listCacheHas; + ListCache.prototype.set = listCacheSet; + + /*------------------------------------------------------------------------*/ + + /** + * Creates a map cache object to store key-value pairs. + * + * @private + * @constructor + * @param {Array} [entries] The key-value pairs to cache. + */ + function MapCache(entries) { + var index = -1, + length = entries == null ? 0 : entries.length; + + this.clear(); + while (++index < length) { + var entry = entries[index]; + this.set(entry[0], entry[1]); + } + } + + /** + * Removes all key-value entries from the map. + * + * @private + * @name clear + * @memberOf MapCache + */ + function mapCacheClear() { + this.size = 0; + this.__data__ = { + 'hash': new Hash, + 'map': new (Map || ListCache), + 'string': new Hash + }; + } + + /** + * Removes `key` and its value from the map. + * + * @private + * @name delete + * @memberOf MapCache + * @param {string} key The key of the value to remove. + * @returns {boolean} Returns `true` if the entry was removed, else `false`. + */ + function mapCacheDelete(key) { + var result = getMapData(this, key)['delete'](key); + this.size -= result ? 1 : 0; + return result; + } + + /** + * Gets the map value for `key`. + * + * @private + * @name get + * @memberOf MapCache + * @param {string} key The key of the value to get. + * @returns {*} Returns the entry value. + */ + function mapCacheGet(key) { + return getMapData(this, key).get(key); + } + + /** + * Checks if a map value for `key` exists. + * + * @private + * @name has + * @memberOf MapCache + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ + function mapCacheHas(key) { + return getMapData(this, key).has(key); + } + + /** + * Sets the map `key` to `value`. + * + * @private + * @name set + * @memberOf MapCache + * @param {string} key The key of the value to set. + * @param {*} value The value to set. + * @returns {Object} Returns the map cache instance. + */ + function mapCacheSet(key, value) { + var data = getMapData(this, key), + size = data.size; + + data.set(key, value); + this.size += data.size == size ? 0 : 1; + return this; + } + + // Add methods to `MapCache`. + MapCache.prototype.clear = mapCacheClear; + MapCache.prototype['delete'] = mapCacheDelete; + MapCache.prototype.get = mapCacheGet; + MapCache.prototype.has = mapCacheHas; + MapCache.prototype.set = mapCacheSet; + + /*------------------------------------------------------------------------*/ + + /** + * + * Creates an array cache object to store unique values. + * + * @private + * @constructor + * @param {Array} [values] The values to cache. + */ + function SetCache(values) { + var index = -1, + length = values == null ? 0 : values.length; + + this.__data__ = new MapCache; + while (++index < length) { + this.add(values[index]); + } + } + + /** + * Adds `value` to the array cache. + * + * @private + * @name add + * @memberOf SetCache + * @alias push + * @param {*} value The value to cache. + * @returns {Object} Returns the cache instance. + */ + function setCacheAdd(value) { + this.__data__.set(value, HASH_UNDEFINED); + return this; + } + + /** + * Checks if `value` is in the array cache. + * + * @private + * @name has + * @memberOf SetCache + * @param {*} value The value to search for. + * @returns {number} Returns `true` if `value` is found, else `false`. + */ + function setCacheHas(value) { + return this.__data__.has(value); + } + + // Add methods to `SetCache`. + SetCache.prototype.add = SetCache.prototype.push = setCacheAdd; + SetCache.prototype.has = setCacheHas; + + /*------------------------------------------------------------------------*/ + + /** + * Creates a stack cache object to store key-value pairs. + * + * @private + * @constructor + * @param {Array} [entries] The key-value pairs to cache. + */ + function Stack(entries) { + var data = this.__data__ = new ListCache(entries); + this.size = data.size; + } + + /** + * Removes all key-value entries from the stack. + * + * @private + * @name clear + * @memberOf Stack + */ + function stackClear() { + this.__data__ = new ListCache; + this.size = 0; + } + + /** + * Removes `key` and its value from the stack. + * + * @private + * @name delete + * @memberOf Stack + * @param {string} key The key of the value to remove. + * @returns {boolean} Returns `true` if the entry was removed, else `false`. + */ + function stackDelete(key) { + var data = this.__data__, + result = data['delete'](key); + + this.size = data.size; + return result; + } + + /** + * Gets the stack value for `key`. + * + * @private + * @name get + * @memberOf Stack + * @param {string} key The key of the value to get. + * @returns {*} Returns the entry value. + */ + function stackGet(key) { + return this.__data__.get(key); + } + + /** + * Checks if a stack value for `key` exists. + * + * @private + * @name has + * @memberOf Stack + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ + function stackHas(key) { + return this.__data__.has(key); + } + + /** + * Sets the stack `key` to `value`. + * + * @private + * @name set + * @memberOf Stack + * @param {string} key The key of the value to set. + * @param {*} value The value to set. + * @returns {Object} Returns the stack cache instance. + */ + function stackSet(key, value) { + var data = this.__data__; + if (data instanceof ListCache) { + var pairs = data.__data__; + if (!Map || (pairs.length < LARGE_ARRAY_SIZE - 1)) { + pairs.push([key, value]); + this.size = ++data.size; + return this; + } + data = this.__data__ = new MapCache(pairs); + } + data.set(key, value); + this.size = data.size; + return this; + } + + // Add methods to `Stack`. + Stack.prototype.clear = stackClear; + Stack.prototype['delete'] = stackDelete; + Stack.prototype.get = stackGet; + Stack.prototype.has = stackHas; + Stack.prototype.set = stackSet; + + /*------------------------------------------------------------------------*/ + + /** + * Creates an array of the enumerable property names of the array-like `value`. + * + * @private + * @param {*} value The value to query. + * @param {boolean} inherited Specify returning inherited property names. + * @returns {Array} Returns the array of property names. + */ + function arrayLikeKeys(value, inherited) { + var isArr = isArray(value), + isArg = !isArr && isArguments(value), + isBuff = !isArr && !isArg && isBuffer(value), + isType = !isArr && !isArg && !isBuff && isTypedArray(value), + skipIndexes = isArr || isArg || isBuff || isType, + result = skipIndexes ? baseTimes(value.length, String) : [], + length = result.length; + + for (var key in value) { + if ((inherited || hasOwnProperty.call(value, key)) && + !(skipIndexes && ( + // Safari 9 has enumerable `arguments.length` in strict mode. + key == 'length' || + // Node.js 0.10 has enumerable non-index properties on buffers. + (isBuff && (key == 'offset' || key == 'parent')) || + // PhantomJS 2 has enumerable non-index properties on typed arrays. + (isType && (key == 'buffer' || key == 'byteLength' || key == 'byteOffset')) || + // Skip index properties. + isIndex(key, length) + ))) { + result.push(key); + } + } + return result; + } + + /** + * A specialized version of `_.sample` for arrays. + * + * @private + * @param {Array} array The array to sample. + * @returns {*} Returns the random element. + */ + function arraySample(array) { + var length = array.length; + return length ? array[baseRandom(0, length - 1)] : undefined; + } + + /** + * A specialized version of `_.sampleSize` for arrays. + * + * @private + * @param {Array} array The array to sample. + * @param {number} n The number of elements to sample. + * @returns {Array} Returns the random elements. + */ + function arraySampleSize(array, n) { + return shuffleSelf(copyArray(array), baseClamp(n, 0, array.length)); + } + + /** + * A specialized version of `_.shuffle` for arrays. + * + * @private + * @param {Array} array The array to shuffle. + * @returns {Array} Returns the new shuffled array. + */ + function arrayShuffle(array) { + return shuffleSelf(copyArray(array)); + } + + /** + * This function is like `assignValue` except that it doesn't assign + * `undefined` values. + * + * @private + * @param {Object} object The object to modify. + * @param {string} key The key of the property to assign. + * @param {*} value The value to assign. + */ + function assignMergeValue(object, key, value) { + if ((value !== undefined && !eq(object[key], value)) || + (value === undefined && !(key in object))) { + baseAssignValue(object, key, value); + } + } + + /** + * Assigns `value` to `key` of `object` if the existing value is not equivalent + * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * for equality comparisons. + * + * @private + * @param {Object} object The object to modify. + * @param {string} key The key of the property to assign. + * @param {*} value The value to assign. + */ + function assignValue(object, key, value) { + var objValue = object[key]; + if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) || + (value === undefined && !(key in object))) { + baseAssignValue(object, key, value); + } + } + + /** + * Gets the index at which the `key` is found in `array` of key-value pairs. + * + * @private + * @param {Array} array The array to inspect. + * @param {*} key The key to search for. + * @returns {number} Returns the index of the matched value, else `-1`. + */ + function assocIndexOf(array, key) { + var length = array.length; + while (length--) { + if (eq(array[length][0], key)) { + return length; + } + } + return -1; + } + + /** + * Aggregates elements of `collection` on `accumulator` with keys transformed + * by `iteratee` and values set by `setter`. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} setter The function to set `accumulator` values. + * @param {Function} iteratee The iteratee to transform keys. + * @param {Object} accumulator The initial aggregated object. + * @returns {Function} Returns `accumulator`. + */ + function baseAggregator(collection, setter, iteratee, accumulator) { + baseEach(collection, function(value, key, collection) { + setter(accumulator, value, iteratee(value), collection); + }); + return accumulator; + } + + /** + * The base implementation of `_.assign` without support for multiple sources + * or `customizer` functions. + * + * @private + * @param {Object} object The destination object. + * @param {Object} source The source object. + * @returns {Object} Returns `object`. + */ + function baseAssign(object, source) { + return object && copyObject(source, keys(source), object); + } + + /** + * The base implementation of `_.assignIn` without support for multiple sources + * or `customizer` functions. + * + * @private + * @param {Object} object The destination object. + * @param {Object} source The source object. + * @returns {Object} Returns `object`. + */ + function baseAssignIn(object, source) { + return object && copyObject(source, keysIn(source), object); + } + + /** + * The base implementation of `assignValue` and `assignMergeValue` without + * value checks. + * + * @private + * @param {Object} object The object to modify. + * @param {string} key The key of the property to assign. + * @param {*} value The value to assign. + */ + function baseAssignValue(object, key, value) { + if (key == '__proto__' && defineProperty) { + defineProperty(object, key, { + 'configurable': true, + 'enumerable': true, + 'value': value, + 'writable': true + }); + } else { + object[key] = value; + } + } + + /** + * The base implementation of `_.at` without support for individual paths. + * + * @private + * @param {Object} object The object to iterate over. + * @param {string[]} paths The property paths to pick. + * @returns {Array} Returns the picked elements. + */ + function baseAt(object, paths) { + var index = -1, + length = paths.length, + result = Array(length), + skip = object == null; + + while (++index < length) { + result[index] = skip ? undefined : get(object, paths[index]); + } + return result; + } + + /** + * The base implementation of `_.clamp` which doesn't coerce arguments. + * + * @private + * @param {number} number The number to clamp. + * @param {number} [lower] The lower bound. + * @param {number} upper The upper bound. + * @returns {number} Returns the clamped number. + */ + function baseClamp(number, lower, upper) { + if (number === number) { + if (upper !== undefined) { + number = number <= upper ? number : upper; + } + if (lower !== undefined) { + number = number >= lower ? number : lower; + } + } + return number; + } + + /** + * The base implementation of `_.clone` and `_.cloneDeep` which tracks + * traversed objects. + * + * @private + * @param {*} value The value to clone. + * @param {boolean} bitmask The bitmask flags. + * 1 - Deep clone + * 2 - Flatten inherited properties + * 4 - Clone symbols + * @param {Function} [customizer] The function to customize cloning. + * @param {string} [key] The key of `value`. + * @param {Object} [object] The parent object of `value`. + * @param {Object} [stack] Tracks traversed objects and their clone counterparts. + * @returns {*} Returns the cloned value. + */ + function baseClone(value, bitmask, customizer, key, object, stack) { + var result, + isDeep = bitmask & CLONE_DEEP_FLAG, + isFlat = bitmask & CLONE_FLAT_FLAG, + isFull = bitmask & CLONE_SYMBOLS_FLAG; + + if (customizer) { + result = object ? customizer(value, key, object, stack) : customizer(value); + } + if (result !== undefined) { + return result; + } + if (!isObject(value)) { + return value; + } + var isArr = isArray(value); + if (isArr) { + result = initCloneArray(value); + if (!isDeep) { + return copyArray(value, result); + } + } else { + var tag = getTag(value), + isFunc = tag == funcTag || tag == genTag; + + if (isBuffer(value)) { + return cloneBuffer(value, isDeep); + } + if (tag == objectTag || tag == argsTag || (isFunc && !object)) { + result = (isFlat || isFunc) ? {} : initCloneObject(value); + if (!isDeep) { + return isFlat + ? copySymbolsIn(value, baseAssignIn(result, value)) + : copySymbols(value, baseAssign(result, value)); + } + } else { + if (!cloneableTags[tag]) { + return object ? value : {}; + } + result = initCloneByTag(value, tag, isDeep); + } + } + // Check for circular references and return its corresponding clone. + stack || (stack = new Stack); + var stacked = stack.get(value); + if (stacked) { + return stacked; + } + stack.set(value, result); + + if (isSet(value)) { + value.forEach(function(subValue) { + result.add(baseClone(subValue, bitmask, customizer, subValue, value, stack)); + }); + + return result; + } + + if (isMap(value)) { + value.forEach(function(subValue, key) { + result.set(key, baseClone(subValue, bitmask, customizer, key, value, stack)); + }); + + return result; + } + + var keysFunc = isFull + ? (isFlat ? getAllKeysIn : getAllKeys) + : (isFlat ? keysIn : keys); + + var props = isArr ? undefined : keysFunc(value); + arrayEach(props || value, function(subValue, key) { + if (props) { + key = subValue; + subValue = value[key]; + } + // Recursively populate clone (susceptible to call stack limits). + assignValue(result, key, baseClone(subValue, bitmask, customizer, key, value, stack)); + }); + return result; + } + + /** + * The base implementation of `_.conforms` which doesn't clone `source`. + * + * @private + * @param {Object} source The object of property predicates to conform to. + * @returns {Function} Returns the new spec function. + */ + function baseConforms(source) { + var props = keys(source); + return function(object) { + return baseConformsTo(object, source, props); + }; + } + + /** + * The base implementation of `_.conformsTo` which accepts `props` to check. + * + * @private + * @param {Object} object The object to inspect. + * @param {Object} source The object of property predicates to conform to. + * @returns {boolean} Returns `true` if `object` conforms, else `false`. + */ + function baseConformsTo(object, source, props) { + var length = props.length; + if (object == null) { + return !length; + } + object = Object(object); + while (length--) { + var key = props[length], + predicate = source[key], + value = object[key]; + + if ((value === undefined && !(key in object)) || !predicate(value)) { + return false; + } + } + return true; + } + + /** + * The base implementation of `_.delay` and `_.defer` which accepts `args` + * to provide to `func`. + * + * @private + * @param {Function} func The function to delay. + * @param {number} wait The number of milliseconds to delay invocation. + * @param {Array} args The arguments to provide to `func`. + * @returns {number|Object} Returns the timer id or timeout object. + */ + function baseDelay(func, wait, args) { + if (typeof func != 'function') { + throw new TypeError(FUNC_ERROR_TEXT); + } + return setTimeout(function() { func.apply(undefined, args); }, wait); + } + + /** + * The base implementation of methods like `_.difference` without support + * for excluding multiple arrays or iteratee shorthands. + * + * @private + * @param {Array} array The array to inspect. + * @param {Array} values The values to exclude. + * @param {Function} [iteratee] The iteratee invoked per element. + * @param {Function} [comparator] The comparator invoked per element. + * @returns {Array} Returns the new array of filtered values. + */ + function baseDifference(array, values, iteratee, comparator) { + var index = -1, + includes = arrayIncludes, + isCommon = true, + length = array.length, + result = [], + valuesLength = values.length; + + if (!length) { + return result; + } + if (iteratee) { + values = arrayMap(values, baseUnary(iteratee)); + } + if (comparator) { + includes = arrayIncludesWith; + isCommon = false; + } + else if (values.length >= LARGE_ARRAY_SIZE) { + includes = cacheHas; + isCommon = false; + values = new SetCache(values); + } + outer: + while (++index < length) { + var value = array[index], + computed = iteratee == null ? value : iteratee(value); + + value = (comparator || value !== 0) ? value : 0; + if (isCommon && computed === computed) { + var valuesIndex = valuesLength; + while (valuesIndex--) { + if (values[valuesIndex] === computed) { + continue outer; + } + } + result.push(value); + } + else if (!includes(values, computed, comparator)) { + result.push(value); + } + } + return result; + } + + /** + * The base implementation of `_.forEach` without support for iteratee shorthands. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array|Object} Returns `collection`. + */ + var baseEach = createBaseEach(baseForOwn); + + /** + * The base implementation of `_.forEachRight` without support for iteratee shorthands. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array|Object} Returns `collection`. + */ + var baseEachRight = createBaseEach(baseForOwnRight, true); + + /** + * The base implementation of `_.every` without support for iteratee shorthands. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} predicate The function invoked per iteration. + * @returns {boolean} Returns `true` if all elements pass the predicate check, + * else `false` + */ + function baseEvery(collection, predicate) { + var result = true; + baseEach(collection, function(value, index, collection) { + result = !!predicate(value, index, collection); + return result; + }); + return result; + } + + /** + * The base implementation of methods like `_.max` and `_.min` which accepts a + * `comparator` to determine the extremum value. + * + * @private + * @param {Array} array The array to iterate over. + * @param {Function} iteratee The iteratee invoked per iteration. + * @param {Function} comparator The comparator used to compare values. + * @returns {*} Returns the extremum value. + */ + function baseExtremum(array, iteratee, comparator) { + var index = -1, + length = array.length; + + while (++index < length) { + var value = array[index], + current = iteratee(value); + + if (current != null && (computed === undefined + ? (current === current && !isSymbol(current)) + : comparator(current, computed) + )) { + var computed = current, + result = value; + } + } + return result; + } + + /** + * The base implementation of `_.fill` without an iteratee call guard. + * + * @private + * @param {Array} array The array to fill. + * @param {*} value The value to fill `array` with. + * @param {number} [start=0] The start position. + * @param {number} [end=array.length] The end position. + * @returns {Array} Returns `array`. + */ + function baseFill(array, value, start, end) { + var length = array.length; + + start = toInteger(start); + if (start < 0) { + start = -start > length ? 0 : (length + start); + } + end = (end === undefined || end > length) ? length : toInteger(end); + if (end < 0) { + end += length; + } + end = start > end ? 0 : toLength(end); + while (start < end) { + array[start++] = value; + } + return array; + } + + /** + * The base implementation of `_.filter` without support for iteratee shorthands. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} predicate The function invoked per iteration. + * @returns {Array} Returns the new filtered array. + */ + function baseFilter(collection, predicate) { + var result = []; + baseEach(collection, function(value, index, collection) { + if (predicate(value, index, collection)) { + result.push(value); + } + }); + return result; + } + + /** + * The base implementation of `_.flatten` with support for restricting flattening. + * + * @private + * @param {Array} array The array to flatten. + * @param {number} depth The maximum recursion depth. + * @param {boolean} [predicate=isFlattenable] The function invoked per iteration. + * @param {boolean} [isStrict] Restrict to values that pass `predicate` checks. + * @param {Array} [result=[]] The initial result value. + * @returns {Array} Returns the new flattened array. + */ + function baseFlatten(array, depth, predicate, isStrict, result) { + var index = -1, + length = array.length; + + predicate || (predicate = isFlattenable); + result || (result = []); + + while (++index < length) { + var value = array[index]; + if (depth > 0 && predicate(value)) { + if (depth > 1) { + // Recursively flatten arrays (susceptible to call stack limits). + baseFlatten(value, depth - 1, predicate, isStrict, result); + } else { + arrayPush(result, value); + } + } else if (!isStrict) { + result[result.length] = value; + } + } + return result; + } + + /** + * The base implementation of `baseForOwn` which iterates over `object` + * properties returned by `keysFunc` and invokes `iteratee` for each property. + * Iteratee functions may exit iteration early by explicitly returning `false`. + * + * @private + * @param {Object} object The object to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @param {Function} keysFunc The function to get the keys of `object`. + * @returns {Object} Returns `object`. + */ + var baseFor = createBaseFor(); + + /** + * This function is like `baseFor` except that it iterates over properties + * in the opposite order. + * + * @private + * @param {Object} object The object to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @param {Function} keysFunc The function to get the keys of `object`. + * @returns {Object} Returns `object`. + */ + var baseForRight = createBaseFor(true); + + /** + * The base implementation of `_.forOwn` without support for iteratee shorthands. + * + * @private + * @param {Object} object The object to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Object} Returns `object`. + */ + function baseForOwn(object, iteratee) { + return object && baseFor(object, iteratee, keys); + } + + /** + * The base implementation of `_.forOwnRight` without support for iteratee shorthands. + * + * @private + * @param {Object} object The object to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Object} Returns `object`. + */ + function baseForOwnRight(object, iteratee) { + return object && baseForRight(object, iteratee, keys); + } + + /** + * The base implementation of `_.functions` which creates an array of + * `object` function property names filtered from `props`. + * + * @private + * @param {Object} object The object to inspect. + * @param {Array} props The property names to filter. + * @returns {Array} Returns the function names. + */ + function baseFunctions(object, props) { + return arrayFilter(props, function(key) { + return isFunction(object[key]); + }); + } + + /** + * The base implementation of `_.get` without support for default values. + * + * @private + * @param {Object} object The object to query. + * @param {Array|string} path The path of the property to get. + * @returns {*} Returns the resolved value. + */ + function baseGet(object, path) { + path = castPath(path, object); + + var index = 0, + length = path.length; + + while (object != null && index < length) { + object = object[toKey(path[index++])]; + } + return (index && index == length) ? object : undefined; + } + + /** + * The base implementation of `getAllKeys` and `getAllKeysIn` which uses + * `keysFunc` and `symbolsFunc` to get the enumerable property names and + * symbols of `object`. + * + * @private + * @param {Object} object The object to query. + * @param {Function} keysFunc The function to get the keys of `object`. + * @param {Function} symbolsFunc The function to get the symbols of `object`. + * @returns {Array} Returns the array of property names and symbols. + */ + function baseGetAllKeys(object, keysFunc, symbolsFunc) { + var result = keysFunc(object); + return isArray(object) ? result : arrayPush(result, symbolsFunc(object)); + } + + /** + * The base implementation of `getTag` without fallbacks for buggy environments. + * + * @private + * @param {*} value The value to query. + * @returns {string} Returns the `toStringTag`. + */ + function baseGetTag(value) { + if (value == null) { + return value === undefined ? undefinedTag : nullTag; + } + return (symToStringTag && symToStringTag in Object(value)) + ? getRawTag(value) + : objectToString(value); + } + + /** + * The base implementation of `_.gt` which doesn't coerce arguments. + * + * @private + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if `value` is greater than `other`, + * else `false`. + */ + function baseGt(value, other) { + return value > other; + } + + /** + * The base implementation of `_.has` without support for deep paths. + * + * @private + * @param {Object} [object] The object to query. + * @param {Array|string} key The key to check. + * @returns {boolean} Returns `true` if `key` exists, else `false`. + */ + function baseHas(object, key) { + return object != null && hasOwnProperty.call(object, key); + } + + /** + * The base implementation of `_.hasIn` without support for deep paths. + * + * @private + * @param {Object} [object] The object to query. + * @param {Array|string} key The key to check. + * @returns {boolean} Returns `true` if `key` exists, else `false`. + */ + function baseHasIn(object, key) { + return object != null && key in Object(object); + } + + /** + * The base implementation of `_.inRange` which doesn't coerce arguments. + * + * @private + * @param {number} number The number to check. + * @param {number} start The start of the range. + * @param {number} end The end of the range. + * @returns {boolean} Returns `true` if `number` is in the range, else `false`. + */ + function baseInRange(number, start, end) { + return number >= nativeMin(start, end) && number < nativeMax(start, end); + } + + /** + * The base implementation of methods like `_.intersection`, without support + * for iteratee shorthands, that accepts an array of arrays to inspect. + * + * @private + * @param {Array} arrays The arrays to inspect. + * @param {Function} [iteratee] The iteratee invoked per element. + * @param {Function} [comparator] The comparator invoked per element. + * @returns {Array} Returns the new array of shared values. + */ + function baseIntersection(arrays, iteratee, comparator) { + var includes = comparator ? arrayIncludesWith : arrayIncludes, + length = arrays[0].length, + othLength = arrays.length, + othIndex = othLength, + caches = Array(othLength), + maxLength = Infinity, + result = []; + + while (othIndex--) { + var array = arrays[othIndex]; + if (othIndex && iteratee) { + array = arrayMap(array, baseUnary(iteratee)); + } + maxLength = nativeMin(array.length, maxLength); + caches[othIndex] = !comparator && (iteratee || (length >= 120 && array.length >= 120)) + ? new SetCache(othIndex && array) + : undefined; + } + array = arrays[0]; + + var index = -1, + seen = caches[0]; + + outer: + while (++index < length && result.length < maxLength) { + var value = array[index], + computed = iteratee ? iteratee(value) : value; + + value = (comparator || value !== 0) ? value : 0; + if (!(seen + ? cacheHas(seen, computed) + : includes(result, computed, comparator) + )) { + othIndex = othLength; + while (--othIndex) { + var cache = caches[othIndex]; + if (!(cache + ? cacheHas(cache, computed) + : includes(arrays[othIndex], computed, comparator)) + ) { + continue outer; + } + } + if (seen) { + seen.push(computed); + } + result.push(value); + } + } + return result; + } + + /** + * The base implementation of `_.invert` and `_.invertBy` which inverts + * `object` with values transformed by `iteratee` and set by `setter`. + * + * @private + * @param {Object} object The object to iterate over. + * @param {Function} setter The function to set `accumulator` values. + * @param {Function} iteratee The iteratee to transform values. + * @param {Object} accumulator The initial inverted object. + * @returns {Function} Returns `accumulator`. + */ + function baseInverter(object, setter, iteratee, accumulator) { + baseForOwn(object, function(value, key, object) { + setter(accumulator, iteratee(value), key, object); + }); + return accumulator; + } + + /** + * The base implementation of `_.invoke` without support for individual + * method arguments. + * + * @private + * @param {Object} object The object to query. + * @param {Array|string} path The path of the method to invoke. + * @param {Array} args The arguments to invoke the method with. + * @returns {*} Returns the result of the invoked method. + */ + function baseInvoke(object, path, args) { + path = castPath(path, object); + object = parent(object, path); + var func = object == null ? object : object[toKey(last(path))]; + return func == null ? undefined : apply(func, object, args); + } + + /** + * The base implementation of `_.isArguments`. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an `arguments` object, + */ + function baseIsArguments(value) { + return isObjectLike(value) && baseGetTag(value) == argsTag; + } + + /** + * The base implementation of `_.isArrayBuffer` without Node.js optimizations. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an array buffer, else `false`. + */ + function baseIsArrayBuffer(value) { + return isObjectLike(value) && baseGetTag(value) == arrayBufferTag; + } + + /** + * The base implementation of `_.isDate` without Node.js optimizations. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a date object, else `false`. + */ + function baseIsDate(value) { + return isObjectLike(value) && baseGetTag(value) == dateTag; + } + + /** + * The base implementation of `_.isEqual` which supports partial comparisons + * and tracks traversed objects. + * + * @private + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @param {boolean} bitmask The bitmask flags. + * 1 - Unordered comparison + * 2 - Partial comparison + * @param {Function} [customizer] The function to customize comparisons. + * @param {Object} [stack] Tracks traversed `value` and `other` objects. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. + */ + function baseIsEqual(value, other, bitmask, customizer, stack) { + if (value === other) { + return true; + } + if (value == null || other == null || (!isObjectLike(value) && !isObjectLike(other))) { + return value !== value && other !== other; + } + return baseIsEqualDeep(value, other, bitmask, customizer, baseIsEqual, stack); + } + + /** + * A specialized version of `baseIsEqual` for arrays and objects which performs + * deep comparisons and tracks traversed objects enabling objects with circular + * references to be compared. + * + * @private + * @param {Object} object The object to compare. + * @param {Object} other The other object to compare. + * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. + * @param {Function} customizer The function to customize comparisons. + * @param {Function} equalFunc The function to determine equivalents of values. + * @param {Object} [stack] Tracks traversed `object` and `other` objects. + * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. + */ + function baseIsEqualDeep(object, other, bitmask, customizer, equalFunc, stack) { + var objIsArr = isArray(object), + othIsArr = isArray(other), + objTag = objIsArr ? arrayTag : getTag(object), + othTag = othIsArr ? arrayTag : getTag(other); + + objTag = objTag == argsTag ? objectTag : objTag; + othTag = othTag == argsTag ? objectTag : othTag; + + var objIsObj = objTag == objectTag, + othIsObj = othTag == objectTag, + isSameTag = objTag == othTag; + + if (isSameTag && isBuffer(object)) { + if (!isBuffer(other)) { + return false; + } + objIsArr = true; + objIsObj = false; + } + if (isSameTag && !objIsObj) { + stack || (stack = new Stack); + return (objIsArr || isTypedArray(object)) + ? equalArrays(object, other, bitmask, customizer, equalFunc, stack) + : equalByTag(object, other, objTag, bitmask, customizer, equalFunc, stack); + } + if (!(bitmask & COMPARE_PARTIAL_FLAG)) { + var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'), + othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__'); + + if (objIsWrapped || othIsWrapped) { + var objUnwrapped = objIsWrapped ? object.value() : object, + othUnwrapped = othIsWrapped ? other.value() : other; + + stack || (stack = new Stack); + return equalFunc(objUnwrapped, othUnwrapped, bitmask, customizer, stack); + } + } + if (!isSameTag) { + return false; + } + stack || (stack = new Stack); + return equalObjects(object, other, bitmask, customizer, equalFunc, stack); + } + + /** + * The base implementation of `_.isMap` without Node.js optimizations. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a map, else `false`. + */ + function baseIsMap(value) { + return isObjectLike(value) && getTag(value) == mapTag; + } + + /** + * The base implementation of `_.isMatch` without support for iteratee shorthands. + * + * @private + * @param {Object} object The object to inspect. + * @param {Object} source The object of property values to match. + * @param {Array} matchData The property names, values, and compare flags to match. + * @param {Function} [customizer] The function to customize comparisons. + * @returns {boolean} Returns `true` if `object` is a match, else `false`. + */ + function baseIsMatch(object, source, matchData, customizer) { + var index = matchData.length, + length = index, + noCustomizer = !customizer; + + if (object == null) { + return !length; + } + object = Object(object); + while (index--) { + var data = matchData[index]; + if ((noCustomizer && data[2]) + ? data[1] !== object[data[0]] + : !(data[0] in object) + ) { + return false; + } + } + while (++index < length) { + data = matchData[index]; + var key = data[0], + objValue = object[key], + srcValue = data[1]; + + if (noCustomizer && data[2]) { + if (objValue === undefined && !(key in object)) { + return false; + } + } else { + var stack = new Stack; + if (customizer) { + var result = customizer(objValue, srcValue, key, object, source, stack); + } + if (!(result === undefined + ? baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG, customizer, stack) + : result + )) { + return false; + } + } + } + return true; + } + + /** + * The base implementation of `_.isNative` without bad shim checks. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a native function, + * else `false`. + */ + function baseIsNative(value) { + if (!isObject(value) || isMasked(value)) { + return false; + } + var pattern = isFunction(value) ? reIsNative : reIsHostCtor; + return pattern.test(toSource(value)); + } + + /** + * The base implementation of `_.isRegExp` without Node.js optimizations. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a regexp, else `false`. + */ + function baseIsRegExp(value) { + return isObjectLike(value) && baseGetTag(value) == regexpTag; + } + + /** + * The base implementation of `_.isSet` without Node.js optimizations. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a set, else `false`. + */ + function baseIsSet(value) { + return isObjectLike(value) && getTag(value) == setTag; + } + + /** + * The base implementation of `_.isTypedArray` without Node.js optimizations. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a typed array, else `false`. + */ + function baseIsTypedArray(value) { + return isObjectLike(value) && + isLength(value.length) && !!typedArrayTags[baseGetTag(value)]; + } + + /** + * The base implementation of `_.iteratee`. + * + * @private + * @param {*} [value=_.identity] The value to convert to an iteratee. + * @returns {Function} Returns the iteratee. + */ + function baseIteratee(value) { + // Don't store the `typeof` result in a variable to avoid a JIT bug in Safari 9. + // See https://bugs.webkit.org/show_bug.cgi?id=156034 for more details. + if (typeof value == 'function') { + return value; + } + if (value == null) { + return identity; + } + if (typeof value == 'object') { + return isArray(value) + ? baseMatchesProperty(value[0], value[1]) + : baseMatches(value); + } + return property(value); + } + + /** + * The base implementation of `_.keys` which doesn't treat sparse arrays as dense. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + */ + function baseKeys(object) { + if (!isPrototype(object)) { + return nativeKeys(object); + } + var result = []; + for (var key in Object(object)) { + if (hasOwnProperty.call(object, key) && key != 'constructor') { + result.push(key); + } + } + return result; + } + + /** + * The base implementation of `_.keysIn` which doesn't treat sparse arrays as dense. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + */ + function baseKeysIn(object) { + if (!isObject(object)) { + return nativeKeysIn(object); + } + var isProto = isPrototype(object), + result = []; + + for (var key in object) { + if (!(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) { + result.push(key); + } + } + return result; + } + + /** + * The base implementation of `_.lt` which doesn't coerce arguments. + * + * @private + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if `value` is less than `other`, + * else `false`. + */ + function baseLt(value, other) { + return value < other; + } + + /** + * The base implementation of `_.map` without support for iteratee shorthands. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns the new mapped array. + */ + function baseMap(collection, iteratee) { + var index = -1, + result = isArrayLike(collection) ? Array(collection.length) : []; + + baseEach(collection, function(value, key, collection) { + result[++index] = iteratee(value, key, collection); + }); + return result; + } + + /** + * The base implementation of `_.matches` which doesn't clone `source`. + * + * @private + * @param {Object} source The object of property values to match. + * @returns {Function} Returns the new spec function. + */ + function baseMatches(source) { + var matchData = getMatchData(source); + if (matchData.length == 1 && matchData[0][2]) { + return matchesStrictComparable(matchData[0][0], matchData[0][1]); + } + return function(object) { + return object === source || baseIsMatch(object, source, matchData); + }; + } + + /** + * The base implementation of `_.matchesProperty` which doesn't clone `srcValue`. + * + * @private + * @param {string} path The path of the property to get. + * @param {*} srcValue The value to match. + * @returns {Function} Returns the new spec function. + */ + function baseMatchesProperty(path, srcValue) { + if (isKey(path) && isStrictComparable(srcValue)) { + return matchesStrictComparable(toKey(path), srcValue); + } + return function(object) { + var objValue = get(object, path); + return (objValue === undefined && objValue === srcValue) + ? hasIn(object, path) + : baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG); + }; + } + + /** + * The base implementation of `_.merge` without support for multiple sources. + * + * @private + * @param {Object} object The destination object. + * @param {Object} source The source object. + * @param {number} srcIndex The index of `source`. + * @param {Function} [customizer] The function to customize merged values. + * @param {Object} [stack] Tracks traversed source values and their merged + * counterparts. + */ + function baseMerge(object, source, srcIndex, customizer, stack) { + if (object === source) { + return; + } + baseFor(source, function(srcValue, key) { + if (isObject(srcValue)) { + stack || (stack = new Stack); + baseMergeDeep(object, source, key, srcIndex, baseMerge, customizer, stack); + } + else { + var newValue = customizer + ? customizer(safeGet(object, key), srcValue, (key + ''), object, source, stack) + : undefined; + + if (newValue === undefined) { + newValue = srcValue; + } + assignMergeValue(object, key, newValue); + } + }, keysIn); + } + + /** + * A specialized version of `baseMerge` for arrays and objects which performs + * deep merges and tracks traversed objects enabling objects with circular + * references to be merged. + * + * @private + * @param {Object} object The destination object. + * @param {Object} source The source object. + * @param {string} key The key of the value to merge. + * @param {number} srcIndex The index of `source`. + * @param {Function} mergeFunc The function to merge values. + * @param {Function} [customizer] The function to customize assigned values. + * @param {Object} [stack] Tracks traversed source values and their merged + * counterparts. + */ + function baseMergeDeep(object, source, key, srcIndex, mergeFunc, customizer, stack) { + var objValue = safeGet(object, key), + srcValue = safeGet(source, key), + stacked = stack.get(srcValue); + + if (stacked) { + assignMergeValue(object, key, stacked); + return; + } + var newValue = customizer + ? customizer(objValue, srcValue, (key + ''), object, source, stack) + : undefined; + + var isCommon = newValue === undefined; + + if (isCommon) { + var isArr = isArray(srcValue), + isBuff = !isArr && isBuffer(srcValue), + isTyped = !isArr && !isBuff && isTypedArray(srcValue); + + newValue = srcValue; + if (isArr || isBuff || isTyped) { + if (isArray(objValue)) { + newValue = objValue; + } + else if (isArrayLikeObject(objValue)) { + newValue = copyArray(objValue); + } + else if (isBuff) { + isCommon = false; + newValue = cloneBuffer(srcValue, true); + } + else if (isTyped) { + isCommon = false; + newValue = cloneTypedArray(srcValue, true); + } + else { + newValue = []; + } + } + else if (isPlainObject(srcValue) || isArguments(srcValue)) { + newValue = objValue; + if (isArguments(objValue)) { + newValue = toPlainObject(objValue); + } + else if (!isObject(objValue) || (srcIndex && isFunction(objValue))) { + newValue = initCloneObject(srcValue); + } + } + else { + isCommon = false; + } + } + if (isCommon) { + // Recursively merge objects and arrays (susceptible to call stack limits). + stack.set(srcValue, newValue); + mergeFunc(newValue, srcValue, srcIndex, customizer, stack); + stack['delete'](srcValue); + } + assignMergeValue(object, key, newValue); + } + + /** + * The base implementation of `_.nth` which doesn't coerce arguments. + * + * @private + * @param {Array} array The array to query. + * @param {number} n The index of the element to return. + * @returns {*} Returns the nth element of `array`. + */ + function baseNth(array, n) { + var length = array.length; + if (!length) { + return; + } + n += n < 0 ? length : 0; + return isIndex(n, length) ? array[n] : undefined; + } + + /** + * The base implementation of `_.orderBy` without param guards. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function[]|Object[]|string[]} iteratees The iteratees to sort by. + * @param {string[]} orders The sort orders of `iteratees`. + * @returns {Array} Returns the new sorted array. + */ + function baseOrderBy(collection, iteratees, orders) { + var index = -1; + iteratees = arrayMap(iteratees.length ? iteratees : [identity], baseUnary(getIteratee())); + + var result = baseMap(collection, function(value, key, collection) { + var criteria = arrayMap(iteratees, function(iteratee) { + return iteratee(value); + }); + return { 'criteria': criteria, 'index': ++index, 'value': value }; + }); + + return baseSortBy(result, function(object, other) { + return compareMultiple(object, other, orders); + }); + } + + /** + * The base implementation of `_.pick` without support for individual + * property identifiers. + * + * @private + * @param {Object} object The source object. + * @param {string[]} paths The property paths to pick. + * @returns {Object} Returns the new object. + */ + function basePick(object, paths) { + return basePickBy(object, paths, function(value, path) { + return hasIn(object, path); + }); + } + + /** + * The base implementation of `_.pickBy` without support for iteratee shorthands. + * + * @private + * @param {Object} object The source object. + * @param {string[]} paths The property paths to pick. + * @param {Function} predicate The function invoked per property. + * @returns {Object} Returns the new object. + */ + function basePickBy(object, paths, predicate) { + var index = -1, + length = paths.length, + result = {}; + + while (++index < length) { + var path = paths[index], + value = baseGet(object, path); + + if (predicate(value, path)) { + baseSet(result, castPath(path, object), value); + } + } + return result; + } + + /** + * A specialized version of `baseProperty` which supports deep paths. + * + * @private + * @param {Array|string} path The path of the property to get. + * @returns {Function} Returns the new accessor function. + */ + function basePropertyDeep(path) { + return function(object) { + return baseGet(object, path); + }; + } + + /** + * The base implementation of `_.pullAllBy` without support for iteratee + * shorthands. + * + * @private + * @param {Array} array The array to modify. + * @param {Array} values The values to remove. + * @param {Function} [iteratee] The iteratee invoked per element. + * @param {Function} [comparator] The comparator invoked per element. + * @returns {Array} Returns `array`. + */ + function basePullAll(array, values, iteratee, comparator) { + var indexOf = comparator ? baseIndexOfWith : baseIndexOf, + index = -1, + length = values.length, + seen = array; + + if (array === values) { + values = copyArray(values); + } + if (iteratee) { + seen = arrayMap(array, baseUnary(iteratee)); + } + while (++index < length) { + var fromIndex = 0, + value = values[index], + computed = iteratee ? iteratee(value) : value; + + while ((fromIndex = indexOf(seen, computed, fromIndex, comparator)) > -1) { + if (seen !== array) { + splice.call(seen, fromIndex, 1); + } + splice.call(array, fromIndex, 1); + } + } + return array; + } + + /** + * The base implementation of `_.pullAt` without support for individual + * indexes or capturing the removed elements. + * + * @private + * @param {Array} array The array to modify. + * @param {number[]} indexes The indexes of elements to remove. + * @returns {Array} Returns `array`. + */ + function basePullAt(array, indexes) { + var length = array ? indexes.length : 0, + lastIndex = length - 1; + + while (length--) { + var index = indexes[length]; + if (length == lastIndex || index !== previous) { + var previous = index; + if (isIndex(index)) { + splice.call(array, index, 1); + } else { + baseUnset(array, index); + } + } + } + return array; + } + + /** + * The base implementation of `_.random` without support for returning + * floating-point numbers. + * + * @private + * @param {number} lower The lower bound. + * @param {number} upper The upper bound. + * @returns {number} Returns the random number. + */ + function baseRandom(lower, upper) { + return lower + nativeFloor(nativeRandom() * (upper - lower + 1)); + } + + /** + * The base implementation of `_.range` and `_.rangeRight` which doesn't + * coerce arguments. + * + * @private + * @param {number} start The start of the range. + * @param {number} end The end of the range. + * @param {number} step The value to increment or decrement by. + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Array} Returns the range of numbers. + */ + function baseRange(start, end, step, fromRight) { + var index = -1, + length = nativeMax(nativeCeil((end - start) / (step || 1)), 0), + result = Array(length); + + while (length--) { + result[fromRight ? length : ++index] = start; + start += step; + } + return result; + } + + /** + * The base implementation of `_.repeat` which doesn't coerce arguments. + * + * @private + * @param {string} string The string to repeat. + * @param {number} n The number of times to repeat the string. + * @returns {string} Returns the repeated string. + */ + function baseRepeat(string, n) { + var result = ''; + if (!string || n < 1 || n > MAX_SAFE_INTEGER) { + return result; + } + // Leverage the exponentiation by squaring algorithm for a faster repeat. + // See https://en.wikipedia.org/wiki/Exponentiation_by_squaring for more details. + do { + if (n % 2) { + result += string; + } + n = nativeFloor(n / 2); + if (n) { + string += string; + } + } while (n); + + return result; + } + + /** + * The base implementation of `_.rest` which doesn't validate or coerce arguments. + * + * @private + * @param {Function} func The function to apply a rest parameter to. + * @param {number} [start=func.length-1] The start position of the rest parameter. + * @returns {Function} Returns the new function. + */ + function baseRest(func, start) { + return setToString(overRest(func, start, identity), func + ''); + } + + /** + * The base implementation of `_.sample`. + * + * @private + * @param {Array|Object} collection The collection to sample. + * @returns {*} Returns the random element. + */ + function baseSample(collection) { + return arraySample(values(collection)); + } + + /** + * The base implementation of `_.sampleSize` without param guards. + * + * @private + * @param {Array|Object} collection The collection to sample. + * @param {number} n The number of elements to sample. + * @returns {Array} Returns the random elements. + */ + function baseSampleSize(collection, n) { + var array = values(collection); + return shuffleSelf(array, baseClamp(n, 0, array.length)); + } + + /** + * The base implementation of `_.set`. + * + * @private + * @param {Object} object The object to modify. + * @param {Array|string} path The path of the property to set. + * @param {*} value The value to set. + * @param {Function} [customizer] The function to customize path creation. + * @returns {Object} Returns `object`. + */ + function baseSet(object, path, value, customizer) { + if (!isObject(object)) { + return object; + } + path = castPath(path, object); + + var index = -1, + length = path.length, + lastIndex = length - 1, + nested = object; + + while (nested != null && ++index < length) { + var key = toKey(path[index]), + newValue = value; + + if (index != lastIndex) { + var objValue = nested[key]; + newValue = customizer ? customizer(objValue, key, nested) : undefined; + if (newValue === undefined) { + newValue = isObject(objValue) + ? objValue + : (isIndex(path[index + 1]) ? [] : {}); + } + } + assignValue(nested, key, newValue); + nested = nested[key]; + } + return object; + } + + /** + * The base implementation of `setData` without support for hot loop shorting. + * + * @private + * @param {Function} func The function to associate metadata with. + * @param {*} data The metadata. + * @returns {Function} Returns `func`. + */ + var baseSetData = !metaMap ? identity : function(func, data) { + metaMap.set(func, data); + return func; + }; + + /** + * The base implementation of `setToString` without support for hot loop shorting. + * + * @private + * @param {Function} func The function to modify. + * @param {Function} string The `toString` result. + * @returns {Function} Returns `func`. + */ + var baseSetToString = !defineProperty ? identity : function(func, string) { + return defineProperty(func, 'toString', { + 'configurable': true, + 'enumerable': false, + 'value': constant(string), + 'writable': true + }); + }; + + /** + * The base implementation of `_.shuffle`. + * + * @private + * @param {Array|Object} collection The collection to shuffle. + * @returns {Array} Returns the new shuffled array. + */ + function baseShuffle(collection) { + return shuffleSelf(values(collection)); + } + + /** + * The base implementation of `_.slice` without an iteratee call guard. + * + * @private + * @param {Array} array The array to slice. + * @param {number} [start=0] The start position. + * @param {number} [end=array.length] The end position. + * @returns {Array} Returns the slice of `array`. + */ + function baseSlice(array, start, end) { + var index = -1, + length = array.length; + + if (start < 0) { + start = -start > length ? 0 : (length + start); + } + end = end > length ? length : end; + if (end < 0) { + end += length; + } + length = start > end ? 0 : ((end - start) >>> 0); + start >>>= 0; + + var result = Array(length); + while (++index < length) { + result[index] = array[index + start]; + } + return result; + } + + /** + * The base implementation of `_.some` without support for iteratee shorthands. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} predicate The function invoked per iteration. + * @returns {boolean} Returns `true` if any element passes the predicate check, + * else `false`. + */ + function baseSome(collection, predicate) { + var result; + + baseEach(collection, function(value, index, collection) { + result = predicate(value, index, collection); + return !result; + }); + return !!result; + } + + /** + * The base implementation of `_.sortedIndex` and `_.sortedLastIndex` which + * performs a binary search of `array` to determine the index at which `value` + * should be inserted into `array` in order to maintain its sort order. + * + * @private + * @param {Array} array The sorted array to inspect. + * @param {*} value The value to evaluate. + * @param {boolean} [retHighest] Specify returning the highest qualified index. + * @returns {number} Returns the index at which `value` should be inserted + * into `array`. + */ + function baseSortedIndex(array, value, retHighest) { + var low = 0, + high = array == null ? low : array.length; + + if (typeof value == 'number' && value === value && high <= HALF_MAX_ARRAY_LENGTH) { + while (low < high) { + var mid = (low + high) >>> 1, + computed = array[mid]; + + if (computed !== null && !isSymbol(computed) && + (retHighest ? (computed <= value) : (computed < value))) { + low = mid + 1; + } else { + high = mid; + } + } + return high; + } + return baseSortedIndexBy(array, value, identity, retHighest); + } + + /** + * The base implementation of `_.sortedIndexBy` and `_.sortedLastIndexBy` + * which invokes `iteratee` for `value` and each element of `array` to compute + * their sort ranking. The iteratee is invoked with one argument; (value). + * + * @private + * @param {Array} array The sorted array to inspect. + * @param {*} value The value to evaluate. + * @param {Function} iteratee The iteratee invoked per element. + * @param {boolean} [retHighest] Specify returning the highest qualified index. + * @returns {number} Returns the index at which `value` should be inserted + * into `array`. + */ + function baseSortedIndexBy(array, value, iteratee, retHighest) { + value = iteratee(value); + + var low = 0, + high = array == null ? 0 : array.length, + valIsNaN = value !== value, + valIsNull = value === null, + valIsSymbol = isSymbol(value), + valIsUndefined = value === undefined; + + while (low < high) { + var mid = nativeFloor((low + high) / 2), + computed = iteratee(array[mid]), + othIsDefined = computed !== undefined, + othIsNull = computed === null, + othIsReflexive = computed === computed, + othIsSymbol = isSymbol(computed); + + if (valIsNaN) { + var setLow = retHighest || othIsReflexive; + } else if (valIsUndefined) { + setLow = othIsReflexive && (retHighest || othIsDefined); + } else if (valIsNull) { + setLow = othIsReflexive && othIsDefined && (retHighest || !othIsNull); + } else if (valIsSymbol) { + setLow = othIsReflexive && othIsDefined && !othIsNull && (retHighest || !othIsSymbol); + } else if (othIsNull || othIsSymbol) { + setLow = false; + } else { + setLow = retHighest ? (computed <= value) : (computed < value); + } + if (setLow) { + low = mid + 1; + } else { + high = mid; + } + } + return nativeMin(high, MAX_ARRAY_INDEX); + } + + /** + * The base implementation of `_.sortedUniq` and `_.sortedUniqBy` without + * support for iteratee shorthands. + * + * @private + * @param {Array} array The array to inspect. + * @param {Function} [iteratee] The iteratee invoked per element. + * @returns {Array} Returns the new duplicate free array. + */ + function baseSortedUniq(array, iteratee) { + var index = -1, + length = array.length, + resIndex = 0, + result = []; + + while (++index < length) { + var value = array[index], + computed = iteratee ? iteratee(value) : value; + + if (!index || !eq(computed, seen)) { + var seen = computed; + result[resIndex++] = value === 0 ? 0 : value; + } + } + return result; + } + + /** + * The base implementation of `_.toNumber` which doesn't ensure correct + * conversions of binary, hexadecimal, or octal string values. + * + * @private + * @param {*} value The value to process. + * @returns {number} Returns the number. + */ + function baseToNumber(value) { + if (typeof value == 'number') { + return value; + } + if (isSymbol(value)) { + return NAN; + } + return +value; + } + + /** + * The base implementation of `_.toString` which doesn't convert nullish + * values to empty strings. + * + * @private + * @param {*} value The value to process. + * @returns {string} Returns the string. + */ + function baseToString(value) { + // Exit early for strings to avoid a performance hit in some environments. + if (typeof value == 'string') { + return value; + } + if (isArray(value)) { + // Recursively convert values (susceptible to call stack limits). + return arrayMap(value, baseToString) + ''; + } + if (isSymbol(value)) { + return symbolToString ? symbolToString.call(value) : ''; + } + var result = (value + ''); + return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result; + } + + /** + * The base implementation of `_.uniqBy` without support for iteratee shorthands. + * + * @private + * @param {Array} array The array to inspect. + * @param {Function} [iteratee] The iteratee invoked per element. + * @param {Function} [comparator] The comparator invoked per element. + * @returns {Array} Returns the new duplicate free array. + */ + function baseUniq(array, iteratee, comparator) { + var index = -1, + includes = arrayIncludes, + length = array.length, + isCommon = true, + result = [], + seen = result; + + if (comparator) { + isCommon = false; + includes = arrayIncludesWith; + } + else if (length >= LARGE_ARRAY_SIZE) { + var set = iteratee ? null : createSet(array); + if (set) { + return setToArray(set); + } + isCommon = false; + includes = cacheHas; + seen = new SetCache; + } + else { + seen = iteratee ? [] : result; + } + outer: + while (++index < length) { + var value = array[index], + computed = iteratee ? iteratee(value) : value; + + value = (comparator || value !== 0) ? value : 0; + if (isCommon && computed === computed) { + var seenIndex = seen.length; + while (seenIndex--) { + if (seen[seenIndex] === computed) { + continue outer; + } + } + if (iteratee) { + seen.push(computed); + } + result.push(value); + } + else if (!includes(seen, computed, comparator)) { + if (seen !== result) { + seen.push(computed); + } + result.push(value); + } + } + return result; + } + + /** + * The base implementation of `_.unset`. + * + * @private + * @param {Object} object The object to modify. + * @param {Array|string} path The property path to unset. + * @returns {boolean} Returns `true` if the property is deleted, else `false`. + */ + function baseUnset(object, path) { + path = castPath(path, object); + object = parent(object, path); + return object == null || delete object[toKey(last(path))]; + } + + /** + * The base implementation of `_.update`. + * + * @private + * @param {Object} object The object to modify. + * @param {Array|string} path The path of the property to update. + * @param {Function} updater The function to produce the updated value. + * @param {Function} [customizer] The function to customize path creation. + * @returns {Object} Returns `object`. + */ + function baseUpdate(object, path, updater, customizer) { + return baseSet(object, path, updater(baseGet(object, path)), customizer); + } + + /** + * The base implementation of methods like `_.dropWhile` and `_.takeWhile` + * without support for iteratee shorthands. + * + * @private + * @param {Array} array The array to query. + * @param {Function} predicate The function invoked per iteration. + * @param {boolean} [isDrop] Specify dropping elements instead of taking them. + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Array} Returns the slice of `array`. + */ + function baseWhile(array, predicate, isDrop, fromRight) { + var length = array.length, + index = fromRight ? length : -1; + + while ((fromRight ? index-- : ++index < length) && + predicate(array[index], index, array)) {} + + return isDrop + ? baseSlice(array, (fromRight ? 0 : index), (fromRight ? index + 1 : length)) + : baseSlice(array, (fromRight ? index + 1 : 0), (fromRight ? length : index)); + } + + /** + * The base implementation of `wrapperValue` which returns the result of + * performing a sequence of actions on the unwrapped `value`, where each + * successive action is supplied the return value of the previous. + * + * @private + * @param {*} value The unwrapped value. + * @param {Array} actions Actions to perform to resolve the unwrapped value. + * @returns {*} Returns the resolved value. + */ + function baseWrapperValue(value, actions) { + var result = value; + if (result instanceof LazyWrapper) { + result = result.value(); + } + return arrayReduce(actions, function(result, action) { + return action.func.apply(action.thisArg, arrayPush([result], action.args)); + }, result); + } + + /** + * The base implementation of methods like `_.xor`, without support for + * iteratee shorthands, that accepts an array of arrays to inspect. + * + * @private + * @param {Array} arrays The arrays to inspect. + * @param {Function} [iteratee] The iteratee invoked per element. + * @param {Function} [comparator] The comparator invoked per element. + * @returns {Array} Returns the new array of values. + */ + function baseXor(arrays, iteratee, comparator) { + var length = arrays.length; + if (length < 2) { + return length ? baseUniq(arrays[0]) : []; + } + var index = -1, + result = Array(length); + + while (++index < length) { + var array = arrays[index], + othIndex = -1; + + while (++othIndex < length) { + if (othIndex != index) { + result[index] = baseDifference(result[index] || array, arrays[othIndex], iteratee, comparator); + } + } + } + return baseUniq(baseFlatten(result, 1), iteratee, comparator); + } + + /** + * This base implementation of `_.zipObject` which assigns values using `assignFunc`. + * + * @private + * @param {Array} props The property identifiers. + * @param {Array} values The property values. + * @param {Function} assignFunc The function to assign values. + * @returns {Object} Returns the new object. + */ + function baseZipObject(props, values, assignFunc) { + var index = -1, + length = props.length, + valsLength = values.length, + result = {}; + + while (++index < length) { + var value = index < valsLength ? values[index] : undefined; + assignFunc(result, props[index], value); + } + return result; + } + + /** + * Casts `value` to an empty array if it's not an array like object. + * + * @private + * @param {*} value The value to inspect. + * @returns {Array|Object} Returns the cast array-like object. + */ + function castArrayLikeObject(value) { + return isArrayLikeObject(value) ? value : []; + } + + /** + * Casts `value` to `identity` if it's not a function. + * + * @private + * @param {*} value The value to inspect. + * @returns {Function} Returns cast function. + */ + function castFunction(value) { + return typeof value == 'function' ? value : identity; + } + + /** + * Casts `value` to a path array if it's not one. + * + * @private + * @param {*} value The value to inspect. + * @param {Object} [object] The object to query keys on. + * @returns {Array} Returns the cast property path array. + */ + function castPath(value, object) { + if (isArray(value)) { + return value; + } + return isKey(value, object) ? [value] : stringToPath(toString(value)); + } + + /** + * A `baseRest` alias which can be replaced with `identity` by module + * replacement plugins. + * + * @private + * @type {Function} + * @param {Function} func The function to apply a rest parameter to. + * @returns {Function} Returns the new function. + */ + var castRest = baseRest; + + /** + * Casts `array` to a slice if it's needed. + * + * @private + * @param {Array} array The array to inspect. + * @param {number} start The start position. + * @param {number} [end=array.length] The end position. + * @returns {Array} Returns the cast slice. + */ + function castSlice(array, start, end) { + var length = array.length; + end = end === undefined ? length : end; + return (!start && end >= length) ? array : baseSlice(array, start, end); + } + + /** + * A simple wrapper around the global [`clearTimeout`](https://mdn.io/clearTimeout). + * + * @private + * @param {number|Object} id The timer id or timeout object of the timer to clear. + */ + var clearTimeout = ctxClearTimeout || function(id) { + return root.clearTimeout(id); + }; + + /** + * Creates a clone of `buffer`. + * + * @private + * @param {Buffer} buffer The buffer to clone. + * @param {boolean} [isDeep] Specify a deep clone. + * @returns {Buffer} Returns the cloned buffer. + */ + function cloneBuffer(buffer, isDeep) { + if (isDeep) { + return buffer.slice(); + } + var length = buffer.length, + result = allocUnsafe ? allocUnsafe(length) : new buffer.constructor(length); + + buffer.copy(result); + return result; + } + + /** + * Creates a clone of `arrayBuffer`. + * + * @private + * @param {ArrayBuffer} arrayBuffer The array buffer to clone. + * @returns {ArrayBuffer} Returns the cloned array buffer. + */ + function cloneArrayBuffer(arrayBuffer) { + var result = new arrayBuffer.constructor(arrayBuffer.byteLength); + new Uint8Array(result).set(new Uint8Array(arrayBuffer)); + return result; + } + + /** + * Creates a clone of `dataView`. + * + * @private + * @param {Object} dataView The data view to clone. + * @param {boolean} [isDeep] Specify a deep clone. + * @returns {Object} Returns the cloned data view. + */ + function cloneDataView(dataView, isDeep) { + var buffer = isDeep ? cloneArrayBuffer(dataView.buffer) : dataView.buffer; + return new dataView.constructor(buffer, dataView.byteOffset, dataView.byteLength); + } + + /** + * Creates a clone of `regexp`. + * + * @private + * @param {Object} regexp The regexp to clone. + * @returns {Object} Returns the cloned regexp. + */ + function cloneRegExp(regexp) { + var result = new regexp.constructor(regexp.source, reFlags.exec(regexp)); + result.lastIndex = regexp.lastIndex; + return result; + } + + /** + * Creates a clone of the `symbol` object. + * + * @private + * @param {Object} symbol The symbol object to clone. + * @returns {Object} Returns the cloned symbol object. + */ + function cloneSymbol(symbol) { + return symbolValueOf ? Object(symbolValueOf.call(symbol)) : {}; + } + + /** + * Creates a clone of `typedArray`. + * + * @private + * @param {Object} typedArray The typed array to clone. + * @param {boolean} [isDeep] Specify a deep clone. + * @returns {Object} Returns the cloned typed array. + */ + function cloneTypedArray(typedArray, isDeep) { + var buffer = isDeep ? cloneArrayBuffer(typedArray.buffer) : typedArray.buffer; + return new typedArray.constructor(buffer, typedArray.byteOffset, typedArray.length); + } + + /** + * Compares values to sort them in ascending order. + * + * @private + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {number} Returns the sort order indicator for `value`. + */ + function compareAscending(value, other) { + if (value !== other) { + var valIsDefined = value !== undefined, + valIsNull = value === null, + valIsReflexive = value === value, + valIsSymbol = isSymbol(value); + + var othIsDefined = other !== undefined, + othIsNull = other === null, + othIsReflexive = other === other, + othIsSymbol = isSymbol(other); + + if ((!othIsNull && !othIsSymbol && !valIsSymbol && value > other) || + (valIsSymbol && othIsDefined && othIsReflexive && !othIsNull && !othIsSymbol) || + (valIsNull && othIsDefined && othIsReflexive) || + (!valIsDefined && othIsReflexive) || + !valIsReflexive) { + return 1; + } + if ((!valIsNull && !valIsSymbol && !othIsSymbol && value < other) || + (othIsSymbol && valIsDefined && valIsReflexive && !valIsNull && !valIsSymbol) || + (othIsNull && valIsDefined && valIsReflexive) || + (!othIsDefined && valIsReflexive) || + !othIsReflexive) { + return -1; + } + } + return 0; + } + + /** + * Used by `_.orderBy` to compare multiple properties of a value to another + * and stable sort them. + * + * If `orders` is unspecified, all values are sorted in ascending order. Otherwise, + * specify an order of "desc" for descending or "asc" for ascending sort order + * of corresponding values. + * + * @private + * @param {Object} object The object to compare. + * @param {Object} other The other object to compare. + * @param {boolean[]|string[]} orders The order to sort by for each property. + * @returns {number} Returns the sort order indicator for `object`. + */ + function compareMultiple(object, other, orders) { + var index = -1, + objCriteria = object.criteria, + othCriteria = other.criteria, + length = objCriteria.length, + ordersLength = orders.length; + + while (++index < length) { + var result = compareAscending(objCriteria[index], othCriteria[index]); + if (result) { + if (index >= ordersLength) { + return result; + } + var order = orders[index]; + return result * (order == 'desc' ? -1 : 1); + } + } + // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications + // that causes it, under certain circumstances, to provide the same value for + // `object` and `other`. See https://github.com/jashkenas/underscore/pull/1247 + // for more details. + // + // This also ensures a stable sort in V8 and other engines. + // See https://bugs.chromium.org/p/v8/issues/detail?id=90 for more details. + return object.index - other.index; + } + + /** + * Creates an array that is the composition of partially applied arguments, + * placeholders, and provided arguments into a single array of arguments. + * + * @private + * @param {Array} args The provided arguments. + * @param {Array} partials The arguments to prepend to those provided. + * @param {Array} holders The `partials` placeholder indexes. + * @params {boolean} [isCurried] Specify composing for a curried function. + * @returns {Array} Returns the new array of composed arguments. + */ + function composeArgs(args, partials, holders, isCurried) { + var argsIndex = -1, + argsLength = args.length, + holdersLength = holders.length, + leftIndex = -1, + leftLength = partials.length, + rangeLength = nativeMax(argsLength - holdersLength, 0), + result = Array(leftLength + rangeLength), + isUncurried = !isCurried; + + while (++leftIndex < leftLength) { + result[leftIndex] = partials[leftIndex]; + } + while (++argsIndex < holdersLength) { + if (isUncurried || argsIndex < argsLength) { + result[holders[argsIndex]] = args[argsIndex]; + } + } + while (rangeLength--) { + result[leftIndex++] = args[argsIndex++]; + } + return result; + } + + /** + * This function is like `composeArgs` except that the arguments composition + * is tailored for `_.partialRight`. + * + * @private + * @param {Array} args The provided arguments. + * @param {Array} partials The arguments to append to those provided. + * @param {Array} holders The `partials` placeholder indexes. + * @params {boolean} [isCurried] Specify composing for a curried function. + * @returns {Array} Returns the new array of composed arguments. + */ + function composeArgsRight(args, partials, holders, isCurried) { + var argsIndex = -1, + argsLength = args.length, + holdersIndex = -1, + holdersLength = holders.length, + rightIndex = -1, + rightLength = partials.length, + rangeLength = nativeMax(argsLength - holdersLength, 0), + result = Array(rangeLength + rightLength), + isUncurried = !isCurried; + + while (++argsIndex < rangeLength) { + result[argsIndex] = args[argsIndex]; + } + var offset = argsIndex; + while (++rightIndex < rightLength) { + result[offset + rightIndex] = partials[rightIndex]; + } + while (++holdersIndex < holdersLength) { + if (isUncurried || argsIndex < argsLength) { + result[offset + holders[holdersIndex]] = args[argsIndex++]; + } + } + return result; + } + + /** + * Copies the values of `source` to `array`. + * + * @private + * @param {Array} source The array to copy values from. + * @param {Array} [array=[]] The array to copy values to. + * @returns {Array} Returns `array`. + */ + function copyArray(source, array) { + var index = -1, + length = source.length; + + array || (array = Array(length)); + while (++index < length) { + array[index] = source[index]; + } + return array; + } + + /** + * Copies properties of `source` to `object`. + * + * @private + * @param {Object} source The object to copy properties from. + * @param {Array} props The property identifiers to copy. + * @param {Object} [object={}] The object to copy properties to. + * @param {Function} [customizer] The function to customize copied values. + * @returns {Object} Returns `object`. + */ + function copyObject(source, props, object, customizer) { + var isNew = !object; + object || (object = {}); + + var index = -1, + length = props.length; + + while (++index < length) { + var key = props[index]; + + var newValue = customizer + ? customizer(object[key], source[key], key, object, source) + : undefined; + + if (newValue === undefined) { + newValue = source[key]; + } + if (isNew) { + baseAssignValue(object, key, newValue); + } else { + assignValue(object, key, newValue); + } + } + return object; + } + + /** + * Copies own symbols of `source` to `object`. + * + * @private + * @param {Object} source The object to copy symbols from. + * @param {Object} [object={}] The object to copy symbols to. + * @returns {Object} Returns `object`. + */ + function copySymbols(source, object) { + return copyObject(source, getSymbols(source), object); + } + + /** + * Copies own and inherited symbols of `source` to `object`. + * + * @private + * @param {Object} source The object to copy symbols from. + * @param {Object} [object={}] The object to copy symbols to. + * @returns {Object} Returns `object`. + */ + function copySymbolsIn(source, object) { + return copyObject(source, getSymbolsIn(source), object); + } + + /** + * Creates a function like `_.groupBy`. + * + * @private + * @param {Function} setter The function to set accumulator values. + * @param {Function} [initializer] The accumulator object initializer. + * @returns {Function} Returns the new aggregator function. + */ + function createAggregator(setter, initializer) { + return function(collection, iteratee) { + var func = isArray(collection) ? arrayAggregator : baseAggregator, + accumulator = initializer ? initializer() : {}; + + return func(collection, setter, getIteratee(iteratee, 2), accumulator); + }; + } + + /** + * Creates a function like `_.assign`. + * + * @private + * @param {Function} assigner The function to assign values. + * @returns {Function} Returns the new assigner function. + */ + function createAssigner(assigner) { + return baseRest(function(object, sources) { + var index = -1, + length = sources.length, + customizer = length > 1 ? sources[length - 1] : undefined, + guard = length > 2 ? sources[2] : undefined; + + customizer = (assigner.length > 3 && typeof customizer == 'function') + ? (length--, customizer) + : undefined; + + if (guard && isIterateeCall(sources[0], sources[1], guard)) { + customizer = length < 3 ? undefined : customizer; + length = 1; + } + object = Object(object); + while (++index < length) { + var source = sources[index]; + if (source) { + assigner(object, source, index, customizer); + } + } + return object; + }); + } + + /** + * Creates a `baseEach` or `baseEachRight` function. + * + * @private + * @param {Function} eachFunc The function to iterate over a collection. + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Function} Returns the new base function. + */ + function createBaseEach(eachFunc, fromRight) { + return function(collection, iteratee) { + if (collection == null) { + return collection; + } + if (!isArrayLike(collection)) { + return eachFunc(collection, iteratee); + } + var length = collection.length, + index = fromRight ? length : -1, + iterable = Object(collection); + + while ((fromRight ? index-- : ++index < length)) { + if (iteratee(iterable[index], index, iterable) === false) { + break; + } + } + return collection; + }; + } + + /** + * Creates a base function for methods like `_.forIn` and `_.forOwn`. + * + * @private + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Function} Returns the new base function. + */ + function createBaseFor(fromRight) { + return function(object, iteratee, keysFunc) { + var index = -1, + iterable = Object(object), + props = keysFunc(object), + length = props.length; + + while (length--) { + var key = props[fromRight ? length : ++index]; + if (iteratee(iterable[key], key, iterable) === false) { + break; + } + } + return object; + }; + } + + /** + * Creates a function that wraps `func` to invoke it with the optional `this` + * binding of `thisArg`. + * + * @private + * @param {Function} func The function to wrap. + * @param {number} bitmask The bitmask flags. See `createWrap` for more details. + * @param {*} [thisArg] The `this` binding of `func`. + * @returns {Function} Returns the new wrapped function. + */ + function createBind(func, bitmask, thisArg) { + var isBind = bitmask & WRAP_BIND_FLAG, + Ctor = createCtor(func); + + function wrapper() { + var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func; + return fn.apply(isBind ? thisArg : this, arguments); + } + return wrapper; + } + + /** + * Creates a function like `_.lowerFirst`. + * + * @private + * @param {string} methodName The name of the `String` case method to use. + * @returns {Function} Returns the new case function. + */ + function createCaseFirst(methodName) { + return function(string) { + string = toString(string); + + var strSymbols = hasUnicode(string) + ? stringToArray(string) + : undefined; + + var chr = strSymbols + ? strSymbols[0] + : string.charAt(0); + + var trailing = strSymbols + ? castSlice(strSymbols, 1).join('') + : string.slice(1); + + return chr[methodName]() + trailing; + }; + } + + /** + * Creates a function like `_.camelCase`. + * + * @private + * @param {Function} callback The function to combine each word. + * @returns {Function} Returns the new compounder function. + */ + function createCompounder(callback) { + return function(string) { + return arrayReduce(words(deburr(string).replace(reApos, '')), callback, ''); + }; + } + + /** + * Creates a function that produces an instance of `Ctor` regardless of + * whether it was invoked as part of a `new` expression or by `call` or `apply`. + * + * @private + * @param {Function} Ctor The constructor to wrap. + * @returns {Function} Returns the new wrapped function. + */ + function createCtor(Ctor) { + return function() { + // Use a `switch` statement to work with class constructors. See + // http://ecma-international.org/ecma-262/7.0/#sec-ecmascript-function-objects-call-thisargument-argumentslist + // for more details. + var args = arguments; + switch (args.length) { + case 0: return new Ctor; + case 1: return new Ctor(args[0]); + case 2: return new Ctor(args[0], args[1]); + case 3: return new Ctor(args[0], args[1], args[2]); + case 4: return new Ctor(args[0], args[1], args[2], args[3]); + case 5: return new Ctor(args[0], args[1], args[2], args[3], args[4]); + case 6: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5]); + case 7: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6]); + } + var thisBinding = baseCreate(Ctor.prototype), + result = Ctor.apply(thisBinding, args); + + // Mimic the constructor's `return` behavior. + // See https://es5.github.io/#x13.2.2 for more details. + return isObject(result) ? result : thisBinding; + }; + } + + /** + * Creates a function that wraps `func` to enable currying. + * + * @private + * @param {Function} func The function to wrap. + * @param {number} bitmask The bitmask flags. See `createWrap` for more details. + * @param {number} arity The arity of `func`. + * @returns {Function} Returns the new wrapped function. + */ + function createCurry(func, bitmask, arity) { + var Ctor = createCtor(func); + + function wrapper() { + var length = arguments.length, + args = Array(length), + index = length, + placeholder = getHolder(wrapper); + + while (index--) { + args[index] = arguments[index]; + } + var holders = (length < 3 && args[0] !== placeholder && args[length - 1] !== placeholder) + ? [] + : replaceHolders(args, placeholder); + + length -= holders.length; + if (length < arity) { + return createRecurry( + func, bitmask, createHybrid, wrapper.placeholder, undefined, + args, holders, undefined, undefined, arity - length); + } + var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func; + return apply(fn, this, args); + } + return wrapper; + } + + /** + * Creates a `_.find` or `_.findLast` function. + * + * @private + * @param {Function} findIndexFunc The function to find the collection index. + * @returns {Function} Returns the new find function. + */ + function createFind(findIndexFunc) { + return function(collection, predicate, fromIndex) { + var iterable = Object(collection); + if (!isArrayLike(collection)) { + var iteratee = getIteratee(predicate, 3); + collection = keys(collection); + predicate = function(key) { return iteratee(iterable[key], key, iterable); }; + } + var index = findIndexFunc(collection, predicate, fromIndex); + return index > -1 ? iterable[iteratee ? collection[index] : index] : undefined; + }; + } + + /** + * Creates a `_.flow` or `_.flowRight` function. + * + * @private + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Function} Returns the new flow function. + */ + function createFlow(fromRight) { + return flatRest(function(funcs) { + var length = funcs.length, + index = length, + prereq = LodashWrapper.prototype.thru; + + if (fromRight) { + funcs.reverse(); + } + while (index--) { + var func = funcs[index]; + if (typeof func != 'function') { + throw new TypeError(FUNC_ERROR_TEXT); + } + if (prereq && !wrapper && getFuncName(func) == 'wrapper') { + var wrapper = new LodashWrapper([], true); + } + } + index = wrapper ? index : length; + while (++index < length) { + func = funcs[index]; + + var funcName = getFuncName(func), + data = funcName == 'wrapper' ? getData(func) : undefined; + + if (data && isLaziable(data[0]) && + data[1] == (WRAP_ARY_FLAG | WRAP_CURRY_FLAG | WRAP_PARTIAL_FLAG | WRAP_REARG_FLAG) && + !data[4].length && data[9] == 1 + ) { + wrapper = wrapper[getFuncName(data[0])].apply(wrapper, data[3]); + } else { + wrapper = (func.length == 1 && isLaziable(func)) + ? wrapper[funcName]() + : wrapper.thru(func); + } + } + return function() { + var args = arguments, + value = args[0]; + + if (wrapper && args.length == 1 && isArray(value)) { + return wrapper.plant(value).value(); + } + var index = 0, + result = length ? funcs[index].apply(this, args) : value; + + while (++index < length) { + result = funcs[index].call(this, result); + } + return result; + }; + }); + } + + /** + * Creates a function that wraps `func` to invoke it with optional `this` + * binding of `thisArg`, partial application, and currying. + * + * @private + * @param {Function|string} func The function or method name to wrap. + * @param {number} bitmask The bitmask flags. See `createWrap` for more details. + * @param {*} [thisArg] The `this` binding of `func`. + * @param {Array} [partials] The arguments to prepend to those provided to + * the new function. + * @param {Array} [holders] The `partials` placeholder indexes. + * @param {Array} [partialsRight] The arguments to append to those provided + * to the new function. + * @param {Array} [holdersRight] The `partialsRight` placeholder indexes. + * @param {Array} [argPos] The argument positions of the new function. + * @param {number} [ary] The arity cap of `func`. + * @param {number} [arity] The arity of `func`. + * @returns {Function} Returns the new wrapped function. + */ + function createHybrid(func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity) { + var isAry = bitmask & WRAP_ARY_FLAG, + isBind = bitmask & WRAP_BIND_FLAG, + isBindKey = bitmask & WRAP_BIND_KEY_FLAG, + isCurried = bitmask & (WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG), + isFlip = bitmask & WRAP_FLIP_FLAG, + Ctor = isBindKey ? undefined : createCtor(func); + + function wrapper() { + var length = arguments.length, + args = Array(length), + index = length; + + while (index--) { + args[index] = arguments[index]; + } + if (isCurried) { + var placeholder = getHolder(wrapper), + holdersCount = countHolders(args, placeholder); + } + if (partials) { + args = composeArgs(args, partials, holders, isCurried); + } + if (partialsRight) { + args = composeArgsRight(args, partialsRight, holdersRight, isCurried); + } + length -= holdersCount; + if (isCurried && length < arity) { + var newHolders = replaceHolders(args, placeholder); + return createRecurry( + func, bitmask, createHybrid, wrapper.placeholder, thisArg, + args, newHolders, argPos, ary, arity - length + ); + } + var thisBinding = isBind ? thisArg : this, + fn = isBindKey ? thisBinding[func] : func; + + length = args.length; + if (argPos) { + args = reorder(args, argPos); + } else if (isFlip && length > 1) { + args.reverse(); + } + if (isAry && ary < length) { + args.length = ary; + } + if (this && this !== root && this instanceof wrapper) { + fn = Ctor || createCtor(fn); + } + return fn.apply(thisBinding, args); + } + return wrapper; + } + + /** + * Creates a function like `_.invertBy`. + * + * @private + * @param {Function} setter The function to set accumulator values. + * @param {Function} toIteratee The function to resolve iteratees. + * @returns {Function} Returns the new inverter function. + */ + function createInverter(setter, toIteratee) { + return function(object, iteratee) { + return baseInverter(object, setter, toIteratee(iteratee), {}); + }; + } + + /** + * Creates a function that performs a mathematical operation on two values. + * + * @private + * @param {Function} operator The function to perform the operation. + * @param {number} [defaultValue] The value used for `undefined` arguments. + * @returns {Function} Returns the new mathematical operation function. + */ + function createMathOperation(operator, defaultValue) { + return function(value, other) { + var result; + if (value === undefined && other === undefined) { + return defaultValue; + } + if (value !== undefined) { + result = value; + } + if (other !== undefined) { + if (result === undefined) { + return other; + } + if (typeof value == 'string' || typeof other == 'string') { + value = baseToString(value); + other = baseToString(other); + } else { + value = baseToNumber(value); + other = baseToNumber(other); + } + result = operator(value, other); + } + return result; + }; + } + + /** + * Creates a function like `_.over`. + * + * @private + * @param {Function} arrayFunc The function to iterate over iteratees. + * @returns {Function} Returns the new over function. + */ + function createOver(arrayFunc) { + return flatRest(function(iteratees) { + iteratees = arrayMap(iteratees, baseUnary(getIteratee())); + return baseRest(function(args) { + var thisArg = this; + return arrayFunc(iteratees, function(iteratee) { + return apply(iteratee, thisArg, args); + }); + }); + }); + } + + /** + * Creates the padding for `string` based on `length`. The `chars` string + * is truncated if the number of characters exceeds `length`. + * + * @private + * @param {number} length The padding length. + * @param {string} [chars=' '] The string used as padding. + * @returns {string} Returns the padding for `string`. + */ + function createPadding(length, chars) { + chars = chars === undefined ? ' ' : baseToString(chars); + + var charsLength = chars.length; + if (charsLength < 2) { + return charsLength ? baseRepeat(chars, length) : chars; + } + var result = baseRepeat(chars, nativeCeil(length / stringSize(chars))); + return hasUnicode(chars) + ? castSlice(stringToArray(result), 0, length).join('') + : result.slice(0, length); + } + + /** + * Creates a function that wraps `func` to invoke it with the `this` binding + * of `thisArg` and `partials` prepended to the arguments it receives. + * + * @private + * @param {Function} func The function to wrap. + * @param {number} bitmask The bitmask flags. See `createWrap` for more details. + * @param {*} thisArg The `this` binding of `func`. + * @param {Array} partials The arguments to prepend to those provided to + * the new function. + * @returns {Function} Returns the new wrapped function. + */ + function createPartial(func, bitmask, thisArg, partials) { + var isBind = bitmask & WRAP_BIND_FLAG, + Ctor = createCtor(func); + + function wrapper() { + var argsIndex = -1, + argsLength = arguments.length, + leftIndex = -1, + leftLength = partials.length, + args = Array(leftLength + argsLength), + fn = (this && this !== root && this instanceof wrapper) ? Ctor : func; + + while (++leftIndex < leftLength) { + args[leftIndex] = partials[leftIndex]; + } + while (argsLength--) { + args[leftIndex++] = arguments[++argsIndex]; + } + return apply(fn, isBind ? thisArg : this, args); + } + return wrapper; + } + + /** + * Creates a `_.range` or `_.rangeRight` function. + * + * @private + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Function} Returns the new range function. + */ + function createRange(fromRight) { + return function(start, end, step) { + if (step && typeof step != 'number' && isIterateeCall(start, end, step)) { + end = step = undefined; + } + // Ensure the sign of `-0` is preserved. + start = toFinite(start); + if (end === undefined) { + end = start; + start = 0; + } else { + end = toFinite(end); + } + step = step === undefined ? (start < end ? 1 : -1) : toFinite(step); + return baseRange(start, end, step, fromRight); + }; + } + + /** + * Creates a function that performs a relational operation on two values. + * + * @private + * @param {Function} operator The function to perform the operation. + * @returns {Function} Returns the new relational operation function. + */ + function createRelationalOperation(operator) { + return function(value, other) { + if (!(typeof value == 'string' && typeof other == 'string')) { + value = toNumber(value); + other = toNumber(other); + } + return operator(value, other); + }; + } + + /** + * Creates a function that wraps `func` to continue currying. + * + * @private + * @param {Function} func The function to wrap. + * @param {number} bitmask The bitmask flags. See `createWrap` for more details. + * @param {Function} wrapFunc The function to create the `func` wrapper. + * @param {*} placeholder The placeholder value. + * @param {*} [thisArg] The `this` binding of `func`. + * @param {Array} [partials] The arguments to prepend to those provided to + * the new function. + * @param {Array} [holders] The `partials` placeholder indexes. + * @param {Array} [argPos] The argument positions of the new function. + * @param {number} [ary] The arity cap of `func`. + * @param {number} [arity] The arity of `func`. + * @returns {Function} Returns the new wrapped function. + */ + function createRecurry(func, bitmask, wrapFunc, placeholder, thisArg, partials, holders, argPos, ary, arity) { + var isCurry = bitmask & WRAP_CURRY_FLAG, + newHolders = isCurry ? holders : undefined, + newHoldersRight = isCurry ? undefined : holders, + newPartials = isCurry ? partials : undefined, + newPartialsRight = isCurry ? undefined : partials; + + bitmask |= (isCurry ? WRAP_PARTIAL_FLAG : WRAP_PARTIAL_RIGHT_FLAG); + bitmask &= ~(isCurry ? WRAP_PARTIAL_RIGHT_FLAG : WRAP_PARTIAL_FLAG); + + if (!(bitmask & WRAP_CURRY_BOUND_FLAG)) { + bitmask &= ~(WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG); + } + var newData = [ + func, bitmask, thisArg, newPartials, newHolders, newPartialsRight, + newHoldersRight, argPos, ary, arity + ]; + + var result = wrapFunc.apply(undefined, newData); + if (isLaziable(func)) { + setData(result, newData); + } + result.placeholder = placeholder; + return setWrapToString(result, func, bitmask); + } + + /** + * Creates a function like `_.round`. + * + * @private + * @param {string} methodName The name of the `Math` method to use when rounding. + * @returns {Function} Returns the new round function. + */ + function createRound(methodName) { + var func = Math[methodName]; + return function(number, precision) { + number = toNumber(number); + precision = precision == null ? 0 : nativeMin(toInteger(precision), 292); + if (precision) { + // Shift with exponential notation to avoid floating-point issues. + // See [MDN](https://mdn.io/round#Examples) for more details. + var pair = (toString(number) + 'e').split('e'), + value = func(pair[0] + 'e' + (+pair[1] + precision)); + + pair = (toString(value) + 'e').split('e'); + return +(pair[0] + 'e' + (+pair[1] - precision)); + } + return func(number); + }; + } + + /** + * Creates a set object of `values`. + * + * @private + * @param {Array} values The values to add to the set. + * @returns {Object} Returns the new set. + */ + var createSet = !(Set && (1 / setToArray(new Set([,-0]))[1]) == INFINITY) ? noop : function(values) { + return new Set(values); + }; + + /** + * Creates a `_.toPairs` or `_.toPairsIn` function. + * + * @private + * @param {Function} keysFunc The function to get the keys of a given object. + * @returns {Function} Returns the new pairs function. + */ + function createToPairs(keysFunc) { + return function(object) { + var tag = getTag(object); + if (tag == mapTag) { + return mapToArray(object); + } + if (tag == setTag) { + return setToPairs(object); + } + return baseToPairs(object, keysFunc(object)); + }; + } + + /** + * Creates a function that either curries or invokes `func` with optional + * `this` binding and partially applied arguments. + * + * @private + * @param {Function|string} func The function or method name to wrap. + * @param {number} bitmask The bitmask flags. + * 1 - `_.bind` + * 2 - `_.bindKey` + * 4 - `_.curry` or `_.curryRight` of a bound function + * 8 - `_.curry` + * 16 - `_.curryRight` + * 32 - `_.partial` + * 64 - `_.partialRight` + * 128 - `_.rearg` + * 256 - `_.ary` + * 512 - `_.flip` + * @param {*} [thisArg] The `this` binding of `func`. + * @param {Array} [partials] The arguments to be partially applied. + * @param {Array} [holders] The `partials` placeholder indexes. + * @param {Array} [argPos] The argument positions of the new function. + * @param {number} [ary] The arity cap of `func`. + * @param {number} [arity] The arity of `func`. + * @returns {Function} Returns the new wrapped function. + */ + function createWrap(func, bitmask, thisArg, partials, holders, argPos, ary, arity) { + var isBindKey = bitmask & WRAP_BIND_KEY_FLAG; + if (!isBindKey && typeof func != 'function') { + throw new TypeError(FUNC_ERROR_TEXT); + } + var length = partials ? partials.length : 0; + if (!length) { + bitmask &= ~(WRAP_PARTIAL_FLAG | WRAP_PARTIAL_RIGHT_FLAG); + partials = holders = undefined; + } + ary = ary === undefined ? ary : nativeMax(toInteger(ary), 0); + arity = arity === undefined ? arity : toInteger(arity); + length -= holders ? holders.length : 0; + + if (bitmask & WRAP_PARTIAL_RIGHT_FLAG) { + var partialsRight = partials, + holdersRight = holders; + + partials = holders = undefined; + } + var data = isBindKey ? undefined : getData(func); + + var newData = [ + func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, + argPos, ary, arity + ]; + + if (data) { + mergeData(newData, data); + } + func = newData[0]; + bitmask = newData[1]; + thisArg = newData[2]; + partials = newData[3]; + holders = newData[4]; + arity = newData[9] = newData[9] === undefined + ? (isBindKey ? 0 : func.length) + : nativeMax(newData[9] - length, 0); + + if (!arity && bitmask & (WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG)) { + bitmask &= ~(WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG); + } + if (!bitmask || bitmask == WRAP_BIND_FLAG) { + var result = createBind(func, bitmask, thisArg); + } else if (bitmask == WRAP_CURRY_FLAG || bitmask == WRAP_CURRY_RIGHT_FLAG) { + result = createCurry(func, bitmask, arity); + } else if ((bitmask == WRAP_PARTIAL_FLAG || bitmask == (WRAP_BIND_FLAG | WRAP_PARTIAL_FLAG)) && !holders.length) { + result = createPartial(func, bitmask, thisArg, partials); + } else { + result = createHybrid.apply(undefined, newData); + } + var setter = data ? baseSetData : setData; + return setWrapToString(setter(result, newData), func, bitmask); + } + + /** + * Used by `_.defaults` to customize its `_.assignIn` use to assign properties + * of source objects to the destination object for all destination properties + * that resolve to `undefined`. + * + * @private + * @param {*} objValue The destination value. + * @param {*} srcValue The source value. + * @param {string} key The key of the property to assign. + * @param {Object} object The parent object of `objValue`. + * @returns {*} Returns the value to assign. + */ + function customDefaultsAssignIn(objValue, srcValue, key, object) { + if (objValue === undefined || + (eq(objValue, objectProto[key]) && !hasOwnProperty.call(object, key))) { + return srcValue; + } + return objValue; + } + + /** + * Used by `_.defaultsDeep` to customize its `_.merge` use to merge source + * objects into destination objects that are passed thru. + * + * @private + * @param {*} objValue The destination value. + * @param {*} srcValue The source value. + * @param {string} key The key of the property to merge. + * @param {Object} object The parent object of `objValue`. + * @param {Object} source The parent object of `srcValue`. + * @param {Object} [stack] Tracks traversed source values and their merged + * counterparts. + * @returns {*} Returns the value to assign. + */ + function customDefaultsMerge(objValue, srcValue, key, object, source, stack) { + if (isObject(objValue) && isObject(srcValue)) { + // Recursively merge objects and arrays (susceptible to call stack limits). + stack.set(srcValue, objValue); + baseMerge(objValue, srcValue, undefined, customDefaultsMerge, stack); + stack['delete'](srcValue); + } + return objValue; + } + + /** + * Used by `_.omit` to customize its `_.cloneDeep` use to only clone plain + * objects. + * + * @private + * @param {*} value The value to inspect. + * @param {string} key The key of the property to inspect. + * @returns {*} Returns the uncloned value or `undefined` to defer cloning to `_.cloneDeep`. + */ + function customOmitClone(value) { + return isPlainObject(value) ? undefined : value; + } + + /** + * A specialized version of `baseIsEqualDeep` for arrays with support for + * partial deep comparisons. + * + * @private + * @param {Array} array The array to compare. + * @param {Array} other The other array to compare. + * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. + * @param {Function} customizer The function to customize comparisons. + * @param {Function} equalFunc The function to determine equivalents of values. + * @param {Object} stack Tracks traversed `array` and `other` objects. + * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`. + */ + function equalArrays(array, other, bitmask, customizer, equalFunc, stack) { + var isPartial = bitmask & COMPARE_PARTIAL_FLAG, + arrLength = array.length, + othLength = other.length; + + if (arrLength != othLength && !(isPartial && othLength > arrLength)) { + return false; + } + // Assume cyclic values are equal. + var stacked = stack.get(array); + if (stacked && stack.get(other)) { + return stacked == other; + } + var index = -1, + result = true, + seen = (bitmask & COMPARE_UNORDERED_FLAG) ? new SetCache : undefined; + + stack.set(array, other); + stack.set(other, array); + + // Ignore non-index properties. + while (++index < arrLength) { + var arrValue = array[index], + othValue = other[index]; + + if (customizer) { + var compared = isPartial + ? customizer(othValue, arrValue, index, other, array, stack) + : customizer(arrValue, othValue, index, array, other, stack); + } + if (compared !== undefined) { + if (compared) { + continue; + } + result = false; + break; + } + // Recursively compare arrays (susceptible to call stack limits). + if (seen) { + if (!arraySome(other, function(othValue, othIndex) { + if (!cacheHas(seen, othIndex) && + (arrValue === othValue || equalFunc(arrValue, othValue, bitmask, customizer, stack))) { + return seen.push(othIndex); + } + })) { + result = false; + break; + } + } else if (!( + arrValue === othValue || + equalFunc(arrValue, othValue, bitmask, customizer, stack) + )) { + result = false; + break; + } + } + stack['delete'](array); + stack['delete'](other); + return result; + } + + /** + * A specialized version of `baseIsEqualDeep` for comparing objects of + * the same `toStringTag`. + * + * **Note:** This function only supports comparing values with tags of + * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`. + * + * @private + * @param {Object} object The object to compare. + * @param {Object} other The other object to compare. + * @param {string} tag The `toStringTag` of the objects to compare. + * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. + * @param {Function} customizer The function to customize comparisons. + * @param {Function} equalFunc The function to determine equivalents of values. + * @param {Object} stack Tracks traversed `object` and `other` objects. + * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. + */ + function equalByTag(object, other, tag, bitmask, customizer, equalFunc, stack) { + switch (tag) { + case dataViewTag: + if ((object.byteLength != other.byteLength) || + (object.byteOffset != other.byteOffset)) { + return false; + } + object = object.buffer; + other = other.buffer; + + case arrayBufferTag: + if ((object.byteLength != other.byteLength) || + !equalFunc(new Uint8Array(object), new Uint8Array(other))) { + return false; + } + return true; + + case boolTag: + case dateTag: + case numberTag: + // Coerce booleans to `1` or `0` and dates to milliseconds. + // Invalid dates are coerced to `NaN`. + return eq(+object, +other); + + case errorTag: + return object.name == other.name && object.message == other.message; + + case regexpTag: + case stringTag: + // Coerce regexes to strings and treat strings, primitives and objects, + // as equal. See http://www.ecma-international.org/ecma-262/7.0/#sec-regexp.prototype.tostring + // for more details. + return object == (other + ''); + + case mapTag: + var convert = mapToArray; + + case setTag: + var isPartial = bitmask & COMPARE_PARTIAL_FLAG; + convert || (convert = setToArray); + + if (object.size != other.size && !isPartial) { + return false; + } + // Assume cyclic values are equal. + var stacked = stack.get(object); + if (stacked) { + return stacked == other; + } + bitmask |= COMPARE_UNORDERED_FLAG; + + // Recursively compare objects (susceptible to call stack limits). + stack.set(object, other); + var result = equalArrays(convert(object), convert(other), bitmask, customizer, equalFunc, stack); + stack['delete'](object); + return result; + + case symbolTag: + if (symbolValueOf) { + return symbolValueOf.call(object) == symbolValueOf.call(other); + } + } + return false; + } + + /** + * A specialized version of `baseIsEqualDeep` for objects with support for + * partial deep comparisons. + * + * @private + * @param {Object} object The object to compare. + * @param {Object} other The other object to compare. + * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details. + * @param {Function} customizer The function to customize comparisons. + * @param {Function} equalFunc The function to determine equivalents of values. + * @param {Object} stack Tracks traversed `object` and `other` objects. + * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. + */ + function equalObjects(object, other, bitmask, customizer, equalFunc, stack) { + var isPartial = bitmask & COMPARE_PARTIAL_FLAG, + objProps = getAllKeys(object), + objLength = objProps.length, + othProps = getAllKeys(other), + othLength = othProps.length; + + if (objLength != othLength && !isPartial) { + return false; + } + var index = objLength; + while (index--) { + var key = objProps[index]; + if (!(isPartial ? key in other : hasOwnProperty.call(other, key))) { + return false; + } + } + // Assume cyclic values are equal. + var stacked = stack.get(object); + if (stacked && stack.get(other)) { + return stacked == other; + } + var result = true; + stack.set(object, other); + stack.set(other, object); + + var skipCtor = isPartial; + while (++index < objLength) { + key = objProps[index]; + var objValue = object[key], + othValue = other[key]; + + if (customizer) { + var compared = isPartial + ? customizer(othValue, objValue, key, other, object, stack) + : customizer(objValue, othValue, key, object, other, stack); + } + // Recursively compare objects (susceptible to call stack limits). + if (!(compared === undefined + ? (objValue === othValue || equalFunc(objValue, othValue, bitmask, customizer, stack)) + : compared + )) { + result = false; + break; + } + skipCtor || (skipCtor = key == 'constructor'); + } + if (result && !skipCtor) { + var objCtor = object.constructor, + othCtor = other.constructor; + + // Non `Object` object instances with different constructors are not equal. + if (objCtor != othCtor && + ('constructor' in object && 'constructor' in other) && + !(typeof objCtor == 'function' && objCtor instanceof objCtor && + typeof othCtor == 'function' && othCtor instanceof othCtor)) { + result = false; + } + } + stack['delete'](object); + stack['delete'](other); + return result; + } + + /** + * A specialized version of `baseRest` which flattens the rest array. + * + * @private + * @param {Function} func The function to apply a rest parameter to. + * @returns {Function} Returns the new function. + */ + function flatRest(func) { + return setToString(overRest(func, undefined, flatten), func + ''); + } + + /** + * Creates an array of own enumerable property names and symbols of `object`. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names and symbols. + */ + function getAllKeys(object) { + return baseGetAllKeys(object, keys, getSymbols); + } + + /** + * Creates an array of own and inherited enumerable property names and + * symbols of `object`. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names and symbols. + */ + function getAllKeysIn(object) { + return baseGetAllKeys(object, keysIn, getSymbolsIn); + } + + /** + * Gets metadata for `func`. + * + * @private + * @param {Function} func The function to query. + * @returns {*} Returns the metadata for `func`. + */ + var getData = !metaMap ? noop : function(func) { + return metaMap.get(func); + }; + + /** + * Gets the name of `func`. + * + * @private + * @param {Function} func The function to query. + * @returns {string} Returns the function name. + */ + function getFuncName(func) { + var result = (func.name + ''), + array = realNames[result], + length = hasOwnProperty.call(realNames, result) ? array.length : 0; + + while (length--) { + var data = array[length], + otherFunc = data.func; + if (otherFunc == null || otherFunc == func) { + return data.name; + } + } + return result; + } + + /** + * Gets the argument placeholder value for `func`. + * + * @private + * @param {Function} func The function to inspect. + * @returns {*} Returns the placeholder value. + */ + function getHolder(func) { + var object = hasOwnProperty.call(lodash, 'placeholder') ? lodash : func; + return object.placeholder; + } + + /** + * Gets the appropriate "iteratee" function. If `_.iteratee` is customized, + * this function returns the custom method, otherwise it returns `baseIteratee`. + * If arguments are provided, the chosen function is invoked with them and + * its result is returned. + * + * @private + * @param {*} [value] The value to convert to an iteratee. + * @param {number} [arity] The arity of the created iteratee. + * @returns {Function} Returns the chosen function or its result. + */ + function getIteratee() { + var result = lodash.iteratee || iteratee; + result = result === iteratee ? baseIteratee : result; + return arguments.length ? result(arguments[0], arguments[1]) : result; + } + + /** + * Gets the data for `map`. + * + * @private + * @param {Object} map The map to query. + * @param {string} key The reference key. + * @returns {*} Returns the map data. + */ + function getMapData(map, key) { + var data = map.__data__; + return isKeyable(key) + ? data[typeof key == 'string' ? 'string' : 'hash'] + : data.map; + } + + /** + * Gets the property names, values, and compare flags of `object`. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the match data of `object`. + */ + function getMatchData(object) { + var result = keys(object), + length = result.length; + + while (length--) { + var key = result[length], + value = object[key]; + + result[length] = [key, value, isStrictComparable(value)]; + } + return result; + } + + /** + * Gets the native function at `key` of `object`. + * + * @private + * @param {Object} object The object to query. + * @param {string} key The key of the method to get. + * @returns {*} Returns the function if it's native, else `undefined`. + */ + function getNative(object, key) { + var value = getValue(object, key); + return baseIsNative(value) ? value : undefined; + } + + /** + * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values. + * + * @private + * @param {*} value The value to query. + * @returns {string} Returns the raw `toStringTag`. + */ + function getRawTag(value) { + var isOwn = hasOwnProperty.call(value, symToStringTag), + tag = value[symToStringTag]; + + try { + value[symToStringTag] = undefined; + var unmasked = true; + } catch (e) {} + + var result = nativeObjectToString.call(value); + if (unmasked) { + if (isOwn) { + value[symToStringTag] = tag; + } else { + delete value[symToStringTag]; + } + } + return result; + } + + /** + * Creates an array of the own enumerable symbols of `object`. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of symbols. + */ + var getSymbols = !nativeGetSymbols ? stubArray : function(object) { + if (object == null) { + return []; + } + object = Object(object); + return arrayFilter(nativeGetSymbols(object), function(symbol) { + return propertyIsEnumerable.call(object, symbol); + }); + }; + + /** + * Creates an array of the own and inherited enumerable symbols of `object`. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of symbols. + */ + var getSymbolsIn = !nativeGetSymbols ? stubArray : function(object) { + var result = []; + while (object) { + arrayPush(result, getSymbols(object)); + object = getPrototype(object); + } + return result; + }; + + /** + * Gets the `toStringTag` of `value`. + * + * @private + * @param {*} value The value to query. + * @returns {string} Returns the `toStringTag`. + */ + var getTag = baseGetTag; + + // Fallback for data views, maps, sets, and weak maps in IE 11 and promises in Node.js < 6. + if ((DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag) || + (Map && getTag(new Map) != mapTag) || + (Promise && getTag(Promise.resolve()) != promiseTag) || + (Set && getTag(new Set) != setTag) || + (WeakMap && getTag(new WeakMap) != weakMapTag)) { + getTag = function(value) { + var result = baseGetTag(value), + Ctor = result == objectTag ? value.constructor : undefined, + ctorString = Ctor ? toSource(Ctor) : ''; + + if (ctorString) { + switch (ctorString) { + case dataViewCtorString: return dataViewTag; + case mapCtorString: return mapTag; + case promiseCtorString: return promiseTag; + case setCtorString: return setTag; + case weakMapCtorString: return weakMapTag; + } + } + return result; + }; + } + + /** + * Gets the view, applying any `transforms` to the `start` and `end` positions. + * + * @private + * @param {number} start The start of the view. + * @param {number} end The end of the view. + * @param {Array} transforms The transformations to apply to the view. + * @returns {Object} Returns an object containing the `start` and `end` + * positions of the view. + */ + function getView(start, end, transforms) { + var index = -1, + length = transforms.length; + + while (++index < length) { + var data = transforms[index], + size = data.size; + + switch (data.type) { + case 'drop': start += size; break; + case 'dropRight': end -= size; break; + case 'take': end = nativeMin(end, start + size); break; + case 'takeRight': start = nativeMax(start, end - size); break; + } + } + return { 'start': start, 'end': end }; + } + + /** + * Extracts wrapper details from the `source` body comment. + * + * @private + * @param {string} source The source to inspect. + * @returns {Array} Returns the wrapper details. + */ + function getWrapDetails(source) { + var match = source.match(reWrapDetails); + return match ? match[1].split(reSplitDetails) : []; + } + + /** + * Checks if `path` exists on `object`. + * + * @private + * @param {Object} object The object to query. + * @param {Array|string} path The path to check. + * @param {Function} hasFunc The function to check properties. + * @returns {boolean} Returns `true` if `path` exists, else `false`. + */ + function hasPath(object, path, hasFunc) { + path = castPath(path, object); + + var index = -1, + length = path.length, + result = false; + + while (++index < length) { + var key = toKey(path[index]); + if (!(result = object != null && hasFunc(object, key))) { + break; + } + object = object[key]; + } + if (result || ++index != length) { + return result; + } + length = object == null ? 0 : object.length; + return !!length && isLength(length) && isIndex(key, length) && + (isArray(object) || isArguments(object)); + } + + /** + * Initializes an array clone. + * + * @private + * @param {Array} array The array to clone. + * @returns {Array} Returns the initialized clone. + */ + function initCloneArray(array) { + var length = array.length, + result = new array.constructor(length); + + // Add properties assigned by `RegExp#exec`. + if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) { + result.index = array.index; + result.input = array.input; + } + return result; + } + + /** + * Initializes an object clone. + * + * @private + * @param {Object} object The object to clone. + * @returns {Object} Returns the initialized clone. + */ + function initCloneObject(object) { + return (typeof object.constructor == 'function' && !isPrototype(object)) + ? baseCreate(getPrototype(object)) + : {}; + } + + /** + * Initializes an object clone based on its `toStringTag`. + * + * **Note:** This function only supports cloning values with tags of + * `Boolean`, `Date`, `Error`, `Map`, `Number`, `RegExp`, `Set`, or `String`. + * + * @private + * @param {Object} object The object to clone. + * @param {string} tag The `toStringTag` of the object to clone. + * @param {boolean} [isDeep] Specify a deep clone. + * @returns {Object} Returns the initialized clone. + */ + function initCloneByTag(object, tag, isDeep) { + var Ctor = object.constructor; + switch (tag) { + case arrayBufferTag: + return cloneArrayBuffer(object); + + case boolTag: + case dateTag: + return new Ctor(+object); + + case dataViewTag: + return cloneDataView(object, isDeep); + + case float32Tag: case float64Tag: + case int8Tag: case int16Tag: case int32Tag: + case uint8Tag: case uint8ClampedTag: case uint16Tag: case uint32Tag: + return cloneTypedArray(object, isDeep); + + case mapTag: + return new Ctor; + + case numberTag: + case stringTag: + return new Ctor(object); + + case regexpTag: + return cloneRegExp(object); + + case setTag: + return new Ctor; + + case symbolTag: + return cloneSymbol(object); + } + } + + /** + * Inserts wrapper `details` in a comment at the top of the `source` body. + * + * @private + * @param {string} source The source to modify. + * @returns {Array} details The details to insert. + * @returns {string} Returns the modified source. + */ + function insertWrapDetails(source, details) { + var length = details.length; + if (!length) { + return source; + } + var lastIndex = length - 1; + details[lastIndex] = (length > 1 ? '& ' : '') + details[lastIndex]; + details = details.join(length > 2 ? ', ' : ' '); + return source.replace(reWrapComment, '{\n/* [wrapped with ' + details + '] */\n'); + } + + /** + * Checks if `value` is a flattenable `arguments` object or array. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is flattenable, else `false`. + */ + function isFlattenable(value) { + return isArray(value) || isArguments(value) || + !!(spreadableSymbol && value && value[spreadableSymbol]); + } + + /** + * Checks if `value` is a valid array-like index. + * + * @private + * @param {*} value The value to check. + * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index. + * @returns {boolean} Returns `true` if `value` is a valid index, else `false`. + */ + function isIndex(value, length) { + var type = typeof value; + length = length == null ? MAX_SAFE_INTEGER : length; + + return !!length && + (type == 'number' || + (type != 'symbol' && reIsUint.test(value))) && + (value > -1 && value % 1 == 0 && value < length); + } + + /** + * Checks if the given arguments are from an iteratee call. + * + * @private + * @param {*} value The potential iteratee value argument. + * @param {*} index The potential iteratee index or key argument. + * @param {*} object The potential iteratee object argument. + * @returns {boolean} Returns `true` if the arguments are from an iteratee call, + * else `false`. + */ + function isIterateeCall(value, index, object) { + if (!isObject(object)) { + return false; + } + var type = typeof index; + if (type == 'number' + ? (isArrayLike(object) && isIndex(index, object.length)) + : (type == 'string' && index in object) + ) { + return eq(object[index], value); + } + return false; + } + + /** + * Checks if `value` is a property name and not a property path. + * + * @private + * @param {*} value The value to check. + * @param {Object} [object] The object to query keys on. + * @returns {boolean} Returns `true` if `value` is a property name, else `false`. + */ + function isKey(value, object) { + if (isArray(value)) { + return false; + } + var type = typeof value; + if (type == 'number' || type == 'symbol' || type == 'boolean' || + value == null || isSymbol(value)) { + return true; + } + return reIsPlainProp.test(value) || !reIsDeepProp.test(value) || + (object != null && value in Object(object)); + } + + /** + * Checks if `value` is suitable for use as unique object key. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is suitable, else `false`. + */ + function isKeyable(value) { + var type = typeof value; + return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean') + ? (value !== '__proto__') + : (value === null); + } + + /** + * Checks if `func` has a lazy counterpart. + * + * @private + * @param {Function} func The function to check. + * @returns {boolean} Returns `true` if `func` has a lazy counterpart, + * else `false`. + */ + function isLaziable(func) { + var funcName = getFuncName(func), + other = lodash[funcName]; + + if (typeof other != 'function' || !(funcName in LazyWrapper.prototype)) { + return false; + } + if (func === other) { + return true; + } + var data = getData(other); + return !!data && func === data[0]; + } + + /** + * Checks if `func` has its source masked. + * + * @private + * @param {Function} func The function to check. + * @returns {boolean} Returns `true` if `func` is masked, else `false`. + */ + function isMasked(func) { + return !!maskSrcKey && (maskSrcKey in func); + } + + /** + * Checks if `func` is capable of being masked. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `func` is maskable, else `false`. + */ + var isMaskable = coreJsData ? isFunction : stubFalse; + + /** + * Checks if `value` is likely a prototype object. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a prototype, else `false`. + */ + function isPrototype(value) { + var Ctor = value && value.constructor, + proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto; + + return value === proto; + } + + /** + * Checks if `value` is suitable for strict equality comparisons, i.e. `===`. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` if suitable for strict + * equality comparisons, else `false`. + */ + function isStrictComparable(value) { + return value === value && !isObject(value); + } + + /** + * A specialized version of `matchesProperty` for source values suitable + * for strict equality comparisons, i.e. `===`. + * + * @private + * @param {string} key The key of the property to get. + * @param {*} srcValue The value to match. + * @returns {Function} Returns the new spec function. + */ + function matchesStrictComparable(key, srcValue) { + return function(object) { + if (object == null) { + return false; + } + return object[key] === srcValue && + (srcValue !== undefined || (key in Object(object))); + }; + } + + /** + * A specialized version of `_.memoize` which clears the memoized function's + * cache when it exceeds `MAX_MEMOIZE_SIZE`. + * + * @private + * @param {Function} func The function to have its output memoized. + * @returns {Function} Returns the new memoized function. + */ + function memoizeCapped(func) { + var result = memoize(func, function(key) { + if (cache.size === MAX_MEMOIZE_SIZE) { + cache.clear(); + } + return key; + }); + + var cache = result.cache; + return result; + } + + /** + * Merges the function metadata of `source` into `data`. + * + * Merging metadata reduces the number of wrappers used to invoke a function. + * This is possible because methods like `_.bind`, `_.curry`, and `_.partial` + * may be applied regardless of execution order. Methods like `_.ary` and + * `_.rearg` modify function arguments, making the order in which they are + * executed important, preventing the merging of metadata. However, we make + * an exception for a safe combined case where curried functions have `_.ary` + * and or `_.rearg` applied. + * + * @private + * @param {Array} data The destination metadata. + * @param {Array} source The source metadata. + * @returns {Array} Returns `data`. + */ + function mergeData(data, source) { + var bitmask = data[1], + srcBitmask = source[1], + newBitmask = bitmask | srcBitmask, + isCommon = newBitmask < (WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG | WRAP_ARY_FLAG); + + var isCombo = + ((srcBitmask == WRAP_ARY_FLAG) && (bitmask == WRAP_CURRY_FLAG)) || + ((srcBitmask == WRAP_ARY_FLAG) && (bitmask == WRAP_REARG_FLAG) && (data[7].length <= source[8])) || + ((srcBitmask == (WRAP_ARY_FLAG | WRAP_REARG_FLAG)) && (source[7].length <= source[8]) && (bitmask == WRAP_CURRY_FLAG)); + + // Exit early if metadata can't be merged. + if (!(isCommon || isCombo)) { + return data; + } + // Use source `thisArg` if available. + if (srcBitmask & WRAP_BIND_FLAG) { + data[2] = source[2]; + // Set when currying a bound function. + newBitmask |= bitmask & WRAP_BIND_FLAG ? 0 : WRAP_CURRY_BOUND_FLAG; + } + // Compose partial arguments. + var value = source[3]; + if (value) { + var partials = data[3]; + data[3] = partials ? composeArgs(partials, value, source[4]) : value; + data[4] = partials ? replaceHolders(data[3], PLACEHOLDER) : source[4]; + } + // Compose partial right arguments. + value = source[5]; + if (value) { + partials = data[5]; + data[5] = partials ? composeArgsRight(partials, value, source[6]) : value; + data[6] = partials ? replaceHolders(data[5], PLACEHOLDER) : source[6]; + } + // Use source `argPos` if available. + value = source[7]; + if (value) { + data[7] = value; + } + // Use source `ary` if it's smaller. + if (srcBitmask & WRAP_ARY_FLAG) { + data[8] = data[8] == null ? source[8] : nativeMin(data[8], source[8]); + } + // Use source `arity` if one is not provided. + if (data[9] == null) { + data[9] = source[9]; + } + // Use source `func` and merge bitmasks. + data[0] = source[0]; + data[1] = newBitmask; + + return data; + } + + /** + * This function is like + * [`Object.keys`](http://ecma-international.org/ecma-262/7.0/#sec-object.keys) + * except that it includes inherited enumerable properties. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + */ + function nativeKeysIn(object) { + var result = []; + if (object != null) { + for (var key in Object(object)) { + result.push(key); + } + } + return result; + } + + /** + * Converts `value` to a string using `Object.prototype.toString`. + * + * @private + * @param {*} value The value to convert. + * @returns {string} Returns the converted string. + */ + function objectToString(value) { + return nativeObjectToString.call(value); + } + + /** + * A specialized version of `baseRest` which transforms the rest array. + * + * @private + * @param {Function} func The function to apply a rest parameter to. + * @param {number} [start=func.length-1] The start position of the rest parameter. + * @param {Function} transform The rest array transform. + * @returns {Function} Returns the new function. + */ + function overRest(func, start, transform) { + start = nativeMax(start === undefined ? (func.length - 1) : start, 0); + return function() { + var args = arguments, + index = -1, + length = nativeMax(args.length - start, 0), + array = Array(length); + + while (++index < length) { + array[index] = args[start + index]; + } + index = -1; + var otherArgs = Array(start + 1); + while (++index < start) { + otherArgs[index] = args[index]; + } + otherArgs[start] = transform(array); + return apply(func, this, otherArgs); + }; + } + + /** + * Gets the parent value at `path` of `object`. + * + * @private + * @param {Object} object The object to query. + * @param {Array} path The path to get the parent value of. + * @returns {*} Returns the parent value. + */ + function parent(object, path) { + return path.length < 2 ? object : baseGet(object, baseSlice(path, 0, -1)); + } + + /** + * Reorder `array` according to the specified indexes where the element at + * the first index is assigned as the first element, the element at + * the second index is assigned as the second element, and so on. + * + * @private + * @param {Array} array The array to reorder. + * @param {Array} indexes The arranged array indexes. + * @returns {Array} Returns `array`. + */ + function reorder(array, indexes) { + var arrLength = array.length, + length = nativeMin(indexes.length, arrLength), + oldArray = copyArray(array); + + while (length--) { + var index = indexes[length]; + array[length] = isIndex(index, arrLength) ? oldArray[index] : undefined; + } + return array; + } + + /** + * Sets metadata for `func`. + * + * **Note:** If this function becomes hot, i.e. is invoked a lot in a short + * period of time, it will trip its breaker and transition to an identity + * function to avoid garbage collection pauses in V8. See + * [V8 issue 2070](https://bugs.chromium.org/p/v8/issues/detail?id=2070) + * for more details. + * + * @private + * @param {Function} func The function to associate metadata with. + * @param {*} data The metadata. + * @returns {Function} Returns `func`. + */ + var setData = shortOut(baseSetData); + + /** + * A simple wrapper around the global [`setTimeout`](https://mdn.io/setTimeout). + * + * @private + * @param {Function} func The function to delay. + * @param {number} wait The number of milliseconds to delay invocation. + * @returns {number|Object} Returns the timer id or timeout object. + */ + var setTimeout = ctxSetTimeout || function(func, wait) { + return root.setTimeout(func, wait); + }; + + /** + * Sets the `toString` method of `func` to return `string`. + * + * @private + * @param {Function} func The function to modify. + * @param {Function} string The `toString` result. + * @returns {Function} Returns `func`. + */ + var setToString = shortOut(baseSetToString); + + /** + * Sets the `toString` method of `wrapper` to mimic the source of `reference` + * with wrapper details in a comment at the top of the source body. + * + * @private + * @param {Function} wrapper The function to modify. + * @param {Function} reference The reference function. + * @param {number} bitmask The bitmask flags. See `createWrap` for more details. + * @returns {Function} Returns `wrapper`. + */ + function setWrapToString(wrapper, reference, bitmask) { + var source = (reference + ''); + return setToString(wrapper, insertWrapDetails(source, updateWrapDetails(getWrapDetails(source), bitmask))); + } + + /** + * Creates a function that'll short out and invoke `identity` instead + * of `func` when it's called `HOT_COUNT` or more times in `HOT_SPAN` + * milliseconds. + * + * @private + * @param {Function} func The function to restrict. + * @returns {Function} Returns the new shortable function. + */ + function shortOut(func) { + var count = 0, + lastCalled = 0; + + return function() { + var stamp = nativeNow(), + remaining = HOT_SPAN - (stamp - lastCalled); + + lastCalled = stamp; + if (remaining > 0) { + if (++count >= HOT_COUNT) { + return arguments[0]; + } + } else { + count = 0; + } + return func.apply(undefined, arguments); + }; + } + + /** + * A specialized version of `_.shuffle` which mutates and sets the size of `array`. + * + * @private + * @param {Array} array The array to shuffle. + * @param {number} [size=array.length] The size of `array`. + * @returns {Array} Returns `array`. + */ + function shuffleSelf(array, size) { + var index = -1, + length = array.length, + lastIndex = length - 1; + + size = size === undefined ? length : size; + while (++index < size) { + var rand = baseRandom(index, lastIndex), + value = array[rand]; + + array[rand] = array[index]; + array[index] = value; + } + array.length = size; + return array; + } + + /** + * Converts `string` to a property path array. + * + * @private + * @param {string} string The string to convert. + * @returns {Array} Returns the property path array. + */ + var stringToPath = memoizeCapped(function(string) { + var result = []; + if (string.charCodeAt(0) === 46 /* . */) { + result.push(''); + } + string.replace(rePropName, function(match, number, quote, subString) { + result.push(quote ? subString.replace(reEscapeChar, '$1') : (number || match)); + }); + return result; + }); + + /** + * Converts `value` to a string key if it's not a string or symbol. + * + * @private + * @param {*} value The value to inspect. + * @returns {string|symbol} Returns the key. + */ + function toKey(value) { + if (typeof value == 'string' || isSymbol(value)) { + return value; + } + var result = (value + ''); + return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result; + } + + /** + * Converts `func` to its source code. + * + * @private + * @param {Function} func The function to convert. + * @returns {string} Returns the source code. + */ + function toSource(func) { + if (func != null) { + try { + return funcToString.call(func); + } catch (e) {} + try { + return (func + ''); + } catch (e) {} + } + return ''; + } + + /** + * Updates wrapper `details` based on `bitmask` flags. + * + * @private + * @returns {Array} details The details to modify. + * @param {number} bitmask The bitmask flags. See `createWrap` for more details. + * @returns {Array} Returns `details`. + */ + function updateWrapDetails(details, bitmask) { + arrayEach(wrapFlags, function(pair) { + var value = '_.' + pair[0]; + if ((bitmask & pair[1]) && !arrayIncludes(details, value)) { + details.push(value); + } + }); + return details.sort(); + } + + /** + * Creates a clone of `wrapper`. + * + * @private + * @param {Object} wrapper The wrapper to clone. + * @returns {Object} Returns the cloned wrapper. + */ + function wrapperClone(wrapper) { + if (wrapper instanceof LazyWrapper) { + return wrapper.clone(); + } + var result = new LodashWrapper(wrapper.__wrapped__, wrapper.__chain__); + result.__actions__ = copyArray(wrapper.__actions__); + result.__index__ = wrapper.__index__; + result.__values__ = wrapper.__values__; + return result; + } + + /*------------------------------------------------------------------------*/ + + /** + * Creates an array of elements split into groups the length of `size`. + * If `array` can't be split evenly, the final chunk will be the remaining + * elements. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Array + * @param {Array} array The array to process. + * @param {number} [size=1] The length of each chunk + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {Array} Returns the new array of chunks. + * @example + * + * _.chunk(['a', 'b', 'c', 'd'], 2); + * // => [['a', 'b'], ['c', 'd']] + * + * _.chunk(['a', 'b', 'c', 'd'], 3); + * // => [['a', 'b', 'c'], ['d']] + */ + function chunk(array, size, guard) { + if ((guard ? isIterateeCall(array, size, guard) : size === undefined)) { + size = 1; + } else { + size = nativeMax(toInteger(size), 0); + } + var length = array == null ? 0 : array.length; + if (!length || size < 1) { + return []; + } + var index = 0, + resIndex = 0, + result = Array(nativeCeil(length / size)); + + while (index < length) { + result[resIndex++] = baseSlice(array, index, (index += size)); + } + return result; + } + + /** + * Creates an array with all falsey values removed. The values `false`, `null`, + * `0`, `""`, `undefined`, and `NaN` are falsey. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Array + * @param {Array} array The array to compact. + * @returns {Array} Returns the new array of filtered values. + * @example + * + * _.compact([0, 1, false, 2, '', 3]); + * // => [1, 2, 3] + */ + function compact(array) { + var index = -1, + length = array == null ? 0 : array.length, + resIndex = 0, + result = []; + + while (++index < length) { + var value = array[index]; + if (value) { + result[resIndex++] = value; + } + } + return result; + } + + /** + * Creates a new array concatenating `array` with any additional arrays + * and/or values. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The array to concatenate. + * @param {...*} [values] The values to concatenate. + * @returns {Array} Returns the new concatenated array. + * @example + * + * var array = [1]; + * var other = _.concat(array, 2, [3], [[4]]); + * + * console.log(other); + * // => [1, 2, 3, [4]] + * + * console.log(array); + * // => [1] + */ + function concat() { + var length = arguments.length; + if (!length) { + return []; + } + var args = Array(length - 1), + array = arguments[0], + index = length; + + while (index--) { + args[index - 1] = arguments[index]; + } + return arrayPush(isArray(array) ? copyArray(array) : [array], baseFlatten(args, 1)); + } + + /** + * Creates an array of `array` values not included in the other given arrays + * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * for equality comparisons. The order and references of result values are + * determined by the first array. + * + * **Note:** Unlike `_.pullAll`, this method returns a new array. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Array + * @param {Array} array The array to inspect. + * @param {...Array} [values] The values to exclude. + * @returns {Array} Returns the new array of filtered values. + * @see _.without, _.xor + * @example + * + * _.difference([2, 1], [2, 3]); + * // => [1] + */ + var difference = baseRest(function(array, values) { + return isArrayLikeObject(array) + ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true)) + : []; + }); + + /** + * This method is like `_.difference` except that it accepts `iteratee` which + * is invoked for each element of `array` and `values` to generate the criterion + * by which they're compared. The order and references of result values are + * determined by the first array. The iteratee is invoked with one argument: + * (value). + * + * **Note:** Unlike `_.pullAllBy`, this method returns a new array. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The array to inspect. + * @param {...Array} [values] The values to exclude. + * @param {Function} [iteratee=_.identity] The iteratee invoked per element. + * @returns {Array} Returns the new array of filtered values. + * @example + * + * _.differenceBy([2.1, 1.2], [2.3, 3.4], Math.floor); + * // => [1.2] + * + * // The `_.property` iteratee shorthand. + * _.differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x'); + * // => [{ 'x': 2 }] + */ + var differenceBy = baseRest(function(array, values) { + var iteratee = last(values); + if (isArrayLikeObject(iteratee)) { + iteratee = undefined; + } + return isArrayLikeObject(array) + ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), getIteratee(iteratee, 2)) + : []; + }); + + /** + * This method is like `_.difference` except that it accepts `comparator` + * which is invoked to compare elements of `array` to `values`. The order and + * references of result values are determined by the first array. The comparator + * is invoked with two arguments: (arrVal, othVal). + * + * **Note:** Unlike `_.pullAllWith`, this method returns a new array. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The array to inspect. + * @param {...Array} [values] The values to exclude. + * @param {Function} [comparator] The comparator invoked per element. + * @returns {Array} Returns the new array of filtered values. + * @example + * + * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]; + * + * _.differenceWith(objects, [{ 'x': 1, 'y': 2 }], _.isEqual); + * // => [{ 'x': 2, 'y': 1 }] + */ + var differenceWith = baseRest(function(array, values) { + var comparator = last(values); + if (isArrayLikeObject(comparator)) { + comparator = undefined; + } + return isArrayLikeObject(array) + ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), undefined, comparator) + : []; + }); + + /** + * Creates a slice of `array` with `n` elements dropped from the beginning. + * + * @static + * @memberOf _ + * @since 0.5.0 + * @category Array + * @param {Array} array The array to query. + * @param {number} [n=1] The number of elements to drop. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {Array} Returns the slice of `array`. + * @example + * + * _.drop([1, 2, 3]); + * // => [2, 3] + * + * _.drop([1, 2, 3], 2); + * // => [3] + * + * _.drop([1, 2, 3], 5); + * // => [] + * + * _.drop([1, 2, 3], 0); + * // => [1, 2, 3] + */ + function drop(array, n, guard) { + var length = array == null ? 0 : array.length; + if (!length) { + return []; + } + n = (guard || n === undefined) ? 1 : toInteger(n); + return baseSlice(array, n < 0 ? 0 : n, length); + } + + /** + * Creates a slice of `array` with `n` elements dropped from the end. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Array + * @param {Array} array The array to query. + * @param {number} [n=1] The number of elements to drop. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {Array} Returns the slice of `array`. + * @example + * + * _.dropRight([1, 2, 3]); + * // => [1, 2] + * + * _.dropRight([1, 2, 3], 2); + * // => [1] + * + * _.dropRight([1, 2, 3], 5); + * // => [] + * + * _.dropRight([1, 2, 3], 0); + * // => [1, 2, 3] + */ + function dropRight(array, n, guard) { + var length = array == null ? 0 : array.length; + if (!length) { + return []; + } + n = (guard || n === undefined) ? 1 : toInteger(n); + n = length - n; + return baseSlice(array, 0, n < 0 ? 0 : n); + } + + /** + * Creates a slice of `array` excluding elements dropped from the end. + * Elements are dropped until `predicate` returns falsey. The predicate is + * invoked with three arguments: (value, index, array). + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Array + * @param {Array} array The array to query. + * @param {Function} [predicate=_.identity] The function invoked per iteration. + * @returns {Array} Returns the slice of `array`. + * @example + * + * var users = [ + * { 'user': 'barney', 'active': true }, + * { 'user': 'fred', 'active': false }, + * { 'user': 'pebbles', 'active': false } + * ]; + * + * _.dropRightWhile(users, function(o) { return !o.active; }); + * // => objects for ['barney'] + * + * // The `_.matches` iteratee shorthand. + * _.dropRightWhile(users, { 'user': 'pebbles', 'active': false }); + * // => objects for ['barney', 'fred'] + * + * // The `_.matchesProperty` iteratee shorthand. + * _.dropRightWhile(users, ['active', false]); + * // => objects for ['barney'] + * + * // The `_.property` iteratee shorthand. + * _.dropRightWhile(users, 'active'); + * // => objects for ['barney', 'fred', 'pebbles'] + */ + function dropRightWhile(array, predicate) { + return (array && array.length) + ? baseWhile(array, getIteratee(predicate, 3), true, true) + : []; + } + + /** + * Creates a slice of `array` excluding elements dropped from the beginning. + * Elements are dropped until `predicate` returns falsey. The predicate is + * invoked with three arguments: (value, index, array). + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Array + * @param {Array} array The array to query. + * @param {Function} [predicate=_.identity] The function invoked per iteration. + * @returns {Array} Returns the slice of `array`. + * @example + * + * var users = [ + * { 'user': 'barney', 'active': false }, + * { 'user': 'fred', 'active': false }, + * { 'user': 'pebbles', 'active': true } + * ]; + * + * _.dropWhile(users, function(o) { return !o.active; }); + * // => objects for ['pebbles'] + * + * // The `_.matches` iteratee shorthand. + * _.dropWhile(users, { 'user': 'barney', 'active': false }); + * // => objects for ['fred', 'pebbles'] + * + * // The `_.matchesProperty` iteratee shorthand. + * _.dropWhile(users, ['active', false]); + * // => objects for ['pebbles'] + * + * // The `_.property` iteratee shorthand. + * _.dropWhile(users, 'active'); + * // => objects for ['barney', 'fred', 'pebbles'] + */ + function dropWhile(array, predicate) { + return (array && array.length) + ? baseWhile(array, getIteratee(predicate, 3), true) + : []; + } + + /** + * Fills elements of `array` with `value` from `start` up to, but not + * including, `end`. + * + * **Note:** This method mutates `array`. + * + * @static + * @memberOf _ + * @since 3.2.0 + * @category Array + * @param {Array} array The array to fill. + * @param {*} value The value to fill `array` with. + * @param {number} [start=0] The start position. + * @param {number} [end=array.length] The end position. + * @returns {Array} Returns `array`. + * @example + * + * var array = [1, 2, 3]; + * + * _.fill(array, 'a'); + * console.log(array); + * // => ['a', 'a', 'a'] + * + * _.fill(Array(3), 2); + * // => [2, 2, 2] + * + * _.fill([4, 6, 8, 10], '*', 1, 3); + * // => [4, '*', '*', 10] + */ + function fill(array, value, start, end) { + var length = array == null ? 0 : array.length; + if (!length) { + return []; + } + if (start && typeof start != 'number' && isIterateeCall(array, value, start)) { + start = 0; + end = length; + } + return baseFill(array, value, start, end); + } + + /** + * This method is like `_.find` except that it returns the index of the first + * element `predicate` returns truthy for instead of the element itself. + * + * @static + * @memberOf _ + * @since 1.1.0 + * @category Array + * @param {Array} array The array to inspect. + * @param {Function} [predicate=_.identity] The function invoked per iteration. + * @param {number} [fromIndex=0] The index to search from. + * @returns {number} Returns the index of the found element, else `-1`. + * @example + * + * var users = [ + * { 'user': 'barney', 'active': false }, + * { 'user': 'fred', 'active': false }, + * { 'user': 'pebbles', 'active': true } + * ]; + * + * _.findIndex(users, function(o) { return o.user == 'barney'; }); + * // => 0 + * + * // The `_.matches` iteratee shorthand. + * _.findIndex(users, { 'user': 'fred', 'active': false }); + * // => 1 + * + * // The `_.matchesProperty` iteratee shorthand. + * _.findIndex(users, ['active', false]); + * // => 0 + * + * // The `_.property` iteratee shorthand. + * _.findIndex(users, 'active'); + * // => 2 + */ + function findIndex(array, predicate, fromIndex) { + var length = array == null ? 0 : array.length; + if (!length) { + return -1; + } + var index = fromIndex == null ? 0 : toInteger(fromIndex); + if (index < 0) { + index = nativeMax(length + index, 0); + } + return baseFindIndex(array, getIteratee(predicate, 3), index); + } + + /** + * This method is like `_.findIndex` except that it iterates over elements + * of `collection` from right to left. + * + * @static + * @memberOf _ + * @since 2.0.0 + * @category Array + * @param {Array} array The array to inspect. + * @param {Function} [predicate=_.identity] The function invoked per iteration. + * @param {number} [fromIndex=array.length-1] The index to search from. + * @returns {number} Returns the index of the found element, else `-1`. + * @example + * + * var users = [ + * { 'user': 'barney', 'active': true }, + * { 'user': 'fred', 'active': false }, + * { 'user': 'pebbles', 'active': false } + * ]; + * + * _.findLastIndex(users, function(o) { return o.user == 'pebbles'; }); + * // => 2 + * + * // The `_.matches` iteratee shorthand. + * _.findLastIndex(users, { 'user': 'barney', 'active': true }); + * // => 0 + * + * // The `_.matchesProperty` iteratee shorthand. + * _.findLastIndex(users, ['active', false]); + * // => 2 + * + * // The `_.property` iteratee shorthand. + * _.findLastIndex(users, 'active'); + * // => 0 + */ + function findLastIndex(array, predicate, fromIndex) { + var length = array == null ? 0 : array.length; + if (!length) { + return -1; + } + var index = length - 1; + if (fromIndex !== undefined) { + index = toInteger(fromIndex); + index = fromIndex < 0 + ? nativeMax(length + index, 0) + : nativeMin(index, length - 1); + } + return baseFindIndex(array, getIteratee(predicate, 3), index, true); + } + + /** + * Flattens `array` a single level deep. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Array + * @param {Array} array The array to flatten. + * @returns {Array} Returns the new flattened array. + * @example + * + * _.flatten([1, [2, [3, [4]], 5]]); + * // => [1, 2, [3, [4]], 5] + */ + function flatten(array) { + var length = array == null ? 0 : array.length; + return length ? baseFlatten(array, 1) : []; + } + + /** + * Recursively flattens `array`. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Array + * @param {Array} array The array to flatten. + * @returns {Array} Returns the new flattened array. + * @example + * + * _.flattenDeep([1, [2, [3, [4]], 5]]); + * // => [1, 2, 3, 4, 5] + */ + function flattenDeep(array) { + var length = array == null ? 0 : array.length; + return length ? baseFlatten(array, INFINITY) : []; + } + + /** + * Recursively flatten `array` up to `depth` times. + * + * @static + * @memberOf _ + * @since 4.4.0 + * @category Array + * @param {Array} array The array to flatten. + * @param {number} [depth=1] The maximum recursion depth. + * @returns {Array} Returns the new flattened array. + * @example + * + * var array = [1, [2, [3, [4]], 5]]; + * + * _.flattenDepth(array, 1); + * // => [1, 2, [3, [4]], 5] + * + * _.flattenDepth(array, 2); + * // => [1, 2, 3, [4], 5] + */ + function flattenDepth(array, depth) { + var length = array == null ? 0 : array.length; + if (!length) { + return []; + } + depth = depth === undefined ? 1 : toInteger(depth); + return baseFlatten(array, depth); + } + + /** + * The inverse of `_.toPairs`; this method returns an object composed + * from key-value `pairs`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} pairs The key-value pairs. + * @returns {Object} Returns the new object. + * @example + * + * _.fromPairs([['a', 1], ['b', 2]]); + * // => { 'a': 1, 'b': 2 } + */ + function fromPairs(pairs) { + var index = -1, + length = pairs == null ? 0 : pairs.length, + result = {}; + + while (++index < length) { + var pair = pairs[index]; + result[pair[0]] = pair[1]; + } + return result; + } + + /** + * Gets the first element of `array`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @alias first + * @category Array + * @param {Array} array The array to query. + * @returns {*} Returns the first element of `array`. + * @example + * + * _.head([1, 2, 3]); + * // => 1 + * + * _.head([]); + * // => undefined + */ + function head(array) { + return (array && array.length) ? array[0] : undefined; + } + + /** + * Gets the index at which the first occurrence of `value` is found in `array` + * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * for equality comparisons. If `fromIndex` is negative, it's used as the + * offset from the end of `array`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Array + * @param {Array} array The array to inspect. + * @param {*} value The value to search for. + * @param {number} [fromIndex=0] The index to search from. + * @returns {number} Returns the index of the matched value, else `-1`. + * @example + * + * _.indexOf([1, 2, 1, 2], 2); + * // => 1 + * + * // Search from the `fromIndex`. + * _.indexOf([1, 2, 1, 2], 2, 2); + * // => 3 + */ + function indexOf(array, value, fromIndex) { + var length = array == null ? 0 : array.length; + if (!length) { + return -1; + } + var index = fromIndex == null ? 0 : toInteger(fromIndex); + if (index < 0) { + index = nativeMax(length + index, 0); + } + return baseIndexOf(array, value, index); + } + + /** + * Gets all but the last element of `array`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Array + * @param {Array} array The array to query. + * @returns {Array} Returns the slice of `array`. + * @example + * + * _.initial([1, 2, 3]); + * // => [1, 2] + */ + function initial(array) { + var length = array == null ? 0 : array.length; + return length ? baseSlice(array, 0, -1) : []; + } + + /** + * Creates an array of unique values that are included in all given arrays + * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * for equality comparisons. The order and references of result values are + * determined by the first array. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Array + * @param {...Array} [arrays] The arrays to inspect. + * @returns {Array} Returns the new array of intersecting values. + * @example + * + * _.intersection([2, 1], [2, 3]); + * // => [2] + */ + var intersection = baseRest(function(arrays) { + var mapped = arrayMap(arrays, castArrayLikeObject); + return (mapped.length && mapped[0] === arrays[0]) + ? baseIntersection(mapped) + : []; + }); + + /** + * This method is like `_.intersection` except that it accepts `iteratee` + * which is invoked for each element of each `arrays` to generate the criterion + * by which they're compared. The order and references of result values are + * determined by the first array. The iteratee is invoked with one argument: + * (value). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {...Array} [arrays] The arrays to inspect. + * @param {Function} [iteratee=_.identity] The iteratee invoked per element. + * @returns {Array} Returns the new array of intersecting values. + * @example + * + * _.intersectionBy([2.1, 1.2], [2.3, 3.4], Math.floor); + * // => [2.1] + * + * // The `_.property` iteratee shorthand. + * _.intersectionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x'); + * // => [{ 'x': 1 }] + */ + var intersectionBy = baseRest(function(arrays) { + var iteratee = last(arrays), + mapped = arrayMap(arrays, castArrayLikeObject); + + if (iteratee === last(mapped)) { + iteratee = undefined; + } else { + mapped.pop(); + } + return (mapped.length && mapped[0] === arrays[0]) + ? baseIntersection(mapped, getIteratee(iteratee, 2)) + : []; + }); + + /** + * This method is like `_.intersection` except that it accepts `comparator` + * which is invoked to compare elements of `arrays`. The order and references + * of result values are determined by the first array. The comparator is + * invoked with two arguments: (arrVal, othVal). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {...Array} [arrays] The arrays to inspect. + * @param {Function} [comparator] The comparator invoked per element. + * @returns {Array} Returns the new array of intersecting values. + * @example + * + * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]; + * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }]; + * + * _.intersectionWith(objects, others, _.isEqual); + * // => [{ 'x': 1, 'y': 2 }] + */ + var intersectionWith = baseRest(function(arrays) { + var comparator = last(arrays), + mapped = arrayMap(arrays, castArrayLikeObject); + + comparator = typeof comparator == 'function' ? comparator : undefined; + if (comparator) { + mapped.pop(); + } + return (mapped.length && mapped[0] === arrays[0]) + ? baseIntersection(mapped, undefined, comparator) + : []; + }); + + /** + * Converts all elements in `array` into a string separated by `separator`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The array to convert. + * @param {string} [separator=','] The element separator. + * @returns {string} Returns the joined string. + * @example + * + * _.join(['a', 'b', 'c'], '~'); + * // => 'a~b~c' + */ + function join(array, separator) { + return array == null ? '' : nativeJoin.call(array, separator); + } + + /** + * Gets the last element of `array`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Array + * @param {Array} array The array to query. + * @returns {*} Returns the last element of `array`. + * @example + * + * _.last([1, 2, 3]); + * // => 3 + */ + function last(array) { + var length = array == null ? 0 : array.length; + return length ? array[length - 1] : undefined; + } + + /** + * This method is like `_.indexOf` except that it iterates over elements of + * `array` from right to left. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Array + * @param {Array} array The array to inspect. + * @param {*} value The value to search for. + * @param {number} [fromIndex=array.length-1] The index to search from. + * @returns {number} Returns the index of the matched value, else `-1`. + * @example + * + * _.lastIndexOf([1, 2, 1, 2], 2); + * // => 3 + * + * // Search from the `fromIndex`. + * _.lastIndexOf([1, 2, 1, 2], 2, 2); + * // => 1 + */ + function lastIndexOf(array, value, fromIndex) { + var length = array == null ? 0 : array.length; + if (!length) { + return -1; + } + var index = length; + if (fromIndex !== undefined) { + index = toInteger(fromIndex); + index = index < 0 ? nativeMax(length + index, 0) : nativeMin(index, length - 1); + } + return value === value + ? strictLastIndexOf(array, value, index) + : baseFindIndex(array, baseIsNaN, index, true); + } + + /** + * Gets the element at index `n` of `array`. If `n` is negative, the nth + * element from the end is returned. + * + * @static + * @memberOf _ + * @since 4.11.0 + * @category Array + * @param {Array} array The array to query. + * @param {number} [n=0] The index of the element to return. + * @returns {*} Returns the nth element of `array`. + * @example + * + * var array = ['a', 'b', 'c', 'd']; + * + * _.nth(array, 1); + * // => 'b' + * + * _.nth(array, -2); + * // => 'c'; + */ + function nth(array, n) { + return (array && array.length) ? baseNth(array, toInteger(n)) : undefined; + } + + /** + * Removes all given values from `array` using + * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * for equality comparisons. + * + * **Note:** Unlike `_.without`, this method mutates `array`. Use `_.remove` + * to remove elements from an array by predicate. + * + * @static + * @memberOf _ + * @since 2.0.0 + * @category Array + * @param {Array} array The array to modify. + * @param {...*} [values] The values to remove. + * @returns {Array} Returns `array`. + * @example + * + * var array = ['a', 'b', 'c', 'a', 'b', 'c']; + * + * _.pull(array, 'a', 'c'); + * console.log(array); + * // => ['b', 'b'] + */ + var pull = baseRest(pullAll); + + /** + * This method is like `_.pull` except that it accepts an array of values to remove. + * + * **Note:** Unlike `_.difference`, this method mutates `array`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The array to modify. + * @param {Array} values The values to remove. + * @returns {Array} Returns `array`. + * @example + * + * var array = ['a', 'b', 'c', 'a', 'b', 'c']; + * + * _.pullAll(array, ['a', 'c']); + * console.log(array); + * // => ['b', 'b'] + */ + function pullAll(array, values) { + return (array && array.length && values && values.length) + ? basePullAll(array, values) + : array; + } + + /** + * This method is like `_.pullAll` except that it accepts `iteratee` which is + * invoked for each element of `array` and `values` to generate the criterion + * by which they're compared. The iteratee is invoked with one argument: (value). + * + * **Note:** Unlike `_.differenceBy`, this method mutates `array`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The array to modify. + * @param {Array} values The values to remove. + * @param {Function} [iteratee=_.identity] The iteratee invoked per element. + * @returns {Array} Returns `array`. + * @example + * + * var array = [{ 'x': 1 }, { 'x': 2 }, { 'x': 3 }, { 'x': 1 }]; + * + * _.pullAllBy(array, [{ 'x': 1 }, { 'x': 3 }], 'x'); + * console.log(array); + * // => [{ 'x': 2 }] + */ + function pullAllBy(array, values, iteratee) { + return (array && array.length && values && values.length) + ? basePullAll(array, values, getIteratee(iteratee, 2)) + : array; + } + + /** + * This method is like `_.pullAll` except that it accepts `comparator` which + * is invoked to compare elements of `array` to `values`. The comparator is + * invoked with two arguments: (arrVal, othVal). + * + * **Note:** Unlike `_.differenceWith`, this method mutates `array`. + * + * @static + * @memberOf _ + * @since 4.6.0 + * @category Array + * @param {Array} array The array to modify. + * @param {Array} values The values to remove. + * @param {Function} [comparator] The comparator invoked per element. + * @returns {Array} Returns `array`. + * @example + * + * var array = [{ 'x': 1, 'y': 2 }, { 'x': 3, 'y': 4 }, { 'x': 5, 'y': 6 }]; + * + * _.pullAllWith(array, [{ 'x': 3, 'y': 4 }], _.isEqual); + * console.log(array); + * // => [{ 'x': 1, 'y': 2 }, { 'x': 5, 'y': 6 }] + */ + function pullAllWith(array, values, comparator) { + return (array && array.length && values && values.length) + ? basePullAll(array, values, undefined, comparator) + : array; + } + + /** + * Removes elements from `array` corresponding to `indexes` and returns an + * array of removed elements. + * + * **Note:** Unlike `_.at`, this method mutates `array`. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Array + * @param {Array} array The array to modify. + * @param {...(number|number[])} [indexes] The indexes of elements to remove. + * @returns {Array} Returns the new array of removed elements. + * @example + * + * var array = ['a', 'b', 'c', 'd']; + * var pulled = _.pullAt(array, [1, 3]); + * + * console.log(array); + * // => ['a', 'c'] + * + * console.log(pulled); + * // => ['b', 'd'] + */ + var pullAt = flatRest(function(array, indexes) { + var length = array == null ? 0 : array.length, + result = baseAt(array, indexes); + + basePullAt(array, arrayMap(indexes, function(index) { + return isIndex(index, length) ? +index : index; + }).sort(compareAscending)); + + return result; + }); + + /** + * Removes all elements from `array` that `predicate` returns truthy for + * and returns an array of the removed elements. The predicate is invoked + * with three arguments: (value, index, array). + * + * **Note:** Unlike `_.filter`, this method mutates `array`. Use `_.pull` + * to pull elements from an array by value. + * + * @static + * @memberOf _ + * @since 2.0.0 + * @category Array + * @param {Array} array The array to modify. + * @param {Function} [predicate=_.identity] The function invoked per iteration. + * @returns {Array} Returns the new array of removed elements. + * @example + * + * var array = [1, 2, 3, 4]; + * var evens = _.remove(array, function(n) { + * return n % 2 == 0; + * }); + * + * console.log(array); + * // => [1, 3] + * + * console.log(evens); + * // => [2, 4] + */ + function remove(array, predicate) { + var result = []; + if (!(array && array.length)) { + return result; + } + var index = -1, + indexes = [], + length = array.length; + + predicate = getIteratee(predicate, 3); + while (++index < length) { + var value = array[index]; + if (predicate(value, index, array)) { + result.push(value); + indexes.push(index); + } + } + basePullAt(array, indexes); + return result; + } + + /** + * Reverses `array` so that the first element becomes the last, the second + * element becomes the second to last, and so on. + * + * **Note:** This method mutates `array` and is based on + * [`Array#reverse`](https://mdn.io/Array/reverse). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The array to modify. + * @returns {Array} Returns `array`. + * @example + * + * var array = [1, 2, 3]; + * + * _.reverse(array); + * // => [3, 2, 1] + * + * console.log(array); + * // => [3, 2, 1] + */ + function reverse(array) { + return array == null ? array : nativeReverse.call(array); + } + + /** + * Creates a slice of `array` from `start` up to, but not including, `end`. + * + * **Note:** This method is used instead of + * [`Array#slice`](https://mdn.io/Array/slice) to ensure dense arrays are + * returned. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Array + * @param {Array} array The array to slice. + * @param {number} [start=0] The start position. + * @param {number} [end=array.length] The end position. + * @returns {Array} Returns the slice of `array`. + */ + function slice(array, start, end) { + var length = array == null ? 0 : array.length; + if (!length) { + return []; + } + if (end && typeof end != 'number' && isIterateeCall(array, start, end)) { + start = 0; + end = length; + } + else { + start = start == null ? 0 : toInteger(start); + end = end === undefined ? length : toInteger(end); + } + return baseSlice(array, start, end); + } + + /** + * Uses a binary search to determine the lowest index at which `value` + * should be inserted into `array` in order to maintain its sort order. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Array + * @param {Array} array The sorted array to inspect. + * @param {*} value The value to evaluate. + * @returns {number} Returns the index at which `value` should be inserted + * into `array`. + * @example + * + * _.sortedIndex([30, 50], 40); + * // => 1 + */ + function sortedIndex(array, value) { + return baseSortedIndex(array, value); + } + + /** + * This method is like `_.sortedIndex` except that it accepts `iteratee` + * which is invoked for `value` and each element of `array` to compute their + * sort ranking. The iteratee is invoked with one argument: (value). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The sorted array to inspect. + * @param {*} value The value to evaluate. + * @param {Function} [iteratee=_.identity] The iteratee invoked per element. + * @returns {number} Returns the index at which `value` should be inserted + * into `array`. + * @example + * + * var objects = [{ 'x': 4 }, { 'x': 5 }]; + * + * _.sortedIndexBy(objects, { 'x': 4 }, function(o) { return o.x; }); + * // => 0 + * + * // The `_.property` iteratee shorthand. + * _.sortedIndexBy(objects, { 'x': 4 }, 'x'); + * // => 0 + */ + function sortedIndexBy(array, value, iteratee) { + return baseSortedIndexBy(array, value, getIteratee(iteratee, 2)); + } + + /** + * This method is like `_.indexOf` except that it performs a binary + * search on a sorted `array`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The array to inspect. + * @param {*} value The value to search for. + * @returns {number} Returns the index of the matched value, else `-1`. + * @example + * + * _.sortedIndexOf([4, 5, 5, 5, 6], 5); + * // => 1 + */ + function sortedIndexOf(array, value) { + var length = array == null ? 0 : array.length; + if (length) { + var index = baseSortedIndex(array, value); + if (index < length && eq(array[index], value)) { + return index; + } + } + return -1; + } + + /** + * This method is like `_.sortedIndex` except that it returns the highest + * index at which `value` should be inserted into `array` in order to + * maintain its sort order. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Array + * @param {Array} array The sorted array to inspect. + * @param {*} value The value to evaluate. + * @returns {number} Returns the index at which `value` should be inserted + * into `array`. + * @example + * + * _.sortedLastIndex([4, 5, 5, 5, 6], 5); + * // => 4 + */ + function sortedLastIndex(array, value) { + return baseSortedIndex(array, value, true); + } + + /** + * This method is like `_.sortedLastIndex` except that it accepts `iteratee` + * which is invoked for `value` and each element of `array` to compute their + * sort ranking. The iteratee is invoked with one argument: (value). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The sorted array to inspect. + * @param {*} value The value to evaluate. + * @param {Function} [iteratee=_.identity] The iteratee invoked per element. + * @returns {number} Returns the index at which `value` should be inserted + * into `array`. + * @example + * + * var objects = [{ 'x': 4 }, { 'x': 5 }]; + * + * _.sortedLastIndexBy(objects, { 'x': 4 }, function(o) { return o.x; }); + * // => 1 + * + * // The `_.property` iteratee shorthand. + * _.sortedLastIndexBy(objects, { 'x': 4 }, 'x'); + * // => 1 + */ + function sortedLastIndexBy(array, value, iteratee) { + return baseSortedIndexBy(array, value, getIteratee(iteratee, 2), true); + } + + /** + * This method is like `_.lastIndexOf` except that it performs a binary + * search on a sorted `array`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The array to inspect. + * @param {*} value The value to search for. + * @returns {number} Returns the index of the matched value, else `-1`. + * @example + * + * _.sortedLastIndexOf([4, 5, 5, 5, 6], 5); + * // => 3 + */ + function sortedLastIndexOf(array, value) { + var length = array == null ? 0 : array.length; + if (length) { + var index = baseSortedIndex(array, value, true) - 1; + if (eq(array[index], value)) { + return index; + } + } + return -1; + } + + /** + * This method is like `_.uniq` except that it's designed and optimized + * for sorted arrays. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The array to inspect. + * @returns {Array} Returns the new duplicate free array. + * @example + * + * _.sortedUniq([1, 1, 2]); + * // => [1, 2] + */ + function sortedUniq(array) { + return (array && array.length) + ? baseSortedUniq(array) + : []; + } + + /** + * This method is like `_.uniqBy` except that it's designed and optimized + * for sorted arrays. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The array to inspect. + * @param {Function} [iteratee] The iteratee invoked per element. + * @returns {Array} Returns the new duplicate free array. + * @example + * + * _.sortedUniqBy([1.1, 1.2, 2.3, 2.4], Math.floor); + * // => [1.1, 2.3] + */ + function sortedUniqBy(array, iteratee) { + return (array && array.length) + ? baseSortedUniq(array, getIteratee(iteratee, 2)) + : []; + } + + /** + * Gets all but the first element of `array`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The array to query. + * @returns {Array} Returns the slice of `array`. + * @example + * + * _.tail([1, 2, 3]); + * // => [2, 3] + */ + function tail(array) { + var length = array == null ? 0 : array.length; + return length ? baseSlice(array, 1, length) : []; + } + + /** + * Creates a slice of `array` with `n` elements taken from the beginning. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Array + * @param {Array} array The array to query. + * @param {number} [n=1] The number of elements to take. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {Array} Returns the slice of `array`. + * @example + * + * _.take([1, 2, 3]); + * // => [1] + * + * _.take([1, 2, 3], 2); + * // => [1, 2] + * + * _.take([1, 2, 3], 5); + * // => [1, 2, 3] + * + * _.take([1, 2, 3], 0); + * // => [] + */ + function take(array, n, guard) { + if (!(array && array.length)) { + return []; + } + n = (guard || n === undefined) ? 1 : toInteger(n); + return baseSlice(array, 0, n < 0 ? 0 : n); + } + + /** + * Creates a slice of `array` with `n` elements taken from the end. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Array + * @param {Array} array The array to query. + * @param {number} [n=1] The number of elements to take. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {Array} Returns the slice of `array`. + * @example + * + * _.takeRight([1, 2, 3]); + * // => [3] + * + * _.takeRight([1, 2, 3], 2); + * // => [2, 3] + * + * _.takeRight([1, 2, 3], 5); + * // => [1, 2, 3] + * + * _.takeRight([1, 2, 3], 0); + * // => [] + */ + function takeRight(array, n, guard) { + var length = array == null ? 0 : array.length; + if (!length) { + return []; + } + n = (guard || n === undefined) ? 1 : toInteger(n); + n = length - n; + return baseSlice(array, n < 0 ? 0 : n, length); + } + + /** + * Creates a slice of `array` with elements taken from the end. Elements are + * taken until `predicate` returns falsey. The predicate is invoked with + * three arguments: (value, index, array). + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Array + * @param {Array} array The array to query. + * @param {Function} [predicate=_.identity] The function invoked per iteration. + * @returns {Array} Returns the slice of `array`. + * @example + * + * var users = [ + * { 'user': 'barney', 'active': true }, + * { 'user': 'fred', 'active': false }, + * { 'user': 'pebbles', 'active': false } + * ]; + * + * _.takeRightWhile(users, function(o) { return !o.active; }); + * // => objects for ['fred', 'pebbles'] + * + * // The `_.matches` iteratee shorthand. + * _.takeRightWhile(users, { 'user': 'pebbles', 'active': false }); + * // => objects for ['pebbles'] + * + * // The `_.matchesProperty` iteratee shorthand. + * _.takeRightWhile(users, ['active', false]); + * // => objects for ['fred', 'pebbles'] + * + * // The `_.property` iteratee shorthand. + * _.takeRightWhile(users, 'active'); + * // => [] + */ + function takeRightWhile(array, predicate) { + return (array && array.length) + ? baseWhile(array, getIteratee(predicate, 3), false, true) + : []; + } + + /** + * Creates a slice of `array` with elements taken from the beginning. Elements + * are taken until `predicate` returns falsey. The predicate is invoked with + * three arguments: (value, index, array). + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Array + * @param {Array} array The array to query. + * @param {Function} [predicate=_.identity] The function invoked per iteration. + * @returns {Array} Returns the slice of `array`. + * @example + * + * var users = [ + * { 'user': 'barney', 'active': false }, + * { 'user': 'fred', 'active': false }, + * { 'user': 'pebbles', 'active': true } + * ]; + * + * _.takeWhile(users, function(o) { return !o.active; }); + * // => objects for ['barney', 'fred'] + * + * // The `_.matches` iteratee shorthand. + * _.takeWhile(users, { 'user': 'barney', 'active': false }); + * // => objects for ['barney'] + * + * // The `_.matchesProperty` iteratee shorthand. + * _.takeWhile(users, ['active', false]); + * // => objects for ['barney', 'fred'] + * + * // The `_.property` iteratee shorthand. + * _.takeWhile(users, 'active'); + * // => [] + */ + function takeWhile(array, predicate) { + return (array && array.length) + ? baseWhile(array, getIteratee(predicate, 3)) + : []; + } + + /** + * Creates an array of unique values, in order, from all given arrays using + * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * for equality comparisons. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Array + * @param {...Array} [arrays] The arrays to inspect. + * @returns {Array} Returns the new array of combined values. + * @example + * + * _.union([2], [1, 2]); + * // => [2, 1] + */ + var union = baseRest(function(arrays) { + return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true)); + }); + + /** + * This method is like `_.union` except that it accepts `iteratee` which is + * invoked for each element of each `arrays` to generate the criterion by + * which uniqueness is computed. Result values are chosen from the first + * array in which the value occurs. The iteratee is invoked with one argument: + * (value). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {...Array} [arrays] The arrays to inspect. + * @param {Function} [iteratee=_.identity] The iteratee invoked per element. + * @returns {Array} Returns the new array of combined values. + * @example + * + * _.unionBy([2.1], [1.2, 2.3], Math.floor); + * // => [2.1, 1.2] + * + * // The `_.property` iteratee shorthand. + * _.unionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x'); + * // => [{ 'x': 1 }, { 'x': 2 }] + */ + var unionBy = baseRest(function(arrays) { + var iteratee = last(arrays); + if (isArrayLikeObject(iteratee)) { + iteratee = undefined; + } + return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true), getIteratee(iteratee, 2)); + }); + + /** + * This method is like `_.union` except that it accepts `comparator` which + * is invoked to compare elements of `arrays`. Result values are chosen from + * the first array in which the value occurs. The comparator is invoked + * with two arguments: (arrVal, othVal). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {...Array} [arrays] The arrays to inspect. + * @param {Function} [comparator] The comparator invoked per element. + * @returns {Array} Returns the new array of combined values. + * @example + * + * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]; + * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }]; + * + * _.unionWith(objects, others, _.isEqual); + * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }] + */ + var unionWith = baseRest(function(arrays) { + var comparator = last(arrays); + comparator = typeof comparator == 'function' ? comparator : undefined; + return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true), undefined, comparator); + }); + + /** + * Creates a duplicate-free version of an array, using + * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * for equality comparisons, in which only the first occurrence of each element + * is kept. The order of result values is determined by the order they occur + * in the array. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Array + * @param {Array} array The array to inspect. + * @returns {Array} Returns the new duplicate free array. + * @example + * + * _.uniq([2, 1, 2]); + * // => [2, 1] + */ + function uniq(array) { + return (array && array.length) ? baseUniq(array) : []; + } + + /** + * This method is like `_.uniq` except that it accepts `iteratee` which is + * invoked for each element in `array` to generate the criterion by which + * uniqueness is computed. The order of result values is determined by the + * order they occur in the array. The iteratee is invoked with one argument: + * (value). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The array to inspect. + * @param {Function} [iteratee=_.identity] The iteratee invoked per element. + * @returns {Array} Returns the new duplicate free array. + * @example + * + * _.uniqBy([2.1, 1.2, 2.3], Math.floor); + * // => [2.1, 1.2] + * + * // The `_.property` iteratee shorthand. + * _.uniqBy([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x'); + * // => [{ 'x': 1 }, { 'x': 2 }] + */ + function uniqBy(array, iteratee) { + return (array && array.length) ? baseUniq(array, getIteratee(iteratee, 2)) : []; + } + + /** + * This method is like `_.uniq` except that it accepts `comparator` which + * is invoked to compare elements of `array`. The order of result values is + * determined by the order they occur in the array.The comparator is invoked + * with two arguments: (arrVal, othVal). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The array to inspect. + * @param {Function} [comparator] The comparator invoked per element. + * @returns {Array} Returns the new duplicate free array. + * @example + * + * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 2 }]; + * + * _.uniqWith(objects, _.isEqual); + * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }] + */ + function uniqWith(array, comparator) { + comparator = typeof comparator == 'function' ? comparator : undefined; + return (array && array.length) ? baseUniq(array, undefined, comparator) : []; + } + + /** + * This method is like `_.zip` except that it accepts an array of grouped + * elements and creates an array regrouping the elements to their pre-zip + * configuration. + * + * @static + * @memberOf _ + * @since 1.2.0 + * @category Array + * @param {Array} array The array of grouped elements to process. + * @returns {Array} Returns the new array of regrouped elements. + * @example + * + * var zipped = _.zip(['a', 'b'], [1, 2], [true, false]); + * // => [['a', 1, true], ['b', 2, false]] + * + * _.unzip(zipped); + * // => [['a', 'b'], [1, 2], [true, false]] + */ + function unzip(array) { + if (!(array && array.length)) { + return []; + } + var length = 0; + array = arrayFilter(array, function(group) { + if (isArrayLikeObject(group)) { + length = nativeMax(group.length, length); + return true; + } + }); + return baseTimes(length, function(index) { + return arrayMap(array, baseProperty(index)); + }); + } + + /** + * This method is like `_.unzip` except that it accepts `iteratee` to specify + * how regrouped values should be combined. The iteratee is invoked with the + * elements of each group: (...group). + * + * @static + * @memberOf _ + * @since 3.8.0 + * @category Array + * @param {Array} array The array of grouped elements to process. + * @param {Function} [iteratee=_.identity] The function to combine + * regrouped values. + * @returns {Array} Returns the new array of regrouped elements. + * @example + * + * var zipped = _.zip([1, 2], [10, 20], [100, 200]); + * // => [[1, 10, 100], [2, 20, 200]] + * + * _.unzipWith(zipped, _.add); + * // => [3, 30, 300] + */ + function unzipWith(array, iteratee) { + if (!(array && array.length)) { + return []; + } + var result = unzip(array); + if (iteratee == null) { + return result; + } + return arrayMap(result, function(group) { + return apply(iteratee, undefined, group); + }); + } + + /** + * Creates an array excluding all given values using + * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * for equality comparisons. + * + * **Note:** Unlike `_.pull`, this method returns a new array. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Array + * @param {Array} array The array to inspect. + * @param {...*} [values] The values to exclude. + * @returns {Array} Returns the new array of filtered values. + * @see _.difference, _.xor + * @example + * + * _.without([2, 1, 2, 3], 1, 2); + * // => [3] + */ + var without = baseRest(function(array, values) { + return isArrayLikeObject(array) + ? baseDifference(array, values) + : []; + }); + + /** + * Creates an array of unique values that is the + * [symmetric difference](https://en.wikipedia.org/wiki/Symmetric_difference) + * of the given arrays. The order of result values is determined by the order + * they occur in the arrays. + * + * @static + * @memberOf _ + * @since 2.4.0 + * @category Array + * @param {...Array} [arrays] The arrays to inspect. + * @returns {Array} Returns the new array of filtered values. + * @see _.difference, _.without + * @example + * + * _.xor([2, 1], [2, 3]); + * // => [1, 3] + */ + var xor = baseRest(function(arrays) { + return baseXor(arrayFilter(arrays, isArrayLikeObject)); + }); + + /** + * This method is like `_.xor` except that it accepts `iteratee` which is + * invoked for each element of each `arrays` to generate the criterion by + * which by which they're compared. The order of result values is determined + * by the order they occur in the arrays. The iteratee is invoked with one + * argument: (value). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {...Array} [arrays] The arrays to inspect. + * @param {Function} [iteratee=_.identity] The iteratee invoked per element. + * @returns {Array} Returns the new array of filtered values. + * @example + * + * _.xorBy([2.1, 1.2], [2.3, 3.4], Math.floor); + * // => [1.2, 3.4] + * + * // The `_.property` iteratee shorthand. + * _.xorBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x'); + * // => [{ 'x': 2 }] + */ + var xorBy = baseRest(function(arrays) { + var iteratee = last(arrays); + if (isArrayLikeObject(iteratee)) { + iteratee = undefined; + } + return baseXor(arrayFilter(arrays, isArrayLikeObject), getIteratee(iteratee, 2)); + }); + + /** + * This method is like `_.xor` except that it accepts `comparator` which is + * invoked to compare elements of `arrays`. The order of result values is + * determined by the order they occur in the arrays. The comparator is invoked + * with two arguments: (arrVal, othVal). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {...Array} [arrays] The arrays to inspect. + * @param {Function} [comparator] The comparator invoked per element. + * @returns {Array} Returns the new array of filtered values. + * @example + * + * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]; + * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }]; + * + * _.xorWith(objects, others, _.isEqual); + * // => [{ 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }] + */ + var xorWith = baseRest(function(arrays) { + var comparator = last(arrays); + comparator = typeof comparator == 'function' ? comparator : undefined; + return baseXor(arrayFilter(arrays, isArrayLikeObject), undefined, comparator); + }); + + /** + * Creates an array of grouped elements, the first of which contains the + * first elements of the given arrays, the second of which contains the + * second elements of the given arrays, and so on. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Array + * @param {...Array} [arrays] The arrays to process. + * @returns {Array} Returns the new array of grouped elements. + * @example + * + * _.zip(['a', 'b'], [1, 2], [true, false]); + * // => [['a', 1, true], ['b', 2, false]] + */ + var zip = baseRest(unzip); + + /** + * This method is like `_.fromPairs` except that it accepts two arrays, + * one of property identifiers and one of corresponding values. + * + * @static + * @memberOf _ + * @since 0.4.0 + * @category Array + * @param {Array} [props=[]] The property identifiers. + * @param {Array} [values=[]] The property values. + * @returns {Object} Returns the new object. + * @example + * + * _.zipObject(['a', 'b'], [1, 2]); + * // => { 'a': 1, 'b': 2 } + */ + function zipObject(props, values) { + return baseZipObject(props || [], values || [], assignValue); + } + + /** + * This method is like `_.zipObject` except that it supports property paths. + * + * @static + * @memberOf _ + * @since 4.1.0 + * @category Array + * @param {Array} [props=[]] The property identifiers. + * @param {Array} [values=[]] The property values. + * @returns {Object} Returns the new object. + * @example + * + * _.zipObjectDeep(['a.b[0].c', 'a.b[1].d'], [1, 2]); + * // => { 'a': { 'b': [{ 'c': 1 }, { 'd': 2 }] } } + */ + function zipObjectDeep(props, values) { + return baseZipObject(props || [], values || [], baseSet); + } + + /** + * This method is like `_.zip` except that it accepts `iteratee` to specify + * how grouped values should be combined. The iteratee is invoked with the + * elements of each group: (...group). + * + * @static + * @memberOf _ + * @since 3.8.0 + * @category Array + * @param {...Array} [arrays] The arrays to process. + * @param {Function} [iteratee=_.identity] The function to combine + * grouped values. + * @returns {Array} Returns the new array of grouped elements. + * @example + * + * _.zipWith([1, 2], [10, 20], [100, 200], function(a, b, c) { + * return a + b + c; + * }); + * // => [111, 222] + */ + var zipWith = baseRest(function(arrays) { + var length = arrays.length, + iteratee = length > 1 ? arrays[length - 1] : undefined; + + iteratee = typeof iteratee == 'function' ? (arrays.pop(), iteratee) : undefined; + return unzipWith(arrays, iteratee); + }); + + /*------------------------------------------------------------------------*/ + + /** + * Creates a `lodash` wrapper instance that wraps `value` with explicit method + * chain sequences enabled. The result of such sequences must be unwrapped + * with `_#value`. + * + * @static + * @memberOf _ + * @since 1.3.0 + * @category Seq + * @param {*} value The value to wrap. + * @returns {Object} Returns the new `lodash` wrapper instance. + * @example + * + * var users = [ + * { 'user': 'barney', 'age': 36 }, + * { 'user': 'fred', 'age': 40 }, + * { 'user': 'pebbles', 'age': 1 } + * ]; + * + * var youngest = _ + * .chain(users) + * .sortBy('age') + * .map(function(o) { + * return o.user + ' is ' + o.age; + * }) + * .head() + * .value(); + * // => 'pebbles is 1' + */ + function chain(value) { + var result = lodash(value); + result.__chain__ = true; + return result; + } + + /** + * This method invokes `interceptor` and returns `value`. The interceptor + * is invoked with one argument; (value). The purpose of this method is to + * "tap into" a method chain sequence in order to modify intermediate results. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Seq + * @param {*} value The value to provide to `interceptor`. + * @param {Function} interceptor The function to invoke. + * @returns {*} Returns `value`. + * @example + * + * _([1, 2, 3]) + * .tap(function(array) { + * // Mutate input array. + * array.pop(); + * }) + * .reverse() + * .value(); + * // => [2, 1] + */ + function tap(value, interceptor) { + interceptor(value); + return value; + } + + /** + * This method is like `_.tap` except that it returns the result of `interceptor`. + * The purpose of this method is to "pass thru" values replacing intermediate + * results in a method chain sequence. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Seq + * @param {*} value The value to provide to `interceptor`. + * @param {Function} interceptor The function to invoke. + * @returns {*} Returns the result of `interceptor`. + * @example + * + * _(' abc ') + * .chain() + * .trim() + * .thru(function(value) { + * return [value]; + * }) + * .value(); + * // => ['abc'] + */ + function thru(value, interceptor) { + return interceptor(value); + } + + /** + * This method is the wrapper version of `_.at`. + * + * @name at + * @memberOf _ + * @since 1.0.0 + * @category Seq + * @param {...(string|string[])} [paths] The property paths to pick. + * @returns {Object} Returns the new `lodash` wrapper instance. + * @example + * + * var object = { 'a': [{ 'b': { 'c': 3 } }, 4] }; + * + * _(object).at(['a[0].b.c', 'a[1]']).value(); + * // => [3, 4] + */ + var wrapperAt = flatRest(function(paths) { + var length = paths.length, + start = length ? paths[0] : 0, + value = this.__wrapped__, + interceptor = function(object) { return baseAt(object, paths); }; + + if (length > 1 || this.__actions__.length || + !(value instanceof LazyWrapper) || !isIndex(start)) { + return this.thru(interceptor); + } + value = value.slice(start, +start + (length ? 1 : 0)); + value.__actions__.push({ + 'func': thru, + 'args': [interceptor], + 'thisArg': undefined + }); + return new LodashWrapper(value, this.__chain__).thru(function(array) { + if (length && !array.length) { + array.push(undefined); + } + return array; + }); + }); + + /** + * Creates a `lodash` wrapper instance with explicit method chain sequences enabled. + * + * @name chain + * @memberOf _ + * @since 0.1.0 + * @category Seq + * @returns {Object} Returns the new `lodash` wrapper instance. + * @example + * + * var users = [ + * { 'user': 'barney', 'age': 36 }, + * { 'user': 'fred', 'age': 40 } + * ]; + * + * // A sequence without explicit chaining. + * _(users).head(); + * // => { 'user': 'barney', 'age': 36 } + * + * // A sequence with explicit chaining. + * _(users) + * .chain() + * .head() + * .pick('user') + * .value(); + * // => { 'user': 'barney' } + */ + function wrapperChain() { + return chain(this); + } + + /** + * Executes the chain sequence and returns the wrapped result. + * + * @name commit + * @memberOf _ + * @since 3.2.0 + * @category Seq + * @returns {Object} Returns the new `lodash` wrapper instance. + * @example + * + * var array = [1, 2]; + * var wrapped = _(array).push(3); + * + * console.log(array); + * // => [1, 2] + * + * wrapped = wrapped.commit(); + * console.log(array); + * // => [1, 2, 3] + * + * wrapped.last(); + * // => 3 + * + * console.log(array); + * // => [1, 2, 3] + */ + function wrapperCommit() { + return new LodashWrapper(this.value(), this.__chain__); + } + + /** + * Gets the next value on a wrapped object following the + * [iterator protocol](https://mdn.io/iteration_protocols#iterator). + * + * @name next + * @memberOf _ + * @since 4.0.0 + * @category Seq + * @returns {Object} Returns the next iterator value. + * @example + * + * var wrapped = _([1, 2]); + * + * wrapped.next(); + * // => { 'done': false, 'value': 1 } + * + * wrapped.next(); + * // => { 'done': false, 'value': 2 } + * + * wrapped.next(); + * // => { 'done': true, 'value': undefined } + */ + function wrapperNext() { + if (this.__values__ === undefined) { + this.__values__ = toArray(this.value()); + } + var done = this.__index__ >= this.__values__.length, + value = done ? undefined : this.__values__[this.__index__++]; + + return { 'done': done, 'value': value }; + } + + /** + * Enables the wrapper to be iterable. + * + * @name Symbol.iterator + * @memberOf _ + * @since 4.0.0 + * @category Seq + * @returns {Object} Returns the wrapper object. + * @example + * + * var wrapped = _([1, 2]); + * + * wrapped[Symbol.iterator]() === wrapped; + * // => true + * + * Array.from(wrapped); + * // => [1, 2] + */ + function wrapperToIterator() { + return this; + } + + /** + * Creates a clone of the chain sequence planting `value` as the wrapped value. + * + * @name plant + * @memberOf _ + * @since 3.2.0 + * @category Seq + * @param {*} value The value to plant. + * @returns {Object} Returns the new `lodash` wrapper instance. + * @example + * + * function square(n) { + * return n * n; + * } + * + * var wrapped = _([1, 2]).map(square); + * var other = wrapped.plant([3, 4]); + * + * other.value(); + * // => [9, 16] + * + * wrapped.value(); + * // => [1, 4] + */ + function wrapperPlant(value) { + var result, + parent = this; + + while (parent instanceof baseLodash) { + var clone = wrapperClone(parent); + clone.__index__ = 0; + clone.__values__ = undefined; + if (result) { + previous.__wrapped__ = clone; + } else { + result = clone; + } + var previous = clone; + parent = parent.__wrapped__; + } + previous.__wrapped__ = value; + return result; + } + + /** + * This method is the wrapper version of `_.reverse`. + * + * **Note:** This method mutates the wrapped array. + * + * @name reverse + * @memberOf _ + * @since 0.1.0 + * @category Seq + * @returns {Object} Returns the new `lodash` wrapper instance. + * @example + * + * var array = [1, 2, 3]; + * + * _(array).reverse().value() + * // => [3, 2, 1] + * + * console.log(array); + * // => [3, 2, 1] + */ + function wrapperReverse() { + var value = this.__wrapped__; + if (value instanceof LazyWrapper) { + var wrapped = value; + if (this.__actions__.length) { + wrapped = new LazyWrapper(this); + } + wrapped = wrapped.reverse(); + wrapped.__actions__.push({ + 'func': thru, + 'args': [reverse], + 'thisArg': undefined + }); + return new LodashWrapper(wrapped, this.__chain__); + } + return this.thru(reverse); + } + + /** + * Executes the chain sequence to resolve the unwrapped value. + * + * @name value + * @memberOf _ + * @since 0.1.0 + * @alias toJSON, valueOf + * @category Seq + * @returns {*} Returns the resolved unwrapped value. + * @example + * + * _([1, 2, 3]).value(); + * // => [1, 2, 3] + */ + function wrapperValue() { + return baseWrapperValue(this.__wrapped__, this.__actions__); + } + + /*------------------------------------------------------------------------*/ + + /** + * Creates an object composed of keys generated from the results of running + * each element of `collection` thru `iteratee`. The corresponding value of + * each key is the number of times the key was returned by `iteratee`. The + * iteratee is invoked with one argument: (value). + * + * @static + * @memberOf _ + * @since 0.5.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [iteratee=_.identity] The iteratee to transform keys. + * @returns {Object} Returns the composed aggregate object. + * @example + * + * _.countBy([6.1, 4.2, 6.3], Math.floor); + * // => { '4': 1, '6': 2 } + * + * // The `_.property` iteratee shorthand. + * _.countBy(['one', 'two', 'three'], 'length'); + * // => { '3': 2, '5': 1 } + */ + var countBy = createAggregator(function(result, value, key) { + if (hasOwnProperty.call(result, key)) { + ++result[key]; + } else { + baseAssignValue(result, key, 1); + } + }); + + /** + * Checks if `predicate` returns truthy for **all** elements of `collection`. + * Iteration is stopped once `predicate` returns falsey. The predicate is + * invoked with three arguments: (value, index|key, collection). + * + * **Note:** This method returns `true` for + * [empty collections](https://en.wikipedia.org/wiki/Empty_set) because + * [everything is true](https://en.wikipedia.org/wiki/Vacuous_truth) of + * elements of empty collections. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [predicate=_.identity] The function invoked per iteration. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {boolean} Returns `true` if all elements pass the predicate check, + * else `false`. + * @example + * + * _.every([true, 1, null, 'yes'], Boolean); + * // => false + * + * var users = [ + * { 'user': 'barney', 'age': 36, 'active': false }, + * { 'user': 'fred', 'age': 40, 'active': false } + * ]; + * + * // The `_.matches` iteratee shorthand. + * _.every(users, { 'user': 'barney', 'active': false }); + * // => false + * + * // The `_.matchesProperty` iteratee shorthand. + * _.every(users, ['active', false]); + * // => true + * + * // The `_.property` iteratee shorthand. + * _.every(users, 'active'); + * // => false + */ + function every(collection, predicate, guard) { + var func = isArray(collection) ? arrayEvery : baseEvery; + if (guard && isIterateeCall(collection, predicate, guard)) { + predicate = undefined; + } + return func(collection, getIteratee(predicate, 3)); + } + + /** + * Iterates over elements of `collection`, returning an array of all elements + * `predicate` returns truthy for. The predicate is invoked with three + * arguments: (value, index|key, collection). + * + * **Note:** Unlike `_.remove`, this method returns a new array. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [predicate=_.identity] The function invoked per iteration. + * @returns {Array} Returns the new filtered array. + * @see _.reject + * @example + * + * var users = [ + * { 'user': 'barney', 'age': 36, 'active': true }, + * { 'user': 'fred', 'age': 40, 'active': false } + * ]; + * + * _.filter(users, function(o) { return !o.active; }); + * // => objects for ['fred'] + * + * // The `_.matches` iteratee shorthand. + * _.filter(users, { 'age': 36, 'active': true }); + * // => objects for ['barney'] + * + * // The `_.matchesProperty` iteratee shorthand. + * _.filter(users, ['active', false]); + * // => objects for ['fred'] + * + * // The `_.property` iteratee shorthand. + * _.filter(users, 'active'); + * // => objects for ['barney'] + */ + function filter(collection, predicate) { + var func = isArray(collection) ? arrayFilter : baseFilter; + return func(collection, getIteratee(predicate, 3)); + } + + /** + * Iterates over elements of `collection`, returning the first element + * `predicate` returns truthy for. The predicate is invoked with three + * arguments: (value, index|key, collection). + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Collection + * @param {Array|Object} collection The collection to inspect. + * @param {Function} [predicate=_.identity] The function invoked per iteration. + * @param {number} [fromIndex=0] The index to search from. + * @returns {*} Returns the matched element, else `undefined`. + * @example + * + * var users = [ + * { 'user': 'barney', 'age': 36, 'active': true }, + * { 'user': 'fred', 'age': 40, 'active': false }, + * { 'user': 'pebbles', 'age': 1, 'active': true } + * ]; + * + * _.find(users, function(o) { return o.age < 40; }); + * // => object for 'barney' + * + * // The `_.matches` iteratee shorthand. + * _.find(users, { 'age': 1, 'active': true }); + * // => object for 'pebbles' + * + * // The `_.matchesProperty` iteratee shorthand. + * _.find(users, ['active', false]); + * // => object for 'fred' + * + * // The `_.property` iteratee shorthand. + * _.find(users, 'active'); + * // => object for 'barney' + */ + var find = createFind(findIndex); + + /** + * This method is like `_.find` except that it iterates over elements of + * `collection` from right to left. + * + * @static + * @memberOf _ + * @since 2.0.0 + * @category Collection + * @param {Array|Object} collection The collection to inspect. + * @param {Function} [predicate=_.identity] The function invoked per iteration. + * @param {number} [fromIndex=collection.length-1] The index to search from. + * @returns {*} Returns the matched element, else `undefined`. + * @example + * + * _.findLast([1, 2, 3, 4], function(n) { + * return n % 2 == 1; + * }); + * // => 3 + */ + var findLast = createFind(findLastIndex); + + /** + * Creates a flattened array of values by running each element in `collection` + * thru `iteratee` and flattening the mapped results. The iteratee is invoked + * with three arguments: (value, index|key, collection). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @returns {Array} Returns the new flattened array. + * @example + * + * function duplicate(n) { + * return [n, n]; + * } + * + * _.flatMap([1, 2], duplicate); + * // => [1, 1, 2, 2] + */ + function flatMap(collection, iteratee) { + return baseFlatten(map(collection, iteratee), 1); + } + + /** + * This method is like `_.flatMap` except that it recursively flattens the + * mapped results. + * + * @static + * @memberOf _ + * @since 4.7.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @returns {Array} Returns the new flattened array. + * @example + * + * function duplicate(n) { + * return [[[n, n]]]; + * } + * + * _.flatMapDeep([1, 2], duplicate); + * // => [1, 1, 2, 2] + */ + function flatMapDeep(collection, iteratee) { + return baseFlatten(map(collection, iteratee), INFINITY); + } + + /** + * This method is like `_.flatMap` except that it recursively flattens the + * mapped results up to `depth` times. + * + * @static + * @memberOf _ + * @since 4.7.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @param {number} [depth=1] The maximum recursion depth. + * @returns {Array} Returns the new flattened array. + * @example + * + * function duplicate(n) { + * return [[[n, n]]]; + * } + * + * _.flatMapDepth([1, 2], duplicate, 2); + * // => [[1, 1], [2, 2]] + */ + function flatMapDepth(collection, iteratee, depth) { + depth = depth === undefined ? 1 : toInteger(depth); + return baseFlatten(map(collection, iteratee), depth); + } + + /** + * Iterates over elements of `collection` and invokes `iteratee` for each element. + * The iteratee is invoked with three arguments: (value, index|key, collection). + * Iteratee functions may exit iteration early by explicitly returning `false`. + * + * **Note:** As with other "Collections" methods, objects with a "length" + * property are iterated like arrays. To avoid this behavior use `_.forIn` + * or `_.forOwn` for object iteration. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @alias each + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @returns {Array|Object} Returns `collection`. + * @see _.forEachRight + * @example + * + * _.forEach([1, 2], function(value) { + * console.log(value); + * }); + * // => Logs `1` then `2`. + * + * _.forEach({ 'a': 1, 'b': 2 }, function(value, key) { + * console.log(key); + * }); + * // => Logs 'a' then 'b' (iteration order is not guaranteed). + */ + function forEach(collection, iteratee) { + var func = isArray(collection) ? arrayEach : baseEach; + return func(collection, getIteratee(iteratee, 3)); + } + + /** + * This method is like `_.forEach` except that it iterates over elements of + * `collection` from right to left. + * + * @static + * @memberOf _ + * @since 2.0.0 + * @alias eachRight + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @returns {Array|Object} Returns `collection`. + * @see _.forEach + * @example + * + * _.forEachRight([1, 2], function(value) { + * console.log(value); + * }); + * // => Logs `2` then `1`. + */ + function forEachRight(collection, iteratee) { + var func = isArray(collection) ? arrayEachRight : baseEachRight; + return func(collection, getIteratee(iteratee, 3)); + } + + /** + * Creates an object composed of keys generated from the results of running + * each element of `collection` thru `iteratee`. The order of grouped values + * is determined by the order they occur in `collection`. The corresponding + * value of each key is an array of elements responsible for generating the + * key. The iteratee is invoked with one argument: (value). + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [iteratee=_.identity] The iteratee to transform keys. + * @returns {Object} Returns the composed aggregate object. + * @example + * + * _.groupBy([6.1, 4.2, 6.3], Math.floor); + * // => { '4': [4.2], '6': [6.1, 6.3] } + * + * // The `_.property` iteratee shorthand. + * _.groupBy(['one', 'two', 'three'], 'length'); + * // => { '3': ['one', 'two'], '5': ['three'] } + */ + var groupBy = createAggregator(function(result, value, key) { + if (hasOwnProperty.call(result, key)) { + result[key].push(value); + } else { + baseAssignValue(result, key, [value]); + } + }); + + /** + * Checks if `value` is in `collection`. If `collection` is a string, it's + * checked for a substring of `value`, otherwise + * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * is used for equality comparisons. If `fromIndex` is negative, it's used as + * the offset from the end of `collection`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Collection + * @param {Array|Object|string} collection The collection to inspect. + * @param {*} value The value to search for. + * @param {number} [fromIndex=0] The index to search from. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.reduce`. + * @returns {boolean} Returns `true` if `value` is found, else `false`. + * @example + * + * _.includes([1, 2, 3], 1); + * // => true + * + * _.includes([1, 2, 3], 1, 2); + * // => false + * + * _.includes({ 'a': 1, 'b': 2 }, 1); + * // => true + * + * _.includes('abcd', 'bc'); + * // => true + */ + function includes(collection, value, fromIndex, guard) { + collection = isArrayLike(collection) ? collection : values(collection); + fromIndex = (fromIndex && !guard) ? toInteger(fromIndex) : 0; + + var length = collection.length; + if (fromIndex < 0) { + fromIndex = nativeMax(length + fromIndex, 0); + } + return isString(collection) + ? (fromIndex <= length && collection.indexOf(value, fromIndex) > -1) + : (!!length && baseIndexOf(collection, value, fromIndex) > -1); + } + + /** + * Invokes the method at `path` of each element in `collection`, returning + * an array of the results of each invoked method. Any additional arguments + * are provided to each invoked method. If `path` is a function, it's invoked + * for, and `this` bound to, each element in `collection`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Array|Function|string} path The path of the method to invoke or + * the function invoked per iteration. + * @param {...*} [args] The arguments to invoke each method with. + * @returns {Array} Returns the array of results. + * @example + * + * _.invokeMap([[5, 1, 7], [3, 2, 1]], 'sort'); + * // => [[1, 5, 7], [1, 2, 3]] + * + * _.invokeMap([123, 456], String.prototype.split, ''); + * // => [['1', '2', '3'], ['4', '5', '6']] + */ + var invokeMap = baseRest(function(collection, path, args) { + var index = -1, + isFunc = typeof path == 'function', + result = isArrayLike(collection) ? Array(collection.length) : []; + + baseEach(collection, function(value) { + result[++index] = isFunc ? apply(path, value, args) : baseInvoke(value, path, args); + }); + return result; + }); + + /** + * Creates an object composed of keys generated from the results of running + * each element of `collection` thru `iteratee`. The corresponding value of + * each key is the last element responsible for generating the key. The + * iteratee is invoked with one argument: (value). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [iteratee=_.identity] The iteratee to transform keys. + * @returns {Object} Returns the composed aggregate object. + * @example + * + * var array = [ + * { 'dir': 'left', 'code': 97 }, + * { 'dir': 'right', 'code': 100 } + * ]; + * + * _.keyBy(array, function(o) { + * return String.fromCharCode(o.code); + * }); + * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } } + * + * _.keyBy(array, 'dir'); + * // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } } + */ + var keyBy = createAggregator(function(result, value, key) { + baseAssignValue(result, key, value); + }); + + /** + * Creates an array of values by running each element in `collection` thru + * `iteratee`. The iteratee is invoked with three arguments: + * (value, index|key, collection). + * + * Many lodash methods are guarded to work as iteratees for methods like + * `_.every`, `_.filter`, `_.map`, `_.mapValues`, `_.reject`, and `_.some`. + * + * The guarded methods are: + * `ary`, `chunk`, `curry`, `curryRight`, `drop`, `dropRight`, `every`, + * `fill`, `invert`, `parseInt`, `random`, `range`, `rangeRight`, `repeat`, + * `sampleSize`, `slice`, `some`, `sortBy`, `split`, `take`, `takeRight`, + * `template`, `trim`, `trimEnd`, `trimStart`, and `words` + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @returns {Array} Returns the new mapped array. + * @example + * + * function square(n) { + * return n * n; + * } + * + * _.map([4, 8], square); + * // => [16, 64] + * + * _.map({ 'a': 4, 'b': 8 }, square); + * // => [16, 64] (iteration order is not guaranteed) + * + * var users = [ + * { 'user': 'barney' }, + * { 'user': 'fred' } + * ]; + * + * // The `_.property` iteratee shorthand. + * _.map(users, 'user'); + * // => ['barney', 'fred'] + */ + function map(collection, iteratee) { + var func = isArray(collection) ? arrayMap : baseMap; + return func(collection, getIteratee(iteratee, 3)); + } + + /** + * This method is like `_.sortBy` except that it allows specifying the sort + * orders of the iteratees to sort by. If `orders` is unspecified, all values + * are sorted in ascending order. Otherwise, specify an order of "desc" for + * descending or "asc" for ascending sort order of corresponding values. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Array[]|Function[]|Object[]|string[]} [iteratees=[_.identity]] + * The iteratees to sort by. + * @param {string[]} [orders] The sort orders of `iteratees`. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.reduce`. + * @returns {Array} Returns the new sorted array. + * @example + * + * var users = [ + * { 'user': 'fred', 'age': 48 }, + * { 'user': 'barney', 'age': 34 }, + * { 'user': 'fred', 'age': 40 }, + * { 'user': 'barney', 'age': 36 } + * ]; + * + * // Sort by `user` in ascending order and by `age` in descending order. + * _.orderBy(users, ['user', 'age'], ['asc', 'desc']); + * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]] + */ + function orderBy(collection, iteratees, orders, guard) { + if (collection == null) { + return []; + } + if (!isArray(iteratees)) { + iteratees = iteratees == null ? [] : [iteratees]; + } + orders = guard ? undefined : orders; + if (!isArray(orders)) { + orders = orders == null ? [] : [orders]; + } + return baseOrderBy(collection, iteratees, orders); + } + + /** + * Creates an array of elements split into two groups, the first of which + * contains elements `predicate` returns truthy for, the second of which + * contains elements `predicate` returns falsey for. The predicate is + * invoked with one argument: (value). + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [predicate=_.identity] The function invoked per iteration. + * @returns {Array} Returns the array of grouped elements. + * @example + * + * var users = [ + * { 'user': 'barney', 'age': 36, 'active': false }, + * { 'user': 'fred', 'age': 40, 'active': true }, + * { 'user': 'pebbles', 'age': 1, 'active': false } + * ]; + * + * _.partition(users, function(o) { return o.active; }); + * // => objects for [['fred'], ['barney', 'pebbles']] + * + * // The `_.matches` iteratee shorthand. + * _.partition(users, { 'age': 1, 'active': false }); + * // => objects for [['pebbles'], ['barney', 'fred']] + * + * // The `_.matchesProperty` iteratee shorthand. + * _.partition(users, ['active', false]); + * // => objects for [['barney', 'pebbles'], ['fred']] + * + * // The `_.property` iteratee shorthand. + * _.partition(users, 'active'); + * // => objects for [['fred'], ['barney', 'pebbles']] + */ + var partition = createAggregator(function(result, value, key) { + result[key ? 0 : 1].push(value); + }, function() { return [[], []]; }); + + /** + * Reduces `collection` to a value which is the accumulated result of running + * each element in `collection` thru `iteratee`, where each successive + * invocation is supplied the return value of the previous. If `accumulator` + * is not given, the first element of `collection` is used as the initial + * value. The iteratee is invoked with four arguments: + * (accumulator, value, index|key, collection). + * + * Many lodash methods are guarded to work as iteratees for methods like + * `_.reduce`, `_.reduceRight`, and `_.transform`. + * + * The guarded methods are: + * `assign`, `defaults`, `defaultsDeep`, `includes`, `merge`, `orderBy`, + * and `sortBy` + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @param {*} [accumulator] The initial value. + * @returns {*} Returns the accumulated value. + * @see _.reduceRight + * @example + * + * _.reduce([1, 2], function(sum, n) { + * return sum + n; + * }, 0); + * // => 3 + * + * _.reduce({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) { + * (result[value] || (result[value] = [])).push(key); + * return result; + * }, {}); + * // => { '1': ['a', 'c'], '2': ['b'] } (iteration order is not guaranteed) + */ + function reduce(collection, iteratee, accumulator) { + var func = isArray(collection) ? arrayReduce : baseReduce, + initAccum = arguments.length < 3; + + return func(collection, getIteratee(iteratee, 4), accumulator, initAccum, baseEach); + } + + /** + * This method is like `_.reduce` except that it iterates over elements of + * `collection` from right to left. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @param {*} [accumulator] The initial value. + * @returns {*} Returns the accumulated value. + * @see _.reduce + * @example + * + * var array = [[0, 1], [2, 3], [4, 5]]; + * + * _.reduceRight(array, function(flattened, other) { + * return flattened.concat(other); + * }, []); + * // => [4, 5, 2, 3, 0, 1] + */ + function reduceRight(collection, iteratee, accumulator) { + var func = isArray(collection) ? arrayReduceRight : baseReduce, + initAccum = arguments.length < 3; + + return func(collection, getIteratee(iteratee, 4), accumulator, initAccum, baseEachRight); + } + + /** + * The opposite of `_.filter`; this method returns the elements of `collection` + * that `predicate` does **not** return truthy for. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [predicate=_.identity] The function invoked per iteration. + * @returns {Array} Returns the new filtered array. + * @see _.filter + * @example + * + * var users = [ + * { 'user': 'barney', 'age': 36, 'active': false }, + * { 'user': 'fred', 'age': 40, 'active': true } + * ]; + * + * _.reject(users, function(o) { return !o.active; }); + * // => objects for ['fred'] + * + * // The `_.matches` iteratee shorthand. + * _.reject(users, { 'age': 40, 'active': true }); + * // => objects for ['barney'] + * + * // The `_.matchesProperty` iteratee shorthand. + * _.reject(users, ['active', false]); + * // => objects for ['fred'] + * + * // The `_.property` iteratee shorthand. + * _.reject(users, 'active'); + * // => objects for ['barney'] + */ + function reject(collection, predicate) { + var func = isArray(collection) ? arrayFilter : baseFilter; + return func(collection, negate(getIteratee(predicate, 3))); + } + + /** + * Gets a random element from `collection`. + * + * @static + * @memberOf _ + * @since 2.0.0 + * @category Collection + * @param {Array|Object} collection The collection to sample. + * @returns {*} Returns the random element. + * @example + * + * _.sample([1, 2, 3, 4]); + * // => 2 + */ + function sample(collection) { + var func = isArray(collection) ? arraySample : baseSample; + return func(collection); + } + + /** + * Gets `n` random elements at unique keys from `collection` up to the + * size of `collection`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Collection + * @param {Array|Object} collection The collection to sample. + * @param {number} [n=1] The number of elements to sample. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {Array} Returns the random elements. + * @example + * + * _.sampleSize([1, 2, 3], 2); + * // => [3, 1] + * + * _.sampleSize([1, 2, 3], 4); + * // => [2, 3, 1] + */ + function sampleSize(collection, n, guard) { + if ((guard ? isIterateeCall(collection, n, guard) : n === undefined)) { + n = 1; + } else { + n = toInteger(n); + } + var func = isArray(collection) ? arraySampleSize : baseSampleSize; + return func(collection, n); + } + + /** + * Creates an array of shuffled values, using a version of the + * [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher-Yates_shuffle). + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Collection + * @param {Array|Object} collection The collection to shuffle. + * @returns {Array} Returns the new shuffled array. + * @example + * + * _.shuffle([1, 2, 3, 4]); + * // => [4, 1, 3, 2] + */ + function shuffle(collection) { + var func = isArray(collection) ? arrayShuffle : baseShuffle; + return func(collection); + } + + /** + * Gets the size of `collection` by returning its length for array-like + * values or the number of own enumerable string keyed properties for objects. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Collection + * @param {Array|Object|string} collection The collection to inspect. + * @returns {number} Returns the collection size. + * @example + * + * _.size([1, 2, 3]); + * // => 3 + * + * _.size({ 'a': 1, 'b': 2 }); + * // => 2 + * + * _.size('pebbles'); + * // => 7 + */ + function size(collection) { + if (collection == null) { + return 0; + } + if (isArrayLike(collection)) { + return isString(collection) ? stringSize(collection) : collection.length; + } + var tag = getTag(collection); + if (tag == mapTag || tag == setTag) { + return collection.size; + } + return baseKeys(collection).length; + } + + /** + * Checks if `predicate` returns truthy for **any** element of `collection`. + * Iteration is stopped once `predicate` returns truthy. The predicate is + * invoked with three arguments: (value, index|key, collection). + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [predicate=_.identity] The function invoked per iteration. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {boolean} Returns `true` if any element passes the predicate check, + * else `false`. + * @example + * + * _.some([null, 0, 'yes', false], Boolean); + * // => true + * + * var users = [ + * { 'user': 'barney', 'active': true }, + * { 'user': 'fred', 'active': false } + * ]; + * + * // The `_.matches` iteratee shorthand. + * _.some(users, { 'user': 'barney', 'active': false }); + * // => false + * + * // The `_.matchesProperty` iteratee shorthand. + * _.some(users, ['active', false]); + * // => true + * + * // The `_.property` iteratee shorthand. + * _.some(users, 'active'); + * // => true + */ + function some(collection, predicate, guard) { + var func = isArray(collection) ? arraySome : baseSome; + if (guard && isIterateeCall(collection, predicate, guard)) { + predicate = undefined; + } + return func(collection, getIteratee(predicate, 3)); + } + + /** + * Creates an array of elements, sorted in ascending order by the results of + * running each element in a collection thru each iteratee. This method + * performs a stable sort, that is, it preserves the original sort order of + * equal elements. The iteratees are invoked with one argument: (value). + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {...(Function|Function[])} [iteratees=[_.identity]] + * The iteratees to sort by. + * @returns {Array} Returns the new sorted array. + * @example + * + * var users = [ + * { 'user': 'fred', 'age': 48 }, + * { 'user': 'barney', 'age': 36 }, + * { 'user': 'fred', 'age': 40 }, + * { 'user': 'barney', 'age': 34 } + * ]; + * + * _.sortBy(users, [function(o) { return o.user; }]); + * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]] + * + * _.sortBy(users, ['user', 'age']); + * // => objects for [['barney', 34], ['barney', 36], ['fred', 40], ['fred', 48]] + */ + var sortBy = baseRest(function(collection, iteratees) { + if (collection == null) { + return []; + } + var length = iteratees.length; + if (length > 1 && isIterateeCall(collection, iteratees[0], iteratees[1])) { + iteratees = []; + } else if (length > 2 && isIterateeCall(iteratees[0], iteratees[1], iteratees[2])) { + iteratees = [iteratees[0]]; + } + return baseOrderBy(collection, baseFlatten(iteratees, 1), []); + }); + + /*------------------------------------------------------------------------*/ + + /** + * Gets the timestamp of the number of milliseconds that have elapsed since + * the Unix epoch (1 January 1970 00:00:00 UTC). + * + * @static + * @memberOf _ + * @since 2.4.0 + * @category Date + * @returns {number} Returns the timestamp. + * @example + * + * _.defer(function(stamp) { + * console.log(_.now() - stamp); + * }, _.now()); + * // => Logs the number of milliseconds it took for the deferred invocation. + */ + var now = ctxNow || function() { + return root.Date.now(); + }; + + /*------------------------------------------------------------------------*/ + + /** + * The opposite of `_.before`; this method creates a function that invokes + * `func` once it's called `n` or more times. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Function + * @param {number} n The number of calls before `func` is invoked. + * @param {Function} func The function to restrict. + * @returns {Function} Returns the new restricted function. + * @example + * + * var saves = ['profile', 'settings']; + * + * var done = _.after(saves.length, function() { + * console.log('done saving!'); + * }); + * + * _.forEach(saves, function(type) { + * asyncSave({ 'type': type, 'complete': done }); + * }); + * // => Logs 'done saving!' after the two async saves have completed. + */ + function after(n, func) { + if (typeof func != 'function') { + throw new TypeError(FUNC_ERROR_TEXT); + } + n = toInteger(n); + return function() { + if (--n < 1) { + return func.apply(this, arguments); + } + }; + } + + /** + * Creates a function that invokes `func`, with up to `n` arguments, + * ignoring any additional arguments. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Function + * @param {Function} func The function to cap arguments for. + * @param {number} [n=func.length] The arity cap. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {Function} Returns the new capped function. + * @example + * + * _.map(['6', '8', '10'], _.ary(parseInt, 1)); + * // => [6, 8, 10] + */ + function ary(func, n, guard) { + n = guard ? undefined : n; + n = (func && n == null) ? func.length : n; + return createWrap(func, WRAP_ARY_FLAG, undefined, undefined, undefined, undefined, n); + } + + /** + * Creates a function that invokes `func`, with the `this` binding and arguments + * of the created function, while it's called less than `n` times. Subsequent + * calls to the created function return the result of the last `func` invocation. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Function + * @param {number} n The number of calls at which `func` is no longer invoked. + * @param {Function} func The function to restrict. + * @returns {Function} Returns the new restricted function. + * @example + * + * jQuery(element).on('click', _.before(5, addContactToList)); + * // => Allows adding up to 4 contacts to the list. + */ + function before(n, func) { + var result; + if (typeof func != 'function') { + throw new TypeError(FUNC_ERROR_TEXT); + } + n = toInteger(n); + return function() { + if (--n > 0) { + result = func.apply(this, arguments); + } + if (n <= 1) { + func = undefined; + } + return result; + }; + } + + /** + * Creates a function that invokes `func` with the `this` binding of `thisArg` + * and `partials` prepended to the arguments it receives. + * + * The `_.bind.placeholder` value, which defaults to `_` in monolithic builds, + * may be used as a placeholder for partially applied arguments. + * + * **Note:** Unlike native `Function#bind`, this method doesn't set the "length" + * property of bound functions. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Function + * @param {Function} func The function to bind. + * @param {*} thisArg The `this` binding of `func`. + * @param {...*} [partials] The arguments to be partially applied. + * @returns {Function} Returns the new bound function. + * @example + * + * function greet(greeting, punctuation) { + * return greeting + ' ' + this.user + punctuation; + * } + * + * var object = { 'user': 'fred' }; + * + * var bound = _.bind(greet, object, 'hi'); + * bound('!'); + * // => 'hi fred!' + * + * // Bound with placeholders. + * var bound = _.bind(greet, object, _, '!'); + * bound('hi'); + * // => 'hi fred!' + */ + var bind = baseRest(function(func, thisArg, partials) { + var bitmask = WRAP_BIND_FLAG; + if (partials.length) { + var holders = replaceHolders(partials, getHolder(bind)); + bitmask |= WRAP_PARTIAL_FLAG; + } + return createWrap(func, bitmask, thisArg, partials, holders); + }); + + /** + * Creates a function that invokes the method at `object[key]` with `partials` + * prepended to the arguments it receives. + * + * This method differs from `_.bind` by allowing bound functions to reference + * methods that may be redefined or don't yet exist. See + * [Peter Michaux's article](http://peter.michaux.ca/articles/lazy-function-definition-pattern) + * for more details. + * + * The `_.bindKey.placeholder` value, which defaults to `_` in monolithic + * builds, may be used as a placeholder for partially applied arguments. + * + * @static + * @memberOf _ + * @since 0.10.0 + * @category Function + * @param {Object} object The object to invoke the method on. + * @param {string} key The key of the method. + * @param {...*} [partials] The arguments to be partially applied. + * @returns {Function} Returns the new bound function. + * @example + * + * var object = { + * 'user': 'fred', + * 'greet': function(greeting, punctuation) { + * return greeting + ' ' + this.user + punctuation; + * } + * }; + * + * var bound = _.bindKey(object, 'greet', 'hi'); + * bound('!'); + * // => 'hi fred!' + * + * object.greet = function(greeting, punctuation) { + * return greeting + 'ya ' + this.user + punctuation; + * }; + * + * bound('!'); + * // => 'hiya fred!' + * + * // Bound with placeholders. + * var bound = _.bindKey(object, 'greet', _, '!'); + * bound('hi'); + * // => 'hiya fred!' + */ + var bindKey = baseRest(function(object, key, partials) { + var bitmask = WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG; + if (partials.length) { + var holders = replaceHolders(partials, getHolder(bindKey)); + bitmask |= WRAP_PARTIAL_FLAG; + } + return createWrap(key, bitmask, object, partials, holders); + }); + + /** + * Creates a function that accepts arguments of `func` and either invokes + * `func` returning its result, if at least `arity` number of arguments have + * been provided, or returns a function that accepts the remaining `func` + * arguments, and so on. The arity of `func` may be specified if `func.length` + * is not sufficient. + * + * The `_.curry.placeholder` value, which defaults to `_` in monolithic builds, + * may be used as a placeholder for provided arguments. + * + * **Note:** This method doesn't set the "length" property of curried functions. + * + * @static + * @memberOf _ + * @since 2.0.0 + * @category Function + * @param {Function} func The function to curry. + * @param {number} [arity=func.length] The arity of `func`. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {Function} Returns the new curried function. + * @example + * + * var abc = function(a, b, c) { + * return [a, b, c]; + * }; + * + * var curried = _.curry(abc); + * + * curried(1)(2)(3); + * // => [1, 2, 3] + * + * curried(1, 2)(3); + * // => [1, 2, 3] + * + * curried(1, 2, 3); + * // => [1, 2, 3] + * + * // Curried with placeholders. + * curried(1)(_, 3)(2); + * // => [1, 2, 3] + */ + function curry(func, arity, guard) { + arity = guard ? undefined : arity; + var result = createWrap(func, WRAP_CURRY_FLAG, undefined, undefined, undefined, undefined, undefined, arity); + result.placeholder = curry.placeholder; + return result; + } + + /** + * This method is like `_.curry` except that arguments are applied to `func` + * in the manner of `_.partialRight` instead of `_.partial`. + * + * The `_.curryRight.placeholder` value, which defaults to `_` in monolithic + * builds, may be used as a placeholder for provided arguments. + * + * **Note:** This method doesn't set the "length" property of curried functions. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Function + * @param {Function} func The function to curry. + * @param {number} [arity=func.length] The arity of `func`. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {Function} Returns the new curried function. + * @example + * + * var abc = function(a, b, c) { + * return [a, b, c]; + * }; + * + * var curried = _.curryRight(abc); + * + * curried(3)(2)(1); + * // => [1, 2, 3] + * + * curried(2, 3)(1); + * // => [1, 2, 3] + * + * curried(1, 2, 3); + * // => [1, 2, 3] + * + * // Curried with placeholders. + * curried(3)(1, _)(2); + * // => [1, 2, 3] + */ + function curryRight(func, arity, guard) { + arity = guard ? undefined : arity; + var result = createWrap(func, WRAP_CURRY_RIGHT_FLAG, undefined, undefined, undefined, undefined, undefined, arity); + result.placeholder = curryRight.placeholder; + return result; + } + + /** + * Creates a debounced function that delays invoking `func` until after `wait` + * milliseconds have elapsed since the last time the debounced function was + * invoked. The debounced function comes with a `cancel` method to cancel + * delayed `func` invocations and a `flush` method to immediately invoke them. + * Provide `options` to indicate whether `func` should be invoked on the + * leading and/or trailing edge of the `wait` timeout. The `func` is invoked + * with the last arguments provided to the debounced function. Subsequent + * calls to the debounced function return the result of the last `func` + * invocation. + * + * **Note:** If `leading` and `trailing` options are `true`, `func` is + * invoked on the trailing edge of the timeout only if the debounced function + * is invoked more than once during the `wait` timeout. + * + * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred + * until to the next tick, similar to `setTimeout` with a timeout of `0`. + * + * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/) + * for details over the differences between `_.debounce` and `_.throttle`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Function + * @param {Function} func The function to debounce. + * @param {number} [wait=0] The number of milliseconds to delay. + * @param {Object} [options={}] The options object. + * @param {boolean} [options.leading=false] + * Specify invoking on the leading edge of the timeout. + * @param {number} [options.maxWait] + * The maximum time `func` is allowed to be delayed before it's invoked. + * @param {boolean} [options.trailing=true] + * Specify invoking on the trailing edge of the timeout. + * @returns {Function} Returns the new debounced function. + * @example + * + * // Avoid costly calculations while the window size is in flux. + * jQuery(window).on('resize', _.debounce(calculateLayout, 150)); + * + * // Invoke `sendMail` when clicked, debouncing subsequent calls. + * jQuery(element).on('click', _.debounce(sendMail, 300, { + * 'leading': true, + * 'trailing': false + * })); + * + * // Ensure `batchLog` is invoked once after 1 second of debounced calls. + * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 }); + * var source = new EventSource('/stream'); + * jQuery(source).on('message', debounced); + * + * // Cancel the trailing debounced invocation. + * jQuery(window).on('popstate', debounced.cancel); + */ + function debounce(func, wait, options) { + var lastArgs, + lastThis, + maxWait, + result, + timerId, + lastCallTime, + lastInvokeTime = 0, + leading = false, + maxing = false, + trailing = true; + + if (typeof func != 'function') { + throw new TypeError(FUNC_ERROR_TEXT); + } + wait = toNumber(wait) || 0; + if (isObject(options)) { + leading = !!options.leading; + maxing = 'maxWait' in options; + maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait; + trailing = 'trailing' in options ? !!options.trailing : trailing; + } + + function invokeFunc(time) { + var args = lastArgs, + thisArg = lastThis; + + lastArgs = lastThis = undefined; + lastInvokeTime = time; + result = func.apply(thisArg, args); + return result; + } + + function leadingEdge(time) { + // Reset any `maxWait` timer. + lastInvokeTime = time; + // Start the timer for the trailing edge. + timerId = setTimeout(timerExpired, wait); + // Invoke the leading edge. + return leading ? invokeFunc(time) : result; + } + + function remainingWait(time) { + var timeSinceLastCall = time - lastCallTime, + timeSinceLastInvoke = time - lastInvokeTime, + timeWaiting = wait - timeSinceLastCall; + + return maxing + ? nativeMin(timeWaiting, maxWait - timeSinceLastInvoke) + : timeWaiting; + } + + function shouldInvoke(time) { + var timeSinceLastCall = time - lastCallTime, + timeSinceLastInvoke = time - lastInvokeTime; + + // Either this is the first call, activity has stopped and we're at the + // trailing edge, the system time has gone backwards and we're treating + // it as the trailing edge, or we've hit the `maxWait` limit. + return (lastCallTime === undefined || (timeSinceLastCall >= wait) || + (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait)); + } + + function timerExpired() { + var time = now(); + if (shouldInvoke(time)) { + return trailingEdge(time); + } + // Restart the timer. + timerId = setTimeout(timerExpired, remainingWait(time)); + } + + function trailingEdge(time) { + timerId = undefined; + + // Only invoke if we have `lastArgs` which means `func` has been + // debounced at least once. + if (trailing && lastArgs) { + return invokeFunc(time); + } + lastArgs = lastThis = undefined; + return result; + } + + function cancel() { + if (timerId !== undefined) { + clearTimeout(timerId); + } + lastInvokeTime = 0; + lastArgs = lastCallTime = lastThis = timerId = undefined; + } + + function flush() { + return timerId === undefined ? result : trailingEdge(now()); + } + + function debounced() { + var time = now(), + isInvoking = shouldInvoke(time); + + lastArgs = arguments; + lastThis = this; + lastCallTime = time; + + if (isInvoking) { + if (timerId === undefined) { + return leadingEdge(lastCallTime); + } + if (maxing) { + // Handle invocations in a tight loop. + timerId = setTimeout(timerExpired, wait); + return invokeFunc(lastCallTime); + } + } + if (timerId === undefined) { + timerId = setTimeout(timerExpired, wait); + } + return result; + } + debounced.cancel = cancel; + debounced.flush = flush; + return debounced; + } + + /** + * Defers invoking the `func` until the current call stack has cleared. Any + * additional arguments are provided to `func` when it's invoked. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Function + * @param {Function} func The function to defer. + * @param {...*} [args] The arguments to invoke `func` with. + * @returns {number} Returns the timer id. + * @example + * + * _.defer(function(text) { + * console.log(text); + * }, 'deferred'); + * // => Logs 'deferred' after one millisecond. + */ + var defer = baseRest(function(func, args) { + return baseDelay(func, 1, args); + }); + + /** + * Invokes `func` after `wait` milliseconds. Any additional arguments are + * provided to `func` when it's invoked. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Function + * @param {Function} func The function to delay. + * @param {number} wait The number of milliseconds to delay invocation. + * @param {...*} [args] The arguments to invoke `func` with. + * @returns {number} Returns the timer id. + * @example + * + * _.delay(function(text) { + * console.log(text); + * }, 1000, 'later'); + * // => Logs 'later' after one second. + */ + var delay = baseRest(function(func, wait, args) { + return baseDelay(func, toNumber(wait) || 0, args); + }); + + /** + * Creates a function that invokes `func` with arguments reversed. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Function + * @param {Function} func The function to flip arguments for. + * @returns {Function} Returns the new flipped function. + * @example + * + * var flipped = _.flip(function() { + * return _.toArray(arguments); + * }); + * + * flipped('a', 'b', 'c', 'd'); + * // => ['d', 'c', 'b', 'a'] + */ + function flip(func) { + return createWrap(func, WRAP_FLIP_FLAG); + } + + /** + * Creates a function that memoizes the result of `func`. If `resolver` is + * provided, it determines the cache key for storing the result based on the + * arguments provided to the memoized function. By default, the first argument + * provided to the memoized function is used as the map cache key. The `func` + * is invoked with the `this` binding of the memoized function. + * + * **Note:** The cache is exposed as the `cache` property on the memoized + * function. Its creation may be customized by replacing the `_.memoize.Cache` + * constructor with one whose instances implement the + * [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object) + * method interface of `clear`, `delete`, `get`, `has`, and `set`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Function + * @param {Function} func The function to have its output memoized. + * @param {Function} [resolver] The function to resolve the cache key. + * @returns {Function} Returns the new memoized function. + * @example + * + * var object = { 'a': 1, 'b': 2 }; + * var other = { 'c': 3, 'd': 4 }; + * + * var values = _.memoize(_.values); + * values(object); + * // => [1, 2] + * + * values(other); + * // => [3, 4] + * + * object.a = 2; + * values(object); + * // => [1, 2] + * + * // Modify the result cache. + * values.cache.set(object, ['a', 'b']); + * values(object); + * // => ['a', 'b'] + * + * // Replace `_.memoize.Cache`. + * _.memoize.Cache = WeakMap; + */ + function memoize(func, resolver) { + if (typeof func != 'function' || (resolver != null && typeof resolver != 'function')) { + throw new TypeError(FUNC_ERROR_TEXT); + } + var memoized = function() { + var args = arguments, + key = resolver ? resolver.apply(this, args) : args[0], + cache = memoized.cache; + + if (cache.has(key)) { + return cache.get(key); + } + var result = func.apply(this, args); + memoized.cache = cache.set(key, result) || cache; + return result; + }; + memoized.cache = new (memoize.Cache || MapCache); + return memoized; + } + + // Expose `MapCache`. + memoize.Cache = MapCache; + + /** + * Creates a function that negates the result of the predicate `func`. The + * `func` predicate is invoked with the `this` binding and arguments of the + * created function. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Function + * @param {Function} predicate The predicate to negate. + * @returns {Function} Returns the new negated function. + * @example + * + * function isEven(n) { + * return n % 2 == 0; + * } + * + * _.filter([1, 2, 3, 4, 5, 6], _.negate(isEven)); + * // => [1, 3, 5] + */ + function negate(predicate) { + if (typeof predicate != 'function') { + throw new TypeError(FUNC_ERROR_TEXT); + } + return function() { + var args = arguments; + switch (args.length) { + case 0: return !predicate.call(this); + case 1: return !predicate.call(this, args[0]); + case 2: return !predicate.call(this, args[0], args[1]); + case 3: return !predicate.call(this, args[0], args[1], args[2]); + } + return !predicate.apply(this, args); + }; + } + + /** + * Creates a function that is restricted to invoking `func` once. Repeat calls + * to the function return the value of the first invocation. The `func` is + * invoked with the `this` binding and arguments of the created function. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Function + * @param {Function} func The function to restrict. + * @returns {Function} Returns the new restricted function. + * @example + * + * var initialize = _.once(createApplication); + * initialize(); + * initialize(); + * // => `createApplication` is invoked once + */ + function once(func) { + return before(2, func); + } + + /** + * Creates a function that invokes `func` with its arguments transformed. + * + * @static + * @since 4.0.0 + * @memberOf _ + * @category Function + * @param {Function} func The function to wrap. + * @param {...(Function|Function[])} [transforms=[_.identity]] + * The argument transforms. + * @returns {Function} Returns the new function. + * @example + * + * function doubled(n) { + * return n * 2; + * } + * + * function square(n) { + * return n * n; + * } + * + * var func = _.overArgs(function(x, y) { + * return [x, y]; + * }, [square, doubled]); + * + * func(9, 3); + * // => [81, 6] + * + * func(10, 5); + * // => [100, 10] + */ + var overArgs = castRest(function(func, transforms) { + transforms = (transforms.length == 1 && isArray(transforms[0])) + ? arrayMap(transforms[0], baseUnary(getIteratee())) + : arrayMap(baseFlatten(transforms, 1), baseUnary(getIteratee())); + + var funcsLength = transforms.length; + return baseRest(function(args) { + var index = -1, + length = nativeMin(args.length, funcsLength); + + while (++index < length) { + args[index] = transforms[index].call(this, args[index]); + } + return apply(func, this, args); + }); + }); + + /** + * Creates a function that invokes `func` with `partials` prepended to the + * arguments it receives. This method is like `_.bind` except it does **not** + * alter the `this` binding. + * + * The `_.partial.placeholder` value, which defaults to `_` in monolithic + * builds, may be used as a placeholder for partially applied arguments. + * + * **Note:** This method doesn't set the "length" property of partially + * applied functions. + * + * @static + * @memberOf _ + * @since 0.2.0 + * @category Function + * @param {Function} func The function to partially apply arguments to. + * @param {...*} [partials] The arguments to be partially applied. + * @returns {Function} Returns the new partially applied function. + * @example + * + * function greet(greeting, name) { + * return greeting + ' ' + name; + * } + * + * var sayHelloTo = _.partial(greet, 'hello'); + * sayHelloTo('fred'); + * // => 'hello fred' + * + * // Partially applied with placeholders. + * var greetFred = _.partial(greet, _, 'fred'); + * greetFred('hi'); + * // => 'hi fred' + */ + var partial = baseRest(function(func, partials) { + var holders = replaceHolders(partials, getHolder(partial)); + return createWrap(func, WRAP_PARTIAL_FLAG, undefined, partials, holders); + }); + + /** + * This method is like `_.partial` except that partially applied arguments + * are appended to the arguments it receives. + * + * The `_.partialRight.placeholder` value, which defaults to `_` in monolithic + * builds, may be used as a placeholder for partially applied arguments. + * + * **Note:** This method doesn't set the "length" property of partially + * applied functions. + * + * @static + * @memberOf _ + * @since 1.0.0 + * @category Function + * @param {Function} func The function to partially apply arguments to. + * @param {...*} [partials] The arguments to be partially applied. + * @returns {Function} Returns the new partially applied function. + * @example + * + * function greet(greeting, name) { + * return greeting + ' ' + name; + * } + * + * var greetFred = _.partialRight(greet, 'fred'); + * greetFred('hi'); + * // => 'hi fred' + * + * // Partially applied with placeholders. + * var sayHelloTo = _.partialRight(greet, 'hello', _); + * sayHelloTo('fred'); + * // => 'hello fred' + */ + var partialRight = baseRest(function(func, partials) { + var holders = replaceHolders(partials, getHolder(partialRight)); + return createWrap(func, WRAP_PARTIAL_RIGHT_FLAG, undefined, partials, holders); + }); + + /** + * Creates a function that invokes `func` with arguments arranged according + * to the specified `indexes` where the argument value at the first index is + * provided as the first argument, the argument value at the second index is + * provided as the second argument, and so on. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Function + * @param {Function} func The function to rearrange arguments for. + * @param {...(number|number[])} indexes The arranged argument indexes. + * @returns {Function} Returns the new function. + * @example + * + * var rearged = _.rearg(function(a, b, c) { + * return [a, b, c]; + * }, [2, 0, 1]); + * + * rearged('b', 'c', 'a') + * // => ['a', 'b', 'c'] + */ + var rearg = flatRest(function(func, indexes) { + return createWrap(func, WRAP_REARG_FLAG, undefined, undefined, undefined, indexes); + }); + + /** + * Creates a function that invokes `func` with the `this` binding of the + * created function and arguments from `start` and beyond provided as + * an array. + * + * **Note:** This method is based on the + * [rest parameter](https://mdn.io/rest_parameters). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Function + * @param {Function} func The function to apply a rest parameter to. + * @param {number} [start=func.length-1] The start position of the rest parameter. + * @returns {Function} Returns the new function. + * @example + * + * var say = _.rest(function(what, names) { + * return what + ' ' + _.initial(names).join(', ') + + * (_.size(names) > 1 ? ', & ' : '') + _.last(names); + * }); + * + * say('hello', 'fred', 'barney', 'pebbles'); + * // => 'hello fred, barney, & pebbles' + */ + function rest(func, start) { + if (typeof func != 'function') { + throw new TypeError(FUNC_ERROR_TEXT); + } + start = start === undefined ? start : toInteger(start); + return baseRest(func, start); + } + + /** + * Creates a function that invokes `func` with the `this` binding of the + * create function and an array of arguments much like + * [`Function#apply`](http://www.ecma-international.org/ecma-262/7.0/#sec-function.prototype.apply). + * + * **Note:** This method is based on the + * [spread operator](https://mdn.io/spread_operator). + * + * @static + * @memberOf _ + * @since 3.2.0 + * @category Function + * @param {Function} func The function to spread arguments over. + * @param {number} [start=0] The start position of the spread. + * @returns {Function} Returns the new function. + * @example + * + * var say = _.spread(function(who, what) { + * return who + ' says ' + what; + * }); + * + * say(['fred', 'hello']); + * // => 'fred says hello' + * + * var numbers = Promise.all([ + * Promise.resolve(40), + * Promise.resolve(36) + * ]); + * + * numbers.then(_.spread(function(x, y) { + * return x + y; + * })); + * // => a Promise of 76 + */ + function spread(func, start) { + if (typeof func != 'function') { + throw new TypeError(FUNC_ERROR_TEXT); + } + start = start == null ? 0 : nativeMax(toInteger(start), 0); + return baseRest(function(args) { + var array = args[start], + otherArgs = castSlice(args, 0, start); + + if (array) { + arrayPush(otherArgs, array); + } + return apply(func, this, otherArgs); + }); + } + + /** + * Creates a throttled function that only invokes `func` at most once per + * every `wait` milliseconds. The throttled function comes with a `cancel` + * method to cancel delayed `func` invocations and a `flush` method to + * immediately invoke them. Provide `options` to indicate whether `func` + * should be invoked on the leading and/or trailing edge of the `wait` + * timeout. The `func` is invoked with the last arguments provided to the + * throttled function. Subsequent calls to the throttled function return the + * result of the last `func` invocation. + * + * **Note:** If `leading` and `trailing` options are `true`, `func` is + * invoked on the trailing edge of the timeout only if the throttled function + * is invoked more than once during the `wait` timeout. + * + * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred + * until to the next tick, similar to `setTimeout` with a timeout of `0`. + * + * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/) + * for details over the differences between `_.throttle` and `_.debounce`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Function + * @param {Function} func The function to throttle. + * @param {number} [wait=0] The number of milliseconds to throttle invocations to. + * @param {Object} [options={}] The options object. + * @param {boolean} [options.leading=true] + * Specify invoking on the leading edge of the timeout. + * @param {boolean} [options.trailing=true] + * Specify invoking on the trailing edge of the timeout. + * @returns {Function} Returns the new throttled function. + * @example + * + * // Avoid excessively updating the position while scrolling. + * jQuery(window).on('scroll', _.throttle(updatePosition, 100)); + * + * // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes. + * var throttled = _.throttle(renewToken, 300000, { 'trailing': false }); + * jQuery(element).on('click', throttled); + * + * // Cancel the trailing throttled invocation. + * jQuery(window).on('popstate', throttled.cancel); + */ + function throttle(func, wait, options) { + var leading = true, + trailing = true; + + if (typeof func != 'function') { + throw new TypeError(FUNC_ERROR_TEXT); + } + if (isObject(options)) { + leading = 'leading' in options ? !!options.leading : leading; + trailing = 'trailing' in options ? !!options.trailing : trailing; + } + return debounce(func, wait, { + 'leading': leading, + 'maxWait': wait, + 'trailing': trailing + }); + } + + /** + * Creates a function that accepts up to one argument, ignoring any + * additional arguments. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Function + * @param {Function} func The function to cap arguments for. + * @returns {Function} Returns the new capped function. + * @example + * + * _.map(['6', '8', '10'], _.unary(parseInt)); + * // => [6, 8, 10] + */ + function unary(func) { + return ary(func, 1); + } + + /** + * Creates a function that provides `value` to `wrapper` as its first + * argument. Any additional arguments provided to the function are appended + * to those provided to the `wrapper`. The wrapper is invoked with the `this` + * binding of the created function. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Function + * @param {*} value The value to wrap. + * @param {Function} [wrapper=identity] The wrapper function. + * @returns {Function} Returns the new function. + * @example + * + * var p = _.wrap(_.escape, function(func, text) { + * return '

' + func(text) + '

'; + * }); + * + * p('fred, barney, & pebbles'); + * // => '

fred, barney, & pebbles

' + */ + function wrap(value, wrapper) { + return partial(castFunction(wrapper), value); + } + + /*------------------------------------------------------------------------*/ + + /** + * Casts `value` as an array if it's not one. + * + * @static + * @memberOf _ + * @since 4.4.0 + * @category Lang + * @param {*} value The value to inspect. + * @returns {Array} Returns the cast array. + * @example + * + * _.castArray(1); + * // => [1] + * + * _.castArray({ 'a': 1 }); + * // => [{ 'a': 1 }] + * + * _.castArray('abc'); + * // => ['abc'] + * + * _.castArray(null); + * // => [null] + * + * _.castArray(undefined); + * // => [undefined] + * + * _.castArray(); + * // => [] + * + * var array = [1, 2, 3]; + * console.log(_.castArray(array) === array); + * // => true + */ + function castArray() { + if (!arguments.length) { + return []; + } + var value = arguments[0]; + return isArray(value) ? value : [value]; + } + + /** + * Creates a shallow clone of `value`. + * + * **Note:** This method is loosely based on the + * [structured clone algorithm](https://mdn.io/Structured_clone_algorithm) + * and supports cloning arrays, array buffers, booleans, date objects, maps, + * numbers, `Object` objects, regexes, sets, strings, symbols, and typed + * arrays. The own enumerable properties of `arguments` objects are cloned + * as plain objects. An empty object is returned for uncloneable values such + * as error objects, functions, DOM nodes, and WeakMaps. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to clone. + * @returns {*} Returns the cloned value. + * @see _.cloneDeep + * @example + * + * var objects = [{ 'a': 1 }, { 'b': 2 }]; + * + * var shallow = _.clone(objects); + * console.log(shallow[0] === objects[0]); + * // => true + */ + function clone(value) { + return baseClone(value, CLONE_SYMBOLS_FLAG); + } + + /** + * This method is like `_.clone` except that it accepts `customizer` which + * is invoked to produce the cloned value. If `customizer` returns `undefined`, + * cloning is handled by the method instead. The `customizer` is invoked with + * up to four arguments; (value [, index|key, object, stack]). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to clone. + * @param {Function} [customizer] The function to customize cloning. + * @returns {*} Returns the cloned value. + * @see _.cloneDeepWith + * @example + * + * function customizer(value) { + * if (_.isElement(value)) { + * return value.cloneNode(false); + * } + * } + * + * var el = _.cloneWith(document.body, customizer); + * + * console.log(el === document.body); + * // => false + * console.log(el.nodeName); + * // => 'BODY' + * console.log(el.childNodes.length); + * // => 0 + */ + function cloneWith(value, customizer) { + customizer = typeof customizer == 'function' ? customizer : undefined; + return baseClone(value, CLONE_SYMBOLS_FLAG, customizer); + } + + /** + * This method is like `_.clone` except that it recursively clones `value`. + * + * @static + * @memberOf _ + * @since 1.0.0 + * @category Lang + * @param {*} value The value to recursively clone. + * @returns {*} Returns the deep cloned value. + * @see _.clone + * @example + * + * var objects = [{ 'a': 1 }, { 'b': 2 }]; + * + * var deep = _.cloneDeep(objects); + * console.log(deep[0] === objects[0]); + * // => false + */ + function cloneDeep(value) { + return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG); + } + + /** + * This method is like `_.cloneWith` except that it recursively clones `value`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to recursively clone. + * @param {Function} [customizer] The function to customize cloning. + * @returns {*} Returns the deep cloned value. + * @see _.cloneWith + * @example + * + * function customizer(value) { + * if (_.isElement(value)) { + * return value.cloneNode(true); + * } + * } + * + * var el = _.cloneDeepWith(document.body, customizer); + * + * console.log(el === document.body); + * // => false + * console.log(el.nodeName); + * // => 'BODY' + * console.log(el.childNodes.length); + * // => 20 + */ + function cloneDeepWith(value, customizer) { + customizer = typeof customizer == 'function' ? customizer : undefined; + return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG, customizer); + } + + /** + * Checks if `object` conforms to `source` by invoking the predicate + * properties of `source` with the corresponding property values of `object`. + * + * **Note:** This method is equivalent to `_.conforms` when `source` is + * partially applied. + * + * @static + * @memberOf _ + * @since 4.14.0 + * @category Lang + * @param {Object} object The object to inspect. + * @param {Object} source The object of property predicates to conform to. + * @returns {boolean} Returns `true` if `object` conforms, else `false`. + * @example + * + * var object = { 'a': 1, 'b': 2 }; + * + * _.conformsTo(object, { 'b': function(n) { return n > 1; } }); + * // => true + * + * _.conformsTo(object, { 'b': function(n) { return n > 2; } }); + * // => false + */ + function conformsTo(object, source) { + return source == null || baseConformsTo(object, source, keys(source)); + } + + /** + * Performs a + * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * comparison between two values to determine if they are equivalent. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. + * @example + * + * var object = { 'a': 1 }; + * var other = { 'a': 1 }; + * + * _.eq(object, object); + * // => true + * + * _.eq(object, other); + * // => false + * + * _.eq('a', 'a'); + * // => true + * + * _.eq('a', Object('a')); + * // => false + * + * _.eq(NaN, NaN); + * // => true + */ + function eq(value, other) { + return value === other || (value !== value && other !== other); + } + + /** + * Checks if `value` is greater than `other`. + * + * @static + * @memberOf _ + * @since 3.9.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if `value` is greater than `other`, + * else `false`. + * @see _.lt + * @example + * + * _.gt(3, 1); + * // => true + * + * _.gt(3, 3); + * // => false + * + * _.gt(1, 3); + * // => false + */ + var gt = createRelationalOperation(baseGt); + + /** + * Checks if `value` is greater than or equal to `other`. + * + * @static + * @memberOf _ + * @since 3.9.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if `value` is greater than or equal to + * `other`, else `false`. + * @see _.lte + * @example + * + * _.gte(3, 1); + * // => true + * + * _.gte(3, 3); + * // => true + * + * _.gte(1, 3); + * // => false + */ + var gte = createRelationalOperation(function(value, other) { + return value >= other; + }); + + /** + * Checks if `value` is likely an `arguments` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an `arguments` object, + * else `false`. + * @example + * + * _.isArguments(function() { return arguments; }()); + * // => true + * + * _.isArguments([1, 2, 3]); + * // => false + */ + var isArguments = baseIsArguments(function() { return arguments; }()) ? baseIsArguments : function(value) { + return isObjectLike(value) && hasOwnProperty.call(value, 'callee') && + !propertyIsEnumerable.call(value, 'callee'); + }; + + /** + * Checks if `value` is classified as an `Array` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an array, else `false`. + * @example + * + * _.isArray([1, 2, 3]); + * // => true + * + * _.isArray(document.body.children); + * // => false + * + * _.isArray('abc'); + * // => false + * + * _.isArray(_.noop); + * // => false + */ + var isArray = Array.isArray; + + /** + * Checks if `value` is classified as an `ArrayBuffer` object. + * + * @static + * @memberOf _ + * @since 4.3.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an array buffer, else `false`. + * @example + * + * _.isArrayBuffer(new ArrayBuffer(2)); + * // => true + * + * _.isArrayBuffer(new Array(2)); + * // => false + */ + var isArrayBuffer = nodeIsArrayBuffer ? baseUnary(nodeIsArrayBuffer) : baseIsArrayBuffer; + + /** + * Checks if `value` is array-like. A value is considered array-like if it's + * not a function and has a `value.length` that's an integer greater than or + * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is array-like, else `false`. + * @example + * + * _.isArrayLike([1, 2, 3]); + * // => true + * + * _.isArrayLike(document.body.children); + * // => true + * + * _.isArrayLike('abc'); + * // => true + * + * _.isArrayLike(_.noop); + * // => false + */ + function isArrayLike(value) { + return value != null && isLength(value.length) && !isFunction(value); + } + + /** + * This method is like `_.isArrayLike` except that it also checks if `value` + * is an object. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an array-like object, + * else `false`. + * @example + * + * _.isArrayLikeObject([1, 2, 3]); + * // => true + * + * _.isArrayLikeObject(document.body.children); + * // => true + * + * _.isArrayLikeObject('abc'); + * // => false + * + * _.isArrayLikeObject(_.noop); + * // => false + */ + function isArrayLikeObject(value) { + return isObjectLike(value) && isArrayLike(value); + } + + /** + * Checks if `value` is classified as a boolean primitive or object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a boolean, else `false`. + * @example + * + * _.isBoolean(false); + * // => true + * + * _.isBoolean(null); + * // => false + */ + function isBoolean(value) { + return value === true || value === false || + (isObjectLike(value) && baseGetTag(value) == boolTag); + } + + /** + * Checks if `value` is a buffer. + * + * @static + * @memberOf _ + * @since 4.3.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a buffer, else `false`. + * @example + * + * _.isBuffer(new Buffer(2)); + * // => true + * + * _.isBuffer(new Uint8Array(2)); + * // => false + */ + var isBuffer = nativeIsBuffer || stubFalse; + + /** + * Checks if `value` is classified as a `Date` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a date object, else `false`. + * @example + * + * _.isDate(new Date); + * // => true + * + * _.isDate('Mon April 23 2012'); + * // => false + */ + var isDate = nodeIsDate ? baseUnary(nodeIsDate) : baseIsDate; + + /** + * Checks if `value` is likely a DOM element. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a DOM element, else `false`. + * @example + * + * _.isElement(document.body); + * // => true + * + * _.isElement(''); + * // => false + */ + function isElement(value) { + return isObjectLike(value) && value.nodeType === 1 && !isPlainObject(value); + } + + /** + * Checks if `value` is an empty object, collection, map, or set. + * + * Objects are considered empty if they have no own enumerable string keyed + * properties. + * + * Array-like values such as `arguments` objects, arrays, buffers, strings, or + * jQuery-like collections are considered empty if they have a `length` of `0`. + * Similarly, maps and sets are considered empty if they have a `size` of `0`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is empty, else `false`. + * @example + * + * _.isEmpty(null); + * // => true + * + * _.isEmpty(true); + * // => true + * + * _.isEmpty(1); + * // => true + * + * _.isEmpty([1, 2, 3]); + * // => false + * + * _.isEmpty({ 'a': 1 }); + * // => false + */ + function isEmpty(value) { + if (value == null) { + return true; + } + if (isArrayLike(value) && + (isArray(value) || typeof value == 'string' || typeof value.splice == 'function' || + isBuffer(value) || isTypedArray(value) || isArguments(value))) { + return !value.length; + } + var tag = getTag(value); + if (tag == mapTag || tag == setTag) { + return !value.size; + } + if (isPrototype(value)) { + return !baseKeys(value).length; + } + for (var key in value) { + if (hasOwnProperty.call(value, key)) { + return false; + } + } + return true; + } + + /** + * Performs a deep comparison between two values to determine if they are + * equivalent. + * + * **Note:** This method supports comparing arrays, array buffers, booleans, + * date objects, error objects, maps, numbers, `Object` objects, regexes, + * sets, strings, symbols, and typed arrays. `Object` objects are compared + * by their own, not inherited, enumerable properties. Functions and DOM + * nodes are compared by strict equality, i.e. `===`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. + * @example + * + * var object = { 'a': 1 }; + * var other = { 'a': 1 }; + * + * _.isEqual(object, other); + * // => true + * + * object === other; + * // => false + */ + function isEqual(value, other) { + return baseIsEqual(value, other); + } + + /** + * This method is like `_.isEqual` except that it accepts `customizer` which + * is invoked to compare values. If `customizer` returns `undefined`, comparisons + * are handled by the method instead. The `customizer` is invoked with up to + * six arguments: (objValue, othValue [, index|key, object, other, stack]). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @param {Function} [customizer] The function to customize comparisons. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. + * @example + * + * function isGreeting(value) { + * return /^h(?:i|ello)$/.test(value); + * } + * + * function customizer(objValue, othValue) { + * if (isGreeting(objValue) && isGreeting(othValue)) { + * return true; + * } + * } + * + * var array = ['hello', 'goodbye']; + * var other = ['hi', 'goodbye']; + * + * _.isEqualWith(array, other, customizer); + * // => true + */ + function isEqualWith(value, other, customizer) { + customizer = typeof customizer == 'function' ? customizer : undefined; + var result = customizer ? customizer(value, other) : undefined; + return result === undefined ? baseIsEqual(value, other, undefined, customizer) : !!result; + } + + /** + * Checks if `value` is an `Error`, `EvalError`, `RangeError`, `ReferenceError`, + * `SyntaxError`, `TypeError`, or `URIError` object. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an error object, else `false`. + * @example + * + * _.isError(new Error); + * // => true + * + * _.isError(Error); + * // => false + */ + function isError(value) { + if (!isObjectLike(value)) { + return false; + } + var tag = baseGetTag(value); + return tag == errorTag || tag == domExcTag || + (typeof value.message == 'string' && typeof value.name == 'string' && !isPlainObject(value)); + } + + /** + * Checks if `value` is a finite primitive number. + * + * **Note:** This method is based on + * [`Number.isFinite`](https://mdn.io/Number/isFinite). + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a finite number, else `false`. + * @example + * + * _.isFinite(3); + * // => true + * + * _.isFinite(Number.MIN_VALUE); + * // => true + * + * _.isFinite(Infinity); + * // => false + * + * _.isFinite('3'); + * // => false + */ + function isFinite(value) { + return typeof value == 'number' && nativeIsFinite(value); + } + + /** + * Checks if `value` is classified as a `Function` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a function, else `false`. + * @example + * + * _.isFunction(_); + * // => true + * + * _.isFunction(/abc/); + * // => false + */ + function isFunction(value) { + if (!isObject(value)) { + return false; + } + // The use of `Object#toString` avoids issues with the `typeof` operator + // in Safari 9 which returns 'object' for typed arrays and other constructors. + var tag = baseGetTag(value); + return tag == funcTag || tag == genTag || tag == asyncTag || tag == proxyTag; + } + + /** + * Checks if `value` is an integer. + * + * **Note:** This method is based on + * [`Number.isInteger`](https://mdn.io/Number/isInteger). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an integer, else `false`. + * @example + * + * _.isInteger(3); + * // => true + * + * _.isInteger(Number.MIN_VALUE); + * // => false + * + * _.isInteger(Infinity); + * // => false + * + * _.isInteger('3'); + * // => false + */ + function isInteger(value) { + return typeof value == 'number' && value == toInteger(value); + } + + /** + * Checks if `value` is a valid array-like length. + * + * **Note:** This method is loosely based on + * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. + * @example + * + * _.isLength(3); + * // => true + * + * _.isLength(Number.MIN_VALUE); + * // => false + * + * _.isLength(Infinity); + * // => false + * + * _.isLength('3'); + * // => false + */ + function isLength(value) { + return typeof value == 'number' && + value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; + } + + /** + * Checks if `value` is the + * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) + * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(_.noop); + * // => true + * + * _.isObject(null); + * // => false + */ + function isObject(value) { + var type = typeof value; + return value != null && (type == 'object' || type == 'function'); + } + + /** + * Checks if `value` is object-like. A value is object-like if it's not `null` + * and has a `typeof` result of "object". + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is object-like, else `false`. + * @example + * + * _.isObjectLike({}); + * // => true + * + * _.isObjectLike([1, 2, 3]); + * // => true + * + * _.isObjectLike(_.noop); + * // => false + * + * _.isObjectLike(null); + * // => false + */ + function isObjectLike(value) { + return value != null && typeof value == 'object'; + } + + /** + * Checks if `value` is classified as a `Map` object. + * + * @static + * @memberOf _ + * @since 4.3.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a map, else `false`. + * @example + * + * _.isMap(new Map); + * // => true + * + * _.isMap(new WeakMap); + * // => false + */ + var isMap = nodeIsMap ? baseUnary(nodeIsMap) : baseIsMap; + + /** + * Performs a partial deep comparison between `object` and `source` to + * determine if `object` contains equivalent property values. + * + * **Note:** This method is equivalent to `_.matches` when `source` is + * partially applied. + * + * Partial comparisons will match empty array and empty object `source` + * values against any array or object value, respectively. See `_.isEqual` + * for a list of supported value comparisons. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Lang + * @param {Object} object The object to inspect. + * @param {Object} source The object of property values to match. + * @returns {boolean} Returns `true` if `object` is a match, else `false`. + * @example + * + * var object = { 'a': 1, 'b': 2 }; + * + * _.isMatch(object, { 'b': 2 }); + * // => true + * + * _.isMatch(object, { 'b': 1 }); + * // => false + */ + function isMatch(object, source) { + return object === source || baseIsMatch(object, source, getMatchData(source)); + } + + /** + * This method is like `_.isMatch` except that it accepts `customizer` which + * is invoked to compare values. If `customizer` returns `undefined`, comparisons + * are handled by the method instead. The `customizer` is invoked with five + * arguments: (objValue, srcValue, index|key, object, source). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {Object} object The object to inspect. + * @param {Object} source The object of property values to match. + * @param {Function} [customizer] The function to customize comparisons. + * @returns {boolean} Returns `true` if `object` is a match, else `false`. + * @example + * + * function isGreeting(value) { + * return /^h(?:i|ello)$/.test(value); + * } + * + * function customizer(objValue, srcValue) { + * if (isGreeting(objValue) && isGreeting(srcValue)) { + * return true; + * } + * } + * + * var object = { 'greeting': 'hello' }; + * var source = { 'greeting': 'hi' }; + * + * _.isMatchWith(object, source, customizer); + * // => true + */ + function isMatchWith(object, source, customizer) { + customizer = typeof customizer == 'function' ? customizer : undefined; + return baseIsMatch(object, source, getMatchData(source), customizer); + } + + /** + * Checks if `value` is `NaN`. + * + * **Note:** This method is based on + * [`Number.isNaN`](https://mdn.io/Number/isNaN) and is not the same as + * global [`isNaN`](https://mdn.io/isNaN) which returns `true` for + * `undefined` and other non-number values. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`. + * @example + * + * _.isNaN(NaN); + * // => true + * + * _.isNaN(new Number(NaN)); + * // => true + * + * isNaN(undefined); + * // => true + * + * _.isNaN(undefined); + * // => false + */ + function isNaN(value) { + // An `NaN` primitive is the only value that is not equal to itself. + // Perform the `toStringTag` check first to avoid errors with some + // ActiveX objects in IE. + return isNumber(value) && value != +value; + } + + /** + * Checks if `value` is a pristine native function. + * + * **Note:** This method can't reliably detect native functions in the presence + * of the core-js package because core-js circumvents this kind of detection. + * Despite multiple requests, the core-js maintainer has made it clear: any + * attempt to fix the detection will be obstructed. As a result, we're left + * with little choice but to throw an error. Unfortunately, this also affects + * packages, like [babel-polyfill](https://www.npmjs.com/package/babel-polyfill), + * which rely on core-js. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a native function, + * else `false`. + * @example + * + * _.isNative(Array.prototype.push); + * // => true + * + * _.isNative(_); + * // => false + */ + function isNative(value) { + if (isMaskable(value)) { + throw new Error(CORE_ERROR_TEXT); + } + return baseIsNative(value); + } + + /** + * Checks if `value` is `null`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is `null`, else `false`. + * @example + * + * _.isNull(null); + * // => true + * + * _.isNull(void 0); + * // => false + */ + function isNull(value) { + return value === null; + } + + /** + * Checks if `value` is `null` or `undefined`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is nullish, else `false`. + * @example + * + * _.isNil(null); + * // => true + * + * _.isNil(void 0); + * // => true + * + * _.isNil(NaN); + * // => false + */ + function isNil(value) { + return value == null; + } + + /** + * Checks if `value` is classified as a `Number` primitive or object. + * + * **Note:** To exclude `Infinity`, `-Infinity`, and `NaN`, which are + * classified as numbers, use the `_.isFinite` method. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a number, else `false`. + * @example + * + * _.isNumber(3); + * // => true + * + * _.isNumber(Number.MIN_VALUE); + * // => true + * + * _.isNumber(Infinity); + * // => true + * + * _.isNumber('3'); + * // => false + */ + function isNumber(value) { + return typeof value == 'number' || + (isObjectLike(value) && baseGetTag(value) == numberTag); + } + + /** + * Checks if `value` is a plain object, that is, an object created by the + * `Object` constructor or one with a `[[Prototype]]` of `null`. + * + * @static + * @memberOf _ + * @since 0.8.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a plain object, else `false`. + * @example + * + * function Foo() { + * this.a = 1; + * } + * + * _.isPlainObject(new Foo); + * // => false + * + * _.isPlainObject([1, 2, 3]); + * // => false + * + * _.isPlainObject({ 'x': 0, 'y': 0 }); + * // => true + * + * _.isPlainObject(Object.create(null)); + * // => true + */ + function isPlainObject(value) { + if (!isObjectLike(value) || baseGetTag(value) != objectTag) { + return false; + } + var proto = getPrototype(value); + if (proto === null) { + return true; + } + var Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor; + return typeof Ctor == 'function' && Ctor instanceof Ctor && + funcToString.call(Ctor) == objectCtorString; + } + + /** + * Checks if `value` is classified as a `RegExp` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a regexp, else `false`. + * @example + * + * _.isRegExp(/abc/); + * // => true + * + * _.isRegExp('/abc/'); + * // => false + */ + var isRegExp = nodeIsRegExp ? baseUnary(nodeIsRegExp) : baseIsRegExp; + + /** + * Checks if `value` is a safe integer. An integer is safe if it's an IEEE-754 + * double precision number which isn't the result of a rounded unsafe integer. + * + * **Note:** This method is based on + * [`Number.isSafeInteger`](https://mdn.io/Number/isSafeInteger). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a safe integer, else `false`. + * @example + * + * _.isSafeInteger(3); + * // => true + * + * _.isSafeInteger(Number.MIN_VALUE); + * // => false + * + * _.isSafeInteger(Infinity); + * // => false + * + * _.isSafeInteger('3'); + * // => false + */ + function isSafeInteger(value) { + return isInteger(value) && value >= -MAX_SAFE_INTEGER && value <= MAX_SAFE_INTEGER; + } + + /** + * Checks if `value` is classified as a `Set` object. + * + * @static + * @memberOf _ + * @since 4.3.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a set, else `false`. + * @example + * + * _.isSet(new Set); + * // => true + * + * _.isSet(new WeakSet); + * // => false + */ + var isSet = nodeIsSet ? baseUnary(nodeIsSet) : baseIsSet; + + /** + * Checks if `value` is classified as a `String` primitive or object. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a string, else `false`. + * @example + * + * _.isString('abc'); + * // => true + * + * _.isString(1); + * // => false + */ + function isString(value) { + return typeof value == 'string' || + (!isArray(value) && isObjectLike(value) && baseGetTag(value) == stringTag); + } + + /** + * Checks if `value` is classified as a `Symbol` primitive or object. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a symbol, else `false`. + * @example + * + * _.isSymbol(Symbol.iterator); + * // => true + * + * _.isSymbol('abc'); + * // => false + */ + function isSymbol(value) { + return typeof value == 'symbol' || + (isObjectLike(value) && baseGetTag(value) == symbolTag); + } + + /** + * Checks if `value` is classified as a typed array. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a typed array, else `false`. + * @example + * + * _.isTypedArray(new Uint8Array); + * // => true + * + * _.isTypedArray([]); + * // => false + */ + var isTypedArray = nodeIsTypedArray ? baseUnary(nodeIsTypedArray) : baseIsTypedArray; + + /** + * Checks if `value` is `undefined`. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is `undefined`, else `false`. + * @example + * + * _.isUndefined(void 0); + * // => true + * + * _.isUndefined(null); + * // => false + */ + function isUndefined(value) { + return value === undefined; + } + + /** + * Checks if `value` is classified as a `WeakMap` object. + * + * @static + * @memberOf _ + * @since 4.3.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a weak map, else `false`. + * @example + * + * _.isWeakMap(new WeakMap); + * // => true + * + * _.isWeakMap(new Map); + * // => false + */ + function isWeakMap(value) { + return isObjectLike(value) && getTag(value) == weakMapTag; + } + + /** + * Checks if `value` is classified as a `WeakSet` object. + * + * @static + * @memberOf _ + * @since 4.3.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a weak set, else `false`. + * @example + * + * _.isWeakSet(new WeakSet); + * // => true + * + * _.isWeakSet(new Set); + * // => false + */ + function isWeakSet(value) { + return isObjectLike(value) && baseGetTag(value) == weakSetTag; + } + + /** + * Checks if `value` is less than `other`. + * + * @static + * @memberOf _ + * @since 3.9.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if `value` is less than `other`, + * else `false`. + * @see _.gt + * @example + * + * _.lt(1, 3); + * // => true + * + * _.lt(3, 3); + * // => false + * + * _.lt(3, 1); + * // => false + */ + var lt = createRelationalOperation(baseLt); + + /** + * Checks if `value` is less than or equal to `other`. + * + * @static + * @memberOf _ + * @since 3.9.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if `value` is less than or equal to + * `other`, else `false`. + * @see _.gte + * @example + * + * _.lte(1, 3); + * // => true + * + * _.lte(3, 3); + * // => true + * + * _.lte(3, 1); + * // => false + */ + var lte = createRelationalOperation(function(value, other) { + return value <= other; + }); + + /** + * Converts `value` to an array. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Lang + * @param {*} value The value to convert. + * @returns {Array} Returns the converted array. + * @example + * + * _.toArray({ 'a': 1, 'b': 2 }); + * // => [1, 2] + * + * _.toArray('abc'); + * // => ['a', 'b', 'c'] + * + * _.toArray(1); + * // => [] + * + * _.toArray(null); + * // => [] + */ + function toArray(value) { + if (!value) { + return []; + } + if (isArrayLike(value)) { + return isString(value) ? stringToArray(value) : copyArray(value); + } + if (symIterator && value[symIterator]) { + return iteratorToArray(value[symIterator]()); + } + var tag = getTag(value), + func = tag == mapTag ? mapToArray : (tag == setTag ? setToArray : values); + + return func(value); + } + + /** + * Converts `value` to a finite number. + * + * @static + * @memberOf _ + * @since 4.12.0 + * @category Lang + * @param {*} value The value to convert. + * @returns {number} Returns the converted number. + * @example + * + * _.toFinite(3.2); + * // => 3.2 + * + * _.toFinite(Number.MIN_VALUE); + * // => 5e-324 + * + * _.toFinite(Infinity); + * // => 1.7976931348623157e+308 + * + * _.toFinite('3.2'); + * // => 3.2 + */ + function toFinite(value) { + if (!value) { + return value === 0 ? value : 0; + } + value = toNumber(value); + if (value === INFINITY || value === -INFINITY) { + var sign = (value < 0 ? -1 : 1); + return sign * MAX_INTEGER; + } + return value === value ? value : 0; + } + + /** + * Converts `value` to an integer. + * + * **Note:** This method is loosely based on + * [`ToInteger`](http://www.ecma-international.org/ecma-262/7.0/#sec-tointeger). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to convert. + * @returns {number} Returns the converted integer. + * @example + * + * _.toInteger(3.2); + * // => 3 + * + * _.toInteger(Number.MIN_VALUE); + * // => 0 + * + * _.toInteger(Infinity); + * // => 1.7976931348623157e+308 + * + * _.toInteger('3.2'); + * // => 3 + */ + function toInteger(value) { + var result = toFinite(value), + remainder = result % 1; + + return result === result ? (remainder ? result - remainder : result) : 0; + } + + /** + * Converts `value` to an integer suitable for use as the length of an + * array-like object. + * + * **Note:** This method is based on + * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to convert. + * @returns {number} Returns the converted integer. + * @example + * + * _.toLength(3.2); + * // => 3 + * + * _.toLength(Number.MIN_VALUE); + * // => 0 + * + * _.toLength(Infinity); + * // => 4294967295 + * + * _.toLength('3.2'); + * // => 3 + */ + function toLength(value) { + return value ? baseClamp(toInteger(value), 0, MAX_ARRAY_LENGTH) : 0; + } + + /** + * Converts `value` to a number. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to process. + * @returns {number} Returns the number. + * @example + * + * _.toNumber(3.2); + * // => 3.2 + * + * _.toNumber(Number.MIN_VALUE); + * // => 5e-324 + * + * _.toNumber(Infinity); + * // => Infinity + * + * _.toNumber('3.2'); + * // => 3.2 + */ + function toNumber(value) { + if (typeof value == 'number') { + return value; + } + if (isSymbol(value)) { + return NAN; + } + if (isObject(value)) { + var other = typeof value.valueOf == 'function' ? value.valueOf() : value; + value = isObject(other) ? (other + '') : other; + } + if (typeof value != 'string') { + return value === 0 ? value : +value; + } + value = value.replace(reTrim, ''); + var isBinary = reIsBinary.test(value); + return (isBinary || reIsOctal.test(value)) + ? freeParseInt(value.slice(2), isBinary ? 2 : 8) + : (reIsBadHex.test(value) ? NAN : +value); + } + + /** + * Converts `value` to a plain object flattening inherited enumerable string + * keyed properties of `value` to own properties of the plain object. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Lang + * @param {*} value The value to convert. + * @returns {Object} Returns the converted plain object. + * @example + * + * function Foo() { + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.assign({ 'a': 1 }, new Foo); + * // => { 'a': 1, 'b': 2 } + * + * _.assign({ 'a': 1 }, _.toPlainObject(new Foo)); + * // => { 'a': 1, 'b': 2, 'c': 3 } + */ + function toPlainObject(value) { + return copyObject(value, keysIn(value)); + } + + /** + * Converts `value` to a safe integer. A safe integer can be compared and + * represented correctly. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to convert. + * @returns {number} Returns the converted integer. + * @example + * + * _.toSafeInteger(3.2); + * // => 3 + * + * _.toSafeInteger(Number.MIN_VALUE); + * // => 0 + * + * _.toSafeInteger(Infinity); + * // => 9007199254740991 + * + * _.toSafeInteger('3.2'); + * // => 3 + */ + function toSafeInteger(value) { + return value + ? baseClamp(toInteger(value), -MAX_SAFE_INTEGER, MAX_SAFE_INTEGER) + : (value === 0 ? value : 0); + } + + /** + * Converts `value` to a string. An empty string is returned for `null` + * and `undefined` values. The sign of `-0` is preserved. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to convert. + * @returns {string} Returns the converted string. + * @example + * + * _.toString(null); + * // => '' + * + * _.toString(-0); + * // => '-0' + * + * _.toString([1, 2, 3]); + * // => '1,2,3' + */ + function toString(value) { + return value == null ? '' : baseToString(value); + } + + /*------------------------------------------------------------------------*/ + + /** + * Assigns own enumerable string keyed properties of source objects to the + * destination object. Source objects are applied from left to right. + * Subsequent sources overwrite property assignments of previous sources. + * + * **Note:** This method mutates `object` and is loosely based on + * [`Object.assign`](https://mdn.io/Object/assign). + * + * @static + * @memberOf _ + * @since 0.10.0 + * @category Object + * @param {Object} object The destination object. + * @param {...Object} [sources] The source objects. + * @returns {Object} Returns `object`. + * @see _.assignIn + * @example + * + * function Foo() { + * this.a = 1; + * } + * + * function Bar() { + * this.c = 3; + * } + * + * Foo.prototype.b = 2; + * Bar.prototype.d = 4; + * + * _.assign({ 'a': 0 }, new Foo, new Bar); + * // => { 'a': 1, 'c': 3 } + */ + var assign = createAssigner(function(object, source) { + if (isPrototype(source) || isArrayLike(source)) { + copyObject(source, keys(source), object); + return; + } + for (var key in source) { + if (hasOwnProperty.call(source, key)) { + assignValue(object, key, source[key]); + } + } + }); + + /** + * This method is like `_.assign` except that it iterates over own and + * inherited source properties. + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @alias extend + * @category Object + * @param {Object} object The destination object. + * @param {...Object} [sources] The source objects. + * @returns {Object} Returns `object`. + * @see _.assign + * @example + * + * function Foo() { + * this.a = 1; + * } + * + * function Bar() { + * this.c = 3; + * } + * + * Foo.prototype.b = 2; + * Bar.prototype.d = 4; + * + * _.assignIn({ 'a': 0 }, new Foo, new Bar); + * // => { 'a': 1, 'b': 2, 'c': 3, 'd': 4 } + */ + var assignIn = createAssigner(function(object, source) { + copyObject(source, keysIn(source), object); + }); + + /** + * This method is like `_.assignIn` except that it accepts `customizer` + * which is invoked to produce the assigned values. If `customizer` returns + * `undefined`, assignment is handled by the method instead. The `customizer` + * is invoked with five arguments: (objValue, srcValue, key, object, source). + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @alias extendWith + * @category Object + * @param {Object} object The destination object. + * @param {...Object} sources The source objects. + * @param {Function} [customizer] The function to customize assigned values. + * @returns {Object} Returns `object`. + * @see _.assignWith + * @example + * + * function customizer(objValue, srcValue) { + * return _.isUndefined(objValue) ? srcValue : objValue; + * } + * + * var defaults = _.partialRight(_.assignInWith, customizer); + * + * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 }); + * // => { 'a': 1, 'b': 2 } + */ + var assignInWith = createAssigner(function(object, source, srcIndex, customizer) { + copyObject(source, keysIn(source), object, customizer); + }); + + /** + * This method is like `_.assign` except that it accepts `customizer` + * which is invoked to produce the assigned values. If `customizer` returns + * `undefined`, assignment is handled by the method instead. The `customizer` + * is invoked with five arguments: (objValue, srcValue, key, object, source). + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The destination object. + * @param {...Object} sources The source objects. + * @param {Function} [customizer] The function to customize assigned values. + * @returns {Object} Returns `object`. + * @see _.assignInWith + * @example + * + * function customizer(objValue, srcValue) { + * return _.isUndefined(objValue) ? srcValue : objValue; + * } + * + * var defaults = _.partialRight(_.assignWith, customizer); + * + * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 }); + * // => { 'a': 1, 'b': 2 } + */ + var assignWith = createAssigner(function(object, source, srcIndex, customizer) { + copyObject(source, keys(source), object, customizer); + }); + + /** + * Creates an array of values corresponding to `paths` of `object`. + * + * @static + * @memberOf _ + * @since 1.0.0 + * @category Object + * @param {Object} object The object to iterate over. + * @param {...(string|string[])} [paths] The property paths to pick. + * @returns {Array} Returns the picked values. + * @example + * + * var object = { 'a': [{ 'b': { 'c': 3 } }, 4] }; + * + * _.at(object, ['a[0].b.c', 'a[1]']); + * // => [3, 4] + */ + var at = flatRest(baseAt); + + /** + * Creates an object that inherits from the `prototype` object. If a + * `properties` object is given, its own enumerable string keyed properties + * are assigned to the created object. + * + * @static + * @memberOf _ + * @since 2.3.0 + * @category Object + * @param {Object} prototype The object to inherit from. + * @param {Object} [properties] The properties to assign to the object. + * @returns {Object} Returns the new object. + * @example + * + * function Shape() { + * this.x = 0; + * this.y = 0; + * } + * + * function Circle() { + * Shape.call(this); + * } + * + * Circle.prototype = _.create(Shape.prototype, { + * 'constructor': Circle + * }); + * + * var circle = new Circle; + * circle instanceof Circle; + * // => true + * + * circle instanceof Shape; + * // => true + */ + function create(prototype, properties) { + var result = baseCreate(prototype); + return properties == null ? result : baseAssign(result, properties); + } + + /** + * Assigns own and inherited enumerable string keyed properties of source + * objects to the destination object for all destination properties that + * resolve to `undefined`. Source objects are applied from left to right. + * Once a property is set, additional values of the same property are ignored. + * + * **Note:** This method mutates `object`. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The destination object. + * @param {...Object} [sources] The source objects. + * @returns {Object} Returns `object`. + * @see _.defaultsDeep + * @example + * + * _.defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 }); + * // => { 'a': 1, 'b': 2 } + */ + var defaults = baseRest(function(object, sources) { + object = Object(object); + + var index = -1; + var length = sources.length; + var guard = length > 2 ? sources[2] : undefined; + + if (guard && isIterateeCall(sources[0], sources[1], guard)) { + length = 1; + } + + while (++index < length) { + var source = sources[index]; + var props = keysIn(source); + var propsIndex = -1; + var propsLength = props.length; + + while (++propsIndex < propsLength) { + var key = props[propsIndex]; + var value = object[key]; + + if (value === undefined || + (eq(value, objectProto[key]) && !hasOwnProperty.call(object, key))) { + object[key] = source[key]; + } + } + } + + return object; + }); + + /** + * This method is like `_.defaults` except that it recursively assigns + * default properties. + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 3.10.0 + * @category Object + * @param {Object} object The destination object. + * @param {...Object} [sources] The source objects. + * @returns {Object} Returns `object`. + * @see _.defaults + * @example + * + * _.defaultsDeep({ 'a': { 'b': 2 } }, { 'a': { 'b': 1, 'c': 3 } }); + * // => { 'a': { 'b': 2, 'c': 3 } } + */ + var defaultsDeep = baseRest(function(args) { + args.push(undefined, customDefaultsMerge); + return apply(mergeWith, undefined, args); + }); + + /** + * This method is like `_.find` except that it returns the key of the first + * element `predicate` returns truthy for instead of the element itself. + * + * @static + * @memberOf _ + * @since 1.1.0 + * @category Object + * @param {Object} object The object to inspect. + * @param {Function} [predicate=_.identity] The function invoked per iteration. + * @returns {string|undefined} Returns the key of the matched element, + * else `undefined`. + * @example + * + * var users = { + * 'barney': { 'age': 36, 'active': true }, + * 'fred': { 'age': 40, 'active': false }, + * 'pebbles': { 'age': 1, 'active': true } + * }; + * + * _.findKey(users, function(o) { return o.age < 40; }); + * // => 'barney' (iteration order is not guaranteed) + * + * // The `_.matches` iteratee shorthand. + * _.findKey(users, { 'age': 1, 'active': true }); + * // => 'pebbles' + * + * // The `_.matchesProperty` iteratee shorthand. + * _.findKey(users, ['active', false]); + * // => 'fred' + * + * // The `_.property` iteratee shorthand. + * _.findKey(users, 'active'); + * // => 'barney' + */ + function findKey(object, predicate) { + return baseFindKey(object, getIteratee(predicate, 3), baseForOwn); + } + + /** + * This method is like `_.findKey` except that it iterates over elements of + * a collection in the opposite order. + * + * @static + * @memberOf _ + * @since 2.0.0 + * @category Object + * @param {Object} object The object to inspect. + * @param {Function} [predicate=_.identity] The function invoked per iteration. + * @returns {string|undefined} Returns the key of the matched element, + * else `undefined`. + * @example + * + * var users = { + * 'barney': { 'age': 36, 'active': true }, + * 'fred': { 'age': 40, 'active': false }, + * 'pebbles': { 'age': 1, 'active': true } + * }; + * + * _.findLastKey(users, function(o) { return o.age < 40; }); + * // => returns 'pebbles' assuming `_.findKey` returns 'barney' + * + * // The `_.matches` iteratee shorthand. + * _.findLastKey(users, { 'age': 36, 'active': true }); + * // => 'barney' + * + * // The `_.matchesProperty` iteratee shorthand. + * _.findLastKey(users, ['active', false]); + * // => 'fred' + * + * // The `_.property` iteratee shorthand. + * _.findLastKey(users, 'active'); + * // => 'pebbles' + */ + function findLastKey(object, predicate) { + return baseFindKey(object, getIteratee(predicate, 3), baseForOwnRight); + } + + /** + * Iterates over own and inherited enumerable string keyed properties of an + * object and invokes `iteratee` for each property. The iteratee is invoked + * with three arguments: (value, key, object). Iteratee functions may exit + * iteration early by explicitly returning `false`. + * + * @static + * @memberOf _ + * @since 0.3.0 + * @category Object + * @param {Object} object The object to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @returns {Object} Returns `object`. + * @see _.forInRight + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.forIn(new Foo, function(value, key) { + * console.log(key); + * }); + * // => Logs 'a', 'b', then 'c' (iteration order is not guaranteed). + */ + function forIn(object, iteratee) { + return object == null + ? object + : baseFor(object, getIteratee(iteratee, 3), keysIn); + } + + /** + * This method is like `_.forIn` except that it iterates over properties of + * `object` in the opposite order. + * + * @static + * @memberOf _ + * @since 2.0.0 + * @category Object + * @param {Object} object The object to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @returns {Object} Returns `object`. + * @see _.forIn + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.forInRight(new Foo, function(value, key) { + * console.log(key); + * }); + * // => Logs 'c', 'b', then 'a' assuming `_.forIn` logs 'a', 'b', then 'c'. + */ + function forInRight(object, iteratee) { + return object == null + ? object + : baseForRight(object, getIteratee(iteratee, 3), keysIn); + } + + /** + * Iterates over own enumerable string keyed properties of an object and + * invokes `iteratee` for each property. The iteratee is invoked with three + * arguments: (value, key, object). Iteratee functions may exit iteration + * early by explicitly returning `false`. + * + * @static + * @memberOf _ + * @since 0.3.0 + * @category Object + * @param {Object} object The object to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @returns {Object} Returns `object`. + * @see _.forOwnRight + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.forOwn(new Foo, function(value, key) { + * console.log(key); + * }); + * // => Logs 'a' then 'b' (iteration order is not guaranteed). + */ + function forOwn(object, iteratee) { + return object && baseForOwn(object, getIteratee(iteratee, 3)); + } + + /** + * This method is like `_.forOwn` except that it iterates over properties of + * `object` in the opposite order. + * + * @static + * @memberOf _ + * @since 2.0.0 + * @category Object + * @param {Object} object The object to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @returns {Object} Returns `object`. + * @see _.forOwn + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.forOwnRight(new Foo, function(value, key) { + * console.log(key); + * }); + * // => Logs 'b' then 'a' assuming `_.forOwn` logs 'a' then 'b'. + */ + function forOwnRight(object, iteratee) { + return object && baseForOwnRight(object, getIteratee(iteratee, 3)); + } + + /** + * Creates an array of function property names from own enumerable properties + * of `object`. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The object to inspect. + * @returns {Array} Returns the function names. + * @see _.functionsIn + * @example + * + * function Foo() { + * this.a = _.constant('a'); + * this.b = _.constant('b'); + * } + * + * Foo.prototype.c = _.constant('c'); + * + * _.functions(new Foo); + * // => ['a', 'b'] + */ + function functions(object) { + return object == null ? [] : baseFunctions(object, keys(object)); + } + + /** + * Creates an array of function property names from own and inherited + * enumerable properties of `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The object to inspect. + * @returns {Array} Returns the function names. + * @see _.functions + * @example + * + * function Foo() { + * this.a = _.constant('a'); + * this.b = _.constant('b'); + * } + * + * Foo.prototype.c = _.constant('c'); + * + * _.functionsIn(new Foo); + * // => ['a', 'b', 'c'] + */ + function functionsIn(object) { + return object == null ? [] : baseFunctions(object, keysIn(object)); + } + + /** + * Gets the value at `path` of `object`. If the resolved value is + * `undefined`, the `defaultValue` is returned in its place. + * + * @static + * @memberOf _ + * @since 3.7.0 + * @category Object + * @param {Object} object The object to query. + * @param {Array|string} path The path of the property to get. + * @param {*} [defaultValue] The value returned for `undefined` resolved values. + * @returns {*} Returns the resolved value. + * @example + * + * var object = { 'a': [{ 'b': { 'c': 3 } }] }; + * + * _.get(object, 'a[0].b.c'); + * // => 3 + * + * _.get(object, ['a', '0', 'b', 'c']); + * // => 3 + * + * _.get(object, 'a.b.c', 'default'); + * // => 'default' + */ + function get(object, path, defaultValue) { + var result = object == null ? undefined : baseGet(object, path); + return result === undefined ? defaultValue : result; + } + + /** + * Checks if `path` is a direct property of `object`. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @param {Array|string} path The path to check. + * @returns {boolean} Returns `true` if `path` exists, else `false`. + * @example + * + * var object = { 'a': { 'b': 2 } }; + * var other = _.create({ 'a': _.create({ 'b': 2 }) }); + * + * _.has(object, 'a'); + * // => true + * + * _.has(object, 'a.b'); + * // => true + * + * _.has(object, ['a', 'b']); + * // => true + * + * _.has(other, 'a'); + * // => false + */ + function has(object, path) { + return object != null && hasPath(object, path, baseHas); + } + + /** + * Checks if `path` is a direct or inherited property of `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The object to query. + * @param {Array|string} path The path to check. + * @returns {boolean} Returns `true` if `path` exists, else `false`. + * @example + * + * var object = _.create({ 'a': _.create({ 'b': 2 }) }); + * + * _.hasIn(object, 'a'); + * // => true + * + * _.hasIn(object, 'a.b'); + * // => true + * + * _.hasIn(object, ['a', 'b']); + * // => true + * + * _.hasIn(object, 'b'); + * // => false + */ + function hasIn(object, path) { + return object != null && hasPath(object, path, baseHasIn); + } + + /** + * Creates an object composed of the inverted keys and values of `object`. + * If `object` contains duplicate values, subsequent values overwrite + * property assignments of previous values. + * + * @static + * @memberOf _ + * @since 0.7.0 + * @category Object + * @param {Object} object The object to invert. + * @returns {Object} Returns the new inverted object. + * @example + * + * var object = { 'a': 1, 'b': 2, 'c': 1 }; + * + * _.invert(object); + * // => { '1': 'c', '2': 'b' } + */ + var invert = createInverter(function(result, value, key) { + if (value != null && + typeof value.toString != 'function') { + value = nativeObjectToString.call(value); + } + + result[value] = key; + }, constant(identity)); + + /** + * This method is like `_.invert` except that the inverted object is generated + * from the results of running each element of `object` thru `iteratee`. The + * corresponding inverted value of each inverted key is an array of keys + * responsible for generating the inverted value. The iteratee is invoked + * with one argument: (value). + * + * @static + * @memberOf _ + * @since 4.1.0 + * @category Object + * @param {Object} object The object to invert. + * @param {Function} [iteratee=_.identity] The iteratee invoked per element. + * @returns {Object} Returns the new inverted object. + * @example + * + * var object = { 'a': 1, 'b': 2, 'c': 1 }; + * + * _.invertBy(object); + * // => { '1': ['a', 'c'], '2': ['b'] } + * + * _.invertBy(object, function(value) { + * return 'group' + value; + * }); + * // => { 'group1': ['a', 'c'], 'group2': ['b'] } + */ + var invertBy = createInverter(function(result, value, key) { + if (value != null && + typeof value.toString != 'function') { + value = nativeObjectToString.call(value); + } + + if (hasOwnProperty.call(result, value)) { + result[value].push(key); + } else { + result[value] = [key]; + } + }, getIteratee); + + /** + * Invokes the method at `path` of `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The object to query. + * @param {Array|string} path The path of the method to invoke. + * @param {...*} [args] The arguments to invoke the method with. + * @returns {*} Returns the result of the invoked method. + * @example + * + * var object = { 'a': [{ 'b': { 'c': [1, 2, 3, 4] } }] }; + * + * _.invoke(object, 'a[0].b.c.slice', 1, 3); + * // => [2, 3] + */ + var invoke = baseRest(baseInvoke); + + /** + * Creates an array of the own enumerable property names of `object`. + * + * **Note:** Non-object values are coerced to objects. See the + * [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys) + * for more details. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.keys(new Foo); + * // => ['a', 'b'] (iteration order is not guaranteed) + * + * _.keys('hi'); + * // => ['0', '1'] + */ + function keys(object) { + return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object); + } + + /** + * Creates an array of the own and inherited enumerable property names of `object`. + * + * **Note:** Non-object values are coerced to objects. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.keysIn(new Foo); + * // => ['a', 'b', 'c'] (iteration order is not guaranteed) + */ + function keysIn(object) { + return isArrayLike(object) ? arrayLikeKeys(object, true) : baseKeysIn(object); + } + + /** + * The opposite of `_.mapValues`; this method creates an object with the + * same values as `object` and keys generated by running each own enumerable + * string keyed property of `object` thru `iteratee`. The iteratee is invoked + * with three arguments: (value, key, object). + * + * @static + * @memberOf _ + * @since 3.8.0 + * @category Object + * @param {Object} object The object to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @returns {Object} Returns the new mapped object. + * @see _.mapValues + * @example + * + * _.mapKeys({ 'a': 1, 'b': 2 }, function(value, key) { + * return key + value; + * }); + * // => { 'a1': 1, 'b2': 2 } + */ + function mapKeys(object, iteratee) { + var result = {}; + iteratee = getIteratee(iteratee, 3); + + baseForOwn(object, function(value, key, object) { + baseAssignValue(result, iteratee(value, key, object), value); + }); + return result; + } + + /** + * Creates an object with the same keys as `object` and values generated + * by running each own enumerable string keyed property of `object` thru + * `iteratee`. The iteratee is invoked with three arguments: + * (value, key, object). + * + * @static + * @memberOf _ + * @since 2.4.0 + * @category Object + * @param {Object} object The object to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @returns {Object} Returns the new mapped object. + * @see _.mapKeys + * @example + * + * var users = { + * 'fred': { 'user': 'fred', 'age': 40 }, + * 'pebbles': { 'user': 'pebbles', 'age': 1 } + * }; + * + * _.mapValues(users, function(o) { return o.age; }); + * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed) + * + * // The `_.property` iteratee shorthand. + * _.mapValues(users, 'age'); + * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed) + */ + function mapValues(object, iteratee) { + var result = {}; + iteratee = getIteratee(iteratee, 3); + + baseForOwn(object, function(value, key, object) { + baseAssignValue(result, key, iteratee(value, key, object)); + }); + return result; + } + + /** + * This method is like `_.assign` except that it recursively merges own and + * inherited enumerable string keyed properties of source objects into the + * destination object. Source properties that resolve to `undefined` are + * skipped if a destination value exists. Array and plain object properties + * are merged recursively. Other objects and value types are overridden by + * assignment. Source objects are applied from left to right. Subsequent + * sources overwrite property assignments of previous sources. + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 0.5.0 + * @category Object + * @param {Object} object The destination object. + * @param {...Object} [sources] The source objects. + * @returns {Object} Returns `object`. + * @example + * + * var object = { + * 'a': [{ 'b': 2 }, { 'd': 4 }] + * }; + * + * var other = { + * 'a': [{ 'c': 3 }, { 'e': 5 }] + * }; + * + * _.merge(object, other); + * // => { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] } + */ + var merge = createAssigner(function(object, source, srcIndex) { + baseMerge(object, source, srcIndex); + }); + + /** + * This method is like `_.merge` except that it accepts `customizer` which + * is invoked to produce the merged values of the destination and source + * properties. If `customizer` returns `undefined`, merging is handled by the + * method instead. The `customizer` is invoked with six arguments: + * (objValue, srcValue, key, object, source, stack). + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The destination object. + * @param {...Object} sources The source objects. + * @param {Function} customizer The function to customize assigned values. + * @returns {Object} Returns `object`. + * @example + * + * function customizer(objValue, srcValue) { + * if (_.isArray(objValue)) { + * return objValue.concat(srcValue); + * } + * } + * + * var object = { 'a': [1], 'b': [2] }; + * var other = { 'a': [3], 'b': [4] }; + * + * _.mergeWith(object, other, customizer); + * // => { 'a': [1, 3], 'b': [2, 4] } + */ + var mergeWith = createAssigner(function(object, source, srcIndex, customizer) { + baseMerge(object, source, srcIndex, customizer); + }); + + /** + * The opposite of `_.pick`; this method creates an object composed of the + * own and inherited enumerable property paths of `object` that are not omitted. + * + * **Note:** This method is considerably slower than `_.pick`. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The source object. + * @param {...(string|string[])} [paths] The property paths to omit. + * @returns {Object} Returns the new object. + * @example + * + * var object = { 'a': 1, 'b': '2', 'c': 3 }; + * + * _.omit(object, ['a', 'c']); + * // => { 'b': '2' } + */ + var omit = flatRest(function(object, paths) { + var result = {}; + if (object == null) { + return result; + } + var isDeep = false; + paths = arrayMap(paths, function(path) { + path = castPath(path, object); + isDeep || (isDeep = path.length > 1); + return path; + }); + copyObject(object, getAllKeysIn(object), result); + if (isDeep) { + result = baseClone(result, CLONE_DEEP_FLAG | CLONE_FLAT_FLAG | CLONE_SYMBOLS_FLAG, customOmitClone); + } + var length = paths.length; + while (length--) { + baseUnset(result, paths[length]); + } + return result; + }); + + /** + * The opposite of `_.pickBy`; this method creates an object composed of + * the own and inherited enumerable string keyed properties of `object` that + * `predicate` doesn't return truthy for. The predicate is invoked with two + * arguments: (value, key). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The source object. + * @param {Function} [predicate=_.identity] The function invoked per property. + * @returns {Object} Returns the new object. + * @example + * + * var object = { 'a': 1, 'b': '2', 'c': 3 }; + * + * _.omitBy(object, _.isNumber); + * // => { 'b': '2' } + */ + function omitBy(object, predicate) { + return pickBy(object, negate(getIteratee(predicate))); + } + + /** + * Creates an object composed of the picked `object` properties. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The source object. + * @param {...(string|string[])} [paths] The property paths to pick. + * @returns {Object} Returns the new object. + * @example + * + * var object = { 'a': 1, 'b': '2', 'c': 3 }; + * + * _.pick(object, ['a', 'c']); + * // => { 'a': 1, 'c': 3 } + */ + var pick = flatRest(function(object, paths) { + return object == null ? {} : basePick(object, paths); + }); + + /** + * Creates an object composed of the `object` properties `predicate` returns + * truthy for. The predicate is invoked with two arguments: (value, key). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The source object. + * @param {Function} [predicate=_.identity] The function invoked per property. + * @returns {Object} Returns the new object. + * @example + * + * var object = { 'a': 1, 'b': '2', 'c': 3 }; + * + * _.pickBy(object, _.isNumber); + * // => { 'a': 1, 'c': 3 } + */ + function pickBy(object, predicate) { + if (object == null) { + return {}; + } + var props = arrayMap(getAllKeysIn(object), function(prop) { + return [prop]; + }); + predicate = getIteratee(predicate); + return basePickBy(object, props, function(value, path) { + return predicate(value, path[0]); + }); + } + + /** + * This method is like `_.get` except that if the resolved value is a + * function it's invoked with the `this` binding of its parent object and + * its result is returned. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @param {Array|string} path The path of the property to resolve. + * @param {*} [defaultValue] The value returned for `undefined` resolved values. + * @returns {*} Returns the resolved value. + * @example + * + * var object = { 'a': [{ 'b': { 'c1': 3, 'c2': _.constant(4) } }] }; + * + * _.result(object, 'a[0].b.c1'); + * // => 3 + * + * _.result(object, 'a[0].b.c2'); + * // => 4 + * + * _.result(object, 'a[0].b.c3', 'default'); + * // => 'default' + * + * _.result(object, 'a[0].b.c3', _.constant('default')); + * // => 'default' + */ + function result(object, path, defaultValue) { + path = castPath(path, object); + + var index = -1, + length = path.length; + + // Ensure the loop is entered when path is empty. + if (!length) { + length = 1; + object = undefined; + } + while (++index < length) { + var value = object == null ? undefined : object[toKey(path[index])]; + if (value === undefined) { + index = length; + value = defaultValue; + } + object = isFunction(value) ? value.call(object) : value; + } + return object; + } + + /** + * Sets the value at `path` of `object`. If a portion of `path` doesn't exist, + * it's created. Arrays are created for missing index properties while objects + * are created for all other missing properties. Use `_.setWith` to customize + * `path` creation. + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 3.7.0 + * @category Object + * @param {Object} object The object to modify. + * @param {Array|string} path The path of the property to set. + * @param {*} value The value to set. + * @returns {Object} Returns `object`. + * @example + * + * var object = { 'a': [{ 'b': { 'c': 3 } }] }; + * + * _.set(object, 'a[0].b.c', 4); + * console.log(object.a[0].b.c); + * // => 4 + * + * _.set(object, ['x', '0', 'y', 'z'], 5); + * console.log(object.x[0].y.z); + * // => 5 + */ + function set(object, path, value) { + return object == null ? object : baseSet(object, path, value); + } + + /** + * This method is like `_.set` except that it accepts `customizer` which is + * invoked to produce the objects of `path`. If `customizer` returns `undefined` + * path creation is handled by the method instead. The `customizer` is invoked + * with three arguments: (nsValue, key, nsObject). + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The object to modify. + * @param {Array|string} path The path of the property to set. + * @param {*} value The value to set. + * @param {Function} [customizer] The function to customize assigned values. + * @returns {Object} Returns `object`. + * @example + * + * var object = {}; + * + * _.setWith(object, '[0][1]', 'a', Object); + * // => { '0': { '1': 'a' } } + */ + function setWith(object, path, value, customizer) { + customizer = typeof customizer == 'function' ? customizer : undefined; + return object == null ? object : baseSet(object, path, value, customizer); + } + + /** + * Creates an array of own enumerable string keyed-value pairs for `object` + * which can be consumed by `_.fromPairs`. If `object` is a map or set, its + * entries are returned. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @alias entries + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the key-value pairs. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.toPairs(new Foo); + * // => [['a', 1], ['b', 2]] (iteration order is not guaranteed) + */ + var toPairs = createToPairs(keys); + + /** + * Creates an array of own and inherited enumerable string keyed-value pairs + * for `object` which can be consumed by `_.fromPairs`. If `object` is a map + * or set, its entries are returned. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @alias entriesIn + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the key-value pairs. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.toPairsIn(new Foo); + * // => [['a', 1], ['b', 2], ['c', 3]] (iteration order is not guaranteed) + */ + var toPairsIn = createToPairs(keysIn); + + /** + * An alternative to `_.reduce`; this method transforms `object` to a new + * `accumulator` object which is the result of running each of its own + * enumerable string keyed properties thru `iteratee`, with each invocation + * potentially mutating the `accumulator` object. If `accumulator` is not + * provided, a new object with the same `[[Prototype]]` will be used. The + * iteratee is invoked with four arguments: (accumulator, value, key, object). + * Iteratee functions may exit iteration early by explicitly returning `false`. + * + * @static + * @memberOf _ + * @since 1.3.0 + * @category Object + * @param {Object} object The object to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @param {*} [accumulator] The custom accumulator value. + * @returns {*} Returns the accumulated value. + * @example + * + * _.transform([2, 3, 4], function(result, n) { + * result.push(n *= n); + * return n % 2 == 0; + * }, []); + * // => [4, 9] + * + * _.transform({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) { + * (result[value] || (result[value] = [])).push(key); + * }, {}); + * // => { '1': ['a', 'c'], '2': ['b'] } + */ + function transform(object, iteratee, accumulator) { + var isArr = isArray(object), + isArrLike = isArr || isBuffer(object) || isTypedArray(object); + + iteratee = getIteratee(iteratee, 4); + if (accumulator == null) { + var Ctor = object && object.constructor; + if (isArrLike) { + accumulator = isArr ? new Ctor : []; + } + else if (isObject(object)) { + accumulator = isFunction(Ctor) ? baseCreate(getPrototype(object)) : {}; + } + else { + accumulator = {}; + } + } + (isArrLike ? arrayEach : baseForOwn)(object, function(value, index, object) { + return iteratee(accumulator, value, index, object); + }); + return accumulator; + } + + /** + * Removes the property at `path` of `object`. + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The object to modify. + * @param {Array|string} path The path of the property to unset. + * @returns {boolean} Returns `true` if the property is deleted, else `false`. + * @example + * + * var object = { 'a': [{ 'b': { 'c': 7 } }] }; + * _.unset(object, 'a[0].b.c'); + * // => true + * + * console.log(object); + * // => { 'a': [{ 'b': {} }] }; + * + * _.unset(object, ['a', '0', 'b', 'c']); + * // => true + * + * console.log(object); + * // => { 'a': [{ 'b': {} }] }; + */ + function unset(object, path) { + return object == null ? true : baseUnset(object, path); + } + + /** + * This method is like `_.set` except that accepts `updater` to produce the + * value to set. Use `_.updateWith` to customize `path` creation. The `updater` + * is invoked with one argument: (value). + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 4.6.0 + * @category Object + * @param {Object} object The object to modify. + * @param {Array|string} path The path of the property to set. + * @param {Function} updater The function to produce the updated value. + * @returns {Object} Returns `object`. + * @example + * + * var object = { 'a': [{ 'b': { 'c': 3 } }] }; + * + * _.update(object, 'a[0].b.c', function(n) { return n * n; }); + * console.log(object.a[0].b.c); + * // => 9 + * + * _.update(object, 'x[0].y.z', function(n) { return n ? n + 1 : 0; }); + * console.log(object.x[0].y.z); + * // => 0 + */ + function update(object, path, updater) { + return object == null ? object : baseUpdate(object, path, castFunction(updater)); + } + + /** + * This method is like `_.update` except that it accepts `customizer` which is + * invoked to produce the objects of `path`. If `customizer` returns `undefined` + * path creation is handled by the method instead. The `customizer` is invoked + * with three arguments: (nsValue, key, nsObject). + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 4.6.0 + * @category Object + * @param {Object} object The object to modify. + * @param {Array|string} path The path of the property to set. + * @param {Function} updater The function to produce the updated value. + * @param {Function} [customizer] The function to customize assigned values. + * @returns {Object} Returns `object`. + * @example + * + * var object = {}; + * + * _.updateWith(object, '[0][1]', _.constant('a'), Object); + * // => { '0': { '1': 'a' } } + */ + function updateWith(object, path, updater, customizer) { + customizer = typeof customizer == 'function' ? customizer : undefined; + return object == null ? object : baseUpdate(object, path, castFunction(updater), customizer); + } + + /** + * Creates an array of the own enumerable string keyed property values of `object`. + * + * **Note:** Non-object values are coerced to objects. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property values. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.values(new Foo); + * // => [1, 2] (iteration order is not guaranteed) + * + * _.values('hi'); + * // => ['h', 'i'] + */ + function values(object) { + return object == null ? [] : baseValues(object, keys(object)); + } + + /** + * Creates an array of the own and inherited enumerable string keyed property + * values of `object`. + * + * **Note:** Non-object values are coerced to objects. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property values. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.valuesIn(new Foo); + * // => [1, 2, 3] (iteration order is not guaranteed) + */ + function valuesIn(object) { + return object == null ? [] : baseValues(object, keysIn(object)); + } + + /*------------------------------------------------------------------------*/ + + /** + * Clamps `number` within the inclusive `lower` and `upper` bounds. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Number + * @param {number} number The number to clamp. + * @param {number} [lower] The lower bound. + * @param {number} upper The upper bound. + * @returns {number} Returns the clamped number. + * @example + * + * _.clamp(-10, -5, 5); + * // => -5 + * + * _.clamp(10, -5, 5); + * // => 5 + */ + function clamp(number, lower, upper) { + if (upper === undefined) { + upper = lower; + lower = undefined; + } + if (upper !== undefined) { + upper = toNumber(upper); + upper = upper === upper ? upper : 0; + } + if (lower !== undefined) { + lower = toNumber(lower); + lower = lower === lower ? lower : 0; + } + return baseClamp(toNumber(number), lower, upper); + } + + /** + * Checks if `n` is between `start` and up to, but not including, `end`. If + * `end` is not specified, it's set to `start` with `start` then set to `0`. + * If `start` is greater than `end` the params are swapped to support + * negative ranges. + * + * @static + * @memberOf _ + * @since 3.3.0 + * @category Number + * @param {number} number The number to check. + * @param {number} [start=0] The start of the range. + * @param {number} end The end of the range. + * @returns {boolean} Returns `true` if `number` is in the range, else `false`. + * @see _.range, _.rangeRight + * @example + * + * _.inRange(3, 2, 4); + * // => true + * + * _.inRange(4, 8); + * // => true + * + * _.inRange(4, 2); + * // => false + * + * _.inRange(2, 2); + * // => false + * + * _.inRange(1.2, 2); + * // => true + * + * _.inRange(5.2, 4); + * // => false + * + * _.inRange(-3, -2, -6); + * // => true + */ + function inRange(number, start, end) { + start = toFinite(start); + if (end === undefined) { + end = start; + start = 0; + } else { + end = toFinite(end); + } + number = toNumber(number); + return baseInRange(number, start, end); + } + + /** + * Produces a random number between the inclusive `lower` and `upper` bounds. + * If only one argument is provided a number between `0` and the given number + * is returned. If `floating` is `true`, or either `lower` or `upper` are + * floats, a floating-point number is returned instead of an integer. + * + * **Note:** JavaScript follows the IEEE-754 standard for resolving + * floating-point values which can produce unexpected results. + * + * @static + * @memberOf _ + * @since 0.7.0 + * @category Number + * @param {number} [lower=0] The lower bound. + * @param {number} [upper=1] The upper bound. + * @param {boolean} [floating] Specify returning a floating-point number. + * @returns {number} Returns the random number. + * @example + * + * _.random(0, 5); + * // => an integer between 0 and 5 + * + * _.random(5); + * // => also an integer between 0 and 5 + * + * _.random(5, true); + * // => a floating-point number between 0 and 5 + * + * _.random(1.2, 5.2); + * // => a floating-point number between 1.2 and 5.2 + */ + function random(lower, upper, floating) { + if (floating && typeof floating != 'boolean' && isIterateeCall(lower, upper, floating)) { + upper = floating = undefined; + } + if (floating === undefined) { + if (typeof upper == 'boolean') { + floating = upper; + upper = undefined; + } + else if (typeof lower == 'boolean') { + floating = lower; + lower = undefined; + } + } + if (lower === undefined && upper === undefined) { + lower = 0; + upper = 1; + } + else { + lower = toFinite(lower); + if (upper === undefined) { + upper = lower; + lower = 0; + } else { + upper = toFinite(upper); + } + } + if (lower > upper) { + var temp = lower; + lower = upper; + upper = temp; + } + if (floating || lower % 1 || upper % 1) { + var rand = nativeRandom(); + return nativeMin(lower + (rand * (upper - lower + freeParseFloat('1e-' + ((rand + '').length - 1)))), upper); + } + return baseRandom(lower, upper); + } + + /*------------------------------------------------------------------------*/ + + /** + * Converts `string` to [camel case](https://en.wikipedia.org/wiki/CamelCase). + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to convert. + * @returns {string} Returns the camel cased string. + * @example + * + * _.camelCase('Foo Bar'); + * // => 'fooBar' + * + * _.camelCase('--foo-bar--'); + * // => 'fooBar' + * + * _.camelCase('__FOO_BAR__'); + * // => 'fooBar' + */ + var camelCase = createCompounder(function(result, word, index) { + word = word.toLowerCase(); + return result + (index ? capitalize(word) : word); + }); + + /** + * Converts the first character of `string` to upper case and the remaining + * to lower case. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to capitalize. + * @returns {string} Returns the capitalized string. + * @example + * + * _.capitalize('FRED'); + * // => 'Fred' + */ + function capitalize(string) { + return upperFirst(toString(string).toLowerCase()); + } + + /** + * Deburrs `string` by converting + * [Latin-1 Supplement](https://en.wikipedia.org/wiki/Latin-1_Supplement_(Unicode_block)#Character_table) + * and [Latin Extended-A](https://en.wikipedia.org/wiki/Latin_Extended-A) + * letters to basic Latin letters and removing + * [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks). + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to deburr. + * @returns {string} Returns the deburred string. + * @example + * + * _.deburr('déjà vu'); + * // => 'deja vu' + */ + function deburr(string) { + string = toString(string); + return string && string.replace(reLatin, deburrLetter).replace(reComboMark, ''); + } + + /** + * Checks if `string` ends with the given target string. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to inspect. + * @param {string} [target] The string to search for. + * @param {number} [position=string.length] The position to search up to. + * @returns {boolean} Returns `true` if `string` ends with `target`, + * else `false`. + * @example + * + * _.endsWith('abc', 'c'); + * // => true + * + * _.endsWith('abc', 'b'); + * // => false + * + * _.endsWith('abc', 'b', 2); + * // => true + */ + function endsWith(string, target, position) { + string = toString(string); + target = baseToString(target); + + var length = string.length; + position = position === undefined + ? length + : baseClamp(toInteger(position), 0, length); + + var end = position; + position -= target.length; + return position >= 0 && string.slice(position, end) == target; + } + + /** + * Converts the characters "&", "<", ">", '"', and "'" in `string` to their + * corresponding HTML entities. + * + * **Note:** No other characters are escaped. To escape additional + * characters use a third-party library like [_he_](https://mths.be/he). + * + * Though the ">" character is escaped for symmetry, characters like + * ">" and "/" don't need escaping in HTML and have no special meaning + * unless they're part of a tag or unquoted attribute value. See + * [Mathias Bynens's article](https://mathiasbynens.be/notes/ambiguous-ampersands) + * (under "semi-related fun fact") for more details. + * + * When working with HTML you should always + * [quote attribute values](http://wonko.com/post/html-escaping) to reduce + * XSS vectors. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category String + * @param {string} [string=''] The string to escape. + * @returns {string} Returns the escaped string. + * @example + * + * _.escape('fred, barney, & pebbles'); + * // => 'fred, barney, & pebbles' + */ + function escape(string) { + string = toString(string); + return (string && reHasUnescapedHtml.test(string)) + ? string.replace(reUnescapedHtml, escapeHtmlChar) + : string; + } + + /** + * Escapes the `RegExp` special characters "^", "$", "\", ".", "*", "+", + * "?", "(", ")", "[", "]", "{", "}", and "|" in `string`. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to escape. + * @returns {string} Returns the escaped string. + * @example + * + * _.escapeRegExp('[lodash](https://lodash.com/)'); + * // => '\[lodash\]\(https://lodash\.com/\)' + */ + function escapeRegExp(string) { + string = toString(string); + return (string && reHasRegExpChar.test(string)) + ? string.replace(reRegExpChar, '\\$&') + : string; + } + + /** + * Converts `string` to + * [kebab case](https://en.wikipedia.org/wiki/Letter_case#Special_case_styles). + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to convert. + * @returns {string} Returns the kebab cased string. + * @example + * + * _.kebabCase('Foo Bar'); + * // => 'foo-bar' + * + * _.kebabCase('fooBar'); + * // => 'foo-bar' + * + * _.kebabCase('__FOO_BAR__'); + * // => 'foo-bar' + */ + var kebabCase = createCompounder(function(result, word, index) { + return result + (index ? '-' : '') + word.toLowerCase(); + }); + + /** + * Converts `string`, as space separated words, to lower case. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category String + * @param {string} [string=''] The string to convert. + * @returns {string} Returns the lower cased string. + * @example + * + * _.lowerCase('--Foo-Bar--'); + * // => 'foo bar' + * + * _.lowerCase('fooBar'); + * // => 'foo bar' + * + * _.lowerCase('__FOO_BAR__'); + * // => 'foo bar' + */ + var lowerCase = createCompounder(function(result, word, index) { + return result + (index ? ' ' : '') + word.toLowerCase(); + }); + + /** + * Converts the first character of `string` to lower case. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category String + * @param {string} [string=''] The string to convert. + * @returns {string} Returns the converted string. + * @example + * + * _.lowerFirst('Fred'); + * // => 'fred' + * + * _.lowerFirst('FRED'); + * // => 'fRED' + */ + var lowerFirst = createCaseFirst('toLowerCase'); + + /** + * Pads `string` on the left and right sides if it's shorter than `length`. + * Padding characters are truncated if they can't be evenly divided by `length`. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to pad. + * @param {number} [length=0] The padding length. + * @param {string} [chars=' '] The string used as padding. + * @returns {string} Returns the padded string. + * @example + * + * _.pad('abc', 8); + * // => ' abc ' + * + * _.pad('abc', 8, '_-'); + * // => '_-abc_-_' + * + * _.pad('abc', 3); + * // => 'abc' + */ + function pad(string, length, chars) { + string = toString(string); + length = toInteger(length); + + var strLength = length ? stringSize(string) : 0; + if (!length || strLength >= length) { + return string; + } + var mid = (length - strLength) / 2; + return ( + createPadding(nativeFloor(mid), chars) + + string + + createPadding(nativeCeil(mid), chars) + ); + } + + /** + * Pads `string` on the right side if it's shorter than `length`. Padding + * characters are truncated if they exceed `length`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category String + * @param {string} [string=''] The string to pad. + * @param {number} [length=0] The padding length. + * @param {string} [chars=' '] The string used as padding. + * @returns {string} Returns the padded string. + * @example + * + * _.padEnd('abc', 6); + * // => 'abc ' + * + * _.padEnd('abc', 6, '_-'); + * // => 'abc_-_' + * + * _.padEnd('abc', 3); + * // => 'abc' + */ + function padEnd(string, length, chars) { + string = toString(string); + length = toInteger(length); + + var strLength = length ? stringSize(string) : 0; + return (length && strLength < length) + ? (string + createPadding(length - strLength, chars)) + : string; + } + + /** + * Pads `string` on the left side if it's shorter than `length`. Padding + * characters are truncated if they exceed `length`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category String + * @param {string} [string=''] The string to pad. + * @param {number} [length=0] The padding length. + * @param {string} [chars=' '] The string used as padding. + * @returns {string} Returns the padded string. + * @example + * + * _.padStart('abc', 6); + * // => ' abc' + * + * _.padStart('abc', 6, '_-'); + * // => '_-_abc' + * + * _.padStart('abc', 3); + * // => 'abc' + */ + function padStart(string, length, chars) { + string = toString(string); + length = toInteger(length); + + var strLength = length ? stringSize(string) : 0; + return (length && strLength < length) + ? (createPadding(length - strLength, chars) + string) + : string; + } + + /** + * Converts `string` to an integer of the specified radix. If `radix` is + * `undefined` or `0`, a `radix` of `10` is used unless `value` is a + * hexadecimal, in which case a `radix` of `16` is used. + * + * **Note:** This method aligns with the + * [ES5 implementation](https://es5.github.io/#x15.1.2.2) of `parseInt`. + * + * @static + * @memberOf _ + * @since 1.1.0 + * @category String + * @param {string} string The string to convert. + * @param {number} [radix=10] The radix to interpret `value` by. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {number} Returns the converted integer. + * @example + * + * _.parseInt('08'); + * // => 8 + * + * _.map(['6', '08', '10'], _.parseInt); + * // => [6, 8, 10] + */ + function parseInt(string, radix, guard) { + if (guard || radix == null) { + radix = 0; + } else if (radix) { + radix = +radix; + } + return nativeParseInt(toString(string).replace(reTrimStart, ''), radix || 0); + } + + /** + * Repeats the given string `n` times. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to repeat. + * @param {number} [n=1] The number of times to repeat the string. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {string} Returns the repeated string. + * @example + * + * _.repeat('*', 3); + * // => '***' + * + * _.repeat('abc', 2); + * // => 'abcabc' + * + * _.repeat('abc', 0); + * // => '' + */ + function repeat(string, n, guard) { + if ((guard ? isIterateeCall(string, n, guard) : n === undefined)) { + n = 1; + } else { + n = toInteger(n); + } + return baseRepeat(toString(string), n); + } + + /** + * Replaces matches for `pattern` in `string` with `replacement`. + * + * **Note:** This method is based on + * [`String#replace`](https://mdn.io/String/replace). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category String + * @param {string} [string=''] The string to modify. + * @param {RegExp|string} pattern The pattern to replace. + * @param {Function|string} replacement The match replacement. + * @returns {string} Returns the modified string. + * @example + * + * _.replace('Hi Fred', 'Fred', 'Barney'); + * // => 'Hi Barney' + */ + function replace() { + var args = arguments, + string = toString(args[0]); + + return args.length < 3 ? string : string.replace(args[1], args[2]); + } + + /** + * Converts `string` to + * [snake case](https://en.wikipedia.org/wiki/Snake_case). + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to convert. + * @returns {string} Returns the snake cased string. + * @example + * + * _.snakeCase('Foo Bar'); + * // => 'foo_bar' + * + * _.snakeCase('fooBar'); + * // => 'foo_bar' + * + * _.snakeCase('--FOO-BAR--'); + * // => 'foo_bar' + */ + var snakeCase = createCompounder(function(result, word, index) { + return result + (index ? '_' : '') + word.toLowerCase(); + }); + + /** + * Splits `string` by `separator`. + * + * **Note:** This method is based on + * [`String#split`](https://mdn.io/String/split). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category String + * @param {string} [string=''] The string to split. + * @param {RegExp|string} separator The separator pattern to split by. + * @param {number} [limit] The length to truncate results to. + * @returns {Array} Returns the string segments. + * @example + * + * _.split('a-b-c', '-', 2); + * // => ['a', 'b'] + */ + function split(string, separator, limit) { + if (limit && typeof limit != 'number' && isIterateeCall(string, separator, limit)) { + separator = limit = undefined; + } + limit = limit === undefined ? MAX_ARRAY_LENGTH : limit >>> 0; + if (!limit) { + return []; + } + string = toString(string); + if (string && ( + typeof separator == 'string' || + (separator != null && !isRegExp(separator)) + )) { + separator = baseToString(separator); + if (!separator && hasUnicode(string)) { + return castSlice(stringToArray(string), 0, limit); + } + } + return string.split(separator, limit); + } + + /** + * Converts `string` to + * [start case](https://en.wikipedia.org/wiki/Letter_case#Stylistic_or_specialised_usage). + * + * @static + * @memberOf _ + * @since 3.1.0 + * @category String + * @param {string} [string=''] The string to convert. + * @returns {string} Returns the start cased string. + * @example + * + * _.startCase('--foo-bar--'); + * // => 'Foo Bar' + * + * _.startCase('fooBar'); + * // => 'Foo Bar' + * + * _.startCase('__FOO_BAR__'); + * // => 'FOO BAR' + */ + var startCase = createCompounder(function(result, word, index) { + return result + (index ? ' ' : '') + upperFirst(word); + }); + + /** + * Checks if `string` starts with the given target string. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to inspect. + * @param {string} [target] The string to search for. + * @param {number} [position=0] The position to search from. + * @returns {boolean} Returns `true` if `string` starts with `target`, + * else `false`. + * @example + * + * _.startsWith('abc', 'a'); + * // => true + * + * _.startsWith('abc', 'b'); + * // => false + * + * _.startsWith('abc', 'b', 1); + * // => true + */ + function startsWith(string, target, position) { + string = toString(string); + position = position == null + ? 0 + : baseClamp(toInteger(position), 0, string.length); + + target = baseToString(target); + return string.slice(position, position + target.length) == target; + } + + /** + * Creates a compiled template function that can interpolate data properties + * in "interpolate" delimiters, HTML-escape interpolated data properties in + * "escape" delimiters, and execute JavaScript in "evaluate" delimiters. Data + * properties may be accessed as free variables in the template. If a setting + * object is given, it takes precedence over `_.templateSettings` values. + * + * **Note:** In the development build `_.template` utilizes + * [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl) + * for easier debugging. + * + * For more information on precompiling templates see + * [lodash's custom builds documentation](https://lodash.com/custom-builds). + * + * For more information on Chrome extension sandboxes see + * [Chrome's extensions documentation](https://developer.chrome.com/extensions/sandboxingEval). + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category String + * @param {string} [string=''] The template string. + * @param {Object} [options={}] The options object. + * @param {RegExp} [options.escape=_.templateSettings.escape] + * The HTML "escape" delimiter. + * @param {RegExp} [options.evaluate=_.templateSettings.evaluate] + * The "evaluate" delimiter. + * @param {Object} [options.imports=_.templateSettings.imports] + * An object to import into the template as free variables. + * @param {RegExp} [options.interpolate=_.templateSettings.interpolate] + * The "interpolate" delimiter. + * @param {string} [options.sourceURL='lodash.templateSources[n]'] + * The sourceURL of the compiled template. + * @param {string} [options.variable='obj'] + * The data object variable name. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {Function} Returns the compiled template function. + * @example + * + * // Use the "interpolate" delimiter to create a compiled template. + * var compiled = _.template('hello <%= user %>!'); + * compiled({ 'user': 'fred' }); + * // => 'hello fred!' + * + * // Use the HTML "escape" delimiter to escape data property values. + * var compiled = _.template('<%- value %>'); + * compiled({ 'value': ' + + + + + + + diff --git a/integration/messaging/test/static/firebase-messaging-sw.js b/integration/messaging/test/static/firebase-messaging-sw.js new file mode 100644 index 00000000000..d19a00e44f0 --- /dev/null +++ b/integration/messaging/test/static/firebase-messaging-sw.js @@ -0,0 +1,18 @@ +/** + * @license + * Copyright 2017 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. + */ + +importScripts('./sw-base.js'); diff --git a/integration/messaging/test/static/helpers.js b/integration/messaging/test/static/helpers.js new file mode 100644 index 00000000000..7712a636bea --- /dev/null +++ b/integration/messaging/test/static/helpers.js @@ -0,0 +1,52 @@ +/** + * @license + * Copyright 2020 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. + */ + +async function addPayloadToDb(payload) { + const dbOpenReq = indexedDB.open(TEST_DB); + + dbOpenReq.onupgradeneeded = () => { + const db = dbOpenReq.result; + + // store creation is a synchronized call + console.log('creating object store...'); + db.createObjectStore(BACKGROUND_MESSAGES_OBJECT_STORE, { + keyPath: BACKGROUND_MESSAGES_OBJECT_STORE_PRIMARY_KEY + }); + }; + + dbOpenReq.onsuccess = () => { + const db = dbOpenReq.result; + + addPayloadToDbInternal(db, { + ...payload, + // ndx is required as the primary key of the store. It doesn't have any other testing purpose + ndx: BACKGROUND_MESSAGES_OBJECT_STORE_DEFAULT_NDX + }); + }; +} + +async function addPayloadToDbInternal(db, payload) { + // onsuccess might race with onupgradeneeded. Consequently causing "object stores was not found" + // error. Therefore, wait briefly for db.createObjectStore to complete + const delay = ms => new Promise(res => setTimeout(res, ms)); + await delay(/* milliseconds= */ 30000); + + tx = db.transaction(BACKGROUND_MESSAGES_OBJECT_STORE, 'readwrite'); + + console.log('adding message payload to db: ' + JSON.stringify(payload)); + addReq = tx.objectStore(BACKGROUND_MESSAGES_OBJECT_STORE).add(payload); +} diff --git a/integration/messaging/test/static/sw-base.js b/integration/messaging/test/static/sw-base.js new file mode 100644 index 00000000000..d7617bee105 --- /dev/null +++ b/integration/messaging/test/static/sw-base.js @@ -0,0 +1,37 @@ +/** + * @license + * Copyright 2017 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. + */ + +importScripts('../constants.js'); +importScripts('../helpers.js'); + +// HEAD targets served through express +importScripts('/firebase-app.js'); +importScripts('/firebase-messaging.js'); + +firebase.initializeApp(FIREBASE_CONFIG); +const messaging = firebase.messaging(); + +messaging.setBackgroundMessageHandler(payload => { + console.log( + TAG + + 'a background message is received in the background handler hook: ' + + JSON.stringify(payload) + + '. Storing it into idb for tests to read...' + ); + + addPayloadToDb(payload); +}); diff --git a/integration/messaging/test/static/valid-manifest/index.html b/integration/messaging/test/static/valid-manifest/index.html new file mode 100644 index 00000000000..3f482bab7af --- /dev/null +++ b/integration/messaging/test/static/valid-manifest/index.html @@ -0,0 +1,34 @@ + + + + + FCM Demo + + + + +

Valid Manifest

+ + + + + + + + diff --git a/integration/messaging/test/static/valid-manifest/manifest.json b/integration/messaging/test/static/valid-manifest/manifest.json new file mode 100644 index 00000000000..c3181d8bc95 --- /dev/null +++ b/integration/messaging/test/static/valid-manifest/manifest.json @@ -0,0 +1,3 @@ +{ + "gcm_sender_id": "750970317741" +} diff --git a/integration/messaging/test/static/valid-vapid-key-modern-sw/index.html b/integration/messaging/test/static/valid-vapid-key-modern-sw/index.html new file mode 100644 index 00000000000..8d7d93c9f5a --- /dev/null +++ b/integration/messaging/test/static/valid-vapid-key-modern-sw/index.html @@ -0,0 +1,44 @@ + + + + + FCM Demo + + + +

Valid WITH VAPID Key - Modern SW

+ + + + + + + + + diff --git a/integration/messaging/test/static/valid-vapid-key-modern-sw/sw.js b/integration/messaging/test/static/valid-vapid-key-modern-sw/sw.js new file mode 100644 index 00000000000..459e83e948a --- /dev/null +++ b/integration/messaging/test/static/valid-vapid-key-modern-sw/sw.js @@ -0,0 +1,37 @@ +/** + * @license + * Copyright 2017 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. + */ + +importScripts('../constants.js'); +importScripts('../helpers.js'); + +// HEAD targets served through express +importScripts('/firebase-app.js'); +importScripts('/firebase-messaging.js'); + +firebase.initializeApp(FIREBASE_CONFIG); +const messaging = firebase.messaging(); + +messaging.onBackgroundMessage(payload => { + console.log( + TAG + + 'a background message is received in the onBackgroundMessage hook: ' + + JSON.stringify(payload) + + '. Storing it into idb for tests to read...' + ); + + addPayloadToDb(payload); +}); diff --git a/integration/messaging/test/static/valid-vapid-key/index.html b/integration/messaging/test/static/valid-vapid-key/index.html new file mode 100644 index 00000000000..b8d32ec7f00 --- /dev/null +++ b/integration/messaging/test/static/valid-vapid-key/index.html @@ -0,0 +1,44 @@ + + + + + FCM Demo + + + +

Valid WITH VAPID Key

+ + + + + + + + + diff --git a/integration/messaging/test/static/valid-vapid-key/sw.js b/integration/messaging/test/static/valid-vapid-key/sw.js new file mode 100644 index 00000000000..f18afe56bdd --- /dev/null +++ b/integration/messaging/test/static/valid-vapid-key/sw.js @@ -0,0 +1,18 @@ +/** + * @license + * Copyright 2017 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. + */ + +importScripts('../sw-base.js'); diff --git a/integration/messaging/test/test-receive-background.js b/integration/messaging/test/test-receive-background.js new file mode 100644 index 00000000000..258bff32051 --- /dev/null +++ b/integration/messaging/test/test-receive-background.js @@ -0,0 +1,157 @@ +/** + * @license + * Copyright 2017 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. + */ + +const testServer = require('./utils/test-server'); +const sendMessage = require('./utils/sendMessage'); +const retrieveToken = require('./utils/retrieveToken'); +const seleniumAssistant = require('selenium-assistant'); +const getReceivedBackgroundMessages = require('./utils/getReceivedBackgroundMessages'); +const createPermittedWebDriver = require('./utils/createPermittedWebDriver'); +const checkSendResponse = require('./utils/checkSendResponse'); +const checkMessageReceived = require('./utils/checkMessageReceived'); +const openNewTab = require('./utils/openNewTab'); + +const TEST_DOMAINS = ['valid-vapid-key-modern-sw']; + +// 4 minutes. The fact that the flow includes making a request to the Send Service, +// storing/retrieving form indexedDb asynchronously makes these test units to have a execution time +// variance. Therefore, allowing these units to have a longer time to work is crucial. +const TIMEOUT_BACKGROUND_MESSAGE_TEST_UNIT_MILLISECONDS = 240000; + +// 1 minute. Wait for object store to be created and received message to be stored in idb. This +// waiting time MUST be longer than the wait time for adding to db in the sw. +const WAIT_TIME_BEFORE_RETRIEVING_BACKGROUND_MESSAGES_MILLISECONDS = 60000; + +const wait = ms => new Promise(res => setTimeout(res, ms)); + +// 50% of integration test run time is spent in testing receiving in background. Running these +// slower tests last so other tests can fail quickly if they do fail. +require('./test-token-delete'); +require('./test-token-update'); +require('./test-useValidManifest'); +require('./test-useDefaultServiceWorker'); +require('./test-receive-foreground'); + +describe('Firebase Messaging Integration Tests > Test Background Receive', function () { + this.retries(2); + let globalWebDriver; + + before(async function () { + await testServer.start(); + }); + + after(async function () { + await testServer.stop(); + }); + + // TODO: enable testing for firefox + seleniumAssistant.getLocalBrowsers().forEach(assistantBrowser => { + if (assistantBrowser.getId() !== 'chrome') { + return; + } + + TEST_DOMAINS.forEach(domain => { + describe(`Testing browser: ${assistantBrowser.getPrettyName()} : ${domain}`, function () { + before(async function () { + globalWebDriver = createPermittedWebDriver( + /* browser= */ assistantBrowser.getId() + ); + }); + + it('Background app can receive a {} empty message from sw', async function () { + this.timeout(TIMEOUT_BACKGROUND_MESSAGE_TEST_UNIT_MILLISECONDS); + + // Clearing the cache and db data by killing the previously instantiated driver. Note that + // ideally this call is placed inside the after/before hooks. However, Mocha forbids + // operations longer than 2s in hooks. Hence, this clearing call needs to be inside the + // test unit. + await seleniumAssistant.killWebDriver(globalWebDriver); + + globalWebDriver = createPermittedWebDriver( + /* browser= */ assistantBrowser.getId() + ); + + prepareBackgroundApp(globalWebDriver, domain); + + checkSendResponse( + await sendMessage({ + to: await retrieveToken(globalWebDriver) + }) + ); + + await wait( + WAIT_TIME_BEFORE_RETRIEVING_BACKGROUND_MESSAGES_MILLISECONDS + ); + + checkMessageReceived( + await getReceivedBackgroundMessages(globalWebDriver), + /* expectedNotificationPayload= */ null, + /* expectedDataPayload= */ null, + /* isLegacyPayload= */ false + ); + }); + + it('Background app can receive a {"data"} message frow sw', async function () { + this.timeout(TIMEOUT_BACKGROUND_MESSAGE_TEST_UNIT_MILLISECONDS); + + await seleniumAssistant.killWebDriver(globalWebDriver); + + globalWebDriver = createPermittedWebDriver( + /* browser= */ assistantBrowser.getId() + ); + + prepareBackgroundApp(globalWebDriver, domain); + + checkSendResponse( + await sendMessage({ + to: await retrieveToken(globalWebDriver), + data: getTestDataPayload() + }) + ); + + await wait( + WAIT_TIME_BEFORE_RETRIEVING_BACKGROUND_MESSAGES_MILLISECONDS + ); + + checkMessageReceived( + await getReceivedBackgroundMessages(globalWebDriver), + /* expectedNotificationPayload= */ null, + /* expectedDataPayload= */ getTestDataPayload() + ); + }); + }); + }); + }); +}); + +async function prepareBackgroundApp(globalWebDriver, domain) { + await globalWebDriver.get(`${testServer.serverAddress}/${domain}/`); + // TODO: remove the try/catch block once the underlying bug has been resolved. Shift window focus + // away from app window so that background messages can be received/processed + try { + await openNewTab(globalWebDriver); + } catch (err) { + // ChromeDriver seems to have an open bug which throws "JavascriptError: javascript error: + // circular reference". Nevertheless, a new tab can still be opened. Hence, just catch and + // continue here. + console.log('FCM (ignored on purpose): ' + err); + } +} + +function getTestDataPayload() { + return { hello: 'world' }; +} diff --git a/integration/messaging/test/test-receive-foreground.js b/integration/messaging/test/test-receive-foreground.js new file mode 100644 index 00000000000..8c23a07fae6 --- /dev/null +++ b/integration/messaging/test/test-receive-foreground.js @@ -0,0 +1,177 @@ +/** + * @license + * Copyright 2017 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. + */ + +const testServer = require('./utils/test-server'); +const sendMessage = require('./utils/sendMessage'); +const retrieveToken = require('./utils/retrieveToken'); +const seleniumAssistant = require('selenium-assistant'); +const checkSendResponse = require('./utils/checkSendResponse'); +const getReceivedForegroundMessages = require('./utils/getReceivedForegroundMessages'); +const checkMessageReceived = require('./utils/checkMessageReceived'); +const createPermittedWebDriver = require('./utils/createPermittedWebDriver'); + +// Only testing 'valid-vapid-key' because 'valid-vapid-key-modern-sw' has the same behavior +const TEST_DOMAINS = ['valid-vapid-key']; +const TIMEOUT_FOREGROUND_MESSAGE_TEST_UNIT_MILLISECONDS = 120000; + +// Getting and deleting token is the entry step of using FM SDK. Let it run first and fail quickly. +require('./test-token-delete'); + +describe('Firebase Messaging Integration Tests > Test Foreground Receive', function () { + this.retries(2); + let globalWebDriver; + + before(async function () { + await testServer.start(); + }); + + after(async function () { + await testServer.stop(); + }); + + // TODO: enable testing for firefox + seleniumAssistant.getLocalBrowsers().forEach(assistantBrowser => { + if (assistantBrowser.getId() !== 'chrome') { + return; + } + + TEST_DOMAINS.forEach(domain => { + describe(`Testing browser: ${assistantBrowser.getPrettyName()} : ${domain}`, function () { + before(async function () { + globalWebDriver = createPermittedWebDriver( + /* browser= */ assistantBrowser.getId() + ); + }); + }); + + it('Foreground app can receive a {} empty message in onMessage', async function () { + this.timeout(TIMEOUT_FOREGROUND_MESSAGE_TEST_UNIT_MILLISECONDS); + + await seleniumAssistant.killWebDriver(globalWebDriver); + + globalWebDriver = createPermittedWebDriver( + /* browser= */ assistantBrowser.getId() + ); + + await globalWebDriver.get(`${testServer.serverAddress}/${domain}/`); + + let token = await retrieveToken(globalWebDriver); + checkSendResponse( + await sendMessage({ + to: token + }) + ); + + await checkMessageReceived( + await getReceivedForegroundMessages(globalWebDriver), + /* expectedNotificationPayload= */ null, + /* expectedDataPayload= */ null + ); + }); + + it('Foreground app can receive a {"notification"} message in onMessage', async function () { + this.timeout(TIMEOUT_FOREGROUND_MESSAGE_TEST_UNIT_MILLISECONDS); + + await seleniumAssistant.killWebDriver(globalWebDriver); + + globalWebDriver = createPermittedWebDriver( + /* browser= */ assistantBrowser.getId() + ); + + await globalWebDriver.get(`${testServer.serverAddress}/${domain}/`); + + checkSendResponse( + await sendMessage({ + to: await retrieveToken(globalWebDriver), + notification: getTestNotificationPayload() + }) + ); + + await checkMessageReceived( + await getReceivedForegroundMessages(globalWebDriver), + /* expectedNotificationPayload= */ getTestNotificationPayload(), + /* expectedDataPayload= */ null + ); + }); + + it('Foreground app can receive a {"data"} message in onMessage', async function () { + this.timeout(TIMEOUT_FOREGROUND_MESSAGE_TEST_UNIT_MILLISECONDS); + + await seleniumAssistant.killWebDriver(globalWebDriver); + + globalWebDriver = createPermittedWebDriver( + /* browser= */ assistantBrowser.getId() + ); + + await globalWebDriver.get(`${testServer.serverAddress}/${domain}/`); + + checkSendResponse( + await sendMessage({ + to: await retrieveToken(globalWebDriver), + data: getTestDataPayload() + }) + ); + + await checkMessageReceived( + await getReceivedForegroundMessages(globalWebDriver), + /* expectedNotificationPayload= */ null, + /* expectedDataPayload= */ getTestDataPayload() + ); + }); + + it('Foreground app can receive a {"notification", "data"} message in onMessage', async function () { + this.timeout(TIMEOUT_FOREGROUND_MESSAGE_TEST_UNIT_MILLISECONDS); + + await seleniumAssistant.killWebDriver(globalWebDriver); + + globalWebDriver = createPermittedWebDriver( + /* browser= */ assistantBrowser.getId() + ); + + await globalWebDriver.get(`${testServer.serverAddress}/${domain}/`); + + checkSendResponse( + await sendMessage({ + to: await retrieveToken(globalWebDriver), + data: getTestDataPayload(), + notification: getTestNotificationPayload() + }) + ); + + await checkMessageReceived( + await getReceivedForegroundMessages(globalWebDriver), + /* expectedNotificationPayload= */ getTestNotificationPayload(), + /* expectedDataPayload= */ getTestDataPayload() + ); + }); + }); + }); +}); + +function getTestDataPayload() { + return { hello: 'world' }; +} + +function getTestNotificationPayload() { + return { + title: 'test title', + body: 'test body', + icon: '/test/icon.png', + click_action: '/', + tag: 'test-tag' + }; +} diff --git a/integration/messaging/test/test-token-delete.js b/integration/messaging/test/test-token-delete.js new file mode 100644 index 00000000000..d3f0fca908e --- /dev/null +++ b/integration/messaging/test/test-token-delete.js @@ -0,0 +1,82 @@ +/** + * @license + * Copyright 2017 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. + */ + +const expect = require('chai').expect; +const testServer = require('./utils/test-server'); +const deleteToken = require('./utils/deleteToken'); +const clearAppForTest = require('./utils/clearAppForTest'); +const retrieveToken = require('./utils/retrieveToken'); +const seleniumAssistant = require('selenium-assistant'); +const createPermittedWebDriver = require('./utils/createPermittedWebDriver'); + +const TEST_SUITE_TIMEOUT_MS = 20000; +const TEST_DOMAIN = 'valid-vapid-key'; + +describe('Firebase Messaging Integration Tests > get and delete token', function () { + this.timeout(TEST_SUITE_TIMEOUT_MS); + this.retries(2); + let globalWebDriver; + + before(async function () { + await testServer.start(); + }); + + after(async function () { + await testServer.stop(); + }); + + const availableBrowsers = seleniumAssistant.getLocalBrowsers(); + availableBrowsers.forEach(assistantBrowser => { + // TODO: enable testing for firefox + if (assistantBrowser.getId() !== 'chrome') { + return; + } + + describe(`Testing browser: ${assistantBrowser.getPrettyName()} : ${TEST_DOMAIN}`, function () { + before(async function () { + // Use one webDriver per browser instead of one per test to speed up test. + globalWebDriver = createPermittedWebDriver( + /* browser= */ assistantBrowser.getId() + ); + + await globalWebDriver.get( + `${testServer.serverAddress}/${TEST_DOMAIN}/` + ); + }); + + after(async function () { + await seleniumAssistant.killWebDriver(globalWebDriver); + }); + + afterEach(async function () { + await clearAppForTest(globalWebDriver); + }); + + it(`Test app can delete a valid token`, async function () { + const token = await retrieveToken(globalWebDriver); + expect(token).to.exist; + try { + await deleteToken(globalWebDriver, token); + console.log('Token deletion succeed.'); + } catch (e) { + console.log('Error trying to delete FCM token: ', e); + fail(); + } + }); + }); + }); +}); diff --git a/integration/messaging/test/test-token-update.js b/integration/messaging/test/test-token-update.js new file mode 100644 index 00000000000..d2fc33782c1 --- /dev/null +++ b/integration/messaging/test/test-token-update.js @@ -0,0 +1,88 @@ +/** + * @license + * Copyright 2017 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. + */ + +const seleniumAssistant = require('selenium-assistant'); +const expect = require('chai').expect; +const testServer = require('./utils/test-server'); +const retrieveToken = require('./utils/retrieveToken'); +const clearAppForTest = require('./utils/clearAppForTest'); +const createPermittedWebDriver = require('./utils/createPermittedWebDriver'); +const timeForward = require('./utils/forwardTime'); +const triggerGetToken = require('./utils/triggerGetToken'); +const getErrors = require('./utils/getErrors'); + +const TEST_SUITE_TIMEOUT_MS = 70000; +const TEST_DOMAIN = 'valid-vapid-key'; + +// Getting and deleting token is the entry step of using FM SDK. Let it run first and fail quickly. +require('./test-token-delete'); + +describe('Firebase Messaging Integration Tests > update a token', function () { + this.timeout(TEST_SUITE_TIMEOUT_MS); + this.retries(2); + + let globalWebDriver; + + before(async function () { + await testServer.start(); + }); + + after(async function () { + await testServer.stop(); + }); + + const availableBrowsers = seleniumAssistant.getLocalBrowsers(); + // TODO: enable testing for edge and firefox if applicable + availableBrowsers.forEach(assistantBrowser => { + if (assistantBrowser.getId() !== 'chrome') { + return; + } + + describe(`Testing browser: ${assistantBrowser.getPrettyName()} : ${TEST_DOMAIN}`, function () { + before(async function () { + // Use one webDriver per browser instead of one per test to speed up test. + globalWebDriver = createPermittedWebDriver( + /* browser= */ assistantBrowser.getId() + ); + await globalWebDriver.get( + `${testServer.serverAddress}/${TEST_DOMAIN}/` + ); + }); + + after(async function () { + await seleniumAssistant.killWebDriver(globalWebDriver); + }); + + afterEach(async function () { + await clearAppForTest(globalWebDriver); + }); + + it(`should update a token`, async function () { + const token = await retrieveToken(globalWebDriver); + expect(token).to.exist; + + // roll the clock forward > 7days + await timeForward(globalWebDriver); + const updatedToken = await triggerGetToken(globalWebDriver); + const errors = await getErrors(globalWebDriver); + expect(errors).to.exist; + expect(errors.length).to.equal(0); + expect(updatedToken).to.exist; + }); + }); + }); +}); diff --git a/integration/messaging/test/test-useDefaultServiceWorker.js b/integration/messaging/test/test-useDefaultServiceWorker.js new file mode 100644 index 00000000000..0e3a903042f --- /dev/null +++ b/integration/messaging/test/test-useDefaultServiceWorker.js @@ -0,0 +1,64 @@ +/** + * @license + * Copyright 2017 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. + */ + +const expect = require('chai').expect; +const testServer = require('./utils/test-server'); +const retrieveToken = require('./utils/retrieveToken'); +const seleniumAssistant = require('selenium-assistant'); +const createPermittedWebDriver = require('./utils/createPermittedWebDriver'); + +const TEST_DOMAIN = 'default-sw'; +const TEST_SUITE_TIMEOUT_MS = 70000; + +// Getting and deleting token is the entry step of using FM SDK. Let it run first and fail quickly. +require('./test-token-delete'); + +describe(`Firebase Messaging Integration Tests > Use 'firebase-messaging-sw.js' by default`, function () { + this.timeout(TEST_SUITE_TIMEOUT_MS); + + let globalWebDriver; + + before(async function () { + await testServer.start(); + }); + + after(async function () { + await testServer.stop(); + await seleniumAssistant.killWebDriver(globalWebDriver); + }); + + it(`should use default SW by default`, async function () { + globalWebDriver = createPermittedWebDriver('chrome'); + await globalWebDriver.get(`${testServer.serverAddress}/${TEST_DOMAIN}/`); + + // If we have a token, then we know the default SW worked. + const token = await retrieveToken(globalWebDriver); + expect(token).to.exist; + + const result = await globalWebDriver.executeAsyncScript(function (cb) { + navigator.serviceWorker + .getRegistrations() + .then(swReg => { + return ( + swReg[0].scope.indexOf('/firebase-cloud-messaging-push-scope') !== 0 + ); + }) + .then(cb, cb); + }); + expect(result).to.equal(true); + }); +}); diff --git a/integration/messaging/test/test-useValidManifest.js b/integration/messaging/test/test-useValidManifest.js new file mode 100644 index 00000000000..4c566fc9eb4 --- /dev/null +++ b/integration/messaging/test/test-useValidManifest.js @@ -0,0 +1,52 @@ +/** + * @license + * Copyright 2017 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. + */ + +const expect = require('chai').expect; +const testServer = require('./utils/test-server'); +const retrieveToken = require('./utils/retrieveToken'); +const seleniumAssistant = require('selenium-assistant'); +const createPermittedWebDriver = require('./utils/createPermittedWebDriver'); + +const TEST_DOMAIN = 'valid-manifest'; +const TEST_SUITE_TIMEOUT_MS = 70000; + +// Getting and deleting token is the entry step of using FM SDK. Let it run first and fail quickly. +require('./test-token-delete'); + +describe(`Firebase Messaging Integration Tests > Use 'use valid manifest`, function () { + this.timeout(TEST_SUITE_TIMEOUT_MS); + + let globalWebDriver; + + before(async function () { + await testServer.start(); + }); + + after(async function () { + await testServer.stop(); + await seleniumAssistant.killWebDriver(globalWebDriver); + }); + + it(`should allow valid manifest`, async function () { + globalWebDriver = createPermittedWebDriver('chrome'); + await globalWebDriver.get(`${testServer.serverAddress}/${TEST_DOMAIN}/`); + + // If we have a token, then we know the default SW + Manifest worked. + const token = await retrieveToken(globalWebDriver); + expect(token).to.exist; + }); +}); diff --git a/integration/messaging/test/utils/checkMessageReceived.js b/integration/messaging/test/utils/checkMessageReceived.js new file mode 100644 index 00000000000..c407ae6b9b8 --- /dev/null +++ b/integration/messaging/test/utils/checkMessageReceived.js @@ -0,0 +1,66 @@ +/** + * @license + * Copyright 2020 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. + */ +const expect = require('chai').expect; + +const TEST_PROJECT_SENDER_ID = '750970317741'; +const DEFAULT_COLLAPSE_KEY_VALUE = 'do_not_collapse'; +const FIELD_FROM = 'from'; +const FIELD_COLLAPSE_KEY_LEGACY = 'collapse_key'; +const FIELD_COLLAPSE_KEY = 'collapseKey'; + +const FIELD_DATA = 'data'; +const FIELD_NOTIFICATION = 'notification'; + +module.exports = ( + receivedMessages, + expectedNotificationPayload, + expectedDataPayload +) => { + expect(receivedMessages).to.exist; + + const message = receivedMessages[0]; + + expect(message[FIELD_FROM]).to.equal(TEST_PROJECT_SENDER_ID); + const collapseKey = !!message[FIELD_COLLAPSE_KEY_LEGACY] + ? message[FIELD_COLLAPSE_KEY_LEGACY] + : message[FIELD_COLLAPSE_KEY]; + expect(collapseKey).to.equal(DEFAULT_COLLAPSE_KEY_VALUE); + + if (expectedNotificationPayload) { + expect(message[FIELD_NOTIFICATION]).to.deep.equal( + getTestNotificationPayload() + ); + } + + if (expectedDataPayload) { + expect(message[FIELD_DATA]).to.deep.equal(getTestDataPayload()); + } +}; + +function getTestNotificationPayload() { + return { + title: 'test title', + body: 'test body', + icon: '/test/icon.png', + click_action: '/', + tag: 'test-tag' + }; +} + +function getTestDataPayload() { + return { hello: 'world' }; +} diff --git a/integration/messaging/test/utils/checkSendResponse.js b/integration/messaging/test/utils/checkSendResponse.js new file mode 100644 index 00000000000..5c43f78ef8a --- /dev/null +++ b/integration/messaging/test/utils/checkSendResponse.js @@ -0,0 +1,22 @@ +/** + * @license + * Copyright 2020 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. + */ +const expect = require('chai').expect; + +module.exports = response => { + expect(response).to.exist; + expect(response.success).to.equal(1); +}; diff --git a/integration/messaging/test/utils/clearAppForTest.js b/integration/messaging/test/utils/clearAppForTest.js new file mode 100644 index 00000000000..d4e153dda9d --- /dev/null +++ b/integration/messaging/test/utils/clearAppForTest.js @@ -0,0 +1,30 @@ +/** + * @license + * Copyright 2017 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. + */ + +module.exports = async webDriver => { + console.log('Clearing received messaged and errors from the test app.'); + + await webDriver.wait(() => { + return webDriver.executeScript(() => { + return !!window.__test; + }); + }); + + return webDriver.executeScript(() => { + return window.__test.clearInstanceForTest(); + }); +}; diff --git a/integration/messaging/test/utils/createPermittedWebDriver.js b/integration/messaging/test/utils/createPermittedWebDriver.js new file mode 100644 index 00000000000..7adf117ae86 --- /dev/null +++ b/integration/messaging/test/utils/createPermittedWebDriver.js @@ -0,0 +1,50 @@ +/** + * @license + * Copyright 2017 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. + */ + +const testServer = require('./test-server'); +const { Builder } = require('selenium-webdriver'); +const chrome = require('selenium-webdriver/chrome'); + +const CHROME_PREFERENCE_NOTIFICATION_ENABLED = 1; +const SERVER_ADDRESS_SEPARATOR = ',*'; + +module.exports = browser => { + const chromePreferences = { + profile: { + content_settings: { + exceptions: { + notifications: {} + } + } + } + }; + chromePreferences.profile.content_settings.exceptions.notifications[ + testServer.serverAddress + SERVER_ADDRESS_SEPARATOR + ] = { + setting: CHROME_PREFERENCE_NOTIFICATION_ENABLED + }; + + let chromeOptions = new chrome.Options().setUserPreferences( + chromePreferences + ); + + let driver = new Builder() + .forBrowser(browser) + .setChromeOptions(chromeOptions) + .build(); + return driver; +}; diff --git a/integration/messaging/test/utils/deleteToken.js b/integration/messaging/test/utils/deleteToken.js new file mode 100644 index 00000000000..51a0f84b4a8 --- /dev/null +++ b/integration/messaging/test/utils/deleteToken.js @@ -0,0 +1,29 @@ +/** + * @license + * Copyright 2017 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. + */ + +module.exports = async (webdriver, token) => { + console.log('Deleting token: ', token); + await webdriver.wait(() => { + return webdriver.executeScript(() => { + return !!window.__test; + }); + }); + + return webdriver.executeScript(token => { + return window.__test.triggerDeleteToken(token); + }); +}; diff --git a/integration/messaging/test/utils/forwardTime.js b/integration/messaging/test/utils/forwardTime.js new file mode 100644 index 00000000000..f97c25998b0 --- /dev/null +++ b/integration/messaging/test/utils/forwardTime.js @@ -0,0 +1,29 @@ +/** + * @license + * Copyright 2017 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. + */ + +module.exports = async webdriver => { + console.log('Rolling time forward 8 days...'); + await webdriver.wait(() => { + return webdriver.executeScript(() => { + return !!window.__test; + }); + }); + + return webdriver.executeScript(() => { + return window.__test.triggerTimeForward(); + }); +}; diff --git a/integration/messaging/test/utils/getErrors.js b/integration/messaging/test/utils/getErrors.js new file mode 100644 index 00000000000..85a72048f83 --- /dev/null +++ b/integration/messaging/test/utils/getErrors.js @@ -0,0 +1,30 @@ +/** + * @license + * Copyright 2017 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. + */ + +module.exports = async webdriver => { + console.log('Getting errors...'); + + await webdriver.wait(() => { + return webdriver.executeScript(() => { + return !!window.__test; + }); + }); + + return webdriver.executeScript(() => { + return window.__test.errors; + }); +}; diff --git a/integration/messaging/test/utils/getReceivedBackgroundMessages.js b/integration/messaging/test/utils/getReceivedBackgroundMessages.js new file mode 100644 index 00000000000..d6af2471545 --- /dev/null +++ b/integration/messaging/test/utils/getReceivedBackgroundMessages.js @@ -0,0 +1,55 @@ +/** + * @license + * Copyright 2017 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. + */ + +const TEST_DB = 'FCM_INTEGRATION_TEST_DB'; +const BACKGROUND_MESSAGES_OBJECT_STORE = 'background_messages'; + +/** Getting received background messages are trickier than getting foreground messages from app. It + * requires idb object store creation with the service worker. Idb operations are fired as async + * events. This method needs to be called after the idb operations inside sw is done. In tests, + * consider adding a brief timeout before calling the method to give sw some time to work. + */ +module.exports = async webdriver => { + console.log('Getting received background messages from idb: '); + + await webdriver.executeScript(() => { + window.backgroundMessages = []; + + const dbOpenReq = indexedDB.open(TEST_DB); + + dbOpenReq.onsuccess = () => { + const db = dbOpenReq.result; + const tx = db.transaction(BACKGROUND_MESSAGES_OBJECT_STORE, 'readonly'); + + tx.objectStore(BACKGROUND_MESSAGES_OBJECT_STORE).getAll().onsuccess = + e => { + window.backgroundMessages = e.target.result; + }; + }; + }); + + await webdriver.wait(() => { + return webdriver.executeScript(() => { + return window.backgroundMessages.length > 0; + }); + }); + + console.log('Found background messages'); + return webdriver.executeScript(() => { + return window.backgroundMessages; + }); +}; diff --git a/integration/messaging/test/utils/getReceivedForegroundMessages.js b/integration/messaging/test/utils/getReceivedForegroundMessages.js new file mode 100644 index 00000000000..a1edbfee9c2 --- /dev/null +++ b/integration/messaging/test/utils/getReceivedForegroundMessages.js @@ -0,0 +1,31 @@ +/** + * @license + * Copyright 2017 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. + */ + +module.exports = async webdriver => { + console.log('Getting received foreground messages from test app: '); + + await webdriver.wait(() => { + return webdriver.executeScript(() => { + return window.__test.messages.length > 0; + }); + }); + + console.log('Found message.'); + return webdriver.executeScript(() => { + return window.__test.messages; + }); +}; diff --git a/integration/messaging/test/utils/openNewTab.js b/integration/messaging/test/utils/openNewTab.js new file mode 100644 index 00000000000..e141d0764b9 --- /dev/null +++ b/integration/messaging/test/utils/openNewTab.js @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2017 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. + */ + +module.exports = async webdriver => { + console.log('Opening a new tab in the browser...'); + + return webdriver.executeScript(() => { + return window.open(); + }); +}; diff --git a/integration/messaging/test/utils/retrieveToken.js b/integration/messaging/test/utils/retrieveToken.js new file mode 100644 index 00000000000..7be388332c9 --- /dev/null +++ b/integration/messaging/test/utils/retrieveToken.js @@ -0,0 +1,30 @@ +/** + * @license + * Copyright 2017 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. + */ + +module.exports = async webdriver => { + console.log('retrieving token from test app'); + + await webdriver.wait(() => { + return webdriver.executeScript(() => { + return !!window.__test && !!window.__test.token; + }); + }); + + return webdriver.executeScript(() => { + return window.__test.token; + }); +}; diff --git a/integration/messaging/test/utils/sendMessage.js b/integration/messaging/test/utils/sendMessage.js new file mode 100644 index 00000000000..1d2e95054eb --- /dev/null +++ b/integration/messaging/test/utils/sendMessage.js @@ -0,0 +1,43 @@ +/** + * @license + * Copyright 2017 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. + */ + +const undici = require('undici'); +const FCM_SEND_ENDPOINT = 'https://fcm.googleapis.com/fcm/send'; +// Rotatable fcm server key. It's generally a bad idea to expose server keys. The reason is to +// simplify testing process (no need to implement server side decryption of git secret). The +// justification is that a) this is a disposable test project b) the key itself is rotatable. +const FCM_KEY = + 'AAAArtlRq60:APA91bHFulW1dBpIPbArYXPbFtO9M_a9ZNXhnj9hGArfGK55g8fv5s5Qset6984xRIrqhZ_3IlKcG9LgSk3DiTdHMDIOkxboNJquNK1SChC7J0ULTvHPg7t0V6AjR1UEA21DXI22BM5N'; + +module.exports = async payload => { + console.log( + 'Requesting to send an FCM message with payload: ' + JSON.stringify(payload) + ); + + const response = await undici.fetch(FCM_SEND_ENDPOINT, { + method: 'POST', + body: JSON.stringify(payload), + headers: { + Authorization: 'key=' + FCM_KEY, + 'Content-Type': 'application/json' + } + }); + + // Note that FCM Send API responses are in HTML format + let res = await response.text(); + return JSON.parse(res); +}; diff --git a/integration/messaging/test/utils/test-server.js b/integration/messaging/test/utils/test-server.js new file mode 100644 index 00000000000..c178f0c72d4 --- /dev/null +++ b/integration/messaging/test/utils/test-server.js @@ -0,0 +1,74 @@ +/** + * @license + * Copyright 2017 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. + */ + +const path = require('path'); +const express = require('express'); +const PORT_NUMBER = 3000; + +const FIREBASE_HEAD = express.static( + path.join( + /* firebase-js-sdk/integration/messaging */ process.env.PWD, + '../..', + '/packages/firebase' + ) +); + +const INTEGRATION_TEST_ASSETS = express.static( + path.join( + /* firebase-js-sdk/integration/messaging */ process.env.PWD, + 'test/static' + ) +); + +class MessagingTestServer { + constructor() { + this._app = express(); + this._app.use([INTEGRATION_TEST_ASSETS, FIREBASE_HEAD]); + this._server = null; + } + + get serverAddress() { + if (!this._server) { + return null; + } + + return `http://localhost:${PORT_NUMBER}`; + } + + async start() { + if (this._server) { + return; + } + + return new Promise((resolve, reject) => { + this._server = this._app.listen(PORT_NUMBER, () => { + resolve(); + }); + }); + } + + // Sometimes the server doesn't trigger the callback due to currently open sockets. So call close + // this._server + async stop() { + if (this._server) { + this._server.close(); + this._server = null; + } + } +} + +module.exports = new MessagingTestServer(); diff --git a/integration/messaging/test/utils/triggerGetToken.js b/integration/messaging/test/utils/triggerGetToken.js new file mode 100644 index 00000000000..3668fd4a81d --- /dev/null +++ b/integration/messaging/test/utils/triggerGetToken.js @@ -0,0 +1,32 @@ +/** + * @license + * Copyright 2017 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. + */ + +module.exports = async webdriver => { + console.log('Getting token...'); + + await webdriver.wait(() => { + return webdriver.executeScript(() => { + return !!window.__test; + }); + }); + + return webdriver.executeAsyncScript(cb => { + window.__test.triggerGetToken().then(token => { + cb(token); + }); + }); +}; diff --git a/patches/karma-webpack+5.0.0.patch b/patches/karma-webpack+5.0.0.patch new file mode 100644 index 00000000000..3e3424a2912 --- /dev/null +++ b/patches/karma-webpack+5.0.0.patch @@ -0,0 +1,23 @@ +diff --git a/node_modules/karma-webpack/lib/webpack/plugin.js b/node_modules/karma-webpack/lib/webpack/plugin.js +index 47b993c..3b75a9e 100644 +--- a/node_modules/karma-webpack/lib/webpack/plugin.js ++++ b/node_modules/karma-webpack/lib/webpack/plugin.js +@@ -1,4 +1,5 @@ + const fs = require('fs'); ++const path = require('path'); + + class KW_WebpackPlugin { + constructor(options) { +@@ -14,9 +15,10 @@ class KW_WebpackPlugin { + // read generated file content and store for karma preprocessor + this.controller.bundlesContent = {}; + stats.toJson().assets.forEach((webpackFileObj) => { +- const filePath = `${compiler.options.output.path}/${ ++ const filePath = path.resolve( ++ compiler.options.output.path, + webpackFileObj.name +- }`; ++ ); + this.controller.bundlesContent[webpackFileObj.name] = fs.readFileSync( + filePath, + 'utf-8' diff --git a/renovate.json b/renovate.json new file mode 100644 index 00000000000..c8b64a1df38 --- /dev/null +++ b/renovate.json @@ -0,0 +1,33 @@ +{ + "extends": [ + "config:js-lib" + ], + "lockFileMaintenance": { + "enabled": true + }, + "packageRules": [ + { + "matchUpdateTypes": ["patch", "minor"], + "groupName": "all non-major dependencies", + "excludePackageNames": ["typescript"] + } + ], + "ignoreDeps": [ + "karma-sauce-launcher", + "protractor", + "long", + "rollup-plugin-copy-assets", + "whatwg-fetch", + "typedoc", + "@microsoft/tsdoc" + ], + "ignorePaths": [ + "auth/demo", + "auth/cordova/demo", + "auth-compat/demo" + ], + "assignees": [ + "@hsubox76" + ], + "schedule": "before 3am on Friday" +} diff --git a/repo-scripts/api-documenter/CHANGELOG.md b/repo-scripts/api-documenter/CHANGELOG.md new file mode 100644 index 00000000000..f860451d80c --- /dev/null +++ b/repo-scripts/api-documenter/CHANGELOG.md @@ -0,0 +1,21 @@ +# @firebase/api-documenter +## 0.4.0 +### Minor Changes + +- [#7864](https://github.com/firebase/firebase-js-sdk/pull/7864) Fix documentation links for function overloads and improve readability of function headings. +## 0.3.0 +### Minor Changes + +- [#6623](https://github.com/firebase/firebase-js-sdk/pull/6623) Add an option to sort functions by first param. (--sort-functions) +## 0.2.0 +### Minor Changes + +- [#6449](https://github.com/firebase/firebase-js-sdk/pull/6449) Updates to work with devsite changes. Added a required `--project` flag for generating markdown docs. +## 0.1.2 +### Patch Changes + +- [#4931](https://github.com/firebase/firebase-js-sdk/pull/4931) Support toc generation for Firebase devsite. +## 0.1.1 +### Patch Changes + +- [#4869](https://github.com/firebase/firebase-js-sdk/pull/4869) Generate API docs for namespace members diff --git a/repo-scripts/api-documenter/README.md b/repo-scripts/api-documenter/README.md new file mode 100644 index 00000000000..302cdd25bd8 --- /dev/null +++ b/repo-scripts/api-documenter/README.md @@ -0,0 +1,16 @@ +# @firebase/api-documenter + +It is a fork of [API Documenter](https://github.com/microsoft/rushstack/tree/master/apps/api-documenter) +It reads the *.api.json data files produced by [API Extractor](https://api-extractor.com/), +and then generates files in [Markdown](https://en.wikipedia.org/wiki/Markdown) format suitable for displaying in Firebase Devsite. + +## Generate toc for Firebase devsite +`api-documenter-fire toc -i temp -p "/docs/reference/js/v9"` + +`-i` and `-p` (`--host-path`) are required parameters. +Use `-i` to specify the folder that contains api.json files. +Use `-p` to specify the g3 path that contains the reference docs. + +By default, the command will create `toc.yaml` in folder `/toc`. To change the output folder, use the flag `-o`. + +To generate toc for the Firebase JS SDK, also set the flag `-j` to create the top level `firebase` toc item. \ No newline at end of file diff --git a/repo-scripts/api-documenter/gulpfile.js b/repo-scripts/api-documenter/gulpfile.js new file mode 100644 index 00000000000..44a9ffa12c3 --- /dev/null +++ b/repo-scripts/api-documenter/gulpfile.js @@ -0,0 +1,22 @@ +/** + * @license + * Copyright 2020 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. + */ + +const gulp = require('gulp'); + +gulp.task('copy-resources', function () { + return gulp.src('./src/schemas/*').pipe(gulp.dest('./dist/schemas')); +}); diff --git a/repo-scripts/api-documenter/package.json b/repo-scripts/api-documenter/package.json new file mode 100644 index 00000000000..1e6b1a9ac21 --- /dev/null +++ b/repo-scripts/api-documenter/package.json @@ -0,0 +1,38 @@ +{ + "name": "@firebase/api-documenter", + "version": "0.4.0", + "description": "Read JSON files from api-extractor, generate documentation pages", + "repository": { + "directory": "repo-scripts/documenter", + "type": "git", + "url": "git+https://github.com/firebase/firebase-js-sdk.git" + }, + "license": "Apache-2.0", + "scripts": { + "build": "tsc && gulp copy-resources", + "test": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha src/**/*.test.ts --config ../../config/mocharc.node.js" + }, + "bin": { + "api-documenter-fire": "dist/start.js" + }, + "files": [ + "dist" + ], + "main": "dist/index.js", + "typings": "dist/rollup.d.ts", + "dependencies": { + "api-extractor-model-me": "0.1.1", + "@microsoft/tsdoc": "0.12.24", + "@rushstack/node-core-library": "3.59.7", + "@rushstack/ts-command-line": "4.19.1", + "colors": "~1.4.0", + "resolve": "~1.22.0", + "tslib": "^2.1.0", + "js-yaml": "4.1.0" + }, + "devDependencies": { + "@types/js-yaml": "4.0.9", + "@types/resolve": "1.20.6", + "mocha-chai-jest-snapshot": "1.1.3" + } +} diff --git a/repo-scripts/api-documenter/src/cli/ApiDocumenterCommandLine.ts b/repo-scripts/api-documenter/src/cli/ApiDocumenterCommandLine.ts new file mode 100644 index 00000000000..4e98f53df13 --- /dev/null +++ b/repo-scripts/api-documenter/src/cli/ApiDocumenterCommandLine.ts @@ -0,0 +1,45 @@ +/** + * @license + * Copyright 2020 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. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { CommandLineParser } from '@rushstack/ts-command-line'; +import { MarkdownAction } from './MarkdownAction'; +import { TocAction } from './TocAction'; + +export class ApiDocumenterCommandLine extends CommandLineParser { + public constructor() { + super({ + toolFilename: 'api-documenter', + toolDescription: + 'Reads *.api.json files produced by api-extractor, ' + + ' and generates API documentation in various output formats.' + }); + this._populateActions(); + } + + protected onDefineParameters(): void { + // override + // No parameters + } + + private _populateActions(): void { + this.addAction(new MarkdownAction(this)); + this.addAction(new TocAction(this)); + } +} diff --git a/repo-scripts/api-documenter/src/cli/BaseAction.ts b/repo-scripts/api-documenter/src/cli/BaseAction.ts new file mode 100644 index 00000000000..9d33c9c490d --- /dev/null +++ b/repo-scripts/api-documenter/src/cli/BaseAction.ts @@ -0,0 +1,197 @@ +/** + * @license + * Copyright 2020 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. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import * as path from 'path'; +import * as tsdoc from '@microsoft/tsdoc'; +import colors from 'colors'; + +import { + CommandLineAction, + CommandLineFlagParameter, + CommandLineStringParameter +} from '@rushstack/ts-command-line'; +import { FileSystem } from '@rushstack/node-core-library'; +import { + ApiModel, + ApiItem, + ApiItemContainerMixin, + ApiDocumentedItem, + IResolveDeclarationReferenceResult +} from 'api-extractor-model-me'; + +export interface IBuildApiModelResult { + apiModel: ApiModel; + inputFolder: string; + outputFolder: string; + addFileNameSuffix: boolean; + projectName?: string; +} + +export abstract class BaseAction extends CommandLineAction { + private _inputFolderParameter!: CommandLineStringParameter; + private _outputFolderParameter!: CommandLineStringParameter; + private _fileNameSuffixParameter!: CommandLineFlagParameter; + private _projectNameParameter!: CommandLineStringParameter; + + protected onDefineParameters(): void { + // override + this._inputFolderParameter = this.defineStringParameter({ + parameterLongName: '--input-folder', + parameterShortName: '-i', + argumentName: 'FOLDER1', + description: + `Specifies the input folder containing the *.api.json files to be processed.` + + ` If omitted, the default is "./input"` + }); + + this._outputFolderParameter = this.defineStringParameter({ + parameterLongName: '--output-folder', + parameterShortName: '-o', + argumentName: 'FOLDER2', + description: + `Specifies the output folder where the documentation will be written.` + + ` ANY EXISTING CONTENTS WILL BE DELETED!` + + ` If omitted, the default is "./${this.actionName}"` + }); + + this._fileNameSuffixParameter = this.defineFlagParameter({ + parameterLongName: '--name-suffix', + parameterShortName: '-s', + description: + `Add suffix to interface and class names in the file path.` + + `For example, packageA.myinterface_i.md for MyInterface interface, ` + + `Add packageA.myclass_c.md for MyClass class.` + + `This is to avoid name conflict in case packageA also has, for example, an entry point with the same name in lowercase.` + + `This option is specifically designed for the Admin SDK where such case occurs.` + }); + + this._projectNameParameter = this.defineStringParameter({ + parameterLongName: '--project', + argumentName: 'PROJECT', + description: + `Name of the project (js, admin, functions, etc.). This will be ` + + `used in the devsite header path to the _project.yaml file.` + }); + } + + protected buildApiModel(): IBuildApiModelResult { + const apiModel: ApiModel = new ApiModel(); + + const inputFolder: string = this._inputFolderParameter.value || './input'; + if (!FileSystem.exists(inputFolder)) { + throw new Error('The input folder does not exist: ' + inputFolder); + } + + const outputFolder: string = + this._outputFolderParameter.value || `./${this.actionName}`; + FileSystem.ensureFolder(outputFolder); + + const addFileNameSuffix: boolean = this._fileNameSuffixParameter.value; + + for (const filename of FileSystem.readFolder(inputFolder)) { + if (filename.match(/\.api\.json$/i)) { + console.log(`Reading ${filename}`); + const filenamePath: string = path.join(inputFolder, filename); + apiModel.loadPackage(filenamePath); + } + } + + this._applyInheritDoc(apiModel, apiModel); + + return { + apiModel, + inputFolder, + outputFolder, + addFileNameSuffix, + projectName: this._projectNameParameter.value + }; + } + + // TODO: This is a temporary workaround. The long term plan is for API Extractor's DocCommentEnhancer + // to apply all @inheritDoc tags before the .api.json file is written. + // See DocCommentEnhancer._applyInheritDoc() for more info. + private _applyInheritDoc(apiItem: ApiItem, apiModel: ApiModel): void { + if (apiItem instanceof ApiDocumentedItem) { + if (apiItem.tsdocComment) { + const inheritDocTag: tsdoc.DocInheritDocTag | undefined = + apiItem.tsdocComment.inheritDocTag; + + if (inheritDocTag && inheritDocTag.declarationReference) { + // Attempt to resolve the declaration reference + const result: IResolveDeclarationReferenceResult = + apiModel.resolveDeclarationReference( + inheritDocTag.declarationReference, + apiItem + ); + + if (result.errorMessage) { + console.log( + colors.yellow( + `Warning: Unresolved @inheritDoc tag for ${apiItem.displayName}: ` + + result.errorMessage + ) + ); + } else { + if ( + result.resolvedApiItem instanceof ApiDocumentedItem && + result.resolvedApiItem.tsdocComment && + result.resolvedApiItem !== apiItem + ) { + this._copyInheritedDocs( + apiItem.tsdocComment, + result.resolvedApiItem.tsdocComment + ); + } + } + } + } + } + + // Recurse members + if (ApiItemContainerMixin.isBaseClassOf(apiItem)) { + for (const member of apiItem.members) { + this._applyInheritDoc(member, apiModel); + } + } + } + + /** + * Copy the content from `sourceDocComment` to `targetDocComment`. + * This code is borrowed from DocCommentEnhancer as a temporary workaround. + */ + private _copyInheritedDocs( + targetDocComment: tsdoc.DocComment, + sourceDocComment: tsdoc.DocComment + ): void { + targetDocComment.summarySection = sourceDocComment.summarySection; + targetDocComment.remarksBlock = sourceDocComment.remarksBlock; + + targetDocComment.params.clear(); + for (const param of sourceDocComment.params) { + targetDocComment.params.add(param); + } + for (const typeParam of sourceDocComment.typeParams) { + targetDocComment.typeParams.add(typeParam); + } + targetDocComment.returnsBlock = sourceDocComment.returnsBlock; + + targetDocComment.inheritDocTag = undefined; + } +} diff --git a/repo-scripts/api-documenter/src/cli/MarkdownAction.ts b/repo-scripts/api-documenter/src/cli/MarkdownAction.ts new file mode 100644 index 00000000000..903f41ebf49 --- /dev/null +++ b/repo-scripts/api-documenter/src/cli/MarkdownAction.ts @@ -0,0 +1,71 @@ +/** + * @license + * Copyright 2020 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. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { ApiDocumenterCommandLine } from './ApiDocumenterCommandLine'; +import { BaseAction } from './BaseAction'; +import { MarkdownDocumenter } from '../documenters/MarkdownDocumenter'; +import { CommandLineStringParameter } from '@rushstack/ts-command-line'; + +export class MarkdownAction extends BaseAction { + private _sortFunctions!: CommandLineStringParameter; + public constructor(parser: ApiDocumenterCommandLine) { + super({ + actionName: 'markdown', + summary: 'Generate documentation as Markdown files (*.md)', + documentation: + 'Generates API documentation as a collection of files in' + + ' Markdown format, suitable for example for publishing on a GitHub site.' + }); + } + + protected onDefineParameters(): void { + super.onDefineParameters(); + + this._sortFunctions = this.defineStringParameter({ + parameterLongName: '--sort-functions', + argumentName: 'PRIORITY_PARAMS', + description: + `Sorts functions tables and listings by first parameter. ` + + `Provide comma-separated strings for preferred params to be ` + + `ordered first. Alphabetical otherwise.` + }); + } + + protected async onExecute(): Promise { + // override + const { apiModel, outputFolder, addFileNameSuffix, projectName } = + this.buildApiModel(); + const sortFunctions: string = this._sortFunctions.value || ''; + + if (!projectName) { + throw new Error('No project name provided. Use --project.'); + } + + const markdownDocumenter: MarkdownDocumenter = new MarkdownDocumenter({ + apiModel, + documenterConfig: undefined, + outputFolder, + addFileNameSuffix, + projectName, + sortFunctions + }); + markdownDocumenter.generateFiles(); + } +} diff --git a/repo-scripts/api-documenter/src/cli/TocAction.ts b/repo-scripts/api-documenter/src/cli/TocAction.ts new file mode 100644 index 00000000000..316be83a0d2 --- /dev/null +++ b/repo-scripts/api-documenter/src/cli/TocAction.ts @@ -0,0 +1,77 @@ +/** + * @license + * Copyright 2021 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 { + CommandLineFlagParameter, + CommandLineStringParameter +} from '@rushstack/ts-command-line'; +import { ApiDocumenterCommandLine } from './ApiDocumenterCommandLine'; +import { BaseAction } from './BaseAction'; +import { generateToc } from '../toc'; + +export class TocAction extends BaseAction { + private _g3PathParameter!: CommandLineStringParameter; + private _jsSDKParameter!: CommandLineFlagParameter; + public constructor(parser: ApiDocumenterCommandLine) { + super({ + actionName: 'toc', + summary: 'Generate TOC(table of content) for Firebase devsite.', + documentation: 'Generate TOC(table of content) for Firebase devsite.' + }); + } + + protected onDefineParameters(): void { + super.onDefineParameters(); + + this._g3PathParameter = this.defineStringParameter({ + parameterLongName: '--host-path', + parameterShortName: '-p', + argumentName: 'HOSTPATH', + description: `Specifies the path where the reference docs resides (e.g. g3). + Used to generate paths in the toc` + }); + + this._jsSDKParameter = this.defineFlagParameter({ + parameterLongName: '--js-sdk', + parameterShortName: '-j', + description: + `Generating toc for the Firebase JS SDK.` + + `It will create an artificial top level toc item "firebase".` + }); + } + + protected async onExecute(): Promise { + // override + const { apiModel, outputFolder, addFileNameSuffix } = this.buildApiModel(); + const g3Path: string | undefined = this._g3PathParameter.value; + const jsSdk: boolean = this._jsSDKParameter.value; + + if (!g3Path) { + throw new Error( + '--g3-path is a required to generate toc, but it is not provided' + ); + } + + generateToc({ + apiModel, + outputFolder, + addFileNameSuffix, + g3Path, + jsSdk + }); + } +} diff --git a/repo-scripts/api-documenter/src/documenters/DocumenterConfig.ts b/repo-scripts/api-documenter/src/documenters/DocumenterConfig.ts new file mode 100644 index 00000000000..9264905ab23 --- /dev/null +++ b/repo-scripts/api-documenter/src/documenters/DocumenterConfig.ts @@ -0,0 +1,84 @@ +/** + * @license + * Copyright 2020 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. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import * as path from 'path'; +import { + JsonSchema, + JsonFile, + NewlineKind +} from '@rushstack/node-core-library'; +import { IConfigFile } from './IConfigFile'; + +/** + * Helper for loading the api-documenter.json file format. Later when the schema is more mature, + * this class will be used to represent the validated and normalized configuration, whereas `IConfigFile` + * represents the raw JSON file structure. + */ +export class DocumenterConfig { + public readonly configFilePath: string; + public readonly configFile: IConfigFile; + + /** + * Specifies what type of newlines API Documenter should use when writing output files. By default, the output files + * will be written with Windows-style newlines. + */ + public readonly newlineKind: NewlineKind; + + /** + * The JSON Schema for API Extractor config file (api-extractor.schema.json). + */ + public static readonly jsonSchema: JsonSchema = JsonSchema.fromFile( + path.join(__dirname, '..', 'schemas', 'api-documenter.schema.json') + ); + + /** + * The config file name "api-extractor.json". + */ + public static readonly FILENAME: string = 'api-documenter.json'; + + private constructor(filePath: string, configFile: IConfigFile) { + this.configFilePath = filePath; + this.configFile = configFile; + + switch (configFile.newlineKind) { + case 'lf': + this.newlineKind = NewlineKind.Lf; + break; + case 'os': + this.newlineKind = NewlineKind.OsDefault; + break; + default: + this.newlineKind = NewlineKind.CrLf; + break; + } + } + + /** + * Load and validate an api-documenter.json file. + */ + public static loadFile(configFilePath: string): DocumenterConfig { + const configFile: IConfigFile = JsonFile.loadAndValidate( + configFilePath, + DocumenterConfig.jsonSchema + ); + + return new DocumenterConfig(path.resolve(configFilePath), configFile); + } +} diff --git a/repo-scripts/api-documenter/src/documenters/IConfigFile.ts b/repo-scripts/api-documenter/src/documenters/IConfigFile.ts new file mode 100644 index 00000000000..2a7bf8ab358 --- /dev/null +++ b/repo-scripts/api-documenter/src/documenters/IConfigFile.ts @@ -0,0 +1,112 @@ +/** + * @license + * Copyright 2020 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. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +/** + * TypeScript interface describing the config schema for toc.yml file format. + */ +export interface IConfigTableOfContents { + /** + * Optional category name that is recommended to be included along with + * one of the configs: {@link IConfigTableOfContents.categorizeByName} or + * {@link IConfigTableOfContents.categoryInlineTag}. + * Any items that are not matched according to the mentioned configuration options will be placed under this + * catchAll category. If none provided the items will not be included in the final toc.yml file. + */ + catchAllCategory?: string; + + /** + * Toggle either categorization of the API items should be made based on category name presence + * in the API item's name. Useful when there are API items without an inline tag to categorize them, + * but still need to place the items under categories. Note: this type of categorization might place some items + * under wrong categories if the names are similar but belong to different categories. + * In case that {@link IConfigTableOfContents.categoryInlineTag} is provided it will try categorize by + * using it and only if it didn't, it will attempt to categorize by name. + */ + categorizeByName?: boolean; + + /** + * Inline tag that will be used to categorize the API items. Will take precedence over the + * {@link IConfigTableOfContents.categorizeByName} flag in trying to place the API item according to the + * custom inline tag present in documentation of the source code. + */ + categoryInlineTag?: string; + + /** + * Array of node names that might have already items injected at the time of creating the + * {@link IConfigTableOfContents.tocConfig} tree structure but are still needed to be included as category + * nodes where API items will be pushed during the categorization algorithm. + */ + nonEmptyCategoryNodeNames?: string[]; +} + +/** + * Describes plugin packages to be loaded, and which features to enable. + */ +export interface IConfigPlugin { + /** + * Specifies the name of an API Documenter plugin package to be loaded. By convention, the NPM package name + * should have the prefix `doc-plugin-`. Its main entry point should export an object named + * `apiDocumenterPluginManifest` which implements the {@link IApiDocumenterPluginManifest} interface. + */ + packageName: string; + + /** + * A list of features to be enabled. The features are defined in {@link IApiDocumenterPluginManifest.features}. + * The `enabledFeatureNames` strings are matched with {@link IFeatureDefinition.featureName}. + */ + enabledFeatureNames: string[]; +} + +/** + * This interface represents the api-documenter.json file format. + */ +export interface IConfigFile { + /** + * Specifies the output target. + */ + outputTarget: 'docfx' | 'markdown'; + + /** + * Specifies what type of newlines API Documenter should use when writing output files. + * + * @remarks + * By default, the output files will be written with Windows-style newlines. + * To use POSIX-style newlines, specify "lf" instead. + * To use the OS's default newline kind, specify "os". + */ + newlineKind?: 'crlf' | 'lf' | 'os'; + + /** + * This enables an experimental feature that will be officially released with the next major version + * of API Documenter. It requires DocFX 2.46 or newer. It enables documentation for namespaces and + * adds them to the table of contents. This will also affect file layout as namespaced items will be nested + * under a directory for the namespace instead of just within the package. + * + * This setting currently only affects the 'docfx' output target. It is equivalent to the `--new-docfx-namespaces` + * command-line parameter. + */ + newDocfxNamespaces?: boolean; + + /** {@inheritDoc IConfigPlugin} */ + plugins?: IConfigPlugin[]; + + /** {@inheritDoc IConfigTableOfContents} */ + tableOfContents?: IConfigTableOfContents; +} diff --git a/repo-scripts/api-documenter/src/documenters/MarkdownDocumenter.ts b/repo-scripts/api-documenter/src/documenters/MarkdownDocumenter.ts new file mode 100644 index 00000000000..1f24bffb46a --- /dev/null +++ b/repo-scripts/api-documenter/src/documenters/MarkdownDocumenter.ts @@ -0,0 +1,1228 @@ +/** + * @license + * Copyright 2020 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. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { + FileSystem, + NewlineKind, + PackageName +} from '@rushstack/node-core-library'; +import { + DocSection, + TSDocConfiguration, + StringBuilder, + DocNode, + DocComment, + DocParagraph, + DocPlainText, + DocNodeContainer, + DocLinkTag, + DocFencedCode +} from '@microsoft/tsdoc'; +import { + ApiModel, + ApiItem, + ApiItemKind, + ApiReleaseTagMixin, + ReleaseTag, + ApiDocumentedItem, + ApiDeclaredItem, + ApiClass, + ApiPropertyItem, + ApiEnum, + ApiInterface, + ApiParameterListMixin, + ApiReturnTypeMixin, + Excerpt, + ExcerptTokenKind, + IResolveDeclarationReferenceResult, + ApiPackage, + ApiEntryPoint, + ApiNamespace +} from 'api-extractor-model-me'; + +import { CustomDocNodes } from '../nodes/CustomDocNodeKind'; +import { CustomMarkdownEmitter } from '../markdown/CustomMarkdownEmitter'; +import { PluginLoader } from '../plugin/PluginLoader'; +import { + IMarkdownDocumenterFeatureOnBeforeWritePageArgs, + MarkdownDocumenterFeatureContext +} from '../plugin/MarkdownDocumenterFeature'; +import { DocumenterConfig } from './DocumenterConfig'; +import { MarkdownDocumenterAccessor } from '../plugin/MarkdownDocumenterAccessor'; +import { + getLinkForApiItem, + getFilenameForApiItem, + createBetaWarning, + createRemarksSection, + createTitleCell, + createModifiersCell, + createDescriptionCell, + createEnumTables, + createThrowsSection, + createEntryPointTitleCell, + createExampleSection, + getHeadingAnchorForApiItem +} from './MarkdownDocumenterHelpers'; +import * as path from 'path'; +import { DocHeading } from '../nodes/DocHeading'; +import { DocNoteBox } from '../nodes/DocNoteBox'; +import { DocTable } from '../nodes/DocTable'; +import { DocTableRow } from '../nodes/DocTableRow'; +import { DocTableCell } from '../nodes/DocTableCell'; +import { DocEmphasisSpan } from '../nodes/DocEmphasisSpan'; +import { Utilities } from '../utils/Utilities'; + +export interface IMarkdownDocumenterOptions { + apiModel: ApiModel; + documenterConfig: DocumenterConfig | undefined; + outputFolder: string; + addFileNameSuffix: boolean; + projectName: string; + sortFunctions: string; +} + +/** + * Renders API documentation in the Markdown file format. + * For more info: https://en.wikipedia.org/wiki/Markdown + */ +export class MarkdownDocumenter { + private readonly _apiModel: ApiModel; + private readonly _documenterConfig: DocumenterConfig | undefined; + private readonly _tsdocConfiguration: TSDocConfiguration; + private readonly _markdownEmitter: CustomMarkdownEmitter; + private readonly _outputFolder: string; + private readonly _pluginLoader: PluginLoader; + private readonly _addFileNameSuffix: boolean; + private readonly _projectName: string; + private readonly _sortFunctions: string; + + public constructor(options: IMarkdownDocumenterOptions) { + this._apiModel = options.apiModel; + this._documenterConfig = options.documenterConfig; + this._outputFolder = options.outputFolder; + this._addFileNameSuffix = options.addFileNameSuffix; + this._projectName = options.projectName; + this._sortFunctions = options.sortFunctions; + this._tsdocConfiguration = CustomDocNodes.configuration; + this._markdownEmitter = new CustomMarkdownEmitter(this._apiModel); + + this._pluginLoader = new PluginLoader(); + } + + public generateFiles(): void { + if (this._documenterConfig) { + this._pluginLoader.load(this._documenterConfig, () => { + return new MarkdownDocumenterFeatureContext({ + apiModel: this._apiModel, + outputFolder: this._outputFolder, + documenter: new MarkdownDocumenterAccessor({ + getLinkForApiItem: (apiItem: ApiItem) => { + return getLinkForApiItem(apiItem, this._addFileNameSuffix); + } + }) + }); + }); + } + + this._deleteOldOutputFiles(); + + this._writeApiItemPage(this._apiModel); + + if (this._pluginLoader.markdownDocumenterFeature) { + this._pluginLoader.markdownDocumenterFeature.onFinished({}); + } + } + + _writeApiItemPage(apiItem: ApiItem): void { + const output: DocSection = new DocSection({ + configuration: this._tsdocConfiguration + }); + const nodes = this._createCompleteOutputForApiItem(apiItem); + + /** + * Remove the heading of the page from md output. (the first item is always a DocHeading) + * Later we will add the heading to the devsite header {% block title %} + */ + const headingNode: DocHeading = nodes[0] as DocHeading; + const pageWithoutHeading = nodes.slice(1); + output.appendNodes(pageWithoutHeading); + + // write to file + const filename: string = path.join( + this._outputFolder, + getFilenameForApiItem(apiItem, this._addFileNameSuffix) + ); + const stringBuilder: StringBuilder = new StringBuilder(); + + // devsite headers + stringBuilder.append( + `Project: /docs/reference/${this._projectName}/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference +` + ); + + stringBuilder.append(`# ${headingNode.title}\n`); + + this._markdownEmitter.emit(stringBuilder, output, { + contextApiItem: apiItem, + onGetFilenameForApiItem: (apiItemForFilename: ApiItem) => { + return getLinkForApiItem(apiItemForFilename, this._addFileNameSuffix); + } + }); + + let pageContent: string = stringBuilder.toString(); + + if (this._pluginLoader.markdownDocumenterFeature) { + // Allow the plugin to customize the pageContent + const eventArgs: IMarkdownDocumenterFeatureOnBeforeWritePageArgs = { + apiItem: apiItem, + outputFilename: filename, + pageContent: pageContent + }; + this._pluginLoader.markdownDocumenterFeature.onBeforeWritePage(eventArgs); + pageContent = eventArgs.pageContent; + } + + FileSystem.writeFile(filename, pageContent, { + convertLineEndings: NewlineKind.Lf + }); + } + + _functionHeadingLevel(): number { + // If sorting functions by first parameter + // then the function heading will be under + // the parameter heading, so it will be level + // 2. Otherwise, it will be level 1. + return !!this._sortFunctions ? 2 : 1; + } + + _createCompleteOutputForApiItem(apiItem: ApiItem): DocNode[] { + const configuration = this._tsdocConfiguration; + const output: DocNode[] = []; + const scopedName: string = apiItem.getScopedNameWithinPackage(); + + switch (apiItem.kind) { + case ApiItemKind.Class: + output.push( + new DocHeading({ configuration, title: `${scopedName} class` }) + ); + break; + case ApiItemKind.Enum: + output.push(new DocHeading({ configuration, title: `${scopedName}` })); + break; + case ApiItemKind.Interface: + output.push( + new DocHeading({ configuration, title: `${scopedName} interface` }) + ); + break; + case ApiItemKind.Constructor: + case ApiItemKind.ConstructSignature: + output.push(new DocHeading({ configuration, title: scopedName })); + break; + case ApiItemKind.Method: + case ApiItemKind.MethodSignature: + output.push(new DocHeading({ configuration, title: `${scopedName}` })); + break; + case ApiItemKind.Function: + const anchor = getHeadingAnchorForApiItem(apiItem); + output.push( + new DocHeading({ + configuration, + title: Utilities.getConciseSignature(apiItem), + anchor: anchor, + level: this._functionHeadingLevel() + }) + ); + break; + case ApiItemKind.Model: + output.push(new DocHeading({ configuration, title: `API Reference` })); + break; + case ApiItemKind.Namespace: + output.push( + new DocHeading({ configuration, title: `${scopedName} namespace` }) + ); + break; + case ApiItemKind.Package: + const unscopedPackageName: string = PackageName.getUnscopedName( + apiItem.displayName + ); + output.push( + new DocHeading({ + configuration, + title: `${unscopedPackageName} package` + }) + ); + break; + case ApiItemKind.EntryPoint: + const packageName: string = apiItem.parent!.displayName; + output.push( + new DocHeading({ + configuration, + title: `${packageName}${ + apiItem.displayName && '/' + apiItem.displayName + }` + }) + ); + break; + case ApiItemKind.Property: + case ApiItemKind.PropertySignature: + output.push(new DocHeading({ configuration, title: `${scopedName}` })); + break; + case ApiItemKind.TypeAlias: + output.push(new DocHeading({ configuration, title: `${scopedName}` })); + break; + case ApiItemKind.Variable: + output.push(new DocHeading({ configuration, title: `${scopedName}` })); + break; + default: + throw new Error('Unsupported API item kind:1 ' + apiItem.kind); + } + + if (ApiReleaseTagMixin.isBaseClassOf(apiItem)) { + if (apiItem.releaseTag === ReleaseTag.Beta) { + output.push(createBetaWarning(configuration)); + } + } + + if (apiItem instanceof ApiDocumentedItem) { + const tsdocComment: DocComment | undefined = apiItem.tsdocComment; + + if (tsdocComment) { + if (tsdocComment.deprecatedBlock) { + output.push( + new DocNoteBox({ configuration }, [ + new DocParagraph({ configuration }, [ + new DocPlainText({ + configuration, + text: 'Warning: This API is now obsolete. ' + }) + ]), + ...tsdocComment.deprecatedBlock.content.nodes + ]) + ); + } + + output.push(...tsdocComment.summarySection.nodes); + } + } + + // render remark sections + output.push(...createRemarksSection(apiItem, configuration)); + + if (apiItem instanceof ApiDeclaredItem) { + output.push(...this._createSignatureSection(apiItem)); + } + + switch (apiItem.kind) { + case ApiItemKind.Class: + output.push(...this._createClassTables(apiItem as ApiClass)); + break; + case ApiItemKind.Enum: + output.push(...createEnumTables(apiItem as ApiEnum, configuration)); + break; + case ApiItemKind.Interface: + output.push(...this._createInterfaceTables(apiItem as ApiInterface)); + break; + case ApiItemKind.Constructor: + case ApiItemKind.ConstructSignature: + case ApiItemKind.Method: + case ApiItemKind.MethodSignature: + case ApiItemKind.Function: + output.push( + ...this._createParameterTables( + apiItem as ApiParameterListMixin, + this._functionHeadingLevel() + ) + ); + output.push( + ...createThrowsSection( + apiItem, + configuration, + this._functionHeadingLevel() + ) + ); + break; + case ApiItemKind.Namespace: + output.push( + ...this._createEntryPointOrNamespace(apiItem as ApiNamespace) + ); + break; + case ApiItemKind.Model: + output.push(...this._createModelTable(apiItem as ApiModel)); + break; + case ApiItemKind.Package: + output.push(...this._createPackage(apiItem as ApiPackage)); + break; + case ApiItemKind.EntryPoint: + output.push( + ...this._createEntryPointOrNamespace(apiItem as ApiEntryPoint) + ); + break; + case ApiItemKind.Property: + case ApiItemKind.PropertySignature: + break; + case ApiItemKind.TypeAlias: + break; + case ApiItemKind.Variable: + break; + default: + throw new Error('Unsupported API item kind:2 ' + apiItem.kind); + } + + output.push(...createExampleSection(apiItem, configuration)); + + return output; + } + + /** + * GENERATE PAGE: CLASS + * + * TODO: generate member references in the same page + */ + private _createClassTables(apiClass: ApiClass): DocNode[] { + const configuration = this._tsdocConfiguration; + const output: DocNode[] = []; + const eventsTable: DocTable = new DocTable({ + configuration, + headerTitles: ['Property', 'Modifiers', 'Type', 'Description'] + }); + + const constructorsTable: DocTable = new DocTable({ + configuration, + headerTitles: ['Constructor', 'Modifiers', 'Description'] + }); + + const propertiesTable: DocTable = new DocTable({ + configuration, + headerTitles: ['Property', 'Modifiers', 'Type', 'Description'] + }); + + const methodsTable: DocTable = new DocTable({ + configuration, + headerTitles: ['Method', 'Modifiers', 'Description'] + }); + + const constructorsDefinitions: DocNode[] = []; + const methodsDefinitions: DocNode[] = []; + const propertiesDefinitions: DocNode[] = []; + const eventsDefinitions: DocNode[] = []; + + for (const apiMember of apiClass.members) { + switch (apiMember.kind) { + case ApiItemKind.Constructor: { + constructorsTable.addRow( + new DocTableRow({ configuration }, [ + createTitleCell( + apiMember, + configuration, + this._addFileNameSuffix + ), + createModifiersCell(apiMember, configuration), + createDescriptionCell(apiMember, configuration) + ]) + ); + + constructorsDefinitions.push( + ...this._createCompleteOutputForApiItem(apiMember) + ); + break; + } + case ApiItemKind.Method: { + methodsTable.addRow( + new DocTableRow({ configuration }, [ + createTitleCell( + apiMember, + configuration, + this._addFileNameSuffix + ), + createModifiersCell(apiMember, configuration), + createDescriptionCell(apiMember, configuration) + ]) + ); + + methodsDefinitions.push( + ...this._createCompleteOutputForApiItem(apiMember) + ); + break; + } + case ApiItemKind.Property: { + if ((apiMember as ApiPropertyItem).isEventProperty) { + eventsTable.addRow( + new DocTableRow({ configuration }, [ + createTitleCell( + apiMember, + configuration, + this._addFileNameSuffix + ), + createModifiersCell(apiMember, configuration), + this._createPropertyTypeCell(apiMember), + createDescriptionCell(apiMember, configuration) + ]) + ); + + eventsDefinitions.push( + ...this._createCompleteOutputForApiItem(apiMember) + ); + } else { + propertiesTable.addRow( + new DocTableRow({ configuration }, [ + createTitleCell( + apiMember, + configuration, + this._addFileNameSuffix + ), + createModifiersCell(apiMember, configuration), + this._createPropertyTypeCell(apiMember), + createDescriptionCell(apiMember, configuration) + ]) + ); + propertiesDefinitions.push( + ...this._createCompleteOutputForApiItem(apiMember) + ); + } + break; + } + } + } + + if (eventsTable.rows.length > 0) { + output.push(new DocHeading({ configuration, title: 'Events' })); + output.push(eventsTable); + } + + if (constructorsTable.rows.length > 0) { + output.push(new DocHeading({ configuration, title: 'Constructors' })); + output.push(constructorsTable); + } + + if (propertiesTable.rows.length > 0) { + output.push(new DocHeading({ configuration, title: 'Properties' })); + output.push(propertiesTable); + } + + if (methodsTable.rows.length > 0) { + output.push(new DocHeading({ configuration, title: 'Methods' })); + output.push(methodsTable); + } + + output.push(...eventsDefinitions); + output.push(...constructorsDefinitions); + output.push(...propertiesDefinitions); + output.push(...methodsDefinitions); + + return output; + } + + /** + * GENERATE PAGE: INTERFACE + */ + private _createInterfaceTables(apiClass: ApiInterface): DocNode[] { + const configuration = this._tsdocConfiguration; + const output: DocNode[] = []; + const eventsTable: DocTable = new DocTable({ + configuration, + headerTitles: ['Property', 'Type', 'Description'] + }); + + const propertiesTable: DocTable = new DocTable({ + configuration, + headerTitles: ['Property', 'Type', 'Description'] + }); + + const methodsTable: DocTable = new DocTable({ + configuration, + headerTitles: ['Method', 'Description'] + }); + + const methodsDefinitions: DocNode[] = []; + const propertiesDefinitions: DocNode[] = []; + const eventsDefinitions: DocNode[] = []; + + for (const apiMember of apiClass.members) { + switch (apiMember.kind) { + case ApiItemKind.ConstructSignature: + case ApiItemKind.MethodSignature: { + methodsTable.addRow( + new DocTableRow({ configuration }, [ + createTitleCell( + apiMember, + configuration, + this._addFileNameSuffix + ), + createDescriptionCell(apiMember, configuration) + ]) + ); + + methodsDefinitions.push( + ...this._createCompleteOutputForApiItem(apiMember) + ); + break; + } + case ApiItemKind.PropertySignature: { + if ((apiMember as ApiPropertyItem).isEventProperty) { + eventsTable.addRow( + new DocTableRow({ configuration }, [ + createTitleCell( + apiMember, + configuration, + this._addFileNameSuffix + ), + this._createPropertyTypeCell(apiMember), + createDescriptionCell(apiMember, configuration) + ]) + ); + eventsDefinitions.push( + ...this._createCompleteOutputForApiItem(apiMember) + ); + } else { + propertiesTable.addRow( + new DocTableRow({ configuration }, [ + createTitleCell( + apiMember, + configuration, + this._addFileNameSuffix + ), + this._createPropertyTypeCell(apiMember), + createDescriptionCell(apiMember, configuration) + ]) + ); + propertiesDefinitions.push( + ...this._createCompleteOutputForApiItem(apiMember) + ); + } + break; + } + } + } + + if (eventsTable.rows.length > 0) { + output.push(new DocHeading({ configuration, title: 'Events' })); + output.push(eventsTable); + } + + if (propertiesTable.rows.length > 0) { + output.push(new DocHeading({ configuration, title: 'Properties' })); + output.push(propertiesTable); + } + + if (methodsTable.rows.length > 0) { + output.push(new DocHeading({ configuration, title: 'Methods' })); + output.push(methodsTable); + } + + output.push(...eventsDefinitions); + output.push(...propertiesDefinitions); + output.push(...methodsDefinitions); + + return output; + } + + /** + * GENERATE PAGE: FUNCTION-LIKE + */ + private _createParameterTables( + apiParameterListMixin: ApiParameterListMixin, + parentHeadingLevel: number + ): DocNode[] { + const configuration = this._tsdocConfiguration; + const output: DocNode[] = []; + const parametersTable: DocTable = new DocTable({ + configuration, + headerTitles: ['Parameter', 'Type', 'Description'] + }); + for (const apiParameter of apiParameterListMixin.parameters) { + const parameterDescription: DocSection = new DocSection({ + configuration + }); + if (apiParameter.tsdocParamBlock) { + parameterDescription.appendNodes( + apiParameter.tsdocParamBlock.content.nodes + ); + } + + parametersTable.addRow( + new DocTableRow({ configuration }, [ + new DocTableCell({ configuration }, [ + new DocParagraph({ configuration }, [ + new DocPlainText({ configuration, text: apiParameter.name }) + ]) + ]), + new DocTableCell({ configuration }, [ + this._createParagraphForTypeExcerpt( + apiParameter.parameterTypeExcerpt + ) + ]), + new DocTableCell({ configuration }, parameterDescription.nodes) + ]) + ); + } + + if (parametersTable.rows.length > 0) { + output.push( + new DocHeading({ + configuration, + title: 'Parameters', + level: parentHeadingLevel + 1 + }) + ); + output.push(parametersTable); + } + + if (ApiReturnTypeMixin.isBaseClassOf(apiParameterListMixin)) { + const returnTypeExcerpt: Excerpt = + apiParameterListMixin.returnTypeExcerpt; + output.push( + new DocParagraph({ configuration }, [ + new DocEmphasisSpan({ configuration, bold: true }, [ + new DocPlainText({ configuration, text: 'Returns:' }) + ]) + ]) + ); + + output.push(this._createParagraphForTypeExcerpt(returnTypeExcerpt)); + + if (apiParameterListMixin instanceof ApiDocumentedItem) { + if ( + apiParameterListMixin.tsdocComment && + apiParameterListMixin.tsdocComment.returnsBlock + ) { + output.push( + ...apiParameterListMixin.tsdocComment.returnsBlock.content.nodes + ); + } + } + } + + return output; + } + + private _createParagraphForTypeExcerpt(excerpt: Excerpt): DocParagraph { + const configuration = this._tsdocConfiguration; + const paragraph: DocParagraph = new DocParagraph({ configuration }); + + if (!excerpt.text.trim()) { + paragraph.appendNode( + new DocPlainText({ configuration, text: '(not declared)' }) + ); + } else { + this._appendExcerptWithHyperlinks(paragraph, excerpt); + } + + return paragraph; + } + + private _appendExcerptWithHyperlinks( + docNodeContainer: DocNodeContainer, + excerpt: Excerpt + ): void { + const configuration = this._tsdocConfiguration; + for (const token of excerpt.spannedTokens) { + // Markdown doesn't provide a standardized syntax for hyperlinks inside code spans, so we will render + // the type expression as DocPlainText. Instead of creating multiple DocParagraphs, we can simply + // discard any newlines and let the renderer do normal word-wrapping. + const unwrappedTokenText: string = token.text.replace(/[\r\n]+/g, ' '); + + // If it's hyperlinkable, then append a DocLinkTag + if ( + token.kind === ExcerptTokenKind.Reference && + token.canonicalReference + ) { + const apiItemResult: IResolveDeclarationReferenceResult = + this._apiModel.resolveDeclarationReference( + token.canonicalReference, + undefined + ); + + if (apiItemResult.resolvedApiItem) { + docNodeContainer.appendNode( + new DocLinkTag({ + configuration, + tagName: '@link', + linkText: unwrappedTokenText, + urlDestination: getLinkForApiItem( + apiItemResult.resolvedApiItem, + this._addFileNameSuffix + ) + }) + ); + continue; + } + } + + // Otherwise append non-hyperlinked text + docNodeContainer.appendNode( + new DocPlainText({ configuration, text: unwrappedTokenText }) + ); + } + } + + /** + * GENERATE PAGE: MODEL + */ + private _createModelTable(apiModel: ApiModel): DocNode[] { + const configuration = this._tsdocConfiguration; + const output: DocNode[] = []; + + const packagesTable: DocTable = new DocTable({ + configuration, + headerTitles: ['Package', 'Description'] + }); + + for (const apiMember of apiModel.members) { + const row: DocTableRow = new DocTableRow({ configuration }, [ + createTitleCell(apiMember, configuration, this._addFileNameSuffix), + createDescriptionCell(apiMember, configuration) + ]); + + switch (apiMember.kind) { + case ApiItemKind.Package: + packagesTable.addRow(row); + this._writeApiItemPage(apiMember); + break; + } + } + + if (packagesTable.rows.length > 0) { + output.push(new DocHeading({ configuration, title: 'Packages' })); + output.push(packagesTable); + } + + return output; + } + + /**´ + * Generate a table of entry points if there are more than one entry points. + * Otherwise, generate the entry point directly in the package page. + */ + private _createPackage(apiContainer: ApiPackage): DocNode[] { + const configuration = this._tsdocConfiguration; + const output: DocNode[] = []; + // If a package has a single entry point, generate entry point page in the package page directly + if (apiContainer.entryPoints.length === 1) { + return this._createEntryPointOrNamespace( + apiContainer.members[0] as ApiEntryPoint + ); + } + + const entryPointsTable: DocTable = new DocTable({ + configuration, + headerTitles: ['Entry Point', 'Description'] + }); + + for (const entryPoint of apiContainer.entryPoints) { + const row: DocTableRow = new DocTableRow({ configuration }, [ + createEntryPointTitleCell( + entryPoint, + configuration, + this._addFileNameSuffix + ), + createDescriptionCell(entryPoint, configuration) + ]); + + entryPointsTable.addRow(row); + } + + output.push(entryPointsTable); + + // write entry point pages + for (const entryPoint of apiContainer.entryPoints) { + this._writeApiItemPage(entryPoint); + } + + return output; + } + + /** + * GENERATE PAGE: ENTRYPOINT or NAMESPACE + */ + private _createEntryPointOrNamespace( + apiContainer: ApiEntryPoint | ApiNamespace + ): DocNode[] { + const configuration = this._tsdocConfiguration; + const output: DocNode[] = []; + + const classesTable: DocTable = new DocTable({ + configuration, + headerTitles: ['Class', 'Description'] + }); + + const enumerationsTable: DocTable = new DocTable({ + configuration, + headerTitles: ['Enumeration', 'Description'] + }); + + const finalFunctionsTable: DocTable = new DocTable({ + configuration, + headerTitles: ['Function', 'Description'] + }); + + const functionsRowGroup: Record = {}; + + const interfacesTable: DocTable = new DocTable({ + configuration, + headerTitles: ['Interface', 'Description'] + }); + + const namespacesTable: DocTable = new DocTable({ + configuration, + headerTitles: ['Namespace', 'Description'] + }); + + const variablesTable: DocTable = new DocTable({ + configuration, + headerTitles: ['Variable', 'Description'] + }); + + const typeAliasesTable: DocTable = new DocTable({ + configuration, + headerTitles: ['Type Alias', 'Description'] + }); + + const functionsDefinitionsGroup: Record = {}; + const finalFunctionsDefinitions: DocNode[] = []; + const variablesDefinitions: DocNode[] = []; + const typeAliasDefinitions: DocNode[] = []; + const enumsDefinitions: DocNode[] = []; + + const apiMembers: ReadonlyArray = + apiContainer.kind === ApiItemKind.EntryPoint + ? (apiContainer as ApiEntryPoint).members + : (apiContainer as ApiNamespace).members; + + for (const apiMember of apiMembers) { + const row: DocTableRow = new DocTableRow({ configuration }, [ + createTitleCell(apiMember, configuration, this._addFileNameSuffix), + createDescriptionCell(apiMember, configuration) + ]); + + switch (apiMember.kind) { + case ApiItemKind.Class: + classesTable.addRow(row); + this._writeApiItemPage(apiMember); + break; + + case ApiItemKind.Enum: + enumerationsTable.addRow(row); + enumsDefinitions.push( + ...this._createCompleteOutputForApiItem(apiMember) + ); + break; + + case ApiItemKind.Interface: + interfacesTable.addRow(row); + this._writeApiItemPage(apiMember); + break; + + case ApiItemKind.Namespace: + namespacesTable.addRow(row); + this._writeApiItemPage(apiMember); + break; + + case ApiItemKind.Function: + /** + * If this option is set, group functions by first param. + * Organize using a map where the key is the first param. + */ + if (this._sortFunctions) { + const firstParam = (apiMember as ApiParameterListMixin) + .parameters[0] || { name: '' }; + if (!functionsRowGroup[firstParam.name]) { + functionsRowGroup[firstParam.name] = []; + } + if (!functionsDefinitionsGroup[firstParam.name]) { + functionsDefinitionsGroup[firstParam.name] = []; + } + functionsRowGroup[firstParam.name].push(row); + functionsDefinitionsGroup[firstParam.name].push( + ...this._createCompleteOutputForApiItem(apiMember) + ); + } else { + finalFunctionsTable.addRow(row); + finalFunctionsDefinitions.push( + ...this._createCompleteOutputForApiItem(apiMember) + ); + } + break; + + case ApiItemKind.TypeAlias: + typeAliasesTable.addRow(row); + typeAliasDefinitions.push( + ...this._createCompleteOutputForApiItem(apiMember) + ); + break; + + case ApiItemKind.Variable: + variablesTable.addRow(row); + variablesDefinitions.push( + ...this._createCompleteOutputForApiItem(apiMember) + ); + break; + } + } + + /** + * Sort the functions groups by first param. If priority params were + * provided to --sort-functions, will put them first in the order + * given. + */ + if (this._sortFunctions) { + let priorityParams: string[] = []; + if (this._sortFunctions.includes(',')) { + priorityParams = this._sortFunctions.split(','); + } else { + priorityParams = [this._sortFunctions]; + } + const sortedFunctionsFirstParamKeys = Object.keys(functionsRowGroup).sort( + (a, b) => { + if (priorityParams.includes(a) && priorityParams.includes(b)) { + return priorityParams.indexOf(a) - priorityParams.indexOf(b); + } else if (priorityParams.includes(a)) { + return -1; + } else if (priorityParams.includes(b)) { + return 1; + } + return a.localeCompare(b); + } + ); + + for (const paramKey of sortedFunctionsFirstParamKeys) { + // Header for each group of functions grouped by first param. + // Doesn't make sense if there's only one group. + const headerText = paramKey + ? `function(${paramKey}, ...)` + : 'function()'; + if (sortedFunctionsFirstParamKeys.length > 1) { + finalFunctionsTable.addRow( + new DocTableRow({ configuration }, [ + new DocTableCell({ configuration }, [ + new DocParagraph({ configuration }, [ + new DocEmphasisSpan({ configuration, bold: true }, [ + new DocPlainText({ configuration, text: headerText }) + ]) + ]) + ]) + ]) + ); + } + for (const functionsRow of functionsRowGroup[paramKey]) { + finalFunctionsTable.addRow(functionsRow); + } + + // Create a heading that groups functions by the first param + finalFunctionsDefinitions.push( + new DocHeading({ + configuration, + title: headerText + }) + ); + + for (const functionDefinition of functionsDefinitionsGroup[paramKey]) { + // const originalDocHeading = functionDefinition as DocHeading; + + // // Increase the doc heading level so that this is a sub-section + // // of the function grouping heading + // const newDocHeading = new DocHeading({ + // configuration: originalDocHeading.configuration, + // title: originalDocHeading.title, + // level: originalDocHeading.level + 1, + // anchor: originalDocHeading.anchor + // }) + // finalFunctionsDefinitions.push(newDocHeading); + finalFunctionsDefinitions.push(functionDefinition); + } + } + } + + if (finalFunctionsTable.rows.length > 0) { + output.push(new DocHeading({ configuration, title: 'Functions' })); + output.push(finalFunctionsTable); + } + + if (classesTable.rows.length > 0) { + output.push(new DocHeading({ configuration, title: 'Classes' })); + output.push(classesTable); + } + + if (enumerationsTable.rows.length > 0) { + output.push(new DocHeading({ configuration, title: 'Enumerations' })); + output.push(enumerationsTable); + } + + if (interfacesTable.rows.length > 0) { + output.push(new DocHeading({ configuration, title: 'Interfaces' })); + output.push(interfacesTable); + } + + if (namespacesTable.rows.length > 0) { + output.push(new DocHeading({ configuration, title: 'Namespaces' })); + output.push(namespacesTable); + } + + if (variablesTable.rows.length > 0) { + output.push(new DocHeading({ configuration, title: 'Variables' })); + output.push(variablesTable); + } + + if (typeAliasesTable.rows.length > 0) { + output.push(new DocHeading({ configuration, title: 'Type Aliases' })); + output.push(typeAliasesTable); + } + + if (finalFunctionsDefinitions.length > 0) { + output.push(...finalFunctionsDefinitions); + } + + if (variablesDefinitions.length > 0) { + output.push(...variablesDefinitions); + } + + if (typeAliasDefinitions.length > 0) { + output.push(...typeAliasDefinitions); + } + + if (enumsDefinitions.length > 0) { + output.push(...enumsDefinitions); + } + + return output; + } + + private _createPropertyTypeCell(apiItem: ApiItem): DocTableCell { + const section: DocSection = new DocSection({ + configuration: this._tsdocConfiguration + }); + + if (apiItem instanceof ApiPropertyItem) { + section.appendNode( + this._createParagraphForTypeExcerpt(apiItem.propertyTypeExcerpt) + ); + } + + return new DocTableCell( + { configuration: this._tsdocConfiguration }, + section.nodes + ); + } + + private _createSignatureSection(apiItem: ApiDeclaredItem): DocNode[] { + const configuration = this._tsdocConfiguration; + const nodes: DocNode[] = []; + if (apiItem.excerpt.text.length > 0) { + nodes.push( + new DocParagraph({ configuration }, [ + new DocEmphasisSpan({ configuration, bold: true }, [ + new DocPlainText({ configuration, text: 'Signature:' }) + ]) + ]) + ); + nodes.push( + new DocFencedCode({ + configuration, + code: apiItem.getExcerptWithModifiers(), + language: 'typescript' + }) + ); + } + + nodes.push(...this._writeHeritageTypes(apiItem)); + return nodes; + } + + private _writeHeritageTypes(apiItem: ApiDeclaredItem): DocNode[] { + const configuration = this._tsdocConfiguration; + const nodes: DocNode[] = []; + if (apiItem instanceof ApiClass) { + if (apiItem.extendsType) { + const extendsParagraph: DocParagraph = new DocParagraph( + { configuration }, + [ + new DocEmphasisSpan({ configuration, bold: true }, [ + new DocPlainText({ configuration, text: 'Extends: ' }) + ]) + ] + ); + this._appendExcerptWithHyperlinks( + extendsParagraph, + apiItem.extendsType.excerpt + ); + nodes.push(extendsParagraph); + } + if (apiItem.implementsTypes.length > 0) { + const implementsParagraph: DocParagraph = new DocParagraph( + { configuration }, + [ + new DocEmphasisSpan({ configuration, bold: true }, [ + new DocPlainText({ configuration, text: 'Implements: ' }) + ]) + ] + ); + let needsComma: boolean = false; + for (const implementsType of apiItem.implementsTypes) { + if (needsComma) { + implementsParagraph.appendNode( + new DocPlainText({ configuration, text: ', ' }) + ); + } + this._appendExcerptWithHyperlinks( + implementsParagraph, + implementsType.excerpt + ); + needsComma = true; + } + nodes.push(implementsParagraph); + } + } + + if (apiItem instanceof ApiInterface) { + if (apiItem.extendsTypes.length > 0) { + const extendsParagraph: DocParagraph = new DocParagraph( + { configuration }, + [ + new DocEmphasisSpan({ configuration, bold: true }, [ + new DocPlainText({ configuration, text: 'Extends: ' }) + ]) + ] + ); + let needsComma: boolean = false; + for (const extendsType of apiItem.extendsTypes) { + if (needsComma) { + extendsParagraph.appendNode( + new DocPlainText({ configuration, text: ', ' }) + ); + } + this._appendExcerptWithHyperlinks( + extendsParagraph, + extendsType.excerpt + ); + needsComma = true; + } + nodes.push(extendsParagraph); + } + } + + return nodes; + } + + private _deleteOldOutputFiles(): void { + console.log('Deleting old output from ' + this._outputFolder); + FileSystem.ensureEmptyFolder(this._outputFolder); + } +} diff --git a/repo-scripts/api-documenter/src/documenters/MarkdownDocumenterHelpers.ts b/repo-scripts/api-documenter/src/documenters/MarkdownDocumenterHelpers.ts new file mode 100644 index 00000000000..eb8798d3705 --- /dev/null +++ b/repo-scripts/api-documenter/src/documenters/MarkdownDocumenterHelpers.ts @@ -0,0 +1,449 @@ +/** + * @license + * Copyright 2020 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 { + DocPlainText, + DocLinkTag, + TSDocConfiguration, + DocParagraph, + DocNode, + DocBlock, + DocComment, + DocSection, + DocCodeSpan, + StandardTags, + DocNodeKind +} from '@microsoft/tsdoc'; +import { + ApiItem, + ApiItemKind, + ApiParameterListMixin, + ApiPackage, + ApiReleaseTagMixin, + ReleaseTag, + ApiDocumentedItem, + ApiEntryPoint, + ApiStaticMixin, + ApiEnum +} from 'api-extractor-model-me'; +import { DocEmphasisSpan } from '../nodes/DocEmphasisSpan'; +import { DocHeading } from '../nodes/DocHeading'; +import { DocTable } from '../nodes/DocTable'; +import { Utilities } from '../utils/Utilities'; +import { PackageName } from '@rushstack/node-core-library'; +import { DocNoteBox } from '../nodes/DocNoteBox'; +import { DocTableRow } from '../nodes/DocTableRow'; +import { DocTableCell } from '../nodes/DocTableCell'; +import { createHash } from 'crypto'; + +export function getLinkForApiItem( + apiItem: ApiItem, + addFileNameSuffix: boolean +) { + const fileName = getFilenameForApiItem(apiItem, addFileNameSuffix); + const headingAnchor = getHeadingAnchorForApiItem(apiItem); + return `./${fileName}#${headingAnchor}`; +} + +export function getFilenameForApiItem( + apiItem: ApiItem, + addFileNameSuffix: boolean +): string { + if (apiItem.kind === ApiItemKind.Model) { + return 'index.md'; + } + + let baseName: string = ''; + let multipleEntryPoints: boolean = false; + for (const hierarchyItem of apiItem.getHierarchy()) { + // For overloaded methods, add a suffix such as "MyClass.myMethod_2". + let qualifiedName: string = Utilities.getSafeFilenameForName( + hierarchyItem.displayName + ); + if (ApiParameterListMixin.isBaseClassOf(hierarchyItem)) { + if (hierarchyItem.overloadIndex > 1) { + // Subtract one for compatibility with earlier releases of API Documenter. + // (This will get revamped when we fix GitHub issue #1308) + qualifiedName += `_${hierarchyItem.overloadIndex - 1}`; + } + } + + switch (hierarchyItem.kind) { + case ApiItemKind.Model: + break; + case ApiItemKind.EntryPoint: + const packageName: string = hierarchyItem.parent!.displayName; + let entryPointName: string = PackageName.getUnscopedName(packageName); + if (multipleEntryPoints) { + entryPointName = `${PackageName.getUnscopedName(packageName)}/${ + hierarchyItem.displayName + }`; + } + baseName = Utilities.getSafeFilenameForName(entryPointName); + break; + case ApiItemKind.Package: + baseName = Utilities.getSafeFilenameForName( + PackageName.getUnscopedName(hierarchyItem.displayName) + ); + if ((hierarchyItem as ApiPackage).entryPoints.length > 1) { + multipleEntryPoints = true; + } + break; + case ApiItemKind.Namespace: + baseName += '.' + qualifiedName; + if (addFileNameSuffix) { + baseName += '_n'; + } + break; + case ApiItemKind.Class: + case ApiItemKind.Interface: + baseName += '.' + qualifiedName; + break; + } + } + return baseName + '.md'; +} + +// TODO: handle namespace? +export function getHeadingAnchorForApiItem(apiItem: ApiItem): string { + const scopedName: string = lowercaseAndRemoveSymbols( + apiItem.getScopedNameWithinPackage() + ); + + switch (apiItem.kind) { + case ApiItemKind.Function: + return lowercaseAndRemoveSymbols(getFunctionOverloadAnchor(apiItem)); + case ApiItemKind.Variable: + return `${scopedName}`; + case ApiItemKind.TypeAlias: + return `${scopedName}`; + case ApiItemKind.Enum: + return `${scopedName}`; + case ApiItemKind.Method: + case ApiItemKind.MethodSignature: + return `${scopedName}`; + case ApiItemKind.Property: + case ApiItemKind.PropertySignature: + return `${scopedName}`; + case ApiItemKind.Constructor: + case ApiItemKind.ConstructSignature: + return `${scopedName}`; + case ApiItemKind.Class: + return `${scopedName}_class`; + case ApiItemKind.Interface: + return `${scopedName}_interface`; + case ApiItemKind.Model: + return `api-reference`; + case ApiItemKind.Namespace: + return `${scopedName}_namespace`; + case ApiItemKind.Package: + const unscopedPackageName: string = lowercaseAndRemoveSymbols( + PackageName.getUnscopedName(apiItem.displayName) + ); + return `${unscopedPackageName}_package`; + case ApiItemKind.EntryPoint: + const packageName: string = apiItem.parent!.displayName; + return lowercaseAndRemoveSymbols( + `${packageName}${apiItem.displayName && '/' + apiItem.displayName}` + ); + case ApiItemKind.EnumMember: + return `${scopedName}_enummember`; + default: + throw new Error( + 'Unsupported API item kind:3 ' + apiItem.kind + apiItem.displayName + ); + } +} + +/** + * Generates a unique link for a function. Example: "getArea_paramhashhere" + */ +function getFunctionOverloadAnchor(apiItem: ApiItem): string { + if ( + ApiParameterListMixin.isBaseClassOf(apiItem) && + apiItem.parameters.length > 0 + ) { + // Create a sha256 hash from the parameter names and types. + const hash = createHash('sha256'); + apiItem.parameters.forEach(param => + hash.update(`${param.name}:${param.parameterTypeExcerpt.text}`) + ); + // Use the first 7 characters of the hash for an easier to read URL. + const paramHash = hash.digest('hex').substring(0, 7); + + // Suffix the API item name with the paramHash to generate a unique + // anchor for function overloads + return apiItem.getScopedNameWithinPackage() + '_' + paramHash; + } + return apiItem.getScopedNameWithinPackage(); +} + +function lowercaseAndRemoveSymbols(input: string): string { + return input.replace(/[\.()]/g, '').toLowerCase(); +} + +export function createBetaWarning(configuration: TSDocConfiguration): DocNode { + const betaWarning: string = + 'This API is provided as a preview for developers and may change' + + ' based on feedback that we receive. Do not use this API in a production environment.'; + return new DocNoteBox({ configuration }, [ + new DocParagraph({ configuration }, [ + new DocPlainText({ configuration, text: betaWarning }) + ]) + ]); +} + +export function createRemarksSection( + apiItem: ApiItem, + configuration: TSDocConfiguration +): DocNode[] { + const nodes: DocNode[] = []; + if (apiItem instanceof ApiDocumentedItem) { + const tsdocComment: DocComment | undefined = apiItem.tsdocComment; + + if (tsdocComment) { + // Write the @remarks block + if (tsdocComment.remarksBlock) { + nodes.push(...tsdocComment.remarksBlock.content.nodes); + } + } + } + + return nodes; +} + +export function createExampleSection( + apiItem: ApiItem, + configuration: TSDocConfiguration +): DocNode[] { + const nodes: DocNode[] = []; + if (apiItem instanceof ApiDocumentedItem) { + const tsdocComment: DocComment | undefined = apiItem.tsdocComment; + + if (tsdocComment) { + // Write the @example blocks + const exampleBlocks: DocBlock[] = tsdocComment.customBlocks.filter( + x => + x.blockTag.tagNameWithUpperCase === + StandardTags.example.tagNameWithUpperCase + ); + + let exampleNumber: number = 1; + for (const exampleBlock of exampleBlocks) { + const heading: string = + exampleBlocks.length > 1 ? `Example ${exampleNumber}` : 'Example'; + + nodes.push(new DocHeading({ configuration, title: heading, level: 2 })); + + nodes.push(...exampleBlock.content.nodes); + + ++exampleNumber; + } + } + } + + return nodes; +} + +export function createTitleCell( + apiItem: ApiItem, + configuration: TSDocConfiguration, + addFileNameSuffix: boolean +): DocTableCell { + return new DocTableCell({ configuration }, [ + new DocParagraph({ configuration }, [ + new DocLinkTag({ + configuration, + tagName: '@link', + linkText: Utilities.getConciseSignature(apiItem), + urlDestination: getLinkForApiItem(apiItem, addFileNameSuffix) + }) + ]) + ]); +} + +/** + * This generates a DocTableCell for an ApiItem including the summary section and "(BETA)" annotation. + * + * @remarks + * We mostly assume that the input is an ApiDocumentedItem, but it's easier to perform this as a runtime + * check than to have each caller perform a type cast. + */ +export function createDescriptionCell( + apiItem: ApiItem, + configuration: TSDocConfiguration +): DocTableCell { + const section: DocSection = new DocSection({ configuration }); + + if (ApiReleaseTagMixin.isBaseClassOf(apiItem)) { + if (apiItem.releaseTag === ReleaseTag.Beta) { + section.appendNodesInParagraph([ + new DocEmphasisSpan({ configuration, bold: true, italic: true }, [ + new DocPlainText({ configuration, text: '(BETA)' }) + ]), + new DocPlainText({ configuration, text: ' ' }) + ]); + } + } + + if (apiItem instanceof ApiDocumentedItem) { + if (apiItem.tsdocComment !== undefined) { + appendAndMergeSection(section, apiItem.tsdocComment.summarySection); + } + } + + return new DocTableCell({ configuration }, section.nodes); +} + +export function createModifiersCell( + apiItem: ApiItem, + configuration: TSDocConfiguration +): DocTableCell { + const section: DocSection = new DocSection({ configuration }); + + if (ApiStaticMixin.isBaseClassOf(apiItem)) { + if (apiItem.isStatic) { + section.appendNodeInParagraph( + new DocCodeSpan({ configuration, code: 'static' }) + ); + } + } + + return new DocTableCell({ configuration }, section.nodes); +} + +function appendAndMergeSection( + output: DocSection, + docSection: DocSection +): void { + let firstNode: boolean = true; + for (const node of docSection.nodes) { + if (firstNode) { + if (node.kind === DocNodeKind.Paragraph) { + output.appendNodesInParagraph(node.getChildNodes()); + firstNode = false; + continue; + } + } + firstNode = false; + + output.appendNode(node); + } +} + +export function createThrowsSection( + apiItem: ApiItem, + configuration: TSDocConfiguration, + parentHeadingLevel: number +): DocNode[] { + const output: DocNode[] = []; + if (apiItem instanceof ApiDocumentedItem) { + const tsdocComment: DocComment | undefined = apiItem.tsdocComment; + + if (tsdocComment) { + // Write the @throws blocks + const throwsBlocks: DocBlock[] = tsdocComment.customBlocks.filter( + x => + x.blockTag.tagNameWithUpperCase === + StandardTags.throws.tagNameWithUpperCase + ); + + if (throwsBlocks.length > 0) { + const heading: string = 'Exceptions'; + output.push( + new DocHeading({ + configuration, + title: heading, + level: parentHeadingLevel + 1 + }) + ); + + for (const throwsBlock of throwsBlocks) { + output.push(...throwsBlock.content.nodes); + } + } + } + } + + return output; +} + +export function createEntryPointTitleCell( + apiItem: ApiEntryPoint, + configuration: TSDocConfiguration, + addFileNameSuffix: boolean +): DocTableCell { + return new DocTableCell({ configuration }, [ + new DocParagraph({ configuration }, [ + new DocLinkTag({ + configuration, + tagName: '@link', + linkText: `/${apiItem.displayName}`, + urlDestination: getLinkForApiItem(apiItem, addFileNameSuffix) + }) + ]) + ]); +} + +/** + * GENERATE PAGE: ENUM + */ +export function createEnumTables( + apiEnum: ApiEnum, + configuration: TSDocConfiguration +): DocNode[] { + const output: DocNode[] = []; + const enumMembersTable: DocTable = new DocTable({ + configuration, + headerTitles: ['Member', 'Value', 'Description'] + }); + + for (const apiEnumMember of apiEnum.members) { + enumMembersTable.addRow( + new DocTableRow({ configuration }, [ + new DocTableCell({ configuration }, [ + new DocParagraph({ configuration }, [ + new DocPlainText({ + configuration, + text: Utilities.getConciseSignature(apiEnumMember) + }) + ]) + ]), + + new DocTableCell({ configuration }, [ + new DocParagraph({ configuration }, [ + new DocCodeSpan({ + configuration, + code: apiEnumMember.initializerExcerpt.text + }) + ]) + ]), + + createDescriptionCell(apiEnumMember, configuration) + ]) + ); + } + + if (enumMembersTable.rows.length > 0) { + output.push( + new DocHeading({ configuration, title: 'Enumeration Members' }) + ); + output.push(enumMembersTable); + } + + return output; +} diff --git a/repo-scripts/api-documenter/src/index.ts b/repo-scripts/api-documenter/src/index.ts new file mode 100644 index 00000000000..1845059c0c4 --- /dev/null +++ b/repo-scripts/api-documenter/src/index.ts @@ -0,0 +1,44 @@ +/** + * @license + * Copyright 2020 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. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +/** + * API Documenter generates an API reference website from the .api.json files created by API Extractor. + * The `@microsoft/api-documenter` package provides the command-line tool. It also exposes a developer API that you + * can use to create plugins that customize how API Documenter generates documentation. + * + * @packageDocumentation + */ + +export { + IFeatureDefinition, + IApiDocumenterPluginManifest +} from './plugin/IApiDocumenterPluginManifest'; +export { MarkdownDocumenterAccessor } from './plugin/MarkdownDocumenterAccessor'; +export { + MarkdownDocumenterFeatureContext, + IMarkdownDocumenterFeatureOnBeforeWritePageArgs, + IMarkdownDocumenterFeatureOnFinishedArgs, + MarkdownDocumenterFeature +} from './plugin/MarkdownDocumenterFeature'; +export { + PluginFeature, + PluginFeatureContext, + PluginFeatureInitialization +} from './plugin/PluginFeature'; diff --git a/repo-scripts/api-documenter/src/markdown/CustomMarkdownEmitter.ts b/repo-scripts/api-documenter/src/markdown/CustomMarkdownEmitter.ts new file mode 100644 index 00000000000..6f8f4785bdb --- /dev/null +++ b/repo-scripts/api-documenter/src/markdown/CustomMarkdownEmitter.ts @@ -0,0 +1,232 @@ +/** + * @license + * Copyright 2020 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. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import colors from 'colors'; + +import { DocNode, DocLinkTag, StringBuilder } from '@microsoft/tsdoc'; +import { + ApiModel, + IResolveDeclarationReferenceResult, + ApiItem +} from 'api-extractor-model-me'; + +import { CustomDocNodeKind } from '../nodes/CustomDocNodeKind'; +import { DocHeading } from '../nodes/DocHeading'; +import { DocNoteBox } from '../nodes/DocNoteBox'; +import { DocTable } from '../nodes/DocTable'; +import { DocTableCell } from '../nodes/DocTableCell'; +import { DocEmphasisSpan } from '../nodes/DocEmphasisSpan'; +import { + MarkdownEmitter, + IMarkdownEmitterContext, + IMarkdownEmitterOptions +} from './MarkdownEmitter'; +import { IndentedWriter } from '../utils/IndentedWriter'; + +export interface ICustomMarkdownEmitterOptions extends IMarkdownEmitterOptions { + contextApiItem: ApiItem | undefined; + + onGetFilenameForApiItem: (apiItem: ApiItem) => string | undefined; +} + +export class CustomMarkdownEmitter extends MarkdownEmitter { + private _apiModel: ApiModel; + + public constructor(apiModel: ApiModel) { + super(); + + this._apiModel = apiModel; + } + + public emit( + stringBuilder: StringBuilder, + docNode: DocNode, + options: ICustomMarkdownEmitterOptions + ): string { + return super.emit(stringBuilder, docNode, options); + } + + /** @override */ + protected writeNode( + docNode: DocNode, + context: IMarkdownEmitterContext, + docNodeSiblings: boolean + ): void { + const writer: IndentedWriter = context.writer; + + switch (docNode.kind) { + case CustomDocNodeKind.Heading: { + const docHeading: DocHeading = docNode as DocHeading; + writer.ensureSkippedLine(); + + let prefix: string; + switch (docHeading.level) { + case 1: + prefix = '##'; + break; + case 2: + prefix = '###'; + break; + case 3: + prefix = '####'; + break; + default: + prefix = '####'; + } + + let mdLine = prefix + ' ' + this.getEscapedText(docHeading.title); + if (docHeading.anchor) { + mdLine = mdLine + ` {:#${docHeading.anchor}}`; + } + writer.writeLine(mdLine); + writer.writeLine(); + break; + } + case CustomDocNodeKind.NoteBox: { + const docNoteBox: DocNoteBox = docNode as DocNoteBox; + writer.ensureNewLine(); + + writer.increaseIndent('> '); + + this.writeNode(docNoteBox.content, context, false); + writer.ensureNewLine(); + + writer.decreaseIndent(); + + writer.writeLine(); + break; + } + case CustomDocNodeKind.Table: { + const docTable: DocTable = docNode as DocTable; + // GitHub's markdown renderer chokes on tables that don't have a blank line above them, + // whereas VS Code's renderer is totally fine with it. + writer.ensureSkippedLine(); + + context.insideTable = true; + + // Markdown table rows can have inconsistent cell counts. Size the table based on the longest row. + let columnCount: number = 0; + if (docTable.header) { + columnCount = docTable.header.cells.length; + } + for (const row of docTable.rows) { + if (row.cells.length > columnCount) { + columnCount = row.cells.length; + } + } + + // write the table header (which is required by Markdown) + writer.write('| '); + for (let i: number = 0; i < columnCount; ++i) { + writer.write(' '); + if (docTable.header) { + const cell: DocTableCell | undefined = docTable.header.cells[i]; + if (cell) { + this.writeNode(cell.content, context, false); + } + } + writer.write(' |'); + } + writer.writeLine(); + + // write the divider + writer.write('| '); + for (let i: number = 0; i < columnCount; ++i) { + writer.write(' --- |'); + } + writer.writeLine(); + + for (const row of docTable.rows) { + writer.write('| '); + for (const cell of row.cells) { + writer.write(' '); + this.writeNode(cell.content, context, false); + writer.write(' |'); + } + writer.writeLine(); + } + writer.writeLine(); + + context.insideTable = false; + + break; + } + case CustomDocNodeKind.EmphasisSpan: { + const docEmphasisSpan: DocEmphasisSpan = docNode as DocEmphasisSpan; + const oldBold: boolean = context.boldRequested; + const oldItalic: boolean = context.italicRequested; + context.boldRequested = docEmphasisSpan.bold; + context.italicRequested = docEmphasisSpan.italic; + this.writeNodes(docEmphasisSpan.nodes, context); + context.boldRequested = oldBold; + context.italicRequested = oldItalic; + break; + } + default: + super.writeNode(docNode, context, false); + } + } + + /** @override */ + protected writeLinkTagWithCodeDestination( + docLinkTag: DocLinkTag, + context: IMarkdownEmitterContext + ): void { + const options: ICustomMarkdownEmitterOptions = context.options; + + const result: IResolveDeclarationReferenceResult = + this._apiModel.resolveDeclarationReference( + docLinkTag.codeDestination!, + options.contextApiItem + ); + + if (result.resolvedApiItem) { + const filename: string | undefined = options.onGetFilenameForApiItem( + result.resolvedApiItem + ); + + if (filename) { + let linkText: string = docLinkTag.linkText || ''; + if (linkText.length === 0) { + // Generate a name such as Namespace1.Namespace2.MyClass.myMethod() + linkText = result.resolvedApiItem.getScopedNameWithinPackage(); + } + if (linkText.length > 0) { + const encodedLinkText: string = this.getEscapedText( + linkText.replace(/\s+/g, ' ') + ); + + context.writer.write('['); + context.writer.write(encodedLinkText); + context.writer.write(`](${filename!})`); + } else { + console.log(colors.yellow('WARNING: Unable to determine link text')); + } + } + } else if (result.errorMessage) { + console.log( + colors.yellow( + `WARNING: Unable to resolve reference "${docLinkTag.codeDestination!.emitAsTsdoc()}": ` + + result.errorMessage + ) + ); + } + } +} diff --git a/repo-scripts/api-documenter/src/markdown/MarkdownEmitter.ts b/repo-scripts/api-documenter/src/markdown/MarkdownEmitter.ts new file mode 100644 index 00000000000..1bc306bb044 --- /dev/null +++ b/repo-scripts/api-documenter/src/markdown/MarkdownEmitter.ts @@ -0,0 +1,319 @@ +/** + * @license + * Copyright 2020 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. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { + DocNode, + DocNodeKind, + StringBuilder, + DocPlainText, + DocHtmlStartTag, + DocHtmlEndTag, + DocCodeSpan, + DocLinkTag, + DocParagraph, + DocFencedCode, + DocSection, + DocNodeTransforms, + DocEscapedText, + DocErrorText, + DocBlockTag +} from '@microsoft/tsdoc'; +import { InternalError } from '@rushstack/node-core-library'; + +import { IndentedWriter } from '../utils/IndentedWriter'; + +export interface IMarkdownEmitterOptions {} + +export interface IMarkdownEmitterContext { + writer: IndentedWriter; + insideTable: boolean; + + boldRequested: boolean; + italicRequested: boolean; + + writingBold: boolean; + writingItalic: boolean; + + options: TOptions; +} + +/** + * Renders MarkupElement content in the Markdown file format. + * For more info: https://en.wikipedia.org/wiki/Markdown + */ +export class MarkdownEmitter { + public emit( + stringBuilder: StringBuilder, + docNode: DocNode, + options: IMarkdownEmitterOptions + ): string { + const writer: IndentedWriter = new IndentedWriter(stringBuilder); + + const context: IMarkdownEmitterContext = { + writer, + insideTable: false, + + boldRequested: false, + italicRequested: false, + + writingBold: false, + writingItalic: false, + + options + }; + + this.writeNode(docNode, context, false); + + writer.ensureNewLine(); // finish the last line + + return writer.toString(); + } + + protected getEscapedText(text: string): string { + const textWithBackslashes: string = text + .replace(/\\/g, '\\\\') // first replace the escape character + .replace(/[*#[\]_|`~]/g, x => '\\' + x) // then escape any special characters + .replace(/---/g, '\\-\\-\\-') // hyphens only if it's 3 or more + .replace(/&/g, '&') + .replace(//g, '>'); + return textWithBackslashes; + } + + protected getTableEscapedText(text: string): string { + return text + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(//g, '>') + .replace(/\|/g, '|'); + } + + /** + * @virtual + */ + protected writeNode( + docNode: DocNode, + context: IMarkdownEmitterContext, + docNodeSiblings: boolean + ): void { + const writer: IndentedWriter = context.writer; + + switch (docNode.kind) { + case DocNodeKind.PlainText: { + const docPlainText: DocPlainText = docNode as DocPlainText; + this.writePlainText(docPlainText.text, context); + break; + } + case DocNodeKind.HtmlStartTag: + case DocNodeKind.HtmlEndTag: { + const docHtmlTag: DocHtmlStartTag | DocHtmlEndTag = docNode as + | DocHtmlStartTag + | DocHtmlEndTag; + // write the HTML element verbatim into the output + writer.write(docHtmlTag.emitAsHtml()); + break; + } + case DocNodeKind.CodeSpan: { + const docCodeSpan: DocCodeSpan = docNode as DocCodeSpan; + if (context.insideTable) { + writer.write(''); + } else { + writer.write('`'); + } + if (context.insideTable) { + const code: string = this.getTableEscapedText(docCodeSpan.code); + const parts: string[] = code.split(/\r?\n/g); + writer.write(parts.join('
')); + } else { + writer.write(docCodeSpan.code); + } + if (context.insideTable) { + writer.write(''); + } else { + writer.write('`'); + } + break; + } + case DocNodeKind.LinkTag: { + const docLinkTag: DocLinkTag = docNode as DocLinkTag; + if (docLinkTag.codeDestination) { + this.writeLinkTagWithCodeDestination(docLinkTag, context); + } else if (docLinkTag.urlDestination) { + this.writeLinkTagWithUrlDestination(docLinkTag, context); + } else if (docLinkTag.linkText) { + this.writePlainText(docLinkTag.linkText, context); + } + break; + } + case DocNodeKind.Paragraph: { + const docParagraph: DocParagraph = docNode as DocParagraph; + const trimmedParagraph: DocParagraph = + DocNodeTransforms.trimSpacesInParagraph(docParagraph); + if (context.insideTable) { + if (docNodeSiblings) { + writer.write('

'); + this.writeNodes(trimmedParagraph.nodes, context); + writer.write('

'); + } else { + // Special case: If we are the only element inside this table cell, then we can omit the

container. + this.writeNodes(trimmedParagraph.nodes, context); + } + } else { + this.writeNodes(trimmedParagraph.nodes, context); + writer.ensureNewLine(); + writer.writeLine(); + } + break; + } + case DocNodeKind.FencedCode: { + const docFencedCode: DocFencedCode = docNode as DocFencedCode; + writer.ensureNewLine(); + writer.write('```'); + writer.write(docFencedCode.language); + writer.writeLine(); + writer.write(docFencedCode.code); + writer.writeLine(); + writer.writeLine('```'); + break; + } + case DocNodeKind.Section: { + const docSection: DocSection = docNode as DocSection; + this.writeNodes(docSection.nodes, context); + break; + } + case DocNodeKind.SoftBreak: { + if (!/^\s?$/.test(writer.peekLastCharacter())) { + writer.write(' '); + } + break; + } + case DocNodeKind.EscapedText: { + const docEscapedText: DocEscapedText = docNode as DocEscapedText; + this.writePlainText(docEscapedText.decodedText, context); + break; + } + case DocNodeKind.ErrorText: { + const docErrorText: DocErrorText = docNode as DocErrorText; + this.writePlainText(docErrorText.text, context); + break; + } + case DocNodeKind.InlineTag: { + break; + } + case DocNodeKind.BlockTag: { + const tagNode: DocBlockTag = docNode as DocBlockTag; + console.warn('Unsupported block tag: ' + tagNode.tagName); + break; + } + default: + throw new InternalError( + 'Unsupported DocNodeKind kind: ' + docNode.kind + ); + } + } + + /** @virtual */ + protected writeLinkTagWithCodeDestination( + docLinkTag: DocLinkTag, + context: IMarkdownEmitterContext + ): void { + // The subclass needs to implement this to support code destinations + throw new InternalError('writeLinkTagWithCodeDestination()'); + } + + /** @virtual */ + protected writeLinkTagWithUrlDestination( + docLinkTag: DocLinkTag, + context: IMarkdownEmitterContext + ): void { + const linkText: string = + docLinkTag.linkText !== undefined + ? docLinkTag.linkText + : docLinkTag.urlDestination!; + + const encodedLinkText: string = this.getEscapedText( + linkText.replace(/\s+/g, ' ') + ); + + context.writer.write('['); + context.writer.write(encodedLinkText); + context.writer.write(`](${docLinkTag.urlDestination!})`); + } + + protected writePlainText( + text: string, + context: IMarkdownEmitterContext + ): void { + const writer: IndentedWriter = context.writer; + + // split out the [ leading whitespace, content, trailing whitespace ] + const parts: string[] = text.match(/^(\s*)(.*?)(\s*)$/) || []; + + writer.write(parts[1]); // write leading whitespace + + const middle: string = parts[2]; + + if (middle !== '') { + switch (writer.peekLastCharacter()) { + case '': + case '\n': + case ' ': + case '[': + case '>': + // okay to put a symbol + break; + default: + // This is no problem: "**one** *two* **three**" + // But this is trouble: "**one***two***three**" + // The most general solution: "**one***two***three**" + writer.write(''); + break; + } + + if (context.boldRequested) { + writer.write(''); + } + if (context.italicRequested) { + writer.write(''); + } + + writer.write(this.getEscapedText(middle)); + + if (context.italicRequested) { + writer.write(''); + } + if (context.boldRequested) { + writer.write(''); + } + } + + writer.write(parts[3]); // write trailing whitespace + } + + protected writeNodes( + docNodes: ReadonlyArray, + context: IMarkdownEmitterContext + ): void { + for (const docNode of docNodes) { + this.writeNode(docNode, context, docNodes.length > 1); + } + } +} diff --git a/repo-scripts/api-documenter/src/markdown/test/CustomMarkdownEmitter.test.ts b/repo-scripts/api-documenter/src/markdown/test/CustomMarkdownEmitter.test.ts new file mode 100644 index 00000000000..ed8799e0023 --- /dev/null +++ b/repo-scripts/api-documenter/src/markdown/test/CustomMarkdownEmitter.test.ts @@ -0,0 +1,237 @@ +/** + * @license + * Copyright 2020 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. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { + DocSection, + TSDocConfiguration, + DocPlainText, + StringBuilder, + DocParagraph, + DocSoftBreak, + DocLinkTag, + DocHtmlStartTag, + DocHtmlEndTag, + DocBlockTag +} from '@microsoft/tsdoc'; + +import { CustomDocNodes } from '../../nodes/CustomDocNodeKind'; +import { DocHeading } from '../../nodes/DocHeading'; +import { DocEmphasisSpan } from '../../nodes/DocEmphasisSpan'; +import { DocTable } from '../../nodes/DocTable'; +import { DocTableRow } from '../../nodes/DocTableRow'; +import { DocTableCell } from '../../nodes/DocTableCell'; +import { CustomMarkdownEmitter } from '../CustomMarkdownEmitter'; +import { ApiModel, ApiItem } from 'api-extractor-model-me'; +import { expect, use } from 'chai'; +import { jestSnapshotPlugin } from 'mocha-chai-jest-snapshot'; + +use(jestSnapshotPlugin()); + +it('render Markdown from TSDoc', () => { + const configuration: TSDocConfiguration = CustomDocNodes.configuration; + + const output: DocSection = new DocSection({ configuration }); + + output.appendNodes([ + new DocHeading({ configuration, title: 'Simple bold test' }), + new DocParagraph({ configuration }, [ + new DocPlainText({ configuration, text: 'This is a ' }), + new DocEmphasisSpan({ configuration, bold: true }, [ + new DocPlainText({ configuration, text: 'bold' }) + ]), + new DocPlainText({ configuration, text: ' word.' }) + ]) + ]); + + output.appendNodes([ + new DocHeading({ configuration, title: 'All whitespace bold' }), + new DocParagraph({ configuration }, [ + new DocEmphasisSpan({ configuration, bold: true }, [ + new DocPlainText({ configuration, text: ' ' }) + ]) + ]) + ]); + + output.appendNodes([ + new DocHeading({ configuration, title: 'Newline bold' }), + new DocParagraph({ configuration }, [ + new DocEmphasisSpan({ configuration, bold: true }, [ + new DocPlainText({ configuration, text: 'line 1' }), + new DocSoftBreak({ configuration }), + new DocPlainText({ configuration, text: 'line 2' }) + ]) + ]) + ]); + + output.appendNodes([ + new DocHeading({ configuration, title: 'Newline bold with spaces' }), + new DocParagraph({ configuration }, [ + new DocEmphasisSpan({ configuration, bold: true }, [ + new DocPlainText({ configuration, text: ' line 1 ' }), + new DocSoftBreak({ configuration }), + new DocPlainText({ configuration, text: ' line 2 ' }), + new DocSoftBreak({ configuration }), + new DocPlainText({ configuration, text: ' line 3 ' }) + ]) + ]) + ]); + + output.appendNodes([ + new DocHeading({ configuration, title: 'Adjacent bold regions' }), + new DocParagraph({ configuration }, [ + new DocEmphasisSpan({ configuration, bold: true }, [ + new DocPlainText({ configuration, text: 'one' }) + ]), + new DocEmphasisSpan({ configuration, bold: true }, [ + new DocPlainText({ configuration, text: 'two' }) + ]), + new DocEmphasisSpan({ configuration, bold: true }, [ + new DocPlainText({ configuration, text: ' three ' }) + ]), + new DocPlainText({ configuration, text: '' }), + new DocEmphasisSpan({ configuration, bold: true }, [ + new DocPlainText({ configuration, text: 'four' }) + ]), + new DocPlainText({ configuration, text: 'non-bold' }), + new DocEmphasisSpan({ configuration, bold: true }, [ + new DocPlainText({ configuration, text: 'five' }) + ]) + ]) + ]); + + output.appendNodes([ + new DocHeading({ configuration, title: 'Adjacent to other characters' }), + new DocParagraph({ configuration }, [ + new DocLinkTag({ + configuration, + tagName: '@link', + linkText: 'a link', + urlDestination: './index.md' + }), + new DocEmphasisSpan({ configuration, bold: true }, [ + new DocPlainText({ configuration, text: 'bold' }) + ]), + new DocPlainText({ configuration, text: 'non-bold' }), + new DocPlainText({ configuration, text: 'more-non-bold' }) + ]) + ]); + + output.appendNodes([ + new DocHeading({ configuration, title: 'Unknown block tag' }), + new DocParagraph({ configuration }, [ + new DocBlockTag({ + configuration, + tagName: '@unknown' + }), + new DocEmphasisSpan({ configuration, bold: true }, [ + new DocPlainText({ configuration, text: 'bold' }) + ]), + new DocPlainText({ configuration, text: 'non-bold' }), + new DocPlainText({ configuration, text: 'more-non-bold' }) + ]) + ]); + + output.appendNodes([ + new DocHeading({ configuration, title: 'Bad characters' }), + new DocParagraph({ configuration }, [ + new DocEmphasisSpan({ configuration, bold: true }, [ + new DocPlainText({ configuration, text: '*one*two*' }) + ]), + new DocEmphasisSpan({ configuration, bold: true }, [ + new DocPlainText({ configuration, text: 'three*four' }) + ]) + ]) + ]); + + output.appendNodes([ + new DocHeading({ + configuration, + title: 'Characters that should be escaped' + }), + new DocParagraph({ configuration }, [ + new DocPlainText({ + configuration, + text: 'Double-encoded JSON: "{ \\"A\\": 123}"' + }) + ]), + new DocParagraph({ configuration }, [ + new DocPlainText({ + configuration, + text: 'HTML chars: ' + }) + ]), + new DocParagraph({ configuration }, [ + new DocPlainText({ configuration, text: 'HTML escape: "' }) + ]), + new DocParagraph({ configuration }, [ + new DocPlainText({ + configuration, + text: '3 or more hyphens: - -- --- ---- ----- ------' + }) + ]) + ]); + + output.appendNodes([ + new DocHeading({ configuration, title: 'HTML tag' }), + new DocParagraph({ configuration }, [ + new DocHtmlStartTag({ configuration, name: 'b' }), + new DocPlainText({ configuration, text: 'bold' }), + new DocHtmlEndTag({ configuration, name: 'b' }) + ]) + ]); + + output.appendNodes([ + new DocHeading({ configuration, title: 'Table' }), + new DocTable( + { + configuration, + headerTitles: ['Header 1', 'Header 2'] + }, + [ + new DocTableRow({ configuration }, [ + new DocTableCell({ configuration }, [ + new DocParagraph({ configuration }, [ + new DocPlainText({ configuration, text: 'Cell 1' }) + ]) + ]), + new DocTableCell({ configuration }, [ + new DocParagraph({ configuration }, [ + new DocPlainText({ configuration, text: 'Cell 2' }) + ]) + ]) + ]) + ] + ) + ]); + + const stringBuilder: StringBuilder = new StringBuilder(); + const apiModel: ApiModel = new ApiModel(); + const markdownEmitter: CustomMarkdownEmitter = new CustomMarkdownEmitter( + apiModel + ); + markdownEmitter.emit(stringBuilder, output, { + contextApiItem: undefined, + onGetFilenameForApiItem: (apiItem: ApiItem) => { + return '#'; + } + }); + + expect(stringBuilder).toMatchSnapshot(); +}); diff --git a/repo-scripts/api-documenter/src/markdown/test/__snapshots__/CustomMarkdownEmitter.test.ts.snap b/repo-scripts/api-documenter/src/markdown/test/__snapshots__/CustomMarkdownEmitter.test.ts.snap new file mode 100644 index 00000000000..ae459a5a8d7 --- /dev/null +++ b/repo-scripts/api-documenter/src/markdown/test/__snapshots__/CustomMarkdownEmitter.test.ts.snap @@ -0,0 +1,62 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`render Markdown from TSDoc 1`] = ` +StringBuilder { + "_chunks": Array [ + " +## Simple bold test + +This is a bold word. + +## All whitespace bold + + + +## Newline bold + +line 1 line 2 + +## Newline bold with spaces + + line 1 line 2 line 3 + +## Adjacent bold regions + +onetwo three fournon-boldfive + +## Adjacent to other characters + +[a link](./index.md)boldnon-boldmore-non-bold + +## Unknown block tag + +boldnon-boldmore-non-bold + +## Bad characters + +\\\\*one\\\\*two\\\\*three\\\\*four + +## Characters that should be escaped + +Double-encoded JSON: \\"{ \\\\\\\\\\"A\\\\\\\\\\": 123}\\" + +HTML chars: <script>alert(\\"\\\\[You\\\\] are \\\\#1!\\");</script> + +HTML escape: &quot; + +3 or more hyphens: - -- \\\\-\\\\-\\\\- \\\\-\\\\-\\\\-- \\\\-\\\\-\\\\--- \\\\-\\\\-\\\\-\\\\-\\\\-\\\\- + +## HTML tag + +bold + +## Table + +| Header 1 | Header 2 | +| --- | --- | +| Cell 1 | Cell 2 | + +", + ], +} +`; diff --git a/repo-scripts/api-documenter/src/nodes/CustomDocNodeKind.ts b/repo-scripts/api-documenter/src/nodes/CustomDocNodeKind.ts new file mode 100644 index 00000000000..744673f484d --- /dev/null +++ b/repo-scripts/api-documenter/src/nodes/CustomDocNodeKind.ts @@ -0,0 +1,89 @@ +/** + * @license + * Copyright 2020 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 { TSDocConfiguration, DocNodeKind } from '@microsoft/tsdoc'; +import { DocEmphasisSpan } from './DocEmphasisSpan'; +import { DocHeading } from './DocHeading'; +import { DocNoteBox } from './DocNoteBox'; +import { DocTable } from './DocTable'; +import { DocTableCell } from './DocTableCell'; +import { DocTableRow } from './DocTableRow'; + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +/** + * Identifies custom subclasses of {@link DocNode}. + */ +export const enum CustomDocNodeKind { + EmphasisSpan = 'EmphasisSpan', + Heading = 'Heading', + NoteBox = 'NoteBox', + Table = 'Table', + TableCell = 'TableCell', + TableRow = 'TableRow' +} + +export class CustomDocNodes { + private static _configuration: TSDocConfiguration | undefined; + + public static get configuration(): TSDocConfiguration { + if (CustomDocNodes._configuration === undefined) { + const configuration: TSDocConfiguration = new TSDocConfiguration(); + + configuration.docNodeManager.registerDocNodes( + '@micrososft/api-documenter', + [ + { + docNodeKind: CustomDocNodeKind.EmphasisSpan, + constructor: DocEmphasisSpan + }, + { docNodeKind: CustomDocNodeKind.Heading, constructor: DocHeading }, + { docNodeKind: CustomDocNodeKind.NoteBox, constructor: DocNoteBox }, + { docNodeKind: CustomDocNodeKind.Table, constructor: DocTable }, + { + docNodeKind: CustomDocNodeKind.TableCell, + constructor: DocTableCell + }, + { docNodeKind: CustomDocNodeKind.TableRow, constructor: DocTableRow } + ] + ); + + configuration.docNodeManager.registerAllowableChildren( + CustomDocNodeKind.EmphasisSpan, + [DocNodeKind.PlainText, DocNodeKind.SoftBreak] + ); + + configuration.docNodeManager.registerAllowableChildren( + DocNodeKind.Section, + [ + CustomDocNodeKind.Heading, + CustomDocNodeKind.NoteBox, + CustomDocNodeKind.Table + ] + ); + + configuration.docNodeManager.registerAllowableChildren( + DocNodeKind.Paragraph, + [CustomDocNodeKind.EmphasisSpan] + ); + + CustomDocNodes._configuration = configuration; + } + return CustomDocNodes._configuration; + } +} diff --git a/repo-scripts/api-documenter/src/nodes/DocEmphasisSpan.ts b/repo-scripts/api-documenter/src/nodes/DocEmphasisSpan.ts new file mode 100644 index 00000000000..51d6cc79e4e --- /dev/null +++ b/repo-scripts/api-documenter/src/nodes/DocEmphasisSpan.ts @@ -0,0 +1,58 @@ +/** + * @license + * Copyright 2020 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. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { + DocNode, + DocNodeContainer, + IDocNodeContainerParameters +} from '@microsoft/tsdoc'; +import { CustomDocNodeKind } from './CustomDocNodeKind'; + +/** + * Constructor parameters for {@link DocEmphasisSpan}. + */ +export interface IDocEmphasisSpanParameters + extends IDocNodeContainerParameters { + bold?: boolean; + italic?: boolean; +} + +/** + * Represents a span of text that is styled with CommonMark emphasis (italics), strong emphasis (boldface), + * or both. + */ +export class DocEmphasisSpan extends DocNodeContainer { + public readonly bold: boolean; + public readonly italic: boolean; + + public constructor( + parameters: IDocEmphasisSpanParameters, + children?: DocNode[] + ) { + super(parameters, children); + this.bold = !!parameters.bold; + this.italic = !!parameters.italic; + } + + /** @override */ + public get kind(): string { + return CustomDocNodeKind.EmphasisSpan; + } +} diff --git a/repo-scripts/api-documenter/src/nodes/DocHeading.ts b/repo-scripts/api-documenter/src/nodes/DocHeading.ts new file mode 100644 index 00000000000..3e13fb01736 --- /dev/null +++ b/repo-scripts/api-documenter/src/nodes/DocHeading.ts @@ -0,0 +1,62 @@ +/** + * @license + * Copyright 2020 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. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { IDocNodeParameters, DocNode } from '@microsoft/tsdoc'; +import { CustomDocNodeKind } from './CustomDocNodeKind'; + +/** + * Constructor parameters for {@link DocHeading}. + */ +export interface IDocHeadingParameters extends IDocNodeParameters { + title: string; + level?: number; + anchor?: string; +} + +/** + * Represents a section header similar to an HTML `

` or `

` element. + */ +export class DocHeading extends DocNode { + public readonly title: string; + public readonly level: number; + public readonly anchor?: string; + + /** + * Don't call this directly. Instead use {@link TSDocParser} + * @internal + */ + public constructor(parameters: IDocHeadingParameters) { + super(parameters); + this.title = parameters.title; + this.level = parameters.level !== undefined ? parameters.level : 1; + this.anchor = parameters.anchor; + + if (this.level < 1 || this.level > 5) { + throw new Error( + 'IDocHeadingParameters.level must be a number between 1 and 5' + ); + } + } + + /** @override */ + public get kind(): string { + return CustomDocNodeKind.Heading; + } +} diff --git a/repo-scripts/api-documenter/src/nodes/DocNoteBox.ts b/repo-scripts/api-documenter/src/nodes/DocNoteBox.ts new file mode 100644 index 00000000000..dd2133828f7 --- /dev/null +++ b/repo-scripts/api-documenter/src/nodes/DocNoteBox.ts @@ -0,0 +1,55 @@ +/** + * @license + * Copyright 2020 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. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { IDocNodeParameters, DocNode, DocSection } from '@microsoft/tsdoc'; +import { CustomDocNodeKind } from './CustomDocNodeKind'; + +/** + * Constructor parameters for {@link DocNoteBox}. + */ +export interface IDocNoteBoxParameters extends IDocNodeParameters {} + +/** + * Represents a note box, which is typically displayed as a bordered box containing informational text. + */ +export class DocNoteBox extends DocNode { + public readonly content: DocSection; + + public constructor( + parameters: IDocNoteBoxParameters, + sectionChildNodes?: ReadonlyArray + ) { + super(parameters); + this.content = new DocSection( + { configuration: this.configuration }, + sectionChildNodes + ); + } + + /** @override */ + public get kind(): string { + return CustomDocNodeKind.NoteBox; + } + + /** @override */ + protected onGetChildNodes(): ReadonlyArray { + return [this.content]; + } +} diff --git a/repo-scripts/api-documenter/src/nodes/DocTable.ts b/repo-scripts/api-documenter/src/nodes/DocTable.ts new file mode 100644 index 00000000000..49f319f0326 --- /dev/null +++ b/repo-scripts/api-documenter/src/nodes/DocTable.ts @@ -0,0 +1,101 @@ +/** + * @license + * Copyright 2020 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. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { IDocNodeParameters, DocNode } from '@microsoft/tsdoc'; +import { CustomDocNodeKind } from './CustomDocNodeKind'; +import { DocTableRow } from './DocTableRow'; +import { DocTableCell } from './DocTableCell'; + +/** + * Constructor parameters for {@link DocTable}. + */ +export interface IDocTableParameters extends IDocNodeParameters { + headerCells?: ReadonlyArray; + headerTitles?: string[]; +} + +/** + * Represents table, similar to an HTML `

` element. - */ -export class DocTableCell extends DocNode { - public readonly content: DocSection; - - public constructor( - parameters: IDocTableCellParameters, - sectionChildNodes?: ReadonlyArray - ) { - super(parameters); - - this.content = new DocSection( - { configuration: this.configuration }, - sectionChildNodes - ); - } - - /** @override */ - public get kind(): string { - return CustomDocNodeKind.TableCell; - } -} diff --git a/repo-scripts/api-documenter/src/nodes/DocTableRow.ts b/repo-scripts/api-documenter/src/nodes/DocTableRow.ts deleted file mode 100644 index 4d14b7b3d03..00000000000 --- a/repo-scripts/api-documenter/src/nodes/DocTableRow.ts +++ /dev/null @@ -1,86 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -import { IDocNodeParameters, DocNode, DocPlainText } from '@microsoft/tsdoc'; -import { CustomDocNodeKind } from './CustomDocNodeKind'; -import { DocTableCell } from './DocTableCell'; - -/** - * Constructor parameters for {@link DocTableRow}. - */ -export interface IDocTableRowParameters extends IDocNodeParameters {} - -/** - * Represents table row, similar to an HTML `
` element. + */ +export class DocTable extends DocNode { + public readonly header: DocTableRow; + + private _rows: DocTableRow[]; + + public constructor( + parameters: IDocTableParameters, + rows?: ReadonlyArray + ) { + super(parameters); + + this.header = new DocTableRow({ configuration: this.configuration }); + this._rows = []; + + if (parameters) { + if (parameters.headerTitles) { + if (parameters.headerCells) { + throw new Error( + 'IDocTableParameters.headerCells and IDocTableParameters.headerTitles' + + ' cannot both be specified' + ); + } + for (const cellText of parameters.headerTitles) { + this.header.addPlainTextCell(cellText); + } + } else if (parameters.headerCells) { + for (const cell of parameters.headerCells) { + this.header.addCell(cell); + } + } + } + + if (rows) { + for (const row of rows) { + this.addRow(row); + } + } + } + + /** @override */ + public get kind(): string { + return CustomDocNodeKind.Table; + } + + public get rows(): ReadonlyArray { + return this._rows; + } + + public addRow(row: DocTableRow): void { + this._rows.push(row); + } + + public createAndAddRow(): DocTableRow { + const row: DocTableRow = new DocTableRow({ + configuration: this.configuration + }); + this.addRow(row); + return row; + } + + /** @override */ + protected onGetChildNodes(): ReadonlyArray { + return [this.header, ...this._rows]; + } +} diff --git a/repo-scripts/api-documenter/src/nodes/DocTableCell.ts b/repo-scripts/api-documenter/src/nodes/DocTableCell.ts new file mode 100644 index 00000000000..6fa4f79159c --- /dev/null +++ b/repo-scripts/api-documenter/src/nodes/DocTableCell.ts @@ -0,0 +1,51 @@ +/** + * @license + * Copyright 2020 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. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { IDocNodeParameters, DocNode, DocSection } from '@microsoft/tsdoc'; +import { CustomDocNodeKind } from './CustomDocNodeKind'; + +/** + * Constructor parameters for {@link DocTableCell}. + */ +export interface IDocTableCellParameters extends IDocNodeParameters {} + +/** + * Represents table cell, similar to an HTML `` element. + */ +export class DocTableRow extends DocNode { + private readonly _cells: DocTableCell[]; + + public constructor( + parameters: IDocTableRowParameters, + cells?: ReadonlyArray + ) { + super(parameters); + + this._cells = []; + if (cells) { + for (const cell of cells) { + this.addCell(cell); + } + } + } + + /** @override */ + public get kind(): string { + return CustomDocNodeKind.TableRow; + } + + public get cells(): ReadonlyArray { + return this._cells; + } + + public addCell(cell: DocTableCell): void { + this._cells.push(cell); + } + + public createAndAddCell(): DocTableCell { + const newCell: DocTableCell = new DocTableCell({ + configuration: this.configuration + }); + this.addCell(newCell); + return newCell; + } + + public addPlainTextCell(cellContent: string): DocTableCell { + const cell: DocTableCell = this.createAndAddCell(); + cell.content.appendNodeInParagraph( + new DocPlainText({ + configuration: this.configuration, + text: cellContent + }) + ); + return cell; + } + + /** @override */ + protected onGetChildNodes(): ReadonlyArray { + return this._cells; + } +} diff --git a/repo-scripts/api-documenter/src/plugin/IApiDocumenterPluginManifest.ts b/repo-scripts/api-documenter/src/plugin/IApiDocumenterPluginManifest.ts new file mode 100644 index 00000000000..d8a44b95019 --- /dev/null +++ b/repo-scripts/api-documenter/src/plugin/IApiDocumenterPluginManifest.ts @@ -0,0 +1,96 @@ +/** + * @license + * Copyright 2020 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. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { MarkdownDocumenterFeature } from './MarkdownDocumenterFeature'; +import { PluginFeatureInitialization } from './PluginFeature'; + +/** + * Defines a "feature" that is provided by an API Documenter plugin. A feature is a user-defined module + * that customizes the behavior of API Documenter. + * + * @public + */ +export interface IFeatureDefinition { + /** + * The name of this feature, as it will appear in the config file. + * + * The name should consist of one or more words separated by hyphens. Each word should consist of lower case + * letters and numbers. Example: `my-feature` + */ + featureName: string; + + /** + * Determines the kind of feature. The specified value is the name of the base class that `subclass` inherits from. + * + * @remarks + * For now, `MarkdownDocumenterFeature` is the only supported value. + */ + kind: 'MarkdownDocumenterFeature'; + + /** + * Your subclass that extends from the base class. + */ + subclass: { + new ( + initialization: PluginFeatureInitialization + ): MarkdownDocumenterFeature; + }; +} + +/** + * The manifest for an API Documenter plugin. + * + * @remarks + * An API documenter plugin is an NPM package. By convention, the NPM package name should have the prefix + * `doc-plugin-`. Its main entry point should export an object named `apiDocumenterPluginManifest` which implements + * the `IApiDocumenterPluginManifest` interface. + * + * For example: + * ```ts + * class MyMarkdownDocumenter extends MarkdownDocumenterFeature { + * public onInitialized(): void { + * console.log('MyMarkdownDocumenter: onInitialized()'); + * } + * } + * + * export const apiDocumenterPluginManifest: IApiDocumenterPluginManifest = { + * manifestVersion: 1000, + * features: [ + * { + * featureName: 'my-markdown-documenter', + * kind: 'MarkdownDocumenterFeature', + * subclass: MyMarkdownDocumenter + * } + * ] + * }; + * ``` + * @public + */ +export interface IApiDocumenterPluginManifest { + /** + * The manifest version number. For now, this must always be `1000`. + */ + manifestVersion: 1000; + + /** + * The list of features provided by this plugin. + */ + features: IFeatureDefinition[]; +} diff --git a/repo-scripts/api-documenter/src/plugin/MarkdownDocumenterAccessor.ts b/repo-scripts/api-documenter/src/plugin/MarkdownDocumenterAccessor.ts new file mode 100644 index 00000000000..87e9ca09c0f --- /dev/null +++ b/repo-scripts/api-documenter/src/plugin/MarkdownDocumenterAccessor.ts @@ -0,0 +1,55 @@ +/** + * @license + * Copyright 2020 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. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { ApiItem } from 'api-extractor-model-me'; + +/** @internal */ +export interface IMarkdownDocumenterAccessorImplementation { + getLinkForApiItem(apiItem: ApiItem): string | undefined; +} + +/** + * Provides access to the documenter that is generating the output. + * + * @privateRemarks + * This class is wrapper that provides access to the underlying MarkdownDocumenter, while hiding the implementation + * details to ensure that the plugin API contract is stable. + * + * @public + */ +export class MarkdownDocumenterAccessor { + private _implementation: IMarkdownDocumenterAccessorImplementation; + + /** @internal */ + public constructor( + implementation: IMarkdownDocumenterAccessorImplementation + ) { + this._implementation = implementation; + } + + /** + * For a given `ApiItem`, return its markdown hyperlink. + * + * @returns The hyperlink, or `undefined` if the `ApiItem` object does not have a hyperlink. + */ + public getLinkForApiItem(apiItem: ApiItem): string | undefined { + return this._implementation.getLinkForApiItem(apiItem); + } +} diff --git a/repo-scripts/api-documenter/src/plugin/MarkdownDocumenterFeature.ts b/repo-scripts/api-documenter/src/plugin/MarkdownDocumenterFeature.ts new file mode 100644 index 00000000000..00fa9d03785 --- /dev/null +++ b/repo-scripts/api-documenter/src/plugin/MarkdownDocumenterFeature.ts @@ -0,0 +1,124 @@ +/** + * @license + * Copyright 2020 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. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { ApiItem, ApiModel } from 'api-extractor-model-me'; +import { TypeUuid } from '@rushstack/node-core-library'; +import { PluginFeature } from './PluginFeature'; +import { MarkdownDocumenterAccessor } from './MarkdownDocumenterAccessor'; + +/** + * Context object for {@link MarkdownDocumenterFeature}. + * Exposes various services that can be used by a plugin. + * + * @public + */ +export class MarkdownDocumenterFeatureContext { + /** + * Provides access to the `ApiModel` for the documentation being generated. + */ + public readonly apiModel: ApiModel; + + /** + * The full path to the output folder. + */ + public readonly outputFolder: string; + + /** + * Exposes functionality of the documenter. + */ + public readonly documenter: MarkdownDocumenterAccessor; + + /** @internal */ + public constructor(options: MarkdownDocumenterFeatureContext) { + this.apiModel = options.apiModel; + this.outputFolder = options.outputFolder; + this.documenter = options.documenter; + } +} + +/** + * Event arguments for MarkdownDocumenterFeature.onBeforeWritePage() + * @public + */ +export interface IMarkdownDocumenterFeatureOnBeforeWritePageArgs { + /** + * The API item corresponding to this page. + */ + readonly apiItem: ApiItem; + + /** + * The page content. The {@link MarkdownDocumenterFeature.onBeforeWritePage} handler can reassign this + * string to customize the page appearance. + */ + pageContent: string; + + /** + * The filename where the output will be written. + */ + readonly outputFilename: string; +} + +/** + * Event arguments for MarkdownDocumenterFeature.onFinished() + * @public + */ +export interface IMarkdownDocumenterFeatureOnFinishedArgs {} + +const uuidMarkdownDocumenterFeature: string = + '34196154-9eb3-4de0-a8c8-7e9539dfe216'; + +/** + * Inherit from this base class to implement an API Documenter plugin feature that customizes + * the generation of markdown output. + * + * @public + */ +export class MarkdownDocumenterFeature extends PluginFeature { + /** {@inheritdoc PluginFeature.context} */ + public context!: MarkdownDocumenterFeatureContext; + + /** + * This event occurs before each markdown file is written. It provides an opportunity to customize the + * content of the file. + * @virtual + */ + public onBeforeWritePage( + eventArgs: IMarkdownDocumenterFeatureOnBeforeWritePageArgs + ): void { + // (implemented by child class) + } + + /** + * This event occurs after all output files have been written. + * @virtual + */ + public onFinished(eventArgs: IMarkdownDocumenterFeatureOnFinishedArgs): void { + // (implemented by child class) + } + + public static [Symbol.hasInstance](instance: object): boolean { + return TypeUuid.isInstanceOf(instance, uuidMarkdownDocumenterFeature); + } +} + +TypeUuid.registerClass( + MarkdownDocumenterFeature, + uuidMarkdownDocumenterFeature +); diff --git a/repo-scripts/api-documenter/src/plugin/PluginFeature.ts b/repo-scripts/api-documenter/src/plugin/PluginFeature.ts new file mode 100644 index 00000000000..4fcef9f4eab --- /dev/null +++ b/repo-scripts/api-documenter/src/plugin/PluginFeature.ts @@ -0,0 +1,85 @@ +/** + * @license + * Copyright 2020 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. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { TypeUuid } from '@rushstack/node-core-library'; + +/** + * This is an internal part of the plugin infrastructure. + * + * @remarks + * This object is the constructor parameter for API Documenter plugin features. + * + * @public + */ +export class PluginFeatureInitialization { + /** @internal */ + public _context!: PluginFeatureContext; + + /** @internal */ + public constructor() { + // reserved for future use + } +} + +/** + * Context object for {@link PluginFeature}. + * Exposes various services that can be used by a plugin. + * + * @public + */ +export class PluginFeatureContext {} + +const uuidPluginFeature: string = '56876472-7134-4812-819e-533de0ee10e6'; + +/** + * The abstract base class for all API Documenter plugin features. + * @public + */ +export abstract class PluginFeature { + /** + * Exposes various services that can be used by a plugin. + */ + public context: PluginFeatureContext; + + /** + * The subclass should pass the `initialization` through to the base class. + * Do not put custom initialization code in the constructor. Instead perform your initialization in the + * `onInitialized()` event function. + * @internal + */ + public constructor(initialization: PluginFeatureInitialization) { + // reserved for future expansion + this.context = initialization._context; + } + + /** + * This event function is called after the feature is initialized, but before any processing occurs. + * @virtual + */ + public onInitialized(): void { + // (implemented by child class) + } + + public static [Symbol.hasInstance](instance: object): boolean { + return TypeUuid.isInstanceOf(instance, uuidPluginFeature); + } +} + +TypeUuid.registerClass(PluginFeature, uuidPluginFeature); diff --git a/repo-scripts/api-documenter/src/plugin/PluginLoader.ts b/repo-scripts/api-documenter/src/plugin/PluginLoader.ts new file mode 100644 index 00000000000..3bf868c86e2 --- /dev/null +++ b/repo-scripts/api-documenter/src/plugin/PluginLoader.ts @@ -0,0 +1,163 @@ +/** + * @license + * Copyright 2020 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. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import * as path from 'path'; +import * as resolve from 'resolve'; + +import { + IApiDocumenterPluginManifest, + IFeatureDefinition +} from './IApiDocumenterPluginManifest'; +import { + MarkdownDocumenterFeature, + MarkdownDocumenterFeatureContext +} from './MarkdownDocumenterFeature'; +import { PluginFeatureInitialization } from './PluginFeature'; +import { DocumenterConfig } from '../documenters/DocumenterConfig'; + +interface ILoadedPlugin { + packageName: string; + manifest: IApiDocumenterPluginManifest; +} + +export class PluginLoader { + public markdownDocumenterFeature: MarkdownDocumenterFeature | undefined; + + public load( + documenterConfig: DocumenterConfig, + createContext: () => MarkdownDocumenterFeatureContext + ): void { + const configFileFolder: string = path.dirname( + documenterConfig.configFilePath + ); + for (const configPlugin of documenterConfig.configFile.plugins || []) { + try { + // Look for the package name in the same place as the config file + const resolvedEntryPointPath: string = resolve.sync( + configPlugin.packageName, + { + basedir: configFileFolder + } + ); + + // Load the package + const entryPoint: + | { apiDocumenterPluginManifest?: IApiDocumenterPluginManifest } + | undefined = require(resolvedEntryPointPath); + + if (!entryPoint) { + throw new Error('Invalid entry point'); + } + + if (!entryPoint.apiDocumenterPluginManifest) { + throw new Error( + `The package is not an API documenter plugin;` + + ` the "apiDocumenterPluginManifest" export was not found` + ); + } + + const manifest: IApiDocumenterPluginManifest = + entryPoint.apiDocumenterPluginManifest; + + if (manifest.manifestVersion !== 1000) { + throw new Error( + `The plugin is not compatible with this version of API Documenter;` + + ` unsupported manifestVersion` + ); + } + + const loadedPlugin: ILoadedPlugin = { + packageName: configPlugin.packageName, + manifest + }; + + const featureDefinitionsByName: Map = + new Map(); + for (const featureDefinition of manifest.features) { + featureDefinitionsByName.set( + featureDefinition.featureName, + featureDefinition + ); + } + + for (const featureName of configPlugin.enabledFeatureNames) { + const featureDefinition: IFeatureDefinition | undefined = + featureDefinitionsByName.get(featureName); + if (!featureDefinition) { + throw new Error( + `The plugin ${loadedPlugin.packageName} does not have a feature with name "${featureName}"` + ); + } + + if (featureDefinition.kind === 'MarkdownDocumenterFeature') { + if (this.markdownDocumenterFeature) { + throw new Error('A MarkdownDocumenterFeature is already loaded'); + } + + const initialization: PluginFeatureInitialization = + new PluginFeatureInitialization(); + initialization._context = createContext(); + + let markdownDocumenterFeature: + | MarkdownDocumenterFeature + | undefined = undefined; + try { + markdownDocumenterFeature = new featureDefinition.subclass( + initialization + ); + } catch (e) { + throw new Error( + `Failed to construct feature subclass:\n` + + (e as Error)?.toString() + ); + } + if ( + !(markdownDocumenterFeature instanceof MarkdownDocumenterFeature) + ) { + throw new Error( + 'The constructed subclass was not an instance of MarkdownDocumenterFeature' + ); + } + + try { + markdownDocumenterFeature.onInitialized(); + } catch (e) { + throw new Error( + 'Error occurred during the onInitialized() event: ' + + (e as Error)?.toString() + ); + } + + this.markdownDocumenterFeature = markdownDocumenterFeature; + } else { + throw new Error( + `Unknown feature definition kind: "${featureDefinition.kind}"` + ); + } + } + } catch (e) { + throw new Error( + `Error loading plugin ${configPlugin.packageName}: ` + + (e as Error)?.message + ); + } + } + } +} diff --git a/repo-scripts/api-documenter/src/schemas/api-documenter-template.json b/repo-scripts/api-documenter/src/schemas/api-documenter-template.json new file mode 100644 index 00000000000..94da49b03e9 --- /dev/null +++ b/repo-scripts/api-documenter/src/schemas/api-documenter-template.json @@ -0,0 +1,92 @@ +/** + * Config file for API Documenter. For more info, please visit: https://api-extractor.com + */ +{ + "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-documenter.schema.json", + + /** + * Specifies the output target. + * Supported values are "docfx" or "markdown" + */ + // "outputTarget": "markdown", + + /** + * Specifies what type of newlines API Documenter should use when writing output files. By default, the output files + * will be written with Windows-style newlines. To use POSIX-style newlines, specify "lf" instead. + * To use the OS's default newline kind, specify "os". + * + * DEFAULT VALUE: "crlf" + */ + "newlineKind": "lf", + + /** + * This enables an experimental feature that will be officially released with the next major version + * of API Documenter. It requires DocFX 2.46 or newer. It enables documentation for namespaces and + * adds them to the table of contents. This will also affect file layout as namespaced items will be nested + * under a directory for the namespace instead of just within the package. + * + * This setting currently only affects the 'docfx' output target. It is equivalent to the `--new-docfx-namespaces` + * command-line parameter. + */ + // "newDocfxNamespaces": false, + + /** + * Describes plugin packages to be loaded, and which features to enable. + */ + "plugins": [ + // { + // "packageName": "doc-plugin-example", + // "enabledFeatureNames": [ "example-feature" ] + // } + ], + + /** + * Configures how the table of contents is generated. + */ + "tableOfContents": { + /** + * Allows hand-coded items to be injected into the table of contents. + * + * DEFAULT VALUE: (none) + */ + // "items": [ + // { "name": "Example Node", "href": "~/homepage/homepage.md" }, + // { + // "name": "API Reference", + // "items": [ + // { "name": "References" } + // ] + // } + // ], + /** + * Optional category name that is recommended to include in the `tocConfig`, + * along with one of the filters: `filterByApiItemName` or `filterByInlineTag`. + * Any items that are not matched to the mentioned filters will be placed under this + * catchAll category. If none provided the items will not be included in the final toc.yml file. + * + * DEFAULT VALUE: (none) + */ + // "catchAllCategory": "References", + /** + * When loading more than one api.json files that might include the same API items, + * toggle either to show duplicates or not. + * + * DEFAULT VALUE: false + */ + // "noDuplicateEntries": true, + /** + * Toggle either sorting of the API items should be made based on category name presence + * in the API item's name. + * + * DEFAULT VALUE: false + */ + // "filterByApiItemName": false, + /** + * Filter that can be used to sort the API items according to an inline custom tag + * that is present on them. + * + * DEFAULT VALUE: (none) + */ + // "filterByInlineTag": "@docCategory" + } +} diff --git a/repo-scripts/api-documenter/src/schemas/api-documenter.schema.json b/repo-scripts/api-documenter/src/schemas/api-documenter.schema.json new file mode 100644 index 00000000000..7282baf3b00 --- /dev/null +++ b/repo-scripts/api-documenter/src/schemas/api-documenter.schema.json @@ -0,0 +1,42 @@ +{ + "title": "API Documenter Configuration", + "description": "Describes how the API Documenter tool will process a project.", + "type": "object", + "properties": { + "$schema": { + "description": "Part of the JSON Schema standard, this optional keyword declares the URL of the schema that the file conforms to. Editors may download the schema and use it to perform syntax highlighting.", + "type": "string" + }, + + "outputTarget": { + "description": "Specifies what type of documentation will be generated", + "type": "string", + "enum": ["docfx", "markdown"] + }, + + "newlineKind": { + "description": "Specifies what type of newlines API Documenter should use when writing output files. By default, the output files will be written with Windows-style newlines. To use POSIX-style newlines, specify \"lf\" instead. To use the OS's default newline kind, specify \"os\".", + "type": "string", + "enum": ["crlf", "lf", "os"], + "default": "crlf" + }, + + "newDocfxNamespaces": { + "description": "This enables an experimental feature that will be officially released with the next major version of API Documenter. It requires DocFX 2.46 or newer. It enables documentation for namespaces and adds them to the table of contents. This will also affect file layout as namespaced items will be nested under a directory for the namespace instead of just within the package.", + "type": "boolean" + }, + + "plugins": { + "description": "Specifies plugin packages to be loaded", + "type": "array" + }, + + "tableOfContents": { + "description": "Configures how the table of contents is generated.", + "type": "object", + "additionalProperties": true + } + }, + + "additionalProperties": false +} diff --git a/repo-scripts/api-documenter/src/start.ts b/repo-scripts/api-documenter/src/start.ts new file mode 100644 index 00000000000..92d6a75d9b5 --- /dev/null +++ b/repo-scripts/api-documenter/src/start.ts @@ -0,0 +1,39 @@ +#!/usr/bin/env node + +/** + * @license + * Copyright 2020 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. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import * as os from 'os'; +import colors from 'colors'; + +import { PackageJsonLookup } from '@rushstack/node-core-library'; + +import { ApiDocumenterCommandLine } from './cli/ApiDocumenterCommandLine'; + +const myPackageVersion: string = + PackageJsonLookup.loadOwnPackageJson(__dirname).version; + +console.log( + os.EOL + colors.bold(`@firebase/api-documenter ${myPackageVersion} ` + os.EOL) +); + +const parser: ApiDocumenterCommandLine = new ApiDocumenterCommandLine(); + +parser.execute().catch(console.error); // CommandLineParser.execute() should never reject the promise diff --git a/repo-scripts/api-documenter/src/toc.ts b/repo-scripts/api-documenter/src/toc.ts new file mode 100644 index 00000000000..b4887949407 --- /dev/null +++ b/repo-scripts/api-documenter/src/toc.ts @@ -0,0 +1,108 @@ +/** + * @license + * Copyright 2021 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 yaml from 'js-yaml'; +import { ApiItem, ApiItemKind, ApiModel } from 'api-extractor-model-me'; +import { getFilenameForApiItem } from './documenters/MarkdownDocumenterHelpers'; +import { ModuleSource } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference'; +import { writeFileSync } from 'fs'; +import { resolve } from 'path'; + +export interface ITocGenerationOptions { + apiModel: ApiModel; + g3Path: string; + outputFolder: string; + addFileNameSuffix: boolean; + jsSdk: boolean; +} + +interface ITocItem { + title: string; + path: string; + section?: ITocItem[]; +} + +export function generateToc({ + apiModel, + g3Path, + outputFolder, + addFileNameSuffix, + jsSdk +}: ITocGenerationOptions) { + const toc = []; + + if (jsSdk) { + const firebaseToc: ITocItem = { + title: 'firebase', + path: `${g3Path}/index` + }; + toc.push(firebaseToc); + } + + generateTocRecursively(apiModel, g3Path, addFileNameSuffix, toc); + + writeFileSync( + resolve(outputFolder, 'toc.yaml'), + yaml.dump( + { toc }, + { + quotingType: '"' + } + ) + ); +} + +function generateTocRecursively( + apiItem: ApiItem, + g3Path: string, + addFileNameSuffix: boolean, + toc: ITocItem[] +) { + // generate toc item only for entry points + if (apiItem.kind === ApiItemKind.EntryPoint) { + // Entry point + const entryPointName = ( + apiItem.canonicalReference.source! as ModuleSource + ).escapedPath.replace('@firebase/', ''); + const entryPointToc: ITocItem = { + title: entryPointName, + path: `${g3Path}/${getFilenameForApiItem(apiItem, addFileNameSuffix)}`, + section: [] + }; + + for (const member of apiItem.members) { + // only classes and interfaces have dedicated pages + if ( + member.kind === ApiItemKind.Class || + member.kind === ApiItemKind.Interface + ) { + const fileName = getFilenameForApiItem(member, addFileNameSuffix); + entryPointToc.section!.push({ + title: member.displayName, + path: `${g3Path}/${fileName}` + }); + } + } + + toc.push(entryPointToc); + } else { + // travel the api tree to find the next entry point + for (const member of apiItem.members) { + generateTocRecursively(member, g3Path, addFileNameSuffix, toc); + } + } +} diff --git a/repo-scripts/api-documenter/src/utils/IndentedWriter.ts b/repo-scripts/api-documenter/src/utils/IndentedWriter.ts new file mode 100644 index 00000000000..6b4fc8cbdf5 --- /dev/null +++ b/repo-scripts/api-documenter/src/utils/IndentedWriter.ts @@ -0,0 +1,243 @@ +/** + * @license + * Copyright 2020 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. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { StringBuilder, IStringBuilder } from '@rushstack/node-core-library'; + +/** + * A utility for writing indented text. + * + * @remarks + * + * Note that the indentation is inserted at the last possible opportunity. + * For example, this code... + * + * ```ts + * writer.write('begin\n'); + * writer.increaseIndent(); + * writer.write('one\ntwo\n'); + * writer.decreaseIndent(); + * writer.increaseIndent(); + * writer.decreaseIndent(); + * writer.write('end'); + * ``` + * + * ...would produce this output: + * + * ``` + * begin + * one + * two + * end + * ``` + */ +export class IndentedWriter { + /** + * The text characters used to create one level of indentation. + * Two spaces by default. + */ + public defaultIndentPrefix: string = ' '; + + private readonly _builder: IStringBuilder; + + private _latestChunk: string | undefined; + private _previousChunk: string | undefined; + private _atStartOfLine: boolean; + + private readonly _indentStack: string[]; + private _indentText: string; + + public constructor(builder?: IStringBuilder) { + this._builder = builder === undefined ? new StringBuilder() : builder; + + this._latestChunk = undefined; + this._previousChunk = undefined; + this._atStartOfLine = true; + + this._indentStack = []; + this._indentText = ''; + } + + /** + * Retrieves the output that was built so far. + */ + public getText(): string { + return this._builder.toString(); + } + + public toString(): string { + return this.getText(); + } + + /** + * Increases the indentation. Normally the indentation is two spaces, + * however an arbitrary prefix can optional be specified. (For example, + * the prefix could be "// " to indent and comment simultaneously.) + * Each call to IndentedWriter.increaseIndent() must be followed by a + * corresponding call to IndentedWriter.decreaseIndent(). + */ + public increaseIndent(indentPrefix?: string): void { + this._indentStack.push( + indentPrefix !== undefined ? indentPrefix : this.defaultIndentPrefix + ); + this._updateIndentText(); + } + + /** + * Decreases the indentation, reverting the effect of the corresponding call + * to IndentedWriter.increaseIndent(). + */ + public decreaseIndent(): void { + this._indentStack.pop(); + this._updateIndentText(); + } + + /** + * A shorthand for ensuring that increaseIndent()/decreaseIndent() occur + * in pairs. + */ + public indentScope(scope: () => void, indentPrefix?: string): void { + this.increaseIndent(indentPrefix); + scope(); + this.decreaseIndent(); + } + + /** + * Adds a newline if the file pointer is not already at the start of the line (or start of the stream). + */ + public ensureNewLine(): void { + const lastCharacter: string = this.peekLastCharacter(); + if (lastCharacter !== '\n' && lastCharacter !== '') { + this._writeNewLine(); + } + } + + /** + * Adds up to two newlines to ensure that there is a blank line above the current line. + */ + public ensureSkippedLine(): void { + if (this.peekLastCharacter() !== '\n') { + this._writeNewLine(); + } + + const secondLastCharacter: string = this.peekSecondLastCharacter(); + if (secondLastCharacter !== '\n' && secondLastCharacter !== '') { + this._writeNewLine(); + } + } + + /** + * Returns the last character that was written, or an empty string if no characters have been written yet. + */ + public peekLastCharacter(): string { + if (this._latestChunk !== undefined) { + return this._latestChunk.substr(-1, 1); + } + return ''; + } + + /** + * Returns the second to last character that was written, or an empty string if less than one characters + * have been written yet. + */ + public peekSecondLastCharacter(): string { + if (this._latestChunk !== undefined) { + if (this._latestChunk.length > 1) { + return this._latestChunk.substr(-2, 1); + } + if (this._previousChunk !== undefined) { + return this._previousChunk.substr(-1, 1); + } + } + return ''; + } + + /** + * Writes some text to the internal string buffer, applying indentation according + * to the current indentation level. If the string contains multiple newlines, + * each line will be indented separately. + */ + public write(message: string): void { + if (message.length === 0) { + return; + } + + // If there are no newline characters, then append the string verbatim + if (!/[\r\n]/.test(message)) { + this._writeLinePart(message); + return; + } + + // Otherwise split the lines and write each one individually + let first: boolean = true; + for (const linePart of message.split('\n')) { + if (!first) { + this._writeNewLine(); + } else { + first = false; + } + if (linePart) { + this._writeLinePart(linePart.replace(/[\r]/g, '')); + } + } + } + + /** + * A shorthand for writing an optional message, followed by a newline. + * Indentation is applied following the semantics of IndentedWriter.write(). + */ + public writeLine(message: string = ''): void { + if (message.length > 0) { + this.write(message); + } + this._writeNewLine(); + } + + /** + * Writes a string that does not contain any newline characters. + */ + private _writeLinePart(message: string): void { + if (message.length > 0) { + if (this._atStartOfLine && this._indentText.length > 0) { + this._write(this._indentText); + } + this._write(message); + this._atStartOfLine = false; + } + } + + private _writeNewLine(): void { + if (this._atStartOfLine && this._indentText.length > 0) { + this._write(this._indentText); + } + + this._write('\n'); + this._atStartOfLine = true; + } + + private _write(s: string): void { + this._previousChunk = this._latestChunk; + this._latestChunk = s; + this._builder.append(s); + } + + private _updateIndentText(): void { + this._indentText = this._indentStack.join(''); + } +} diff --git a/repo-scripts/api-documenter/src/utils/Utilities.ts b/repo-scripts/api-documenter/src/utils/Utilities.ts new file mode 100644 index 00000000000..d5a1bd39c13 --- /dev/null +++ b/repo-scripts/api-documenter/src/utils/Utilities.ts @@ -0,0 +1,48 @@ +/** + * @license + * Copyright 2020 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. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { ApiParameterListMixin, ApiItem } from 'api-extractor-model-me'; + +export class Utilities { + private static readonly _badFilenameCharsRegExp: RegExp = /[^a-z0-9_\-\.]/gi; + /** + * Generates a concise signature for a function. Example: "getArea(width, height)" + */ + public static getConciseSignature(apiItem: ApiItem): string { + if (ApiParameterListMixin.isBaseClassOf(apiItem)) { + return ( + apiItem.displayName + + '(' + + apiItem.parameters.map(x => x.name).join(', ') + + ')' + ); + } + return apiItem.displayName; + } + + /** + * Converts bad filename characters to underscores. + */ + public static getSafeFilenameForName(name: string): string { + // TODO: This can introduce naming collisions. + // We will fix that as part of https://github.com/microsoft/rushstack/issues/1308 + return name.replace(Utilities._badFilenameCharsRegExp, '_').toLowerCase(); + } +} diff --git a/repo-scripts/api-documenter/src/utils/test/IndentedWriter.test.ts b/repo-scripts/api-documenter/src/utils/test/IndentedWriter.test.ts new file mode 100644 index 00000000000..e313ffd8123 --- /dev/null +++ b/repo-scripts/api-documenter/src/utils/test/IndentedWriter.test.ts @@ -0,0 +1,99 @@ +/** + * @license + * Copyright 2020 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. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { IndentedWriter } from '../IndentedWriter'; +import { expect, use } from 'chai'; +import { jestSnapshotPlugin } from 'mocha-chai-jest-snapshot'; + +use(jestSnapshotPlugin()); + +it('01 Demo from docs', () => { + const indentedWriter: IndentedWriter = new IndentedWriter(); + indentedWriter.write('begin\n'); + indentedWriter.increaseIndent(); + indentedWriter.write('one\ntwo\n'); + indentedWriter.decreaseIndent(); + indentedWriter.increaseIndent(); + indentedWriter.decreaseIndent(); + indentedWriter.write('end'); + + expect(indentedWriter.toString()).toMatchSnapshot(); +}); + +it('02 Indent something', () => { + const indentedWriter: IndentedWriter = new IndentedWriter(); + indentedWriter.write('a'); + indentedWriter.write('b'); + indentedWriter.increaseIndent(); + indentedWriter.writeLine('c'); + indentedWriter.writeLine('d'); + indentedWriter.decreaseIndent(); + indentedWriter.writeLine('e'); + + indentedWriter.increaseIndent('>>> '); + indentedWriter.writeLine(); + indentedWriter.writeLine(); + indentedWriter.writeLine('g'); + indentedWriter.decreaseIndent(); + + expect(indentedWriter.toString()).toMatchSnapshot(); +}); + +it('03 Two kinds of indents', () => { + const indentedWriter: IndentedWriter = new IndentedWriter(); + + indentedWriter.writeLine('---'); + indentedWriter.indentScope(() => { + indentedWriter.write('a\nb'); + indentedWriter.indentScope(() => { + indentedWriter.write('c\nd\n'); + }); + indentedWriter.write('e\n'); + }, '> '); + indentedWriter.writeLine('---'); + + expect(indentedWriter.toString()).toMatchSnapshot(); +}); + +it('04 Edge cases for ensureNewLine()', () => { + let indentedWriter: IndentedWriter = new IndentedWriter(); + indentedWriter.ensureNewLine(); + indentedWriter.write('line'); + expect(indentedWriter.toString()).toMatchSnapshot(); + + indentedWriter = new IndentedWriter(); + indentedWriter.write('previous'); + indentedWriter.ensureNewLine(); + indentedWriter.write('line'); + expect(indentedWriter.toString()).toMatchSnapshot(); +}); + +it('04 Edge cases for ensureSkippedLine()', () => { + let indentedWriter: IndentedWriter = new IndentedWriter(); + indentedWriter.ensureSkippedLine(); + indentedWriter.write('line'); + expect(indentedWriter.toString()).toMatchSnapshot(); + + indentedWriter = new IndentedWriter(); + indentedWriter.write('previous'); + indentedWriter.ensureSkippedLine(); + indentedWriter.write('line'); + expect(indentedWriter.toString()).toMatchSnapshot(); +}); diff --git a/repo-scripts/api-documenter/src/utils/test/__snapshots__/IndentedWriter.test.ts.snap b/repo-scripts/api-documenter/src/utils/test/__snapshots__/IndentedWriter.test.ts.snap new file mode 100644 index 00000000000..62778d301c8 --- /dev/null +++ b/repo-scripts/api-documenter/src/utils/test/__snapshots__/IndentedWriter.test.ts.snap @@ -0,0 +1,46 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`01 Demo from docs 1`] = ` +"begin + one + two +end" +`; + +exports[`02 Indent something 1`] = ` +"abc + d +e +>>> +>>> +>>> g +" +`; + +exports[`03 Two kinds of indents 1`] = ` +"--- +> a +> bc +> d +> e +--- +" +`; + +exports[`04 Edge cases for ensureNewLine() 1`] = `"line"`; + +exports[`04 Edge cases for ensureNewLine() 2`] = ` +"previous +line" +`; + +exports[`04 Edge cases for ensureSkippedLine() 1`] = ` +" +line" +`; + +exports[`04 Edge cases for ensureSkippedLine() 2`] = ` +"previous + +line" +`; diff --git a/repo-scripts/api-documenter/tsconfig.json b/repo-scripts/api-documenter/tsconfig.json new file mode 100644 index 00000000000..e790468f48f --- /dev/null +++ b/repo-scripts/api-documenter/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../config/tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "downlevelIteration": true, + "module": "CommonJS", + "target": "ES6", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true + }, + "exclude": ["dist/**/*"] +} \ No newline at end of file diff --git a/repo-scripts/changelog-generator/.eslintrc.js b/repo-scripts/changelog-generator/.eslintrc.js new file mode 100644 index 00000000000..ca80aa0f69a --- /dev/null +++ b/repo-scripts/changelog-generator/.eslintrc.js @@ -0,0 +1,26 @@ +/** + * @license + * Copyright 2020 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. + */ + +module.exports = { + extends: '../../config/.eslintrc.js', + parserOptions: { + project: 'tsconfig.json', + // to make vscode-eslint work with monorepo + // https://github.com/typescript-eslint/typescript-eslint/issues/251#issuecomment-463943250 + tsconfigRootDir: __dirname + } +}; diff --git a/repo-scripts/changelog-generator/README.md b/repo-scripts/changelog-generator/README.md new file mode 100644 index 00000000000..a5d605b7973 --- /dev/null +++ b/repo-scripts/changelog-generator/README.md @@ -0,0 +1,3 @@ +# @firebase/changelog-generator + +It works as a plugin for @changesets/cli to generate changelog entries(via running `changeset version`). diff --git a/repo-scripts/changelog-generator/index.ts b/repo-scripts/changelog-generator/index.ts new file mode 100644 index 00000000000..20f3de482f1 --- /dev/null +++ b/repo-scripts/changelog-generator/index.ts @@ -0,0 +1,120 @@ +/** + * @license + * Copyright 2020 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 { ChangelogFunctions } from '@changesets/types'; +import { getInfo } from '@changesets/get-github-info'; +import { fetch as undiciFetch, Response as undiciResponse } from 'undici'; + +const changelogFunctions: ChangelogFunctions = { + getDependencyReleaseLine: async ( + changesets, + dependenciesUpdated, + options + ) => { + if (!options.repo) { + throw new Error( + 'Please provide a repo to this changelog generator like this:\n"changelog": ["@changesets/changelog-github", { "repo": "org/repo" }]' + ); + } + if (dependenciesUpdated.length === 0) { + return ''; + } + + const changesetLink = `- Updated dependencies [${( + await Promise.all( + changesets.map(async cs => { + if (cs.commit) { + const { links } = await getInfo({ + repo: options.repo, + commit: cs.commit + }); + return links.commit; + } + }) + ) + ) + .filter(_ => _) + .join(', ')}]:`; + + const updatedDependenciesList = dependenciesUpdated.map( + dependency => ` - ${dependency.name}@${dependency.newVersion}` + ); + + return [changesetLink, ...updatedDependenciesList].join('\n'); + }, + getReleaseLine: async (changeset, type, options) => { + if (!options || !options.repo) { + throw new Error( + 'Please provide a repo to this changelog generator like this:\n"changelog": ["@changesets/changelog-github", { "repo": "org/repo" }]' + ); + } + const [firstLine, ...futureLines] = changeset.summary + .split('\n') + .map(l => l.trimRight()); + + if (changeset.commit) { + const { pull: pullNumber, links } = await getInfo({ + repo: options.repo, + commit: changeset.commit + }); + + let fixedIssueLink = null; + // If the summary didn't mention any issue, we will look at the PR body to try to generate one automatically + if (!/issues\/[\d+]/i.test(changeset.summary) && pullNumber) { + fixedIssueLink = await getFixedIssueLink(pullNumber, options.repo); + } + + return `\n\n- ${links.commit}${ + links.pull === null ? '' : ` ${links.pull}` + }${ + fixedIssueLink === null ? '' : ` ${fixedIssueLink}` + } - ${firstLine}\n${futureLines.map(l => ` ${l}`).join('\n')}`; + } else { + return `\n\n- ${firstLine}\n${futureLines.map(l => ` ${l}`).join('\n')}`; + } + } +}; + +const fixedIssueRegex = + /(close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved) [^\s]*(#|issues\/)([\d]+)/i; +async function getFixedIssueLink( + prNumber: number, + repo: string +): Promise { + const response = await undiciFetch( + `https://api.github.com/repos/${repo}/pulls/${prNumber}`, + { + method: 'GET', + headers: { + 'Authorization': `Bearer ${process.env.GITHUB_TOKEN}` + } + } + ).then(data => data.json()); + + const body = (response as undiciResponse).body; + if (!body) { + return ''; + } + const match = fixedIssueRegex.exec(body.toString()); + if (!match) { + return ''; + } + const issueNumber = match[3]; + return `(fixes [#${issueNumber}](https://github.com/firebase/firebase-js-sdk/issues/${issueNumber}))`; +} + +exports.default = changelogFunctions; diff --git a/repo-scripts/changelog-generator/package.json b/repo-scripts/changelog-generator/package.json new file mode 100644 index 00000000000..e04bb6f2ecf --- /dev/null +++ b/repo-scripts/changelog-generator/package.json @@ -0,0 +1,43 @@ +{ + "name": "@firebase/changelog-generator", + "version": "0.1.0", + "private": true, + "type": "commonjs", + "description": "A package for generating changelog", + "author": "Firebase (https://firebase.google.com/)", + "main": "dist/index.js", + "files": [ + "dist" + ], + "scripts": { + "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "build": "tsc", + "build:dev": "tsc -w", + "test": "tsc -p . --noEmit" + }, + "dependencies": { + "@changesets/types": "3.3.0", + "@changesets/get-github-info": "0.5.2", + "@types/node": "20.8.10", + "undici": "5.28.4" + }, + "license": "Apache-2.0", + "devDependencies": { + "typescript": "4.7.4" + }, + "repository": { + "directory": "repo-scripts/changelog-generator", + "type": "git", + "url": "git+https://github.com/firebase/firebase-js-sdk.git" + }, + "bugs": { + "url": "https://github.com/firebase/firebase-js-sdk/issues" + }, + "nyc": { + "extension": [ + ".ts" + ], + "reportDir": "./coverage/node" + } +} diff --git a/repo-scripts/changelog-generator/tsconfig.json b/repo-scripts/changelog-generator/tsconfig.json new file mode 100644 index 00000000000..5c910fcca64 --- /dev/null +++ b/repo-scripts/changelog-generator/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "strict": true, + "outDir": "dist", + "lib": [ + "ESNext" + ], + "module": "CommonJS", + "moduleResolution": "node", + "esModuleInterop": true, + "resolveJsonModule": true, + "target": "es5" + } +} \ No newline at end of file diff --git a/repo-scripts/prune-dts/.run/AllTests.run.xml b/repo-scripts/prune-dts/.run/AllTests.run.xml new file mode 100644 index 00000000000..3eec9c1bcc3 --- /dev/null +++ b/repo-scripts/prune-dts/.run/AllTests.run.xml @@ -0,0 +1,35 @@ + + + + + project + + $PROJECT_DIR$/../../node_modules/mocha + $PROJECT_DIR$ + true + + + + + + bdd + --require ts-node/register/type-check + PATTERN + *.test.ts + + + diff --git a/repo-scripts/prune-dts/extract-public-api.ts b/repo-scripts/prune-dts/extract-public-api.ts new file mode 100644 index 00000000000..c5c202f0734 --- /dev/null +++ b/repo-scripts/prune-dts/extract-public-api.ts @@ -0,0 +1,237 @@ +/** + * @license + * Copyright 2020 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 * as fs from 'fs'; +import * as path from 'path'; + +import { Extractor, ExtractorConfig } from 'api-extractor-me'; +import * as tmp from 'tmp'; + +import { addBlankLines, pruneDts, removeUnusedImports } from './prune-dts'; +import * as yargs from 'yargs'; + +/* eslint-disable no-console */ + +// This script takes the output of the API Extractor, post-processes it using +// the pruned-dts script and then invokes API report to generate a report +// that only includes exported symbols. This is all done in temporary folders, +// all configuration is auto-generated for each run. + +const baseApiExtractorConfigFile: string = path.resolve( + __dirname, + '../../config/api-extractor.json' +); +const reportFolder = path.resolve(__dirname, '../../common/api-review'); +const tmpDir = tmp.dirSync().name; + +function writeTypeScriptConfig(packageRoot: string): void { + const tsConfigJson = { + extends: path.resolve(packageRoot, './tsconfig.json'), + include: [path.resolve(packageRoot, './src')], + compilerOptions: { + downlevelIteration: true // Needed for FirebaseApp + } + }; + fs.writeFileSync( + path.resolve(tmpDir, 'tsconfig.json'), + JSON.stringify(tsConfigJson), + { encoding: 'utf-8' } + ); +} + +function writePackageJson(packageName: string): void { + const packageJson = { + 'name': `@firebase/${packageName}` + }; + const packageJsonPath = path.resolve(tmpDir, 'package.json'); + fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson), { + encoding: 'utf-8' + }); +} + +function loadApiExtractorConfig( + packageName: string, + typescriptDtsPath: string, + rollupDtsPath: string, + untrimmedRollupDtsPath: string, + dtsRollupEnabled: boolean, + apiReportEnabled: boolean +): ExtractorConfig { + const apiExtractorJsonPath = path.resolve(tmpDir, 'api-extractor.json'); + const apiExtractorJson = { + extends: baseApiExtractorConfigFile, + mainEntryPointFilePath: typescriptDtsPath, + 'dtsRollup': { + 'enabled': dtsRollupEnabled, + /** + * Include beta APIs in documentation and typings. api-extractor/documenter + * seems to work this way by default but this intermediate step + * seems to restrict it to public tagged APIs. + */ + betaTrimmedFilePath: rollupDtsPath, + untrimmedFilePath: untrimmedRollupDtsPath + }, + 'tsdocMetadata': { + 'enabled': false + }, + 'apiReport': { + 'enabled': apiReportEnabled, + reportFileName: `${packageName}.api.md`, + reportFolder + }, + 'messages': { + 'extractorMessageReporting': { + 'ae-missing-release-tag': { + 'logLevel': 'none' + }, + 'ae-unresolved-link': { + 'logLevel': 'none' + }, + 'ae-forgotten-export': { + 'logLevel': apiReportEnabled ? 'error' : 'none' + } + }, + 'tsdocMessageReporting': { + 'tsdoc-undefined-tag': { + 'logLevel': 'none' + } + } + } + }; + fs.writeFileSync(apiExtractorJsonPath, JSON.stringify(apiExtractorJson), { + encoding: 'utf-8' + }); + console.log(apiExtractorJsonPath); + return ExtractorConfig.loadFileAndPrepare(apiExtractorJsonPath); +} + +/** + * Generates the Public API from generated DTS files. + * + * @param packageName - The name of the Firebase package (e.g. "database" or + * "firestore-lite") + * @param packageRoot - The root path of the package + * @param typescriptDtsPath - The .d.ts file generated by the TypeScript + * compiler as we transpile our sources + * @param rollupDtsPath - A "bundled" version of our d.ts files that includes + * all public and private types + * @param untrimmedRollupDtsPath - A "bundled" version of our d.ts files that + * includes all public and private types, but also include exports marked as + * `@internal`. This file is used by compat APIs to use internal exports + * @param publicDtsPath - The output file for the customer-facing .d.ts file + * that only includes the public APIs + */ +export async function generateApi( + packageName: string, + packageRoot: string, + typescriptDtsPath: string, + rollupDtsPath: string, + untrimmedRollupDtsPath: string, + publicDtsPath: string +): Promise { + console.log(`Configuring API Extractor for ${packageName}`); + writeTypeScriptConfig(packageRoot); + writePackageJson(packageName); + + let extractorConfig = loadApiExtractorConfig( + packageName, + typescriptDtsPath, + rollupDtsPath, + untrimmedRollupDtsPath, + /* dtsRollupEnabled= */ true, + /* apiReportEnabled= */ false + ); + Extractor.invoke(extractorConfig, { + localBuild: true, + showDiagnostics: true, + showVerboseMessages: true + }); + + console.log('Generated rollup DTS'); + pruneDts(rollupDtsPath, publicDtsPath); + console.log('Pruned DTS file'); + await addBlankLines(publicDtsPath); + console.log('Added blank lines after imports'); + await removeUnusedImports(publicDtsPath); + console.log('Removed unused imports'); + + extractorConfig = loadApiExtractorConfig( + packageName, + publicDtsPath, + rollupDtsPath, + untrimmedRollupDtsPath, + /* dtsRollupEnabled= */ false, + /* apiReportEnabled= */ true + ); + Extractor.invoke(extractorConfig, { localBuild: true }); + console.log(`API report for ${packageName} written to ${reportFolder}`); +} + +const argv = yargs + .options({ + package: { + type: 'string', + desc: + 'The name of the Firebase package (e.g. "database" or ' + + '"firestore-lite")', + require: true + }, + packageRoot: { + type: 'string', + desc: 'The root path of the package', + require: true + }, + typescriptDts: { + type: 'string', + desc: + 'The .d.ts file generated by the TypeScript compiler as we transpile ' + + 'our sources', + require: true + }, + rollupDts: { + type: 'string', + desc: + 'A "bundled" version of our d.ts files that include all public and ' + + 'private types', + require: true + }, + untrimmedRollupDts: { + type: 'string', + desc: + ' A "bundled" version of our d.ts files that includes all public ' + + 'and private types, but also include exports marked as `@internal`. ' + + 'This file is used by compat APIs to use internal exports', + require: true + }, + publicDts: { + type: 'string', + desc: + 'The output file for the customer-facing .d.ts file that only ' + + 'includes the public APIs', + require: true + } + }) + .parseSync(); + +void generateApi( + argv.package, + path.resolve(argv.packageRoot), + path.resolve(argv.typescriptDts), + path.resolve(argv.rollupDts), + path.resolve(argv.untrimmedRollupDts), + path.resolve(argv.publicDts) +); diff --git a/repo-scripts/prune-dts/package.json b/repo-scripts/prune-dts/package.json new file mode 100644 index 00000000000..d8d4ab6391b --- /dev/null +++ b/repo-scripts/prune-dts/package.json @@ -0,0 +1,39 @@ +{ + "name": "firebase-repo-scripts-prune-dts", + "version": "0.1.0", + "private": true, + "engines": { + "node": "^8.13.0 || >=10.10.0" + }, + "description": "A script to prune non-exported types from a d.ts.", + "author": "Firebase (https://firebase.google.com/)", + "scripts": { + "prettier": "prettier --write '**/*.ts'", + "test": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' mocha --require ts-node/register --timeout 5000 *.test.ts" + }, + "license": "Apache-2.0", + "dependencies": { + "eslint": "8.56.0", + "eslint-plugin-unused-imports": "3.2.0", + "prettier": "2.8.7" + }, + "repository": { + "directory": "repo-scripts/prune-dts", + "type": "git", + "url": "git+https://github.com/firebase/firebase-js-sdk.git" + }, + "bugs": { + "url": "https://github.com/firebase/firebase-js-sdk/issues" + }, + "nyc": { + "extension": [ + ".ts" + ], + "reportDir": "./coverage/node" + }, + "devDependencies": { + "@types/eslint": "7.29.0", + "@types/prettier": "2.7.2", + "mocha": "9.2.2" + } +} diff --git a/repo-scripts/prune-dts/prune-dts.test.ts b/repo-scripts/prune-dts/prune-dts.test.ts new file mode 100644 index 00000000000..ba770e43cd9 --- /dev/null +++ b/repo-scripts/prune-dts/prune-dts.test.ts @@ -0,0 +1,99 @@ +/** + * @license + * Copyright 2020 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 * as os from 'os'; +import * as fs from 'fs'; +import * as path from 'path'; +import { format, resolveConfig } from 'prettier'; +import { expect } from 'chai'; +import { describe, it } from 'mocha'; +import { pruneDts } from './prune-dts'; + +const testCasesDir = path.resolve(__dirname, 'tests'); +const tmpDir = os.tmpdir(); + +const testDataFilter = /(.*).input.d.ts/; +const testCaseFilterRe = /.*/; + +async function runScript(inputFile: string): Promise { + const outputFile = path.resolve(tmpDir, 'output.d.ts'); + pruneDts(inputFile, outputFile); + return outputFile; +} + +interface TestCase { + name: string; + inputFileName: string; + outputFileName: string; +} + +function getTestCases(): TestCase[] { + if ( + !fs.existsSync(testCasesDir) || + !fs.lstatSync(testCasesDir).isDirectory() + ) { + throw new Error(`${testCasesDir} folder does not exist`); + } + + return fs + .readdirSync(testCasesDir) + .filter((fileName: string) => testDataFilter.test(fileName)) + .filter((fileName: string) => testCaseFilterRe.test(fileName)) + .map((fileName: string) => { + const testCaseName = fileName.match(testDataFilter)![1]; + + const inputFileName = `${testCaseName}.input.d.ts`; + const outputFileName = `${testCaseName}.output.d.ts`; + + const name = testCaseName.replace(/-/g, ' '); + return { name, inputFileName, outputFileName }; + }); +} + +describe('Prune DTS', () => { + for (const testCase of getTestCases()) { + it(testCase.name, async () => { + const absoluteInputFile = path.resolve( + testCasesDir, + testCase.inputFileName + ); + const absoluteOutputFile = path.resolve( + testCasesDir, + testCase.outputFileName + ); + + const tmpFile = await runScript(absoluteInputFile); + const prettierConfig = await resolveConfig(absoluteInputFile); + + const expectedDtsUnformatted = fs.readFileSync( + absoluteOutputFile, + 'utf-8' + ); + const expectedDts = format(expectedDtsUnformatted, { + filepath: absoluteOutputFile, + ...prettierConfig + }); + const actualDtsUnformatted = fs.readFileSync(tmpFile, 'utf-8'); + const actualDts = format(actualDtsUnformatted, { + filepath: tmpFile, + ...prettierConfig + }); + + expect(actualDts).to.equal(expectedDts); + }); + } +}); diff --git a/repo-scripts/prune-dts/prune-dts.ts b/repo-scripts/prune-dts/prune-dts.ts new file mode 100644 index 00000000000..dfd83a62a91 --- /dev/null +++ b/repo-scripts/prune-dts/prune-dts.ts @@ -0,0 +1,561 @@ +/** + * @license + * Copyright 2020 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 * as yargs from 'yargs'; +import * as ts from 'typescript'; +import * as fs from 'fs'; +import { ESLint } from 'eslint'; + +/** + * Prunes a DTS file based on three main rules: + * - Top level types are only included if they are also exported. + * - Underscore-prefixed members of class and interface types are stripped. + * - Constructors are made private or protected if marked with + * `@hideconstructor`/`@hideconstructor protected`. + * + * This function is meant to operate on DTS files generated by API extractor + * and extracts out the API that is relevant for third-party SDK consumers. + * + * @param inputLocation The file path to the .d.ts produced by API explorer. + * @param outputLocation The output location for the pruned .d.ts file. + */ +export function pruneDts(inputLocation: string, outputLocation: string): void { + const compilerOptions = {}; + const host = ts.createCompilerHost(compilerOptions); + const program = ts.createProgram([inputLocation], compilerOptions, host); + const printer: ts.Printer = ts.createPrinter(); + const sourceFile = program.getSourceFile(inputLocation)!; + + const result: ts.TransformationResult = + ts.transform(sourceFile, [ + dropPrivateApiTransformer.bind(null, program, host) + ]); + const transformedSourceFile: ts.SourceFile = result.transformed[0]; + let content = printer.printFile(transformedSourceFile); + + fs.writeFileSync(outputLocation, content); +} + +export async function addBlankLines(outputLocation: string): Promise { + const eslint = new ESLint({ + fix: true, + overrideConfig: { + parserOptions: { + ecmaVersion: 2017, + sourceType: 'module', + tsconfigRootDir: __dirname, + project: ['./tsconfig.eslint.json'] + }, + env: { + es6: true + }, + plugins: ['@typescript-eslint'], + parser: '@typescript-eslint/parser', + rules: { + 'unused-imports/no-unused-imports-ts': ['off'], + // add blank lines after imports. Otherwise removeUnusedImports() will remove the comment + // of the first item after the import block + 'padding-line-between-statements': [ + 'error', + { 'blankLine': 'always', 'prev': 'import', 'next': '*' } + ] + } + } + }); + const results = await eslint.lintFiles(outputLocation); + await ESLint.outputFixes(results); +} + +export async function removeUnusedImports( + outputLocation: string +): Promise { + const eslint = new ESLint({ + fix: true, + overrideConfig: { + parserOptions: { + ecmaVersion: 2017, + sourceType: 'module', + tsconfigRootDir: __dirname, + project: ['./tsconfig.eslint.json'] + }, + env: { + es6: true + }, + plugins: ['unused-imports', '@typescript-eslint'], + parser: '@typescript-eslint/parser', + rules: { + 'unused-imports/no-unused-imports-ts': ['error'] + } + } + }); + const results = await eslint.lintFiles(outputLocation); + await ESLint.outputFixes(results); +} + +/** Determines whether the provided identifier should be hidden. */ +function hasPrivatePrefix(name: ts.Identifier): boolean { + // Identifiers that are prefixed with an underscore are not included in + // the public API. + return !!name.escapedText?.toString().startsWith('_'); +} + +/** Returns whether type identified by `name` is exported. */ +function isExported( + typeChecker: ts.TypeChecker, + sourceFile: ts.SourceFile, + name: ts.Identifier +): boolean { + const declarations = + typeChecker.getSymbolAtLocation(name)?.declarations ?? []; + + // Check is this is a public symbol (e.g. part of the DOM library) + const isTypeScriptType = declarations.find( + d => d.getSourceFile().fileName.indexOf('typescript/lib') != -1 + ); + const isImported = declarations.find(d => ts.isImportSpecifier(d)); + if (isTypeScriptType || isImported) { + return true; + } + + // Check is this is part of the exported symbols of the SDK module + const allExportedSymbols = typeChecker.getExportsOfModule( + typeChecker.getSymbolAtLocation(sourceFile)! + ); + return !!allExportedSymbols.find(s => s.name === name.escapedText); +} + +/** + * Replaces an existing constructor implementation if the constructor is marked + * with the JSDod tag `@hideconstructor`. The replaced constructor can either + * have `private` visibility` or `protected`. To generate a protected + * constructor, specify `@hideconstructor protected`. + * + * Returns either the modified constructor or the existing constructor if no + * modification was needed. + */ +function maybeHideConstructor( + node: ts.ConstructorDeclaration +): ts.ConstructorDeclaration { + const hideConstructorTag = ts + .getJSDocTags(node) + ?.find(t => t.tagName.escapedText === 'hideconstructor'); + + if (hideConstructorTag) { + const modifier = ts.createModifier( + hideConstructorTag.comment === 'protected' + ? ts.SyntaxKind.ProtectedKeyword + : ts.SyntaxKind.PrivateKeyword + ); + return ts.createConstructor( + node.decorators, + [modifier], + /*parameters=*/ [], + /* body= */ undefined + ); + } else { + return node; + } +} + +/** + * Examines `extends` and `implements` clauses and removes or replaces them if + * they refer to a non-exported type. When an export is removed, all members + * from the removed class are merged into the provided class or interface + * declaration. + * + * @example + * Input: + * class Foo { + * foo: string; + * } + * export class Bar extends Foo {} + * + * Output: + * export class Bar { + * foo: string; + * } + */ +function prunePrivateImports< + T extends ts.InterfaceDeclaration | ts.ClassDeclaration +>( + factory: ts.NodeFactory, + program: ts.Program, + host: ts.CompilerHost, + sourceFile: ts.SourceFile, + node: T +): T { + const typeChecker = program.getTypeChecker(); + + // The list of heritage clauses after all private symbols are removed. + const prunedHeritageClauses: ts.HeritageClause[] = []; + // Additional members that are copied from the private symbols into the public + // symbols + const additionalMembers: ts.Node[] = []; + + for (const heritageClause of node.heritageClauses || []) { + const exportedTypes: ts.ExpressionWithTypeArguments[] = []; + for (const type of heritageClause.types) { + if ( + ts.isIdentifier(type.expression) && + isExported(typeChecker, sourceFile, type.expression) + ) { + exportedTypes.push(type); + } else { + // Hide the type we are inheriting from and merge its declarations + // into the current class. + // TODO: We really only need to do this when the type that is extended + // is a class. We should skip this for interfaces. + const privateType = typeChecker.getTypeAtLocation(type); + additionalMembers.push( + ...convertPropertiesForEnclosingClass( + program, + host, + sourceFile, + privateType.getProperties(), + node + ) + ); + } + } + + if (exportedTypes.length > 0) { + prunedHeritageClauses.push( + factory.updateHeritageClause(heritageClause, exportedTypes) + ); + } + } + + if (ts.isClassDeclaration(node)) { + return factory.updateClassDeclaration( + node, + node.decorators, + node.modifiers, + node.name, + node.typeParameters, + prunedHeritageClauses, + [ + ...(node.members as ts.NodeArray), + ...(additionalMembers as ts.ClassElement[]) + ] + ) as T; + } else if (ts.isInterfaceDeclaration(node)) { + return factory.updateInterfaceDeclaration( + node, + node.decorators, + node.modifiers, + node.name, + node.typeParameters, + prunedHeritageClauses, + [ + ...(node.members as ts.NodeArray), + ...(additionalMembers as ts.TypeElement[]) + ] + ) as T; + } else { + throw new Error('Only classes or interfaces are supported'); + } +} + +/** + * Iterates the provided symbols and returns named declarations for these + * symbols if they are missing from `currentClass`. This allows us to merge + * class hierarchies for classes whose inherited types are not part of the + * public API. + * + * This method relies on a private API in TypeScript's `codefix` package. + */ +function convertPropertiesForEnclosingClass( + program: ts.Program, + host: ts.CompilerHost, + sourceFile: ts.SourceFile, + parentClassSymbols: ts.Symbol[], + currentClass: ts.ClassDeclaration | ts.InterfaceDeclaration +): ts.Node[] { + const newMembers: ts.Node[] = []; + // The `codefix` package is not public but it does exactly what we want. It's + // the same package that is used by VSCode to fill in missing members, which + // is what we are using it for in this script. `codefix` handles missing + // properties, methods and correctly deduces generics. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (ts as any).codefix.createMissingMemberNodes( + currentClass, + parentClassSymbols, + sourceFile, + { program, host }, + /* userPreferences= */ {}, + /* importAdder= */ undefined, + (missingMember: ts.ClassElement) => { + const originalSymbol = parentClassSymbols.find( + symbol => + symbol.escapedName == + (missingMember.name as ts.Identifier).escapedText + ); + const jsDocComment = originalSymbol + ? extractJSDocComment(originalSymbol, newMembers) + : undefined; + if (jsDocComment) { + newMembers.push(jsDocComment, missingMember); + } else { + newMembers.push(missingMember); + } + } + ); + return newMembers; +} + +/** Extracts the JSDoc comment from `symbol`. */ +function extractJSDocComment( + symbol: ts.Symbol, + alreadyAddedMembers: ts.Node[] +): ts.Node | null { + const overloadCount = alreadyAddedMembers.filter( + node => + ts.isClassElement(node) && + (node.name as ts.Identifier).escapedText == symbol.name + ).length; + + // Extract the comment from the overload that we are currently processing. + let targetIndex = 0; + const comments = symbol.getDocumentationComment(undefined).filter(symbol => { + // Overload comments are separated by line breaks. + if (symbol.kind == 'lineBreak') { + ++targetIndex; + return false; + } else { + return overloadCount == targetIndex; + } + }); + + if (comments.length > 0 && symbol.declarations) { + const jsDocTags = ts.getJSDocTags(symbol.declarations[overloadCount]); + const maybeNewline = jsDocTags?.length > 0 ? '\n' : ''; + const joinedComments = comments + .map(comment => { + if (comment.kind === 'linkText') { + return comment.text.trim(); + } + return comment.text; + }) + .join(''); + const formattedComments = joinedComments + .replace('*', '\n') + .replace(' \n', '\n') + .replace('\n ', '\n'); + return ts.factory.createJSDocComment( + formattedComments + maybeNewline, + jsDocTags + ); + } + return null; +} + +/** + * Replaces input types of public APIs that consume non-exported types, which + * allows us to exclude private types from the pruned definitions. Returns the + * the name of the exported API or undefined if no type is found. + * + * @example + * Input: + * class PrivateFoo {} + * export class PublicFoo extends PrivateFoo {} + * export function doFoo(foo: PrivateFoo); + * + * Output: + * export class PublicFoo {} + * export function doFoo(foo: PublicFoo); + */ +function extractExportedSymbol( + typeChecker: ts.TypeChecker, + sourceFile: ts.SourceFile, + typeName: ts.Node +): ts.Symbol | undefined { + if (!ts.isIdentifier(typeName)) { + return undefined; + } + + if (isExported(typeChecker, sourceFile, typeName)) { + // Don't replace the type reference if the type is already part of the + // public API. + return undefined; + } + + const localSymbolName = typeName.escapedText; + const allExportedSymbols = typeChecker.getExportsOfModule( + typeChecker.getSymbolAtLocation(sourceFile)! + ); + + // Examine all exported types and check if they extend or implement the + // provided local type. If so, we can use the exported type in lieu of the + // private type. + + // Short circuit if the local types is already part of the public types. + for (const symbol of allExportedSymbols) { + if (symbol.name === localSymbolName) { + return symbol; + } + } + + // See if there is an exported symbol that extends this private symbol. + // In this case, we can safely use the public symbol instead. + for (const symbol of allExportedSymbols) { + if (symbol.declarations) { + for (const declaration of symbol.declarations) { + if ( + ts.isClassDeclaration(declaration) || + ts.isInterfaceDeclaration(declaration) + ) { + for (const heritageClause of declaration.heritageClauses || []) { + for (const type of heritageClause.types || []) { + if (ts.isIdentifier(type.expression)) { + const subclassName = type.expression.escapedText; + if (subclassName === localSymbolName) { + // TODO: We may need to change this to return a Union type if + // more than one public type corresponds to the private type. + return symbol; + } + } + } + } + } + } + } + } + + // If no symbol was found that extends the private symbol, check the reverse. + // We might find an exported symbol in the inheritance chain of the local + // symbol. Note that this is not always safe as we might replace the local + // symbol with a less restrictive type. + const localSymbol = typeChecker.getSymbolAtLocation(typeName); + if (localSymbol?.declarations) { + for (const declaration of localSymbol.declarations) { + if ( + ts.isClassDeclaration(declaration) || + ts.isInterfaceDeclaration(declaration) + ) { + for (const heritageClause of declaration.heritageClauses || []) { + for (const type of heritageClause.types || []) { + if (ts.isIdentifier(type.expression)) { + if (isExported(typeChecker, sourceFile, type.expression)) { + return typeChecker.getSymbolAtLocation(type.expression); + } + } + } + } + } + } + } + + return undefined; +} + +function dropPrivateApiTransformer( + program: ts.Program, + host: ts.CompilerHost, + context: ts.TransformationContext +): ts.Transformer { + const typeChecker = program.getTypeChecker(); + const { factory } = context; + + return (sourceFile: ts.SourceFile) => { + function visit(node: ts.Node): ts.Node { + if ( + ts.isInterfaceDeclaration(node) || + ts.isClassDeclaration(node) || + ts.isFunctionDeclaration(node) || + ts.isVariableStatement(node) || + ts.isTypeAliasDeclaration(node) || + ts.isModuleDeclaration(node) || + ts.isEnumDeclaration(node) + ) { + // Remove any types that are not exported. + if ( + !node.modifiers?.find(m => m.kind === ts.SyntaxKind.ExportKeyword) + ) { + return factory.createNotEmittedStatement(node); + } + } + + if (ts.isConstructorDeclaration(node)) { + // Replace internal constructors with private constructors. + return maybeHideConstructor(node); + } else if ( + ts.isClassDeclaration(node) || + ts.isInterfaceDeclaration(node) + ) { + // Remove any imports that reference internal APIs, while retaining + // their public members. + return prunePrivateImports(factory, program, host, sourceFile, node); + } else if ( + ts.isPropertyDeclaration(node) || + ts.isMethodDeclaration(node) || + ts.isGetAccessor(node) + ) { + // Remove any class and interface members that are prefixed with + // underscores. + if (hasPrivatePrefix(node.name as ts.Identifier)) { + return factory.createNotEmittedStatement(node); + } + } else if (ts.isTypeReferenceNode(node)) { + // For public types that refer internal types, find a public type that + // we can refer to instead. + const publicName = extractExportedSymbol( + typeChecker, + sourceFile, + node.typeName + ); + return publicName + ? factory.updateTypeReferenceNode( + node, + factory.createIdentifier(publicName.name), + node.typeArguments + ) + : node; + } + + return node; + } + + function visitNodeAndChildren(node: T): T { + return ts.visitEachChild( + visit(node), + childNode => visitNodeAndChildren(childNode), + context + ) as T; + } + return visitNodeAndChildren(sourceFile); + }; +} + +const argv = yargs + .options({ + input: { + type: 'string', + desc: 'The location of the index.ts file' + }, + output: { + type: 'string', + desc: 'The location for the index.d.ts file' + } + }) + .parseSync(); + +if (argv.input && argv.output) { + console.log('Removing private exports...'); + pruneDts(argv.input, argv.output); + console.log('Removing unused imports...'); + removeUnusedImports(argv.output).then(() => console.log('Done.')); +} diff --git a/repo-scripts/prune-dts/tests/dom.input.d.ts b/repo-scripts/prune-dts/tests/dom.input.d.ts new file mode 100644 index 00000000000..80cea14308e --- /dev/null +++ b/repo-scripts/prune-dts/tests/dom.input.d.ts @@ -0,0 +1,29 @@ +/** + * @license + * Copyright 2021 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. + */ +export class Foo implements PromiseLike { + then( + onfulfilled?: + | ((value: any) => PromiseLike | TResult1) + | undefined + | null, + onrejected?: + | ((reason: any) => PromiseLike | TResult2) + | undefined + | null + ): PromiseLike; +} +export function bar(foo: PromiseLike): PromiseLike; diff --git a/repo-scripts/prune-dts/tests/dom.output.d.ts b/repo-scripts/prune-dts/tests/dom.output.d.ts new file mode 100644 index 00000000000..80cea14308e --- /dev/null +++ b/repo-scripts/prune-dts/tests/dom.output.d.ts @@ -0,0 +1,29 @@ +/** + * @license + * Copyright 2021 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. + */ +export class Foo implements PromiseLike { + then( + onfulfilled?: + | ((value: any) => PromiseLike | TResult1) + | undefined + | null, + onrejected?: + | ((reason: any) => PromiseLike | TResult2) + | undefined + | null + ): PromiseLike; +} +export function bar(foo: PromiseLike): PromiseLike; diff --git a/repo-scripts/prune-dts/tests/error.input.d.ts b/repo-scripts/prune-dts/tests/error.input.d.ts new file mode 100644 index 00000000000..9a1d8abb6e1 --- /dev/null +++ b/repo-scripts/prune-dts/tests/error.input.d.ts @@ -0,0 +1,23 @@ +/** + * @license + * Copyright 2021 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 { FirebaseError } from '@firebase/util'; + +export declare interface StorageError extends FirebaseError { + serverResponse: string | null; +} + +export {}; diff --git a/repo-scripts/prune-dts/tests/error.output.d.ts b/repo-scripts/prune-dts/tests/error.output.d.ts new file mode 100644 index 00000000000..b45c06687f1 --- /dev/null +++ b/repo-scripts/prune-dts/tests/error.output.d.ts @@ -0,0 +1,21 @@ +/** + * @license + * Copyright 2021 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 { FirebaseError } from '@firebase/util'; +export declare interface StorageError extends FirebaseError { + serverResponse: string | null; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/firestore.input.d.ts b/repo-scripts/prune-dts/tests/firestore.input.d.ts new file mode 100644 index 00000000000..aa71ccffaf0 --- /dev/null +++ b/repo-scripts/prune-dts/tests/firestore.input.d.ts @@ -0,0 +1,5779 @@ +/** + * @license + * Copyright 2021 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 { DocumentData as DocumentData_2 } from '@firebase/firestore-types'; +import { FirebaseApp } from '@firebase/app'; +import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; +import { _FirebaseService } from '@firebase/app'; +import { LogLevelString as LogLevel } from '@firebase/logger'; +import { Provider } from '@firebase/component'; +import { SetOptions as SetOptions_2 } from '@firebase/firestore-types'; + +/** + * Converts Firestore's internal types to the JavaScript types that we expose + * to the user. + */ +declare abstract class AbstractUserDataWriter { + convertValue( + value: Value, + serverTimestampBehavior?: ServerTimestampBehavior + ): unknown; + private convertObject; + private convertGeoPoint; + private convertArray; + private convertServerTimestamp; + private convertTimestamp; + protected convertDocumentKey( + name: string, + expectedDatabaseId: DatabaseId + ): DocumentKey; + protected abstract convertReference(name: string): unknown; + protected abstract convertBytes(bytes: ByteString): unknown; +} + +/** + * Describes a map whose keys are active target ids. We do not care about the type of the + * values. + */ +declare type ActiveTargets = SortedMap; + +/** + * Add a new document to specified `CollectionReference` with the given data, + * assigning it a document ID automatically. + * + * @param reference - A reference to the collection to add this document to. + * @param data - An Object containing the data for the new document. + * @returns A Promise resolved with a `DocumentReference` pointing to the + * newly created document after it has been written to the backend (Note that it + * won't resolve while you're offline). + */ +export declare function addDoc( + reference: CollectionReference, + data: T +): Promise>; + +declare interface ApiClientObjectMap { + [k: string]: T; +} + +/** + * Returns a special value that can be used with {@link (setDoc:1)} or {@link + * updateDoc} that tells the server to remove the given elements from any + * array value that already exists on the server. All instances of each element + * specified will be removed from the array. If the field being modified is not + * already an array it will be overwritten with an empty array. + * + * @param elements - The elements to remove from the array. + * @returns The `FieldValue` sentinel for use in a call to `setDoc()` or + * `updateDoc()` + */ +export declare function arrayRemove(...elements: unknown[]): FieldValue; + +/** + * Returns a special value that can be used with {@link setDoc} or {@link + * updateDoc} that tells the server to union the given elements with any array + * value that already exists on the server. Each specified element that doesn't + * already exist in the array will be added to the end. If the field being + * modified is not already an array it will be overwritten with an array + * containing exactly the specified elements. + * + * @param elements - The elements to union into the array. + * @returns The `FieldValue` sentinel for use in a call to `setDoc()` or + * `updateDoc()`. + */ +export declare function arrayUnion(...elements: unknown[]): FieldValue; + +declare interface AsyncQueue { + readonly isShuttingDown: boolean; + /** + * Adds a new operation to the queue without waiting for it to complete (i.e. + * we ignore the Promise result). + */ + enqueueAndForget(op: () => Promise): void; + /** + * Regardless if the queue has initialized shutdown, adds a new operation to the + * queue without waiting for it to complete (i.e. we ignore the Promise result). + */ + enqueueAndForgetEvenWhileRestricted( + op: () => Promise + ): void; + /** + * Initialize the shutdown of this queue. Once this method is called, the + * only possible way to request running an operation is through + * `enqueueEvenWhileRestricted()`. + */ + enterRestrictedMode(): void; + /** + * Adds a new operation to the queue. Returns a promise that will be resolved + * when the promise returned by the new operation is (with its value). + */ + enqueue(op: () => Promise): Promise; + /** + * Enqueue a retryable operation. + * + * A retryable operation is rescheduled with backoff if it fails with a + * IndexedDbTransactionError (the error type used by SimpleDb). All + * retryable operations are executed in order and only run if all prior + * operations were retried successfully. + */ + enqueueRetryable(op: () => Promise): void; + /** + * Schedules an operation to be queued on the AsyncQueue once the specified + * `delayMs` has elapsed. The returned DelayedOperation can be used to cancel + * or fast-forward the operation prior to its running. + */ + enqueueAfterDelay( + timerId: TimerId, + delayMs: number, + op: () => Promise + ): DelayedOperation; + /** + * Verifies there's an operation currently in-progress on the AsyncQueue. + * Unfortunately we can't verify that the running code is in the promise chain + * of that operation, so this isn't a foolproof check, but it should be enough + * to catch some bugs. + */ + verifyOperationInProgress(): void; +} + +/** + * Path represents an ordered sequence of string segments. + */ +declare abstract class BasePath> { + private segments; + private offset; + private len; + constructor(segments: string[], offset?: number, length?: number); + /** + * Abstract constructor method to construct an instance of B with the given + * parameters. + */ + protected abstract construct( + segments: string[], + offset?: number, + length?: number + ): B; + /** + * Returns a String representation. + * + * Implementing classes are required to provide deterministic implementations as + * the String representation is used to obtain canonical Query IDs. + */ + abstract toString(): string; + get length(): number; + isEqual(other: B): boolean; + child(nameOrPath: string | B): B; + /** The index of one past the last segment of the path. */ + private limit; + popFirst(size?: number): B; + popLast(): B; + firstSegment(): string; + lastSegment(): string; + get(index: number): string; + isEmpty(): boolean; + isPrefixOf(other: this): boolean; + isImmediateParentOf(potentialChild: this): boolean; + forEach(fn: (segment: string) => void): void; + toArray(): string[]; + static comparator>( + p1: BasePath, + p2: BasePath + ): number; +} + +/** + * @license + * Copyright 2017 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. + */ +/** + * BatchID is a locally assigned ID for a batch of mutations that have been + * applied. + */ +declare type BatchId = number; + +/** + * Represents a bound of a query. + * + * The bound is specified with the given components representing a position and + * whether it's just before or just after the position (relative to whatever the + * query order is). + * + * The position represents a logical index position for a query. It's a prefix + * of values for the (potentially implicit) order by clauses of a query. + * + * Bound provides a function to determine whether a document comes before or + * after a bound. This is influenced by whether the position is just before or + * just after the provided values. + */ +declare class Bound { + readonly position: Value[]; + readonly before: boolean; + constructor(position: Value[], before: boolean); +} + +/** + * Provides interfaces to save and read Firestore bundles. + */ +declare interface BundleCache { + /** + * Gets the saved `BundleMetadata` for a given `bundleId`, returns undefined + * if no bundle metadata is found under the given id. + */ + getBundleMetadata( + transaction: PersistenceTransaction, + bundleId: string + ): PersistencePromise; + /** + * Saves a `BundleMetadata` from a bundle into local storage, using its id as + * the persistent key. + */ + saveBundleMetadata( + transaction: PersistenceTransaction, + metadata: BundleMetadata_2 + ): PersistencePromise; + /** + * Gets a saved `NamedQuery` for the given query name. Returns undefined if + * no queries are found under the given name. + */ + getNamedQuery( + transaction: PersistenceTransaction, + queryName: string + ): PersistencePromise; + /** + * Saves a `NamedQuery` from a bundle, using its name as the persistent key. + */ + saveNamedQuery( + transaction: PersistenceTransaction, + query: NamedQuery_2 + ): PersistencePromise; +} + +/** Properties of a BundledQuery. */ +declare interface BundledQuery { + /** BundledQuery parent */ + parent?: string | null; + /** BundledQuery structuredQuery */ + structuredQuery?: StructuredQuery | null; + /** BundledQuery limitType */ + limitType?: LimitType_2 | null; +} + +/** + * Represents a Firestore bundle saved by the SDK in its local storage. + */ +declare interface BundleMetadata { + /** + * Id of the bundle. It is used together with `createTime` to determine if a + * bundle has been loaded by the SDK. + */ + readonly id: string; + /** Schema version of the bundle. */ + readonly version: number; + /** + * Set to the snapshot version of the bundle if created by the Server SDKs. + * Otherwise set to SnapshotVersion.MIN. + */ + readonly createTime: SnapshotVersion; +} + +/** Properties of a BundleMetadata. */ +declare interface BundleMetadata_2 { + /** BundleMetadata id */ + id?: string | null; + /** BundleMetadata createTime */ + createTime?: Timestamp_2 | null; + /** BundleMetadata version */ + version?: number | null; + /** BundleMetadata totalDocuments */ + totalDocuments?: number | null; + /** BundleMetadata totalBytes */ + totalBytes?: number | null; +} + +/** + * An immutable object representing an array of bytes. + */ +export declare class Bytes { + _byteString: ByteString; + /** @hideconstructor */ + constructor(byteString: ByteString); + /** + * Creates a new `Bytes` object from the given Base64 string, converting it to + * bytes. + * + * @param base64 - The Base64 string used to create the `Bytes` object. + */ + static fromBase64String(base64: string): Bytes; + /** + * Creates a new `Bytes` object from the given Uint8Array. + * + * @param array - The Uint8Array used to create the `Bytes` object. + */ + static fromUint8Array(array: Uint8Array): Bytes; + /** + * Returns the underlying bytes as a Base64-encoded string. + * + * @returns The Base64-encoded string created from the `Bytes` object. + */ + toBase64(): string; + /** + * Returns the underlying bytes in a new `Uint8Array`. + * + * @returns The Uint8Array created from the `Bytes` object. + */ + toUint8Array(): Uint8Array; + /** + * Returns a string representation of the `Bytes` object. + * + * @returns A string representation of the `Bytes` object. + */ + toString(): string; + /** + * Returns true if this `Bytes` object is equal to the provided one. + * + * @param other - The `Bytes` object to compare against. + * @returns true if this `Bytes` object is equal to the provided one. + */ + isEqual(other: Bytes): boolean; +} + +/** + * @license + * Copyright 2020 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. + */ +/** + * Immutable class that represents a "proto" byte string. + * + * Proto byte strings can either be Base64-encoded strings or Uint8Arrays when + * sent on the wire. This class abstracts away this differentiation by holding + * the proto byte string in a common class that must be converted into a string + * before being sent as a proto. + */ +declare class ByteString { + private readonly binaryString; + static readonly EMPTY_BYTE_STRING: ByteString; + private constructor(); + static fromBase64String(base64: string): ByteString; + static fromUint8Array(array: Uint8Array): ByteString; + toBase64(): string; + toUint8Array(): Uint8Array; + approximateByteSize(): number; + compareTo(other: ByteString): number; + isEqual(other: ByteString): boolean; +} + +/** + * Constant used to indicate the LRU garbage collection should be disabled. + * Set this value as the `cacheSizeBytes` on the settings passed to the + * `Firestore` instance. + */ +export declare const CACHE_SIZE_UNLIMITED = -1; + +declare const enum ChangeType { + Added = 0, + Removed = 1, + Modified = 2, + Metadata = 3 +} + +/** + * Clears the persistent storage. This includes pending writes and cached + * documents. + * + * Must be called while the `Firestore` instance is not started (after the app is + * terminated or when the app is first initialized). On startup, this function + * must be called before other functions (other than {@link + * initializeFirestore} or {@link getFirestore})). If the `Firestore` + * instance is still running, the promise will be rejected with the error code + * of `failed-precondition`. + * + * Note: `clearIndexedDbPersistence()` is primarily intended to help write + * reliable tests that use Cloud Firestore. It uses an efficient mechanism for + * dropping existing data but does not attempt to securely overwrite or + * otherwise make cached data unrecoverable. For applications that are sensitive + * to the disclosure of cached data in between user sessions, we strongly + * recommend not enabling persistence at all. + * + * @param firestore - The `Firestore` instance to clear persistence for. + * @returns A promise that is resolved when the persistent storage is + * cleared. Otherwise, the promise is rejected with an error. + */ +export declare function clearIndexedDbPersistence( + firestore: FirebaseFirestore +): Promise; + +/** + * A randomly-generated key assigned to each Firestore instance at startup. + */ +declare type ClientId = string; + +/** + * Gets a `CollectionReference` instance that refers to the collection at + * the specified absolute path. + * + * @param firestore - A reference to the root Firestore instance. + * @param path - A slash-separated path to a collection. + * @param pathSegments - Additional path segments to apply relative to the first + * argument. + * @throws If the final path has an even number of segments and does not point + * to a collection. + * @returns The `CollectionReference` instance. + */ +export declare function collection( + firestore: FirebaseFirestore_2, + path: string, + ...pathSegments: string[] +): CollectionReference; + +/** + * Gets a `CollectionReference` instance that refers to a subcollection of + * `reference` at the specified relative path. + * + * @param reference - A reference to a collection. + * @param path - A slash-separated path to a collection. + * @param pathSegments - Additional path segments to apply relative to the first + * argument. + * @throws If the final path has an even number of segments and does not point + * to a collection. + * @returns The `CollectionReference` instance. + */ +export declare function collection( + reference: CollectionReference, + path: string, + ...pathSegments: string[] +): CollectionReference; + +/** + * Gets a `CollectionReference` instance that refers to a subcollection of + * `reference` at the specified relative path. + * + * @param reference - A reference to a Firestore document. + * @param path - A slash-separated path to a collection. + * @param pathSegments - Additional path segments that will be applied relative + * to the first argument. + * @throws If the final path has an even number of segments and does not point + * to a collection. + * @returns The `CollectionReference` instance. + */ +export declare function collection( + reference: DocumentReference, + path: string, + ...pathSegments: string[] +): CollectionReference; + +/** + * Creates and returns a new `Query` instance that includes all documents in the + * database that are contained in a collection or subcollection with the + * given `collectionId`. + * + * @param firestore - A reference to the root Firestore instance. + * @param collectionId - Identifies the collections to query over. Every + * collection or subcollection with this ID as the last segment of its path + * will be included. Cannot contain a slash. + * @returns The created `Query`. + */ +export declare function collectionGroup( + firestore: FirebaseFirestore_2, + collectionId: string +): Query; + +/** + * A `CollectionReference` object can be used for adding documents, getting + * document references, and querying for documents (using {@link query}). + */ +export declare class CollectionReference extends Query { + readonly firestore: FirebaseFirestore_2; + readonly _path: ResourcePath; + readonly type = 'collection'; + /** @hideconstructor */ + constructor( + firestore: FirebaseFirestore_2, + converter: FirestoreDataConverter_2 | null, + _path: ResourcePath + ); + /** The collection's identifier. */ + get id(): string; + /** + * A string representing the path of the referenced collection (relative + * to the root of the database). + */ + get path(): string; + /** + * A reference to the containing `DocumentReference` if this is a + * subcollection. If this isn't a subcollection, the reference is null. + */ + get parent(): DocumentReference | null; + /** + * Applies a custom data converter to this CollectionReference, allowing you + * to use your own custom model objects with Firestore. When you call {@link + * addDoc} with the returned `CollectionReference` instance, the provided + * converter will convert between Firestore data and your custom type `U`. + * + * @param converter - Converts objects to and from Firestore. + * @returns A `CollectionReference` that uses the provided converter. + */ + withConverter( + converter: FirestoreDataConverter_2 + ): CollectionReference; +} + +/** + * @license + * Copyright 2017 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. + */ +declare type Comparator = (key1: K, key2: K) => number; + +declare interface ComponentConfiguration { + asyncQueue: AsyncQueue; + databaseInfo: DatabaseInfo; + credentials: CredentialsProvider; + clientId: ClientId; + initialUser: User; + maxConcurrentLimboResolutions: number; +} + +declare type CompositeFilterOp = 'OPERATOR_UNSPECIFIED' | 'AND'; + +/** + * A Listener for credential change events. The listener should fetch a new + * token and may need to invalidate other state if the current user has also + * changed. + */ +declare type CredentialChangeListener = (user: User) => void; + +/** + * Provides methods for getting the uid and token for the current user and + * listening for changes. + */ +declare interface CredentialsProvider { + /** Requests a token for the current user. */ + getToken(): Promise; + /** + * Marks the last retrieved token as invalid, making the next GetToken request + * force-refresh the token. + */ + invalidateToken(): void; + /** + * Specifies a listener to be notified of credential changes + * (sign-in / sign-out, token changes). It is immediately called once with the + * initial user. + */ + setChangeListener(changeListener: CredentialChangeListener): void; + /** Removes the previously-set change listener. */ + removeChangeListener(): void; +} + +/** Settings for private credentials */ +declare type CredentialsSettings = + | FirstPartyCredentialsSettings + | ProviderCredentialsSettings; + +/** Represents the database ID a Firestore client is associated with. */ +declare class DatabaseId { + readonly projectId: string; + readonly database: string; + constructor(projectId: string, database?: string); + get isDefaultDatabase(): boolean; + isEqual(other: {}): boolean; +} + +/** + * @license + * Copyright 2017 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. + */ +declare class DatabaseInfo { + readonly databaseId: DatabaseId; + readonly persistenceKey: string; + readonly host: string; + readonly ssl: boolean; + readonly forceLongPolling: boolean; + readonly autoDetectLongPolling: boolean; + /** + * Constructs a DatabaseInfo using the provided host, databaseId and + * persistenceKey. + * + * @param databaseId - The database to use. + * @param persistenceKey - A unique identifier for this Firestore's local + * storage (used in conjunction with the databaseId). + * @param host - The Firestore backend host to connect to. + * @param ssl - Whether to use SSL when connecting. + * @param forceLongPolling - Whether to use the forceLongPolling option + * when using WebChannel as the network transport. + * @param autoDetectLongPolling - Whether to use the detectBufferingProxy + * option when using WebChannel as the network transport. + */ + constructor( + databaseId: DatabaseId, + persistenceKey: string, + host: string, + ssl: boolean, + forceLongPolling: boolean, + autoDetectLongPolling: boolean + ); +} + +/** + * Datastore and its related methods are a wrapper around the external Google + * Cloud Datastore grpc API, which provides an interface that is more convenient + * for the rest of the client SDK architecture to consume. + */ +declare abstract class Datastore { + abstract terminate(): void; +} + +/** + * Represents an operation scheduled to be run in the future on an AsyncQueue. + * + * It is created via DelayedOperation.createAndSchedule(). + * + * Supports cancellation (via cancel()) and early execution (via skipDelay()). + * + * Note: We implement `PromiseLike` instead of `Promise`, as the `Promise` type + * in newer versions of TypeScript defines `finally`, which is not available in + * IE. + */ +declare class DelayedOperation implements PromiseLike { + private readonly asyncQueue; + readonly timerId: TimerId; + readonly targetTimeMs: number; + private readonly op; + private readonly removalCallback; + private timerHandle; + private readonly deferred; + private constructor(); + /** + * Creates and returns a DelayedOperation that has been scheduled to be + * executed on the provided asyncQueue after the provided delayMs. + * + * @param asyncQueue - The queue to schedule the operation on. + * @param id - A Timer ID identifying the type of operation this is. + * @param delayMs - The delay (ms) before the operation should be scheduled. + * @param op - The operation to run. + * @param removalCallback - A callback to be called synchronously once the + * operation is executed or canceled, notifying the AsyncQueue to remove it + * from its delayedOperations list. + * PORTING NOTE: This exists to prevent making removeDelayedOperation() and + * the DelayedOperation class public. + */ + static createAndSchedule( + asyncQueue: AsyncQueue, + timerId: TimerId, + delayMs: number, + op: () => Promise, + removalCallback: (op: DelayedOperation) => void + ): DelayedOperation; + /** + * Starts the timer. This is called immediately after construction by + * createAndSchedule(). + */ + private start; + /** + * Queues the operation to run immediately (if it hasn't already been run or + * canceled). + */ + skipDelay(): void; + /** + * Cancels the operation if it hasn't already been executed or canceled. The + * promise will be rejected. + * + * As long as the operation has not yet been run, calling cancel() provides a + * guarantee that the operation will not be run. + */ + cancel(reason?: string): void; + then: ( + onfulfilled?: + | ((value: T) => TResult1 | PromiseLike) + | null + | undefined, + onrejected?: + | ((reason: any) => TResult2 | PromiseLike) + | null + | undefined + ) => Promise; + private handleDelayElapsed; + private clearTimeout; +} + +/** + * Deletes the document referred to by the specified `DocumentReference`. + * + * @param reference - A reference to the document to delete. + * @returns A Promise resolved once the document has been successfully + * deleted from the backend (note that it won't resolve while you're offline). + */ +export declare function deleteDoc( + reference: DocumentReference +): Promise; + +/** + * Returns a sentinel for use with {@link updateDoc} or + * {@link setDoc} with `{merge: true}` to mark a field for deletion. + */ +export declare function deleteField(): FieldValue; + +/** + * The direction of sorting in an order by. + */ +declare const enum Direction { + ASCENDING = 'asc', + DESCENDING = 'desc' +} + +/** + * Disables network usage for this instance. It can be re-enabled via {@link + * enableNetwork}. While the network is disabled, any snapshot listeners, + * `getDoc()` or `getDocs()` calls will return results from cache, and any write + * operations will be queued until the network is restored. + * + * @returns A promise that is resolved once the network has been disabled. + */ +export declare function disableNetwork( + firestore: FirebaseFirestore +): Promise; + +/** + * Gets a `DocumentReference` instance that refers to the document at the + * specified absolute path. + * + * @param firestore - A reference to the root Firestore instance. + * @param path - A slash-separated path to a document. + * @param pathSegments - Additional path segments that will be applied relative + * to the first argument. + * @throws If the final path has an odd number of segments and does not point to + * a document. + * @returns The `DocumentReference` instance. + */ +export declare function doc( + firestore: FirebaseFirestore_2, + path: string, + ...pathSegments: string[] +): DocumentReference; + +/** + * Gets a `DocumentReference` instance that refers to a document within + * `reference` at the specified relative path. If no path is specified, an + * automatically-generated unique ID will be used for the returned + * `DocumentReference`. + * + * @param reference - A reference to a collection. + * @param path - A slash-separated path to a document. Has to be omitted to use + * auto-generated IDs. + * @param pathSegments - Additional path segments that will be applied relative + * to the first argument. + * @throws If the final path has an odd number of segments and does not point to + * a document. + * @returns The `DocumentReference` instance. + */ +export declare function doc( + reference: CollectionReference, + path?: string, + ...pathSegments: string[] +): DocumentReference; + +/** + * Gets a `DocumentReference` instance that refers to a document within + * `reference` at the specified relative path. + * + * @param reference - A reference to a Firestore document. + * @param path - A slash-separated path to a document. + * @param pathSegments - Additional path segments that will be applied relative + * to the first argument. + * @throws If the final path has an odd number of segments and does not point to + * a document. + * @returns The `DocumentReference` instance. + */ +export declare function doc( + reference: DocumentReference, + path: string, + ...pathSegments: string[] +): DocumentReference; + +/** + * Represents a document in Firestore with a key, version, data and whether the + * data has local mutations applied to it. + */ +declare class Document_2 extends MaybeDocument { + private readonly objectValue; + readonly hasLocalMutations: boolean; + readonly hasCommittedMutations: boolean; + constructor( + key: DocumentKey, + version: SnapshotVersion, + objectValue: ObjectValue, + options: DocumentOptions + ); + field(path: FieldPath_2): Value | null; + data(): ObjectValue; + toProto(): { + mapValue: MapValue; + }; + isEqual(other: MaybeDocument | null | undefined): boolean; + toString(): string; + get hasPendingWrites(): boolean; +} + +/** + * A `DocumentChange` represents a change to the documents matching a query. + * It contains the document affected and the type of change that occurred. + */ +export declare interface DocumentChange { + /** The type of change ('added', 'modified', or 'removed'). */ + readonly type: DocumentChangeType; + /** The document affected by this change. */ + readonly doc: QueryDocumentSnapshot; + /** + * The index of the changed document in the result set immediately prior to + * this `DocumentChange` (i.e. supposing that all prior `DocumentChange` objects + * have been applied). Is `-1` for 'added' events. + */ + readonly oldIndex: number; + /** + * The index of the changed document in the result set immediately after + * this `DocumentChange` (i.e. supposing that all prior `DocumentChange` + * objects and the current `DocumentChange` object have been applied). + * Is -1 for 'removed' events. + */ + readonly newIndex: number; +} + +/** + * The type of a `DocumentChange` may be 'added', 'removed', or 'modified'. + */ +export declare type DocumentChangeType = 'added' | 'removed' | 'modified'; + +declare type DocumentComparator = ( + doc1: Document_2, + doc2: Document_2 +) => number; + +/** + * Document data (for use with {@link setDoc}) consists of fields mapped to + * values. + */ +export declare interface DocumentData { + [field: string]: any; +} + +/** + * Returns a special sentinel `FieldPath` to refer to the ID of a document. + * It can be used in queries to sort or filter by the document ID. + */ +export declare function documentId(): FieldPath; + +declare class DocumentKey { + readonly path: ResourcePath; + constructor(path: ResourcePath); + static fromPath(path: string): DocumentKey; + static fromName(name: string): DocumentKey; + /** Returns true if the document is in the specified collectionId. */ + hasCollectionId(collectionId: string): boolean; + isEqual(other: DocumentKey | null): boolean; + toString(): string; + static comparator(k1: DocumentKey, k2: DocumentKey): number; + static isDocumentKey(path: ResourcePath): boolean; + /** + * Creates and returns a new document key with the given segments. + * + * @param segments - The segments of the path to the document + * @returns A new instance of DocumentKey + */ + static fromSegments(segments: string[]): DocumentKey; +} + +declare type DocumentKeySet = SortedSet; + +declare type DocumentMap = SortedMap; + +declare interface DocumentOptions { + hasLocalMutations?: boolean; + hasCommittedMutations?: boolean; +} + +/** + * A `DocumentReference` refers to a document location in a Firestore database + * and can be used to write, read, or listen to the location. The document at + * the referenced location may or may not exist. + */ +export declare class DocumentReference { + readonly _converter: FirestoreDataConverter_2 | null; + readonly _key: DocumentKey; + /** The type of this Firestore reference. */ + readonly type = 'document'; + /** + * The {@link FirebaseFirestore} the document is in. + * This is useful for performing transactions, for example. + */ + readonly firestore: FirebaseFirestore_2; + /** @hideconstructor */ + constructor( + firestore: FirebaseFirestore_2, + _converter: FirestoreDataConverter_2 | null, + _key: DocumentKey + ); + get _path(): ResourcePath; + /** + * The document's identifier within its collection. + */ + get id(): string; + /** + * A string representing the path of the referenced document (relative + * to the root of the database). + */ + get path(): string; + /** + * The collection this `DocumentReference` belongs to. + */ + get parent(): CollectionReference; + /** + * Applies a custom data converter to this `DocumentReference`, allowing you + * to use your own custom model objects with Firestore. When you call {@link + * setDoc}, {@link getDoc}, etc. with the returned `DocumentReference` + * instance, the provided converter will convert between Firestore data and + * your custom type `U`. + * + * @param converter - Converts objects to and from Firestore. + * @returns A `DocumentReference` that uses the provided converter. + */ + withConverter( + converter: FirestoreDataConverter_2 + ): DocumentReference; +} + +/** + * DocumentSet is an immutable (copy-on-write) collection that holds documents + * in order specified by the provided comparator. We always add a document key + * comparator on top of what is provided to guarantee document equality based on + * the key. + */ +declare class DocumentSet { + /** + * Returns an empty copy of the existing DocumentSet, using the same + * comparator. + */ + static emptySet(oldSet: DocumentSet): DocumentSet; + private comparator; + private keyedMap; + private sortedSet; + /** The default ordering is by key if the comparator is omitted */ + constructor(comp?: DocumentComparator); + has(key: DocumentKey): boolean; + get(key: DocumentKey): Document_2 | null; + first(): Document_2 | null; + last(): Document_2 | null; + isEmpty(): boolean; + /** + * Returns the index of the provided key in the document set, or -1 if the + * document key is not present in the set; + */ + indexOf(key: DocumentKey): number; + get size(): number; + /** Iterates documents in order defined by "comparator" */ + forEach(cb: (doc: Document_2) => void): void; + /** Inserts or updates a document with the same key */ + add(doc: Document_2): DocumentSet; + /** Deletes a document with a given key */ + delete(key: DocumentKey): DocumentSet; + isEqual(other: DocumentSet | null | undefined): boolean; + toString(): string; + private copy; +} + +/** + * A `DocumentSnapshot` contains data read from a document in your Firestore + * database. The data can be extracted with `.data()` or `.get()` to + * get a specific field. + * + * For a `DocumentSnapshot` that points to a non-existing document, any data + * access will return 'undefined'. You can use the `exists()` method to + * explicitly verify a document's existence. + */ +export declare class DocumentSnapshot< + T = DocumentData +> extends DocumentSnapshot_2 { + readonly _firestore: FirebaseFirestore; + private readonly _firestoreImpl; + /** + * Metadata about the `DocumentSnapshot`, including information about its + * source and local modifications. + */ + readonly metadata: SnapshotMetadata; + /** @hideconstructor protected */ + constructor( + _firestore: FirebaseFirestore, + userDataWriter: AbstractUserDataWriter, + key: DocumentKey, + document: Document_2 | null, + metadata: SnapshotMetadata, + converter: UntypedFirestoreDataConverter | null + ); + /** + * Property of the `DocumentSnapshot` that signals whether or not the data + * exists. True if the document exists. + */ + exists(): this is QueryDocumentSnapshot; + /** + * Retrieves all fields in the document as an `Object`. Returns `undefined` if + * the document doesn't exist. + * + * By default, `FieldValue.serverTimestamp()` values that have not yet been + * set to their final value will be returned as `null`. You can override + * this by passing an options object. + * + * @param options - An options object to configure how data is retrieved from + * the snapshot (for example the desired behavior for server timestamps that + * have not yet been set to their final value). + * @returns An `Object` containing all fields in the document or `undefined` if + * the document doesn't exist. + */ + data(options?: SnapshotOptions): T | undefined; + /** + * Retrieves the field specified by `fieldPath`. Returns `undefined` if the + * document or field doesn't exist. + * + * By default, a `FieldValue.serverTimestamp()` that has not yet been set to + * its final value will be returned as `null`. You can override this by + * passing an options object. + * + * @param fieldPath - The path (for example 'foo' or 'foo.bar') to a specific + * field. + * @param options - An options object to configure how the field is retrieved + * from the snapshot (for example the desired behavior for server timestamps + * that have not yet been set to their final value). + * @returns The data at the specified field location or undefined if no such + * field exists in the document. + */ + get(fieldPath: string | FieldPath, options?: SnapshotOptions): any; +} + +/** + * A `DocumentSnapshot` contains data read from a document in your Firestore + * database. The data can be extracted with `.data()` or `.get()` to + * get a specific field. + * + * For a `DocumentSnapshot` that points to a non-existing document, any data + * access will return 'undefined'. You can use the `exists()` method to + * explicitly verify a document's existence. + */ +declare class DocumentSnapshot_2 { + _firestore: FirebaseFirestore_2; + _userDataWriter: AbstractUserDataWriter; + _key: DocumentKey; + _document: Document_2 | null; + _converter: UntypedFirestoreDataConverter | null; + /** @hideconstructor protected */ + constructor( + _firestore: FirebaseFirestore_2, + _userDataWriter: AbstractUserDataWriter, + _key: DocumentKey, + _document: Document_2 | null, + _converter: UntypedFirestoreDataConverter | null + ); + /** Property of the `DocumentSnapshot` that provides the document's ID. */ + get id(): string; + /** + * The `DocumentReference` for the document included in the `DocumentSnapshot`. + */ + get ref(): DocumentReference; + /** + * Signals whether or not the document at the snapshot's location exists. + * + * @returns true if the document exists. + */ + exists(): this is QueryDocumentSnapshot_2; + /** + * Retrieves all fields in the document as an `Object`. Returns `undefined` if + * the document doesn't exist. + * + * @returns An `Object` containing all fields in the document or `undefined` + * if the document doesn't exist. + */ + data(): T | undefined; + /** + * Retrieves the field specified by `fieldPath`. Returns `undefined` if the + * document or field doesn't exist. + * + * @param fieldPath - The path (for example 'foo' or 'foo.bar') to a specific + * field. + * @returns The data at the specified field location or undefined if no such + * field exists in the document. + */ + get(fieldPath: string | FieldPath): any; +} + +declare type DocumentVersionMap = SortedMap; + +declare interface DocumentViewChange { + type: ChangeType; + doc: Document_2; +} + +/** + * Attempts to enable persistent storage, if possible. + * + * Must be called before any other functions (other than + * {@link initializeFirestore}, {@link getFirestore} or + * {@link clearIndexedDbPersistence}. + * + * If this fails, `enableIndexedDbPersistence()` will reject the promise it + * returns. Note that even after this failure, the `Firestore` instance will + * remain usable, however offline persistence will be disabled. + * + * There are several reasons why this can fail, which can be identified by + * the `code` on the error. + * + * * failed-precondition: The app is already open in another browser tab. + * * unimplemented: The browser is incompatible with the offline + * persistence implementation. + * + * @param firestore - The `Firestore` instance to enable persistence for. + * @param persistenceSettings - Optional settings object to configure + * persistence. + * @returns A promise that represents successfully enabling persistent storage. + */ +export declare function enableIndexedDbPersistence( + firestore: FirebaseFirestore, + persistenceSettings?: PersistenceSettings +): Promise; + +/** + * Attempts to enable multi-tab persistent storage, if possible. If enabled + * across all tabs, all operations share access to local persistence, including + * shared execution of queries and latency-compensated local document updates + * across all connected instances. + * + * If this fails, `enableMultiTabIndexedDbPersistence()` will reject the promise + * it returns. Note that even after this failure, the `Firestore` instance will + * remain usable, however offline persistence will be disabled. + * + * There are several reasons why this can fail, which can be identified by + * the `code` on the error. + * + * * failed-precondition: The app is already open in another browser tab and + * multi-tab is not enabled. + * * unimplemented: The browser is incompatible with the offline + * persistence implementation. + * + * @param firestore - The `Firestore` instance to enable persistence for. + * @returns A promise that represents successfully enabling persistent + * storage. + */ +export declare function enableMultiTabIndexedDbPersistence( + firestore: FirebaseFirestore +): Promise; + +/** + * Re-enables use of the network for this Firestore instance after a prior + * call to {@link disableNetwork}. + * + * @returns A promise that is resolved once the network has been enabled. + */ +export declare function enableNetwork( + firestore: FirebaseFirestore +): Promise; + +/** + * Creates a `QueryConstraint` that modifies the result set to end at the + * provided document (inclusive). 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. + * + * @param snapshot - The snapshot of the document to end at. + * @returns A `QueryConstraint` to pass to `query()` + */ +export declare function endAt( + snapshot: DocumentSnapshot_2 +): QueryConstraint; + +/** + * Creates a `QueryConstraint` that modifies the result set to end at the + * provided fields relative to the order of the query. The order of the field + * values must match the order of the order by clauses of the query. + * + * @param fieldValues - The field values to end this query at, in order + * of the query's order by. + * @returns A `QueryConstraint` to pass to `query()` + */ +export declare function endAt(...fieldValues: unknown[]): QueryConstraint; + +/** + * Creates a `QueryConstraint` 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. + * + * @param snapshot - The snapshot of the document to end before. + * @returns A `QueryConstraint` to pass to `query()` + */ +export declare function endBefore( + snapshot: DocumentSnapshot_2 +): QueryConstraint; + +/** + * Creates a `QueryConstraint` that modifies the result set to end before the + * provided fields relative to the order of the query. The order of the field + * values must match the order of the order by clauses of the query. + * + * @param fieldValues - The field values to end this query before, in order + * of the query's order by. + * @returns A `QueryConstraint` to pass to `query()` + */ +export declare function endBefore(...fieldValues: unknown[]): QueryConstraint; + +declare interface Entry { + key: K; + value: V; +} + +/** + * EventManager is responsible for mapping queries to query event emitters. + * It handles "fan-out". -- Identical queries will re-use the same watch on the + * backend. + * + * PORTING NOTE: On Web, EventManager `onListen` and `onUnlisten` need to be + * assigned to SyncEngine's `listen()` and `unlisten()` API before usage. This + * allows users to tree-shake the Watch logic. + */ +declare interface EventManager { + onListen?: (query: Query_2) => Promise; + onUnlisten?: (query: Query_2) => Promise; +} + +declare type FieldFilterOp = + | 'OPERATOR_UNSPECIFIED' + | 'LESS_THAN' + | 'LESS_THAN_OR_EQUAL' + | 'GREATER_THAN' + | 'GREATER_THAN_OR_EQUAL' + | 'EQUAL' + | 'NOT_EQUAL' + | 'ARRAY_CONTAINS' + | 'IN' + | 'ARRAY_CONTAINS_ANY' + | 'NOT_IN'; + +/** + * Provides a set of fields that can be used to partially patch a document. + * FieldMask is used in conjunction with ObjectValue. + * Examples: + * foo - Overwrites foo entirely with the provided value. If foo is not + * present in the companion ObjectValue, the field is deleted. + * foo.bar - Overwrites only the field bar of the object foo. + * If foo is not an object, foo is replaced with an object + * containing foo + */ +declare class FieldMask { + readonly fields: FieldPath_2[]; + constructor(fields: FieldPath_2[]); + /** + * Verifies that `fieldPath` is included by at least one field in this field + * mask. + * + * This is an O(n) operation, where `n` is the size of the field mask. + */ + covers(fieldPath: FieldPath_2): boolean; + isEqual(other: FieldMask): boolean; +} + +/** + * A `FieldPath` refers to a field in a document. The path may consist of a + * single field name (referring to a top-level field in the document), or a + * list of field names (referring to a nested field in the document). + * + * Create a `FieldPath` by providing field names. If more than one field + * name is provided, the path will point to a nested field in a document. + */ +export declare class FieldPath { + /** Internal representation of a Firestore field path. */ + readonly _internalPath: FieldPath_2; + /** + * Creates a FieldPath from the provided field names. If more than one field + * name is provided, the path will point to a nested field in a document. + * + * @param fieldNames - A list of field names. + */ + constructor(...fieldNames: string[]); + /** + * Returns true if this `FieldPath` is equal to the provided one. + * + * @param other - The `FieldPath` to compare against. + * @returns true if this `FieldPath` is equal to the provided one. + */ + isEqual(other: FieldPath): boolean; +} + +/** A dot-separated path for navigating sub-objects within a document. */ +declare class FieldPath_2 extends BasePath { + protected construct( + segments: string[], + offset?: number, + length?: number + ): FieldPath_2; + /** + * Returns true if the string could be used as a segment in a field path + * without escaping. + */ + private static isValidIdentifier; + canonicalString(): string; + toString(): string; + /** + * Returns true if this field references the key of a document. + */ + isKeyField(): boolean; + /** + * The field designating the key of a document. + */ + static keyField(): FieldPath_2; + /** + * Parses a field string from the given server-formatted string. + * + * - Splitting the empty string is not allowed (for now at least). + * - Empty segments within the string (e.g. if there are two consecutive + * separators) are not allowed. + * + * TODO(b/37244157): we should make this more strict. Right now, it allows + * non-identifier path components, even if they aren't escaped. + */ + static fromServerFormat(path: string): FieldPath_2; + static emptyPath(): FieldPath_2; +} + +/** A field path and the TransformOperation to perform upon it. */ +declare class FieldTransform { + readonly field: FieldPath_2; + readonly transform: TransformOperation; + constructor(field: FieldPath_2, transform: TransformOperation); +} + +declare type FieldTransformSetToServerValue = + | 'SERVER_VALUE_UNSPECIFIED' + | 'REQUEST_TIME'; + +/** + * Sentinel values that can be used when writing document fields with `set()` + * or `update()`. + */ +export declare abstract class FieldValue { + _methodName: string; + /** + * @param _methodName - The public API endpoint that returns this class. + */ + constructor(_methodName: string); + abstract isEqual(other: FieldValue): boolean; + abstract _toFieldTransform(context: ParseContext): FieldTransform | null; +} + +declare abstract class Filter { + abstract matches(doc: Document_2): boolean; +} + +/** + * The Cloud Firestore service interface. + * + * Do not call this constructor directly. Instead, use {@link getFirestore}. + */ +export declare class FirebaseFirestore extends FirebaseFirestore_2 { + readonly _queue: AsyncQueue; + readonly _persistenceKey: string; + _firestoreClient: FirestoreClient | undefined; + /** @hideconstructor */ + constructor( + databaseIdOrApp: DatabaseId | FirebaseApp, + authProvider: Provider + ); + _terminate(): Promise; +} + +/** + * The Cloud Firestore service interface. + * + * Do not call this constructor directly. Instead, use {@link getFirestore}. + */ +declare class FirebaseFirestore_2 implements FirestoreService { + readonly _databaseId: DatabaseId; + readonly _persistenceKey: string; + _credentials: CredentialsProvider; + private _settings; + private _settingsFrozen; + private _terminateTask?; + private _app?; + /** @hideconstructor */ + constructor( + databaseIdOrApp: DatabaseId | FirebaseApp, + authProvider: Provider + ); + /** + * The {@link FirebaseApp} associated with this `Firestore` service + * instance. + */ + get app(): FirebaseApp; + get _initialized(): boolean; + get _terminated(): boolean; + _setSettings(settings: PrivateSettings): void; + _getSettings(): FirestoreSettings; + _freezeSettings(): FirestoreSettings; + _delete(): Promise; + toJSON(): object; + /** + * Terminates all components used by this client. Subclasses can override + * this method to clean up their own dependencies, but must also call this + * method. + * + * Only ever called once. + */ + protected _terminate(): Promise; +} + +/** + * FirestoreClient is a top-level class that constructs and owns all of the + * pieces of the client SDK architecture. It is responsible for creating the + * async queue that is shared by all of the other components in the system. + */ +declare class FirestoreClient { + private credentials; + /** + * Asynchronous queue responsible for all of our internal processing. When + * we get incoming work from the user (via public API) or the network + * (incoming GRPC messages), we should always schedule onto this queue. + * This ensures all of our work is properly serialized (e.g. we don't + * start processing a new operation while the previous one is waiting for + * an async I/O to complete). + */ + asyncQueue: AsyncQueue; + private databaseInfo; + private user; + private readonly clientId; + private credentialListener; + private readonly receivedInitialUser; + offlineComponents?: OfflineComponentProvider; + onlineComponents?: OnlineComponentProvider; + constructor( + credentials: CredentialsProvider, + /** + * Asynchronous queue responsible for all of our internal processing. When + * we get incoming work from the user (via public API) or the network + * (incoming GRPC messages), we should always schedule onto this queue. + * This ensures all of our work is properly serialized (e.g. we don't + * start processing a new operation while the previous one is waiting for + * an async I/O to complete). + */ + asyncQueue: AsyncQueue, + databaseInfo: DatabaseInfo + ); + getConfiguration(): Promise; + setCredentialChangeListener(listener: (user: User) => void): void; + /** + * Checks that the client has not been terminated. Ensures that other methods on + * this class cannot be called after the client is terminated. + */ + verifyNotTerminated(): void; + terminate(): Promise; +} + +/** + * Converter used by `withConverter()` to transform user objects of type `T` + * into Firestore data. + * + * Using the converter allows you to specify generic type arguments when + * storing and retrieving objects from Firestore. + * + * @example + * ```typescript + * class Post { + * constructor(readonly title: string, readonly author: string) {} + * + * toString(): string { + * return this.title + ', by ' + this.author; + * } + * } + * + * const postConverter = { + * toFirestore(post: Post): firebase.firestore.DocumentData { + * return {title: post.title, author: post.author}; + * }, + * fromFirestore( + * snapshot: firebase.firestore.QueryDocumentSnapshot, + * options: firebase.firestore.SnapshotOptions + * ): Post { + * const data = snapshot.data(options)!; + * return new Post(data.title, data.author); + * } + * }; + * + * const postSnap = await firebase.firestore() + * .collection('posts') + * .withConverter(postConverter) + * .doc().get(); + * const post = postSnap.data(); + * if (post !== undefined) { + * post.title; // string + * post.toString(); // Should be defined + * post.someNonExistentProperty; // TS error + * } + * ``` + */ +export declare interface FirestoreDataConverter + extends FirestoreDataConverter_2 { + /** + * Called by the Firestore SDK to convert a custom model object of type `T` + * into a plain JavaScript object (suitable for writing directly to the + * Firestore database). To use `set()` with `merge` and `mergeFields`, + * `toFirestore()` must be defined with `Partial`. + */ + toFirestore(modelObject: T): DocumentData; + /** + * Called by the Firestore SDK to convert a custom model object of type `T` + * into a plain JavaScript object (suitable for writing directly to the + * Firestore database). Used with {@link setData}, {@link WriteBatch#set} + * and {@link Transaction#set} with `merge:true` or `mergeFields`. + */ + toFirestore(modelObject: Partial, options: SetOptions): DocumentData; + /** + * Called by the Firestore SDK to convert Firestore data into an object of + * type T. You can access your data by calling: `snapshot.data(options)`. + * + * @param snapshot - A `QueryDocumentSnapshot` containing your data and metadata. + * @param options - The `SnapshotOptions` from the initial call to `data()`. + */ + fromFirestore( + snapshot: QueryDocumentSnapshot, + options?: SnapshotOptions + ): T; +} + +/** + * Converter used by `withConverter()` to transform user objects of type `T` + * into Firestore data. + * + * Using the converter allows you to specify generic type arguments when + * storing and retrieving objects from Firestore. + * + * @example + * ```typescript + * class Post { + * constructor(readonly title: string, readonly author: string) {} + * + * toString(): string { + * return this.title + ', by ' + this.author; + * } + * } + * + * const postConverter = { + * toFirestore(post: Post): firebase.firestore.DocumentData { + * return {title: post.title, author: post.author}; + * }, + * fromFirestore(snapshot: firebase.firestore.QueryDocumentSnapshot): Post { + * const data = snapshot.data(options)!; + * return new Post(data.title, data.author); + * } + * }; + * + * const postSnap = await firebase.firestore() + * .collection('posts') + * .withConverter(postConverter) + * .doc().get(); + * const post = postSnap.data(); + * if (post !== undefined) { + * post.title; // string + * post.toString(); // Should be defined + * post.someNonExistentProperty; // TS error + * } + * ``` + */ +declare interface FirestoreDataConverter_2 { + /** + * Called by the Firestore SDK to convert a custom model object of type `T` + * into a plain JavaScript object (suitable for writing directly to the + * Firestore database). Used with {@link setData}, {@link WriteBatch#set} + * and {@link Transaction#set}. + */ + toFirestore(modelObject: T): DocumentData; + /** + * Called by the Firestore SDK to convert a custom model object of type `T` + * into a plain JavaScript object (suitable for writing directly to the + * Firestore database). Used with {@link setData}, {@link WriteBatch#set} + * and {@link Transaction#set} with `merge:true` or `mergeFields`. + */ + toFirestore(modelObject: Partial, options: SetOptions): DocumentData; + /** + * Called by the Firestore SDK to convert Firestore data into an object of + * type T. You can access your data by calling: `snapshot.data()`. + * + * @param snapshot - A `QueryDocumentSnapshot` containing your data and + * metadata. + */ + fromFirestore(snapshot: QueryDocumentSnapshot_2): T; +} + +/** An error returned by a Firestore operation. */ +export declare class FirestoreError extends Error { + readonly code: FirestoreErrorCode; + readonly message: string; + readonly name: string; + readonly stack?: string; + /** @hideconstructor */ + constructor(code: FirestoreErrorCode, message: string); +} + +/** + * @license + * Copyright 2017 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. + */ +/** + * The set of Firestore status codes. The codes are the same at the ones + * exposed by gRPC here: + * https://github.com/grpc/grpc/blob/master/doc/statuscodes.md + * + * Possible values: + * - 'cancelled': The operation was cancelled (typically by the caller). + * - 'unknown': Unknown error or an error from a different error domain. + * - 'invalid-argument': Client specified an invalid argument. Note that this + * differs from 'failed-precondition'. 'invalid-argument' indicates + * arguments that are problematic regardless of the state of the system + * (e.g. an invalid field name). + * - 'deadline-exceeded': Deadline expired before operation could complete. + * For operations that change the state of the system, this error may be + * returned even if the operation has completed successfully. For example, + * a successful response from a server could have been delayed long enough + * for the deadline to expire. + * - 'not-found': Some requested document was not found. + * - 'already-exists': Some document that we attempted to create already + * exists. + * - 'permission-denied': The caller does not have permission to execute the + * specified operation. + * - 'resource-exhausted': Some resource has been exhausted, perhaps a + * per-user quota, or perhaps the entire file system is out of space. + * - 'failed-precondition': Operation was rejected because the system is not + * in a state required for the operation's execution. + * - 'aborted': The operation was aborted, typically due to a concurrency + * issue like transaction aborts, etc. + * - 'out-of-range': Operation was attempted past the valid range. + * - 'unimplemented': Operation is not implemented or not supported/enabled. + * - 'internal': Internal errors. Means some invariants expected by + * underlying system has been broken. If you see one of these errors, + * something is very broken. + * - 'unavailable': The service is currently unavailable. This is most likely + * a transient condition and may be corrected by retrying with a backoff. + * - 'data-loss': Unrecoverable data loss or corruption. + * - 'unauthenticated': The request does not have valid authentication + * credentials for the operation. + */ +export declare type FirestoreErrorCode = + | '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'; + +/** + * An interface implemented by FirebaseFirestore that provides compatibility + * with the usage in this file. + * + * This interface mainly exists to remove a cyclic dependency. + */ +declare interface FirestoreService extends _FirebaseService { + _credentials: CredentialsProvider; + _persistenceKey: string; + _databaseId: DatabaseId; + _terminated: boolean; + _freezeSettings(): FirestoreSettings; +} + +/** + * A concrete type describing all the values that can be applied via a + * user-supplied firestore.Settings object. This is a separate type so that + * defaults can be supplied and the value can be checked for equality. + */ +declare class FirestoreSettings { + /** The hostname to connect to. */ + readonly host: string; + /** Whether to use SSL when connecting. */ + readonly ssl: boolean; + readonly cacheSizeBytes: number; + readonly experimentalForceLongPolling: boolean; + readonly experimentalAutoDetectLongPolling: boolean; + readonly ignoreUndefinedProperties: boolean; + credentials?: any; + constructor(settings: PrivateSettings); + isEqual(other: FirestoreSettings): boolean; +} + +declare namespace firestoreV1ApiClientInterfaces { + interface ArrayValue { + values?: Value[]; + } + interface BatchGetDocumentsRequest { + database?: string; + documents?: string[]; + mask?: DocumentMask; + transaction?: string; + newTransaction?: TransactionOptions; + readTime?: string; + } + interface BatchGetDocumentsResponse { + found?: Document; + missing?: string; + transaction?: string; + readTime?: string; + } + interface BeginTransactionRequest { + options?: TransactionOptions; + } + interface BeginTransactionResponse { + transaction?: string; + } + interface CollectionSelector { + collectionId?: string; + allDescendants?: boolean; + } + interface CommitRequest { + database?: string; + writes?: Write[]; + transaction?: string; + } + interface CommitResponse { + writeResults?: WriteResult[]; + commitTime?: string; + } + interface CompositeFilter { + op?: CompositeFilterOp; + filters?: Filter[]; + } + interface Cursor { + values?: Value[]; + before?: boolean; + } + interface Document { + name?: string; + fields?: ApiClientObjectMap; + createTime?: Timestamp_2; + updateTime?: Timestamp_2; + } + interface DocumentChange { + document?: Document; + targetIds?: number[]; + removedTargetIds?: number[]; + } + interface DocumentDelete { + document?: string; + removedTargetIds?: number[]; + readTime?: Timestamp_2; + } + interface DocumentMask { + fieldPaths?: string[]; + } + interface DocumentRemove { + document?: string; + removedTargetIds?: number[]; + readTime?: string; + } + interface DocumentTransform { + document?: string; + fieldTransforms?: FieldTransform[]; + } + interface DocumentsTarget { + documents?: string[]; + } + interface Empty {} + interface ExistenceFilter { + targetId?: number; + count?: number; + } + interface FieldFilter { + field?: FieldReference; + op?: FieldFilterOp; + value?: Value; + } + interface FieldReference { + fieldPath?: string; + } + interface FieldTransform { + fieldPath?: string; + setToServerValue?: FieldTransformSetToServerValue; + appendMissingElements?: ArrayValue; + removeAllFromArray?: ArrayValue; + increment?: Value; + } + interface Filter { + compositeFilter?: CompositeFilter; + fieldFilter?: FieldFilter; + unaryFilter?: UnaryFilter; + } + interface Index { + name?: string; + collectionId?: string; + fields?: IndexField[]; + state?: IndexState; + } + interface IndexField { + fieldPath?: string; + mode?: IndexFieldMode; + } + interface LatLng { + latitude?: number; + longitude?: number; + } + interface ListCollectionIdsRequest { + pageSize?: number; + pageToken?: string; + } + interface ListCollectionIdsResponse { + collectionIds?: string[]; + nextPageToken?: string; + } + interface ListDocumentsResponse { + documents?: Document[]; + nextPageToken?: string; + } + interface ListIndexesResponse { + indexes?: Index[]; + nextPageToken?: string; + } + interface ListenRequest { + addTarget?: Target; + removeTarget?: number; + labels?: ApiClientObjectMap; + } + interface ListenResponse { + targetChange?: TargetChange; + documentChange?: DocumentChange; + documentDelete?: DocumentDelete; + documentRemove?: DocumentRemove; + filter?: ExistenceFilter; + } + interface MapValue { + fields?: ApiClientObjectMap; + } + interface Operation { + name?: string; + metadata?: ApiClientObjectMap; + done?: boolean; + error?: Status; + response?: ApiClientObjectMap; + } + interface Order { + field?: FieldReference; + direction?: OrderDirection; + } + interface Precondition { + exists?: boolean; + updateTime?: Timestamp_2; + } + interface Projection { + fields?: FieldReference[]; + } + interface QueryTarget { + parent?: string; + structuredQuery?: StructuredQuery; + } + interface ReadOnly { + readTime?: string; + } + interface ReadWrite { + retryTransaction?: string; + } + interface RollbackRequest { + transaction?: string; + } + interface RunQueryRequest { + parent?: string; + structuredQuery?: StructuredQuery; + transaction?: string; + newTransaction?: TransactionOptions; + readTime?: string; + } + interface RunQueryResponse { + transaction?: string; + document?: Document; + readTime?: string; + skippedResults?: number; + } + interface Status { + code?: number; + message?: string; + details?: Array>; + } + interface StructuredQuery { + select?: Projection; + from?: CollectionSelector[]; + where?: Filter; + orderBy?: Order[]; + startAt?: Cursor; + endAt?: Cursor; + offset?: number; + limit?: + | number + | { + value: number; + }; + } + interface Target { + query?: QueryTarget; + documents?: DocumentsTarget; + resumeToken?: string | Uint8Array; + readTime?: Timestamp_2; + targetId?: number; + once?: boolean; + } + interface TargetChange { + targetChangeType?: TargetChangeTargetChangeType; + targetIds?: number[]; + cause?: Status; + resumeToken?: string | Uint8Array; + readTime?: Timestamp_2; + } + interface TransactionOptions { + readOnly?: ReadOnly; + readWrite?: ReadWrite; + } + interface UnaryFilter { + op?: UnaryFilterOp; + field?: FieldReference; + } + interface Value { + nullValue?: ValueNullValue; + booleanValue?: boolean; + integerValue?: string | number; + doubleValue?: string | number; + timestampValue?: Timestamp_2; + stringValue?: string; + bytesValue?: string | Uint8Array; + referenceValue?: string; + geoPointValue?: LatLng; + arrayValue?: ArrayValue; + mapValue?: MapValue; + } + interface Write { + update?: Document; + delete?: string; + verify?: string; + transform?: DocumentTransform; + updateMask?: DocumentMask; + updateTransforms?: FieldTransform[]; + currentDocument?: Precondition; + } + interface WriteRequest { + streamId?: string; + writes?: Write[]; + streamToken?: string | Uint8Array; + labels?: ApiClientObjectMap; + } + interface WriteResponse { + streamId?: string; + streamToken?: string | Uint8Array; + writeResults?: WriteResult[]; + commitTime?: Timestamp_2; + } + interface WriteResult { + updateTime?: Timestamp_2; + transformResults?: Value[]; + } +} + +declare interface FirstPartyCredentialsSettings { + ['type']: 'gapi'; + ['client']: unknown; + ['sessionIndex']: string; +} + +/** + * @license + * Copyright 2017 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. + */ +declare type FulfilledHandler = + | ((result: T) => R | PersistencePromise) + | null; + +/** + * Interface implemented by the LRU scheduler to start(), stop() and restart + * garbage collection. + */ +declare interface GarbageCollectionScheduler { + readonly started: boolean; + start(localStore: LocalStore): void; + stop(): void; +} + +/** + * @license + * Copyright 2017 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. + */ +/** + * An immutable object representing a geographic location in Firestore. The + * location is represented as latitude/longitude pair. + * + * Latitude values are in the range of [-90, 90]. + * Longitude values are in the range of [-180, 180]. + */ +export declare class GeoPoint { + private _lat; + private _long; + /** + * Creates a new immutable `GeoPoint` object with the provided latitude and + * longitude values. + * @param latitude - The latitude as number between -90 and 90. + * @param longitude - The longitude as number between -180 and 180. + */ + constructor(latitude: number, longitude: number); + /** + * The latitude of this `GeoPoint` instance. + */ + get latitude(): number; + /** + * The longitude of this `GeoPoint` instance. + */ + get longitude(): number; + /** + * Returns true if this `GeoPoint` is equal to the provided one. + * + * @param other - The `GeoPoint` to compare against. + * @returns true if this `GeoPoint` is equal to the provided one. + */ + isEqual(other: GeoPoint): boolean; + toJSON(): { + latitude: number; + longitude: number; + }; + /** + * Actually private to JS consumers of our API, so this function is prefixed + * with an underscore. + */ + _compareTo(other: GeoPoint): number; +} + +/** + * Reads the document referred to by this `DocumentReference`. + * + * Note: `getDoc()` attempts to provide up-to-date data when possible by waiting + * for data from the server, but it may return cached data or fail if you are + * offline and the server cannot be reached. To specify this behavior, invoke + * {@link getDocFromCache} or {@link getDocFromServer}. + * + * @param reference - The reference of the document to fetch. + * @returns A Promise resolved with a `DocumentSnapshot` containing the + * current document contents. + */ +export declare function getDoc( + reference: DocumentReference +): Promise>; + +/** + * Reads the document referred to by this `DocumentReference` from cache. + * Returns an error if the document is not currently cached. + * + * @returns A Promise resolved with a `DocumentSnapshot` containing the + * current document contents. + */ +export declare function getDocFromCache( + reference: DocumentReference +): Promise>; + +/** + * Reads the document referred to by this `DocumentReference` from the server. + * Returns an error if the network is not available. + * + * @returns A Promise resolved with a `DocumentSnapshot` containing the + * current document contents. + */ +export declare function getDocFromServer( + reference: DocumentReference +): Promise>; + +/** + * Executes the query and returns the results as a `QuerySnapshot`. + * + * Note: `getDocs()` attempts to provide up-to-date data when possible by + * waiting for data from the server, but it may return cached data or fail if + * you are offline and the server cannot be reached. To specify this behavior, + * invoke {@link getDocsFromCache} or {@link getDocsFromServer}. + * + * @returns A Promise that will be resolved with the results of the query. + */ +export declare function getDocs(query: Query): Promise>; + +/** + * Executes the query and returns the results as a `QuerySnapshot` from cache. + * Returns an error if the document is not currently cached. + * + * @returns A Promise that will be resolved with the results of the query. + */ +export declare function getDocsFromCache( + query: Query +): Promise>; + +/** + * Executes the query and returns the results as a `QuerySnapshot` from the + * server. Returns an error if the network is not available. + * + * @returns A Promise that will be resolved with the results of the query. + */ +export declare function getDocsFromServer( + query: Query +): Promise>; + +/** + * Returns the existing instance of Firestore that is associated with the + * provided {@link FirebaseApp}. If no instance exists, initializes a new + * instance with default settings. + * + * @param app - The {@link FirebaseApp} instance that the returned Firestore + * instance is associated with. + * @returns The `Firestore` instance of the provided app. + */ +export declare function getFirestore(app: FirebaseApp): FirebaseFirestore; + +/** + * Returns a special value that can be used with {@link setDoc} or {@link + * updateDoc} that tells the server to increment the field's current value by + * the given value. + * + * If either the operand or the current field value uses floating point + * precision, all arithmetic follows IEEE 754 semantics. If both values are + * integers, values outside of JavaScript's safe number range + * (`Number.MIN_SAFE_INTEGER` to `Number.MAX_SAFE_INTEGER`) are also subject to + * precision loss. Furthermore, once processed by the Firestore backend, all + * integer operations are capped between -2^63 and 2^63-1. + * + * If the current field value is not of type `number`, or if the field does not + * yet exist, the transformation sets the field to the given value. + * + * @param n - The value to increment by. + * @returns The `FieldValue` sentinel for use in a call to `setDoc()` or + * `updateDoc()` + */ +export declare function increment(n: number): FieldValue; + +declare type IndexFieldMode = 'MODE_UNSPECIFIED' | 'ASCENDING' | 'DESCENDING'; + +/** + * Represents a set of indexes that are used to execute queries efficiently. + * + * Currently the only index is a [collection id] => [parent path] index, used + * to execute Collection Group queries. + */ +declare interface IndexManager { + /** + * Creates an index entry mapping the collectionId (last segment of the path) + * to the parent path (either the containing document location or the empty + * path for root-level collections). Index entries can be retrieved via + * getCollectionParents(). + * + * NOTE: Currently we don't remove index entries. If this ends up being an + * issue we can devise some sort of GC strategy. + */ + addToCollectionParentIndex( + transaction: PersistenceTransaction, + collectionPath: ResourcePath + ): PersistencePromise; + /** + * Retrieves all parent locations containing the given collectionId, as a + * list of paths (each path being either a document location or the empty + * path for a root-level collection). + */ + getCollectionParents( + transaction: PersistenceTransaction, + collectionId: string + ): PersistencePromise; +} + +declare type IndexState = 'STATE_UNSPECIFIED' | 'CREATING' | 'READY' | 'ERROR'; + +/** + * Initializes a new instance of Cloud Firestore with the provided settings. + * Can only be called before any other function, including + * {@link getFirestore}. If the custom settings are empty, this function is + * equivalent to calling {@link getFirestore}. + * + * @param app - The {@link FirebaseApp} with which the `Firestore` instance will + * be associated. + * @param settings - A settings object to configure the `Firestore` instance. + * @returns A newly initialized `Firestore` instance. + */ +export declare function initializeFirestore( + app: FirebaseApp, + settings: Settings +): FirebaseFirestore; + +/** + * Creates a `QueryConstraint` that only returns the first matching documents. + * + * @param limit - The maximum number of items to return. + * @returns The created `Query`. + */ +export declare function limit(limit: number): QueryConstraint; + +/** + * Creates a `QueryConstraint` that only returns the last matching documents. + * + * You must specify at least one `orderBy` clause for `limitToLast` queries, + * otherwise an exception will be thrown during execution. + * + * @param limit - The maximum number of items to return. + * @returns The created `Query`. + */ +export declare function limitToLast(limit: number): QueryConstraint; + +declare const enum LimitType { + First = 'F', + Last = 'L' +} + +/** LimitType enum. */ +declare type LimitType_2 = 'FIRST' | 'LAST'; + +declare type ListenSequenceNumber = number; + +declare class LLRBEmptyNode { + get key(): never; + get value(): never; + get color(): never; + get left(): never; + get right(): never; + size: number; + copy( + key: K | null, + value: V | null, + color: boolean | null, + left: LLRBNode | LLRBEmptyNode | null, + right: LLRBNode | LLRBEmptyNode | null + ): LLRBEmptyNode; + insert(key: K, value: V, comparator: Comparator): LLRBNode; + remove(key: K, comparator: Comparator): LLRBEmptyNode; + isEmpty(): boolean; + inorderTraversal(action: (k: K, v: V) => boolean): boolean; + reverseTraversal(action: (k: K, v: V) => boolean): boolean; + minKey(): K | null; + maxKey(): K | null; + isRed(): boolean; + checkMaxDepth(): boolean; + protected check(): 0; +} + +declare class LLRBNode { + key: K; + value: V; + readonly color: boolean; + readonly left: LLRBNode | LLRBEmptyNode; + readonly right: LLRBNode | LLRBEmptyNode; + readonly size: number; + static EMPTY: LLRBEmptyNode; + static RED: boolean; + static BLACK: boolean; + constructor( + key: K, + value: V, + color?: boolean, + left?: LLRBNode | LLRBEmptyNode, + right?: LLRBNode | LLRBEmptyNode + ); + copy( + key: K | null, + value: V | null, + color: boolean | null, + left: LLRBNode | LLRBEmptyNode | null, + right: LLRBNode | LLRBEmptyNode | null + ): LLRBNode; + isEmpty(): boolean; + inorderTraversal(action: (k: K, v: V) => T): T; + reverseTraversal(action: (k: K, v: V) => T): T; + private min; + minKey(): K | null; + maxKey(): K | null; + insert(key: K, value: V, comparator: Comparator): LLRBNode; + private removeMin; + remove( + key: K, + comparator: Comparator + ): LLRBNode | LLRBEmptyNode; + isRed(): boolean; + private fixUp; + private moveRedLeft; + private moveRedRight; + private rotateLeft; + private rotateRight; + private colorFlip; + checkMaxDepth(): boolean; + protected check(): number; +} + +/** + * Loads a Firestore bundle into the local cache. + * + * @param firestore - The `Firestore` instance to load bundles for for. + * @param bundleData - An object representing the bundle to be loaded. Valid objects are + * `ArrayBuffer`, `ReadableStream` or `string`. + * + * @return + * A `LoadBundleTask` object, which notifies callers with progress updates, and completion + * or error events. It can be used as a `Promise`. + */ +export declare function loadBundle( + firestore: FirebaseFirestore, + bundleData: ReadableStream | ArrayBuffer | string +): LoadBundleTask; + +/** + * Represents the task of loading a Firestore bundle. It provides progress of bundle + * loading, as well as task completion and error events. + * + * The API is compatible with `Promise`. + */ +export declare class LoadBundleTask + implements PromiseLike +{ + private _progressObserver; + private _taskCompletionResolver; + private _lastProgress; + /** + * Registers functions to listen to bundle loading progress events. + * @param next - Called when there is a progress update from bundle loading. Typically `next` calls occur + * each time a Firestore document is loaded from the bundle. + * @param error - Called when an error occurs during bundle loading. The task aborts after reporting the + * error, and there should be no more updates after this. + * @param complete - Called when the loading task is complete. + */ + onProgress( + next?: (progress: LoadBundleTaskProgress) => unknown, + error?: (err: Error) => unknown, + complete?: () => void + ): void; + /** + * Implements the `Promise.catch` interface. + * + * @param onRejected - Called when an error occurs during bundle loading. + */ + catch( + onRejected: (a: Error) => R | PromiseLike + ): Promise; + /** + * Implements the `Promise.then` interface. + * + * @param onFulfilled - Called on the completion of the loading task with a final `LoadBundleTaskProgress` update. + * The update will always have its `taskState` set to `"Success"`. + * @param onRejected - Called when an error occurs during bundle loading. + */ + then( + onFulfilled?: (a: LoadBundleTaskProgress) => T | PromiseLike, + onRejected?: (a: Error) => R | PromiseLike + ): Promise; + /** + * Notifies all observers that bundle loading has completed, with a provided + * `LoadBundleTaskProgress` object. + * + * @private + */ + _completeWith(progress: LoadBundleTaskProgress): void; + /** + * Notifies all observers that bundle loading has failed, with a provided + * `Error` as the reason. + * + * @private + */ + _failWith(error: FirestoreError): void; + /** + * Notifies a progress update of loading a bundle. + * @param progress - The new progress. + * + * @private + */ + _updateProgress(progress: LoadBundleTaskProgress): void; +} + +/** + * Represents a progress update or a final state from loading bundles. + */ +export declare interface LoadBundleTaskProgress { + /** How many documents have been loaded. */ + documentsLoaded: number; + /** How many documents are in the bundle being loaded. */ + totalDocuments: number; + /** How many bytes have been loaded. */ + bytesLoaded: number; + /** How many bytes are in the bundle being loaded. */ + totalBytes: number; + /** Current task state. */ + taskState: TaskState; +} + +declare interface LocalStore { + collectGarbage(garbageCollector: LruGarbageCollector): Promise; +} +export { LogLevel }; + +declare interface LruGarbageCollector { + readonly params: LruParams; + collect( + txn: PersistenceTransaction, + activeTargetIds: ActiveTargets + ): PersistencePromise; + /** Given a percentile of target to collect, returns the number of targets to collect. */ + calculateTargetCount( + txn: PersistenceTransaction, + percentile: number + ): PersistencePromise; + /** Returns the nth sequence number, counting in order from the smallest. */ + nthSequenceNumber( + txn: PersistenceTransaction, + n: number + ): PersistencePromise; + /** + * Removes documents that have a sequence number equal to or less than the + * upper bound and are not otherwise pinned. + */ + removeOrphanedDocuments( + txn: PersistenceTransaction, + upperBound: ListenSequenceNumber + ): PersistencePromise; + getCacheSize(txn: PersistenceTransaction): PersistencePromise; + /** + * Removes targets with a sequence number equal to or less than the given + * upper bound, and removes document associations with those targets. + */ + removeTargets( + txn: PersistenceTransaction, + upperBound: ListenSequenceNumber, + activeTargetIds: ActiveTargets + ): PersistencePromise; +} + +declare class LruParams { + readonly cacheSizeCollectionThreshold: number; + readonly percentileToCollect: number; + readonly maximumSequenceNumbersToCollect: number; + private static readonly DEFAULT_COLLECTION_PERCENTILE; + private static readonly DEFAULT_MAX_SEQUENCE_NUMBERS_TO_COLLECT; + static withCacheSize(cacheSize: number): LruParams; + static readonly DEFAULT: LruParams; + static readonly DISABLED: LruParams; + constructor( + cacheSizeCollectionThreshold: number, + percentileToCollect: number, + maximumSequenceNumbersToCollect: number + ); +} + +/** + * Describes the results of a garbage collection run. `didRun` will be set to + * `false` if collection was skipped (either it is disabled or the cache size + * has not hit the threshold). If collection ran, the other fields will be + * filled in with the details of the results. + */ +declare interface LruResults { + readonly didRun: boolean; + readonly sequenceNumbersCollected: number; + readonly targetsRemoved: number; + readonly documentsRemoved: number; +} + +declare type MapValue = firestoreV1ApiClientInterfaces.MapValue; + +/** + * The result of a lookup for a given path may be an existing document or a + * marker that this document does not exist at a given version. + */ +declare abstract class MaybeDocument { + readonly key: DocumentKey; + readonly version: SnapshotVersion; + constructor(key: DocumentKey, version: SnapshotVersion); + /** + * Whether this document had a local mutation applied that has not yet been + * acknowledged by Watch. + */ + abstract get hasPendingWrites(): boolean; + abstract isEqual(other: MaybeDocument | null | undefined): boolean; + abstract toString(): string; +} + +declare type MaybeDocumentMap = SortedMap; + +/** + * A mutation describes a self-contained change to a document. Mutations can + * create, replace, delete, and update subsets of documents. + * + * Mutations not only act on the value of the document but also its version. + * + * For local mutations (mutations that haven't been committed yet), we preserve + * the existing version for Set and Patch mutations. For Delete mutations, we + * reset the version to 0. + * + * Here's the expected transition table. + * + * MUTATION APPLIED TO RESULTS IN + * + * SetMutation Document(v3) Document(v3) + * SetMutation NoDocument(v3) Document(v0) + * SetMutation null Document(v0) + * PatchMutation Document(v3) Document(v3) + * PatchMutation NoDocument(v3) NoDocument(v3) + * PatchMutation null null + * DeleteMutation Document(v3) NoDocument(v0) + * DeleteMutation NoDocument(v3) NoDocument(v0) + * DeleteMutation null NoDocument(v0) + * + * For acknowledged mutations, we use the updateTime of the WriteResponse as + * the resulting version for Set and Patch mutations. As deletes have no + * explicit update time, we use the commitTime of the WriteResponse for + * Delete mutations. + * + * If a mutation is acknowledged by the backend but fails the precondition check + * locally, we return an `UnknownDocument` and rely on Watch to send us the + * updated version. + * + * Field transforms are used only with Patch and Set Mutations. We use the + * `updateTransforms` message to store transforms, rather than the `transforms`s + * messages. + * + * ## Subclassing Notes + * + * Subclasses of Mutation need to implement applyToRemoteDocument() and + * applyToLocalView() to implement the actual behavior of applying the mutation + * to some source document. + */ +declare abstract class Mutation { + abstract readonly type: MutationType; + abstract readonly key: DocumentKey; + abstract readonly precondition: Precondition; + abstract readonly fieldTransforms: FieldTransform[]; +} + +/** + * A batch of mutations that will be sent as one unit to the backend. + */ +declare class MutationBatch { + batchId: BatchId; + localWriteTime: Timestamp; + baseMutations: Mutation[]; + mutations: Mutation[]; + /** + * @param batchId - The unique ID of this mutation batch. + * @param localWriteTime - The original write time of this mutation. + * @param baseMutations - Mutations that are used to populate the base + * values when this mutation is applied locally. This can be used to locally + * overwrite values that are persisted in the remote document cache. Base + * mutations are never sent to the backend. + * @param mutations - The user-provided mutations in this mutation batch. + * User-provided mutations are applied both locally and remotely on the + * backend. + */ + constructor( + batchId: BatchId, + localWriteTime: Timestamp, + baseMutations: Mutation[], + mutations: Mutation[] + ); + /** + * Applies all the mutations in this MutationBatch to the specified document + * to create a new remote document + * + * @param docKey - The key of the document to apply mutations to. + * @param maybeDoc - The document to apply mutations to. + * @param batchResult - The result of applying the MutationBatch to the + * backend. + */ + applyToRemoteDocument( + docKey: DocumentKey, + maybeDoc: MaybeDocument | null, + batchResult: MutationBatchResult + ): MaybeDocument | null; + /** + * Computes the local view of a document given all the mutations in this + * batch. + * + * @param docKey - The key of the document to apply mutations to. + * @param maybeDoc - The document to apply mutations to. + */ + applyToLocalView( + docKey: DocumentKey, + maybeDoc: MaybeDocument | null + ): MaybeDocument | null; + /** + * Computes the local view for all provided documents given the mutations in + * this batch. + */ + applyToLocalDocumentSet(maybeDocs: MaybeDocumentMap): MaybeDocumentMap; + keys(): DocumentKeySet; + isEqual(other: MutationBatch): boolean; +} + +/** The result of applying a mutation batch to the backend. */ +declare class MutationBatchResult { + readonly batch: MutationBatch; + readonly commitVersion: SnapshotVersion; + readonly mutationResults: MutationResult[]; + /** + * A pre-computed mapping from each mutated document to the resulting + * version. + */ + readonly docVersions: DocumentVersionMap; + private constructor(); + /** + * Creates a new MutationBatchResult for the given batch and results. There + * must be one result for each mutation in the batch. This static factory + * caches a document=>version mapping (docVersions). + */ + static from( + batch: MutationBatch, + commitVersion: SnapshotVersion, + results: MutationResult[] + ): MutationBatchResult; +} + +/** A queue of mutations to apply to the remote store. */ +declare interface MutationQueue { + /** Returns true if this queue contains no mutation batches. */ + checkEmpty(transaction: PersistenceTransaction): PersistencePromise; + /** + * Creates a new mutation batch and adds it to this mutation queue. + * + * @param transaction - The transaction this operation is scoped to. + * @param localWriteTime - The original write time of this mutation. + * @param baseMutations - Mutations that are used to populate the base values + * when this mutation is applied locally. These mutations are used to locally + * overwrite values that are persisted in the remote document cache. + * @param mutations - The user-provided mutations in this mutation batch. + */ + addMutationBatch( + transaction: PersistenceTransaction, + localWriteTime: Timestamp, + baseMutations: Mutation[], + mutations: Mutation[] + ): PersistencePromise; + /** + * Loads the mutation batch with the given batchId. + */ + lookupMutationBatch( + transaction: PersistenceTransaction, + batchId: BatchId + ): PersistencePromise; + /** + * Gets the first unacknowledged mutation batch after the passed in batchId + * in the mutation queue or null if empty. + * + * @param batchId - The batch to search after, or BATCHID_UNKNOWN for the + * first mutation in the queue. + * + * @returns the next mutation or null if there wasn't one. + */ + getNextMutationBatchAfterBatchId( + transaction: PersistenceTransaction, + batchId: BatchId + ): PersistencePromise; + /** + * Gets the largest (latest) batch id in mutation queue for the current user + * that is pending server response, returns `BATCHID_UNKNOWN` if the queue is + * empty. + * + * @returns the largest batch id in the mutation queue that is not + * acknowledged. + */ + getHighestUnacknowledgedBatchId( + transaction: PersistenceTransaction + ): PersistencePromise; + /** Gets all mutation batches in the mutation queue. */ + getAllMutationBatches( + transaction: PersistenceTransaction + ): PersistencePromise; + /** + * Finds all mutation batches that could possibly affect the given + * document key. Not all mutations in a batch will necessarily affect the + * document key, so when looping through the batch you'll need to check that + * the mutation itself matches the key. + * + * Batches are guaranteed to be in sorted order. + * + * Note that because of this requirement implementations are free to return + * mutation batches that don't contain the document key at all if it's + * convenient. + */ + getAllMutationBatchesAffectingDocumentKey( + transaction: PersistenceTransaction, + documentKey: DocumentKey + ): PersistencePromise; + /** + * Finds all mutation batches that could possibly affect the given set of + * document keys. Not all mutations in a batch will necessarily affect each + * key, so when looping through the batch you'll need to check that the + * mutation itself matches the key. + * + * Batches are guaranteed to be in sorted order. + * + * Note that because of this requirement implementations are free to return + * mutation batches that don't contain any of the document keys at all if it's + * convenient. + */ + getAllMutationBatchesAffectingDocumentKeys( + transaction: PersistenceTransaction, + documentKeys: SortedMap + ): PersistencePromise; + /** + * Finds all mutation batches that could affect the results for the given + * query. Not all mutations in a batch will necessarily affect the query, so + * when looping through the batch you'll need to check that the mutation + * itself matches the query. + * + * Batches are guaranteed to be in sorted order. + * + * Note that because of this requirement implementations are free to return + * mutation batches that don't match the query at all if it's convenient. + * + * NOTE: A PatchMutation does not need to include all fields in the query + * filter criteria in order to be a match (but any fields it does contain do + * need to match). + */ + getAllMutationBatchesAffectingQuery( + transaction: PersistenceTransaction, + query: Query_2 + ): PersistencePromise; + /** + * Removes the given mutation batch from the queue. This is useful in two + * circumstances: + * + * + Removing an applied mutation from the head of the queue + * + Removing a rejected mutation from anywhere in the queue + * + * Multi-Tab Note: This operation should only be called by the primary client. + */ + removeMutationBatch( + transaction: PersistenceTransaction, + batch: MutationBatch + ): PersistencePromise; + /** + * Performs a consistency check, examining the mutation queue for any + * leaks, if possible. + */ + performConsistencyCheck( + transaction: PersistenceTransaction + ): PersistencePromise; +} + +/** The result of successfully applying a mutation to the backend. */ +declare class MutationResult { + /** + * The version at which the mutation was committed: + * + * - For most operations, this is the updateTime in the WriteResult. + * - For deletes, the commitTime of the WriteResponse (because deletes are + * not stored and have no updateTime). + * + * Note that these versions can be different: No-op writes will not change + * the updateTime even though the commitTime advances. + */ + readonly version: SnapshotVersion; + /** + * The resulting fields returned from the backend after a mutation + * containing field transforms has been committed. Contains one FieldValue + * for each FieldTransform that was in the mutation. + * + * Will be null if the mutation did not contain any field transforms. + */ + readonly transformResults: Array | null; + constructor( + /** + * The version at which the mutation was committed: + * + * - For most operations, this is the updateTime in the WriteResult. + * - For deletes, the commitTime of the WriteResponse (because deletes are + * not stored and have no updateTime). + * + * Note that these versions can be different: No-op writes will not change + * the updateTime even though the commitTime advances. + */ + version: SnapshotVersion, + /** + * The resulting fields returned from the backend after a mutation + * containing field transforms has been committed. Contains one FieldValue + * for each FieldTransform that was in the mutation. + * + * Will be null if the mutation did not contain any field transforms. + */ + transformResults: Array | null + ); +} + +declare const enum MutationType { + Set = 0, + Patch = 1, + Delete = 2, + Verify = 3 +} + +/** + * Represents a Query saved by the SDK in its local storage. + */ +declare interface NamedQuery { + /** The name of the query. */ + readonly name: string; + /** The underlying query associated with `name`. */ + readonly query: Query_2; + /** The time at which the results for this query were read. */ + readonly readTime: SnapshotVersion; +} + +/** + * Reads a Firestore `Query` from local cache, identified by the given name. + * + * The named queries are packaged into bundles on the server side (along + * with resulting documents), and loaded to local cache using `loadBundle`. Once in local + * cache, use this method to extract a `Query` by name. + */ +export declare function namedQuery( + firestore: FirebaseFirestore, + name: string +): Promise; + +/** Properties of a NamedQuery. */ +declare interface NamedQuery_2 { + /** NamedQuery name */ + name?: string | null; + /** NamedQuery bundledQuery */ + bundledQuery?: BundledQuery | null; + /** NamedQuery readTime */ + readTime?: Timestamp_2 | null; +} + +declare type NullableMaybeDocumentMap = SortedMap< + DocumentKey, + MaybeDocument | null +>; + +/** + * @license + * Copyright 2017 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. + */ +/** + * A map implementation that uses objects as keys. Objects must have an + * associated equals function and must be immutable. Entries in the map are + * stored together with the key being produced from the mapKeyFn. This map + * automatically handles collisions of keys. + */ +declare class ObjectMap { + private mapKeyFn; + private equalsFn; + /** + * The inner map for a key/value pair. Due to the possibility of collisions we + * keep a list of entries that we do a linear search through to find an actual + * match. Note that collisions should be rare, so we still expect near + * constant time lookups in practice. + */ + private inner; + constructor( + mapKeyFn: (key: KeyType) => string, + equalsFn: (l: KeyType, r: KeyType) => boolean + ); + /** Get a value for this key, or undefined if it does not exist. */ + get(key: KeyType): ValueType | undefined; + has(key: KeyType): boolean; + /** Put this key and value in the map. */ + set(key: KeyType, value: ValueType): void; + /** + * Remove this key from the map. Returns a boolean if anything was deleted. + */ + delete(key: KeyType): boolean; + forEach(fn: (key: KeyType, val: ValueType) => void): void; + isEmpty(): boolean; +} + +/** + * An ObjectValue represents a MapValue in the Firestore Proto and offers the + * ability to add and remove fields (via the ObjectValueBuilder). + */ +declare class ObjectValue { + readonly proto: { + mapValue: MapValue; + }; + constructor(proto: { mapValue: MapValue }); + static empty(): ObjectValue; + /** + * Returns the value at the given path or null. + * + * @param path - the path to search + * @returns The value at the path or if there it doesn't exist. + */ + field(path: FieldPath_2): Value | null; + isEqual(other: ObjectValue): boolean; +} + +/** + * Initializes and wires components that are needed to interface with the local + * cache. Implementations override `initialize()` to provide all components. + */ +declare interface OfflineComponentProvider { + persistence: Persistence; + sharedClientState: SharedClientState; + localStore: LocalStore; + gcScheduler: GarbageCollectionScheduler | null; + synchronizeTabs: boolean; + initialize(cfg: ComponentConfiguration): Promise; + terminate(): Promise; +} + +/** + * Initializes and wires the components that are needed to interface with the + * network. + */ +declare class OnlineComponentProvider { + protected localStore: LocalStore; + protected sharedClientState: SharedClientState; + datastore: Datastore; + eventManager: EventManager; + remoteStore: RemoteStore; + syncEngine: SyncEngine; + initialize( + offlineComponentProvider: OfflineComponentProvider, + cfg: ComponentConfiguration + ): Promise; + createEventManager(cfg: ComponentConfiguration): EventManager; + createDatastore(cfg: ComponentConfiguration): Datastore; + createRemoteStore(cfg: ComponentConfiguration): RemoteStore; + createSyncEngine( + cfg: ComponentConfiguration, + startAsPrimary: boolean + ): SyncEngine; + terminate(): Promise; +} + +/** + * Describes the online state of the Firestore client. Note that this does not + * indicate whether or not the remote store is trying to connect or not. This is + * primarily used by the View / EventManager code to change their behavior while + * offline (e.g. get() calls shouldn't wait for data from the server and + * snapshot events should set metadata.isFromCache=true). + * + * The string values should not be changed since they are persisted in + * WebStorage. + */ +declare const enum OnlineState { + /** + * The Firestore client is in an unknown online state. This means the client + * is either not actively trying to establish a connection or it is currently + * trying to establish a connection, but it has not succeeded or failed yet. + * Higher-level components should not operate in offline mode. + */ + Unknown = 'Unknown', + /** + * The client is connected and the connections are healthy. This state is + * reached after a successful connection and there has been at least one + * successful message received from the backends. + */ + Online = 'Online', + /** + * The client is either trying to establish a connection but failing, or it + * has been explicitly marked offline via a call to disableNetwork(). + * Higher-level components should operate in offline mode. + */ + Offline = 'Offline' +} + +/** + * Attaches a listener for `DocumentSnapshot` events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param reference - A reference to the document to listen to. + * @param observer - A single object containing `next` and `error` callbacks. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export declare function onSnapshot( + reference: DocumentReference, + observer: { + next?: (snapshot: DocumentSnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; + } +): Unsubscribe; + +/** + * Attaches a listener for `DocumentSnapshot` events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param reference - A reference to the document to listen to. + * @param options - Options controlling the listen behavior. + * @param observer - A single object containing `next` and `error` callbacks. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export declare function onSnapshot( + reference: DocumentReference, + options: SnapshotListenOptions, + observer: { + next?: (snapshot: DocumentSnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; + } +): Unsubscribe; + +/** + * Attaches a listener for `DocumentSnapshot` events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param reference - A reference to the document to listen to. + * @param onNext - A callback to be called every time a new `DocumentSnapshot` + * is available. + * @param onError - A callback to be called if the listen fails or is + * cancelled. No further callbacks will occur. + * @param onCompletion - Can be provided, but will not be called since streams are + * never ending. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export declare function onSnapshot( + reference: DocumentReference, + onNext: (snapshot: DocumentSnapshot) => void, + onError?: (error: FirestoreError) => void, + onCompletion?: () => void +): Unsubscribe; + +/** + * Attaches a listener for `DocumentSnapshot` events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param reference - A reference to the document to listen to. + * @param options - Options controlling the listen behavior. + * @param onNext - A callback to be called every time a new `DocumentSnapshot` + * is available. + * @param onError - A callback to be called if the listen fails or is + * cancelled. No further callbacks will occur. + * @param onCompletion - Can be provided, but will not be called since streams are + * never ending. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export declare function onSnapshot( + reference: DocumentReference, + options: SnapshotListenOptions, + onNext: (snapshot: DocumentSnapshot) => void, + onError?: (error: FirestoreError) => void, + onCompletion?: () => void +): Unsubscribe; + +/** + * Attaches a listener for `QuerySnapshot` events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. The listener can be cancelled by + * calling the function that is returned when `onSnapshot` is called. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param query - The query to listen to. + * @param observer - A single object containing `next` and `error` callbacks. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export declare function onSnapshot( + query: Query, + observer: { + next?: (snapshot: QuerySnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; + } +): Unsubscribe; + +/** + * Attaches a listener for `QuerySnapshot` events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. The listener can be cancelled by + * calling the function that is returned when `onSnapshot` is called. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param query - The query to listen to. + * @param options - Options controlling the listen behavior. + * @param observer - A single object containing `next` and `error` callbacks. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export declare function onSnapshot( + query: Query, + options: SnapshotListenOptions, + observer: { + next?: (snapshot: QuerySnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; + } +): Unsubscribe; + +/** + * Attaches a listener for `QuerySnapshot` events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. The listener can be cancelled by + * calling the function that is returned when `onSnapshot` is called. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param query - The query to listen to. + * @param onNext - A callback to be called every time a new `QuerySnapshot` + * is available. + * @param onCompletion - Can be provided, but will not be called since streams are + * never ending. + * @param onError - A callback to be called if the listen fails or is + * cancelled. No further callbacks will occur. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export declare function onSnapshot( + query: Query, + onNext: (snapshot: QuerySnapshot) => void, + onError?: (error: FirestoreError) => void, + onCompletion?: () => void +): Unsubscribe; + +/** + * Attaches a listener for `QuerySnapshot` events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. The listener can be cancelled by + * calling the function that is returned when `onSnapshot` is called. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param query - The query to listen to. + * @param options - Options controlling the listen behavior. + * @param onNext - A callback to be called every time a new `QuerySnapshot` + * is available. + * @param onCompletion - Can be provided, but will not be called since streams are + * never ending. + * @param onError - A callback to be called if the listen fails or is + * cancelled. No further callbacks will occur. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export declare function onSnapshot( + query: Query, + options: SnapshotListenOptions, + onNext: (snapshot: QuerySnapshot) => void, + onError?: (error: FirestoreError) => void, + onCompletion?: () => void +): Unsubscribe; + +/** + * Attaches a listener for a snapshots-in-sync event. The snapshots-in-sync + * event indicates that all listeners affected by a given change have fired, + * even if a single server-generated change affects multiple listeners. + * + * NOTE: The snapshots-in-sync event only indicates that listeners are in sync + * with each other, but does not relate to whether those snapshots are in sync + * with the server. Use SnapshotMetadata in the individual listeners to + * determine if a snapshot is from the cache or the server. + * + * @param firestore - The instance of Firestore for synchronizing snapshots. + * @param observer - A single object containing `next` and `error` callbacks. + * @returns An unsubscribe function that can be called to cancel the snapshot + * listener. + */ +export declare function onSnapshotsInSync( + firestore: FirebaseFirestore, + observer: { + next?: (value: void) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; + } +): Unsubscribe; + +/** + * Attaches a listener for a snapshots-in-sync event. The snapshots-in-sync + * event indicates that all listeners affected by a given change have fired, + * even if a single server-generated change affects multiple listeners. + * + * NOTE: The snapshots-in-sync event only indicates that listeners are in sync + * with each other, but does not relate to whether those snapshots are in sync + * with the server. Use SnapshotMetadata in the individual listeners to + * determine if a snapshot is from the cache or the server. + * + * @param firestore - The instance of Firestore for synchronizing snapshots. + * @param onSync - A callback to be called every time all snapshot listeners are + * in sync with each other. + * @returns An unsubscribe function that can be called to cancel the snapshot + * listener. + */ +export declare function onSnapshotsInSync( + firestore: FirebaseFirestore, + onSync: () => void +): Unsubscribe; + +/** + * An ordering on a field, in some Direction. Direction defaults to ASCENDING. + */ +declare class OrderBy { + readonly field: FieldPath_2; + readonly dir: Direction; + constructor(field: FieldPath_2, dir?: Direction); +} + +/** + * Creates a `QueryConstraint` that sorts the query result by the + * specified field, optionally in descending order instead of ascending. + * + * @param fieldPath - The field to sort by. + * @param directionStr - Optional direction to sort by ('asc' or 'desc'). If + * not specified, order will be ascending. + * @returns The created `Query`. + */ +export declare function orderBy( + fieldPath: string | FieldPath, + directionStr?: OrderByDirection +): QueryConstraint; + +/** + * The direction of a {@link orderBy} clause is specified as 'desc' or 'asc' + * (descending or ascending). + */ +export declare type OrderByDirection = 'desc' | 'asc'; + +declare type OrderDirection = + | 'DIRECTION_UNSPECIFIED' + | 'ASCENDING' + | 'DESCENDING'; + +declare interface ParseContext { + readonly databaseId: DatabaseId; + readonly ignoreUndefinedProperties: boolean; +} + +/** The result of parsing document data (e.g. for a setData call). */ +declare class ParsedSetData { + readonly data: ObjectValue; + readonly fieldMask: FieldMask | null; + readonly fieldTransforms: FieldTransform[]; + constructor( + data: ObjectValue, + fieldMask: FieldMask | null, + fieldTransforms: FieldTransform[] + ); + toMutation(key: DocumentKey, precondition: Precondition): Mutation; +} + +/** The result of parsing "update" data (i.e. for an updateData call). */ +declare class ParsedUpdateData { + readonly data: ObjectValue; + readonly fieldMask: FieldMask; + readonly fieldTransforms: FieldTransform[]; + constructor( + data: ObjectValue, + fieldMask: FieldMask, + fieldTransforms: FieldTransform[] + ); + toMutation(key: DocumentKey, precondition: Precondition): Mutation; +} + +/** + * Persistence is the lowest-level shared interface to persistent storage in + * Firestore. + * + * Persistence is used to create MutationQueue and RemoteDocumentCache + * instances backed by persistence (which might be in-memory or LevelDB). + * + * Persistence also exposes an API to create and run PersistenceTransactions + * against persistence. All read / write operations must be wrapped in a + * transaction. Implementations of PersistenceTransaction / Persistence only + * need to guarantee that writes made against the transaction are not made to + * durable storage until the transaction resolves its PersistencePromise. + * Since memory-only storage components do not alter durable storage, they are + * free to ignore the transaction. + * + * This contract is enough to allow the LocalStore be be written + * independently of whether or not the stored state actually is durably + * persisted. If persistent storage is enabled, writes are grouped together to + * avoid inconsistent state that could cause crashes. + * + * Concretely, when persistent storage is enabled, the persistent versions of + * MutationQueue, RemoteDocumentCache, and others (the mutators) will + * defer their writes into a transaction. Once the local store has completed + * one logical operation, it commits the transaction. + * + * When persistent storage is disabled, the non-persistent versions of the + * mutators ignore the transaction. This short-cut is allowed because + * memory-only storage leaves no state so it cannot be inconsistent. + * + * This simplifies the implementations of the mutators and allows memory-only + * implementations to supplement the persistent ones without requiring any + * special dual-store implementation of Persistence. The cost is that the + * LocalStore needs to be slightly careful about the order of its reads and + * writes in order to avoid relying on being able to read back uncommitted + * writes. + */ +declare interface Persistence { + /** + * Whether or not this persistence instance has been started. + */ + readonly started: boolean; + readonly referenceDelegate: ReferenceDelegate; + /** Starts persistence. */ + start(): Promise; + /** + * Releases any resources held during eager shutdown. + */ + shutdown(): Promise; + /** + * Registers a listener that gets called when the database receives a + * version change event indicating that it has deleted. + * + * PORTING NOTE: This is only used for Web multi-tab. + */ + setDatabaseDeletedListener( + databaseDeletedListener: () => Promise + ): void; + /** + * Adjusts the current network state in the client's metadata, potentially + * affecting the primary lease. + * + * PORTING NOTE: This is only used for Web multi-tab. + */ + setNetworkEnabled(networkEnabled: boolean): void; + /** + * Returns a MutationQueue representing the persisted mutations for the + * given user. + * + * Note: The implementation is free to return the same instance every time + * this is called for a given user. In particular, the memory-backed + * implementation does this to emulate the persisted implementation to the + * extent possible (e.g. in the case of uid switching from + * sally=>jack=>sally, sally's mutation queue will be preserved). + */ + getMutationQueue(user: User): MutationQueue; + /** + * Returns a TargetCache representing the persisted cache of targets. + * + * Note: The implementation is free to return the same instance every time + * this is called. In particular, the memory-backed implementation does this + * to emulate the persisted implementation to the extent possible. + */ + getTargetCache(): TargetCache; + /** + * Returns a RemoteDocumentCache representing the persisted cache of remote + * documents. + * + * Note: The implementation is free to return the same instance every time + * this is called. In particular, the memory-backed implementation does this + * to emulate the persisted implementation to the extent possible. + */ + getRemoteDocumentCache(): RemoteDocumentCache; + /** + * Returns a BundleCache representing the persisted cache of loaded bundles. + * + * Note: The implementation is free to return the same instance every time + * this is called. In particular, the memory-backed implementation does this + * to emulate the persisted implementation to the extent possible. + */ + getBundleCache(): BundleCache; + /** + * Returns an IndexManager instance that manages our persisted query indexes. + * + * Note: The implementation is free to return the same instance every time + * this is called. In particular, the memory-backed implementation does this + * to emulate the persisted implementation to the extent possible. + */ + getIndexManager(): IndexManager; + /** + * Performs an operation inside a persistence transaction. Any reads or writes + * against persistence must be performed within a transaction. Writes will be + * committed atomically once the transaction completes. + * + * Persistence operations are asynchronous and therefore the provided + * transactionOperation must return a PersistencePromise. When it is resolved, + * the transaction will be committed and the Promise returned by this method + * will resolve. + * + * @param action - A description of the action performed by this transaction, + * used for logging. + * @param mode - The underlying mode of the IndexedDb transaction. Can be + * 'readonly', 'readwrite' or 'readwrite-primary'. Transactions marked + * 'readwrite-primary' can only be executed by the primary client. In this + * mode, the transactionOperation will not be run if the primary lease cannot + * be acquired and the returned promise will be rejected with a + * FAILED_PRECONDITION error. + * @param transactionOperation - The operation to run inside a transaction. + * @returns A promise that is resolved once the transaction completes. + */ + runTransaction( + action: string, + mode: PersistenceTransactionMode, + transactionOperation: ( + transaction: PersistenceTransaction + ) => PersistencePromise + ): Promise; +} + +/** + * PersistencePromise is essentially a re-implementation of Promise except + * it has a .next() method instead of .then() and .next() and .catch() callbacks + * are executed synchronously when a PersistencePromise resolves rather than + * asynchronously (Promise implementations use setImmediate() or similar). + * + * This is necessary to interoperate with IndexedDB which will automatically + * commit transactions if control is returned to the event loop without + * synchronously initiating another operation on the transaction. + * + * NOTE: .then() and .catch() only allow a single consumer, unlike normal + * Promises. + */ +declare class PersistencePromise { + private nextCallback; + private catchCallback; + private result; + private error; + private isDone; + private callbackAttached; + constructor(callback: (resolve: Resolver, reject: Rejector) => void); + catch( + fn: (error: Error) => R | PersistencePromise + ): PersistencePromise; + next( + nextFn?: FulfilledHandler, + catchFn?: RejectedHandler + ): PersistencePromise; + toPromise(): Promise; + private wrapUserFunction; + private wrapSuccess; + private wrapFailure; + static resolve(): PersistencePromise; + static resolve(result: R): PersistencePromise; + static reject(error: Error): PersistencePromise; + static waitFor(all: { + forEach: (cb: (el: PersistencePromise) => void) => void; + }): PersistencePromise; + /** + * Given an array of predicate functions that asynchronously evaluate to a + * boolean, implements a short-circuiting `or` between the results. Predicates + * will be evaluated until one of them returns `true`, then stop. The final + * result will be whether any of them returned `true`. + */ + static or( + predicates: Array<() => PersistencePromise> + ): PersistencePromise; + /** + * Given an iterable, call the given function on each element in the + * collection and wait for all of the resulting concurrent PersistencePromises + * to resolve. + */ + static forEach( + collection: { + forEach: (cb: (r: R, s: S) => void) => void; + }, + f: + | ((r: R, s: S) => PersistencePromise) + | ((r: R) => PersistencePromise) + ): PersistencePromise; + static forEach( + collection: { + forEach: (cb: (r: R) => void) => void; + }, + f: (r: R) => PersistencePromise + ): PersistencePromise; +} + +export declare interface PersistenceSettings { + forceOwnership?: boolean; +} + +/** + * A base class representing a persistence transaction, encapsulating both the + * transaction's sequence numbers as well as a list of onCommitted listeners. + * + * When you call Persistence.runTransaction(), it will create a transaction and + * pass it to your callback. You then pass it to any method that operates + * on persistence. + */ +declare abstract class PersistenceTransaction { + private readonly onCommittedListeners; + abstract readonly currentSequenceNumber: ListenSequenceNumber; + addOnCommittedListener(listener: () => void): void; + raiseOnCommittedEvent(): void; +} + +/** The different modes supported by `Persistence.runTransaction()`. */ +declare type PersistenceTransactionMode = + | 'readonly' + | 'readwrite' + | 'readwrite-primary'; + +/** + * Encodes a precondition for a mutation. This follows the model that the + * backend accepts with the special case of an explicit "empty" precondition + * (meaning no precondition). + */ +declare class Precondition { + readonly updateTime?: SnapshotVersion | undefined; + readonly exists?: boolean | undefined; + private constructor(); + /** Creates a new empty Precondition. */ + static none(): Precondition; + /** Creates a new Precondition with an exists flag. */ + static exists(exists: boolean): Precondition; + /** Creates a new Precondition based on a version a document exists at. */ + static updateTime(version: SnapshotVersion): Precondition; + /** Returns whether this Precondition is empty. */ + get isNone(): boolean; + isEqual(other: Precondition): boolean; +} + +/** Undocumented, private additional settings not exposed in our public API. */ +declare interface PrivateSettings extends Settings_2 { + credentials?: CredentialsSettings; +} + +declare interface ProviderCredentialsSettings { + ['type']: 'provider'; + ['client']: CredentialsProvider; +} + +/** + * A `Query` refers to a Query which you can read or listen to. You can also + * construct refined `Query` objects by adding filters and ordering. + */ +export declare class Query { + readonly _converter: FirestoreDataConverter_2 | null; + readonly _query: Query_2; + /** The type of this Firestore reference. */ + readonly type: 'query' | 'collection'; + /** + * The `FirebaseFirestore` for the Firestore database (useful for performing + * transactions, etc.). + */ + readonly firestore: FirebaseFirestore_2; + /** @hideconstructor protected */ + constructor( + firestore: FirebaseFirestore_2, + _converter: FirestoreDataConverter_2 | null, + _query: Query_2 + ); + /** + * Applies a custom data converter to this query, allowing you to use your own + * custom model objects with Firestore. When you call {@link getDocs} with + * the returned query, the provided converter will convert between Firestore + * data and your custom type `U`. + * + * @param converter - Converts objects to and from Firestore. + * @returns A `Query` that uses the provided converter. + */ + withConverter(converter: FirestoreDataConverter_2): Query; +} + +/** + * Creates a new immutable instance of `query` that is extended to also include + * additional query constraints. + * + * @param query - The query instance to use as a base for the new constraints. + * @param queryConstraints - The list of `QueryConstraint`s to apply. + * @throws if any of the provided query constraints cannot be combined with the + * existing or new constraints. + */ +export declare function query( + query: Query, + ...queryConstraints: QueryConstraint[] +): Query; + +/** + * The Query interface defines all external properties of a query. + * + * QueryImpl implements this interface to provide memoization for `queryOrderBy` + * and `queryToTarget`. + */ +declare interface Query_2 { + readonly path: ResourcePath; + readonly collectionGroup: string | null; + readonly explicitOrderBy: OrderBy[]; + readonly filters: Filter[]; + readonly limit: number | null; + readonly limitType: LimitType; + readonly startAt: Bound | null; + readonly endAt: Bound | null; +} + +/** + * A `QueryConstraint` is used to narrow the set of documents returned by a + * Firestore query. `QueryConstraint`s are created by invoking {@link where}, + * {@link orderBy}, {@link startAt}, {@link startAfter}, {@link + * endBefore}, {@link endAt}, {@link limit} or {@link limitToLast} and + * can then be passed to {@link query} to create a new query instance that + * also contains this `QueryConstraint`. + */ +export declare abstract class QueryConstraint { + /** The type of this query constraints */ + abstract readonly type: QueryConstraintType; + /** + * Takes the provided `Query` and returns a copy of the `Query` with this + * `QueryConstraint` applied. + */ + abstract _apply(query: Query): Query; +} + +/** Describes the different query constraints available in this SDK. */ +export declare type QueryConstraintType = + | 'where' + | 'orderBy' + | 'limit' + | 'limitToLast' + | 'startAt' + | 'startAfter' + | 'endAt' + | 'endBefore'; + +/** + * A `QueryDocumentSnapshot` contains data read from a document in your + * Firestore database as part of a query. The document is guaranteed to exist + * and its data can be extracted with `.data()` or `.get()` to get a + * specific field. + * + * A `QueryDocumentSnapshot` offers the same API surface as a + * `DocumentSnapshot`. Since query results contain only existing documents, the + * `exists` property will always be true and `data()` will never return + * 'undefined'. + */ +export declare class QueryDocumentSnapshot< + T = DocumentData +> extends DocumentSnapshot { + /** + * Retrieves all fields in the document as an `Object`. + * + * By default, `FieldValue.serverTimestamp()` values that have not yet been + * set to their final value will be returned as `null`. You can override + * this by passing an options object. + * + * @override + * @param options - An options object to configure how data is retrieved from + * the snapshot (for example the desired behavior for server timestamps that + * have not yet been set to their final value). + * @returns An `Object` containing all fields in the document. + */ + data(options?: SnapshotOptions): T; +} + +/** + * A `QueryDocumentSnapshot` contains data read from a document in your + * Firestore database as part of a query. The document is guaranteed to exist + * and its data can be extracted with `.data()` or `.get()` to get a + * specific field. + * + * A `QueryDocumentSnapshot` offers the same API surface as a + * `DocumentSnapshot`. Since query results contain only existing documents, the + * `exists` property will always be true and `data()` will never return + * 'undefined'. + */ +declare class QueryDocumentSnapshot_2< + T = DocumentData +> extends DocumentSnapshot_2 { + /** + * Retrieves all fields in the document as an `Object`. + * + * @override + * @returns An `Object` containing all fields in the document. + */ + data(): T; +} + +/** + * Returns true if the provided queries point to the same collection and apply + * the same constraints. + * + * @param left - A `Query` to compare. + * @param right - A `Query` to compare. + * @returns true if the references point to the same location in the same + * Firestore database. + */ +export declare function queryEqual(left: Query, right: Query): boolean; + +/** + * A `QuerySnapshot` contains zero or more `DocumentSnapshot` objects + * representing the results of a query. The documents can be accessed as an + * array via the `docs` property or enumerated using the `forEach` method. The + * number of documents can be determined via the `empty` and `size` + * properties. + */ +export declare class QuerySnapshot { + readonly _firestore: FirebaseFirestore; + readonly _userDataWriter: AbstractUserDataWriter; + readonly _snapshot: ViewSnapshot; + /** + * Metadata about this snapshot, concerning its source and if it has local + * modifications. + */ + readonly metadata: SnapshotMetadata; + /** + * The query on which you called `get` or `onSnapshot` in order to get this + * `QuerySnapshot`. + */ + readonly query: Query; + private _cachedChanges?; + private _cachedChangesIncludeMetadataChanges?; + /** @hideconstructor */ + constructor( + _firestore: FirebaseFirestore, + _userDataWriter: AbstractUserDataWriter, + query: Query, + _snapshot: ViewSnapshot + ); + /** An array of all the documents in the `QuerySnapshot`. */ + get docs(): Array>; + /** The number of documents in the `QuerySnapshot`. */ + get size(): number; + /** True if there are no documents in the `QuerySnapshot`. */ + get empty(): boolean; + /** + * Enumerates all of the documents in the `QuerySnapshot`. + * + * @param callback - A callback to be called with a `QueryDocumentSnapshot` for + * each document in the snapshot. + * @param thisArg - The `this` binding for the callback. + */ + forEach( + callback: (result: QueryDocumentSnapshot) => void, + thisArg?: unknown + ): void; + /** + * Returns an array of the documents changes since the last snapshot. If this + * is the first snapshot, all documents will be in the list as 'added' + * changes. + * + * @param options - `SnapshotListenOptions` that control whether metadata-only + * changes (i.e. only `DocumentSnapshot.metadata` changed) should trigger + * snapshot events. + */ + docChanges(options?: SnapshotListenOptions): Array>; +} + +/** The different states of a watch target. */ +declare type QueryTargetState = 'not-current' | 'current' | 'rejected'; + +/** + * Returns true if the provided references are equal. + * + * @param left - A reference to compare. + * @param right - A reference to compare. + * @returns true if the references point to the same location in the same + * Firestore database. + */ +export declare function refEqual( + left: DocumentReference | CollectionReference, + right: DocumentReference | CollectionReference +): boolean; + +/** + * A ReferenceDelegate instance handles all of the hooks into the document-reference lifecycle. This + * includes being added to a target, being removed from a target, being subject to mutation, and + * being mutated by the user. + * + * Different implementations may do different things with each of these events. Not every + * implementation needs to do something with every lifecycle hook. + * + * PORTING NOTE: since sequence numbers are attached to transactions in this + * client, the ReferenceDelegate does not need to deal in transactional + * semantics (onTransactionStarted/Committed()), nor does it need to track and + * generate sequence numbers (getCurrentSequenceNumber()). + */ +declare interface ReferenceDelegate { + /** Notify the delegate that the given document was added to a target. */ + addReference( + txn: PersistenceTransaction, + targetId: TargetId, + doc: DocumentKey + ): PersistencePromise; + /** Notify the delegate that the given document was removed from a target. */ + removeReference( + txn: PersistenceTransaction, + targetId: TargetId, + doc: DocumentKey + ): PersistencePromise; + /** + * Notify the delegate that a target was removed. The delegate may, but is not obligated to, + * actually delete the target and associated data. + */ + removeTarget( + txn: PersistenceTransaction, + targetData: TargetData + ): PersistencePromise; + /** + * Notify the delegate that a document may no longer be part of any views or + * have any mutations associated. + */ + markPotentiallyOrphaned( + txn: PersistenceTransaction, + doc: DocumentKey + ): PersistencePromise; + /** Notify the delegate that a limbo document was updated. */ + updateLimboDocument( + txn: PersistenceTransaction, + doc: DocumentKey + ): PersistencePromise; +} + +declare type RejectedHandler = + | ((reason: Error) => R | PersistencePromise) + | null; + +declare type Rejector = (error: Error) => void; + +/** + * Represents cached documents received from the remote backend. + * + * The cache is keyed by DocumentKey and entries in the cache are MaybeDocument + * instances, meaning we can cache both Document instances (an actual document + * with data) as well as NoDocument instances (indicating that the document is + * known to not exist). + */ +declare interface RemoteDocumentCache { + /** + * Looks up an entry in the cache. + * + * @param documentKey - The key of the entry to look up.* + * @returns The cached Document or NoDocument entry, or null if we have + * nothing cached. + */ + getEntry( + transaction: PersistenceTransaction, + documentKey: DocumentKey + ): PersistencePromise; + /** + * Looks up a set of entries in the cache. + * + * @param documentKeys - The keys of the entries to look up. + * @returns The cached Document or NoDocument entries indexed by key. If an + * entry is not cached, the corresponding key will be mapped to a null value. + */ + getEntries( + transaction: PersistenceTransaction, + documentKeys: DocumentKeySet + ): PersistencePromise; + /** + * Executes a query against the cached Document entries. + * + * Implementations may return extra documents if convenient. The results + * should be re-filtered by the consumer before presenting them to the user. + * + * Cached NoDocument entries have no bearing on query results. + * + * @param query - The query to match documents against. + * @param sinceReadTime - If not set to SnapshotVersion.min(), return only + * documents that have been read since this snapshot version (exclusive). + * @returns The set of matching documents. + */ + getDocumentsMatchingQuery( + transaction: PersistenceTransaction, + query: Query_2, + sinceReadTime: SnapshotVersion + ): PersistencePromise; + /** + * Provides access to add or update the contents of the cache. The buffer + * handles proper size accounting for the change. + * + * Multi-Tab Note: This should only be called by the primary client. + * + * @param options - Specify `trackRemovals` to create sentinel entries for + * removed documents, which allows removals to be tracked by + * `getNewDocumentChanges()`. + */ + newChangeBuffer(options?: { + trackRemovals: boolean; + }): RemoteDocumentChangeBuffer; + /** + * Get an estimate of the size of the document cache. Note that for eager + * garbage collection, we don't track sizes so this will return 0. + */ + getSize(transaction: PersistenceTransaction): PersistencePromise; +} + +/** + * Represents a document change to be applied to remote document cache. + */ +declare interface RemoteDocumentChange { + readonly maybeDocument: MaybeDocument | null; + readonly readTime: SnapshotVersion | null; +} + +/** + * An in-memory buffer of entries to be written to a RemoteDocumentCache. + * It can be used to batch up a set of changes to be written to the cache, but + * additionally supports reading entries back with the `getEntry()` method, + * falling back to the underlying RemoteDocumentCache if no entry is + * buffered. + * + * Entries added to the cache *must* be read first. This is to facilitate + * calculating the size delta of the pending changes. + * + * PORTING NOTE: This class was implemented then removed from other platforms. + * If byte-counting ends up being needed on the other platforms, consider + * porting this class as part of that implementation work. + */ +declare abstract class RemoteDocumentChangeBuffer { + protected changes: ObjectMap; + private changesApplied; + protected abstract getFromCache( + transaction: PersistenceTransaction, + documentKey: DocumentKey + ): PersistencePromise; + protected abstract getAllFromCache( + transaction: PersistenceTransaction, + documentKeys: DocumentKeySet + ): PersistencePromise; + protected abstract applyChanges( + transaction: PersistenceTransaction + ): PersistencePromise; + protected getReadTime(key: DocumentKey): SnapshotVersion; + /** + * Buffers a `RemoteDocumentCache.addEntry()` call. + * + * You can only modify documents that have already been retrieved via + * `getEntry()/getEntries()` (enforced via IndexedDbs `apply()`). + */ + addEntry(maybeDocument: MaybeDocument, readTime: SnapshotVersion): void; + /** + * Buffers a `RemoteDocumentCache.removeEntry()` call. + * + * You can only remove documents that have already been retrieved via + * `getEntry()/getEntries()` (enforced via IndexedDbs `apply()`). + */ + removeEntry(key: DocumentKey, readTime?: SnapshotVersion | null): void; + /** + * Looks up an entry in the cache. The buffered changes will first be checked, + * and if no buffered change applies, this will forward to + * `RemoteDocumentCache.getEntry()`. + * + * @param transaction - The transaction in which to perform any persistence + * operations. + * @param documentKey - The key of the entry to look up. + * @returns The cached Document or NoDocument entry, or null if we have + * nothing cached. + */ + getEntry( + transaction: PersistenceTransaction, + documentKey: DocumentKey + ): PersistencePromise; + /** + * Looks up several entries in the cache, forwarding to + * `RemoteDocumentCache.getEntry()`. + * + * @param transaction - The transaction in which to perform any persistence + * operations. + * @param documentKeys - The keys of the entries to look up. + * @returns A map of cached `Document`s or `NoDocument`s, indexed by key. If + * an entry cannot be found, the corresponding key will be mapped to a + * null value. + */ + getEntries( + transaction: PersistenceTransaction, + documentKeys: DocumentKeySet + ): PersistencePromise; + /** + * Applies buffered changes to the underlying RemoteDocumentCache, using + * the provided transaction. + */ + apply(transaction: PersistenceTransaction): PersistencePromise; + /** Helper to assert this.changes is not null */ + protected assertNotApplied(): void; +} + +/** + * An event from the RemoteStore. It is split into targetChanges (changes to the + * state or the set of documents in our watched targets) and documentUpdates + * (changes to the actual documents). + */ +declare class RemoteEvent { + /** + * The snapshot version this event brings us up to, or MIN if not set. + */ + readonly snapshotVersion: SnapshotVersion; + /** + * A map from target to changes to the target. See TargetChange. + */ + readonly targetChanges: Map; + /** + * A set of targets that is known to be inconsistent. Listens for these + * targets should be re-established without resume tokens. + */ + readonly targetMismatches: SortedSet; + /** + * A set of which documents have changed or been deleted, along with the + * doc's new values (if not deleted). + */ + readonly documentUpdates: MaybeDocumentMap; + /** + * A set of which document updates are due only to limbo resolution targets. + */ + readonly resolvedLimboDocuments: DocumentKeySet; + constructor( + /** + * The snapshot version this event brings us up to, or MIN if not set. + */ + snapshotVersion: SnapshotVersion, + /** + * A map from target to changes to the target. See TargetChange. + */ + targetChanges: Map, + /** + * A set of targets that is known to be inconsistent. Listens for these + * targets should be re-established without resume tokens. + */ + targetMismatches: SortedSet, + /** + * A set of which documents have changed or been deleted, along with the + * doc's new values (if not deleted). + */ + documentUpdates: MaybeDocumentMap, + /** + * A set of which document updates are due only to limbo resolution targets. + */ + resolvedLimboDocuments: DocumentKeySet + ); + /** + * HACK: Views require RemoteEvents in order to determine whether the view is + * CURRENT, but secondary tabs don't receive remote events. So this method is + * used to create a synthesized RemoteEvent that can be used to apply a + * CURRENT status change to a View, for queries executed in a different tab. + */ + static createSynthesizedRemoteEventForCurrentChange( + targetId: TargetId, + current: boolean + ): RemoteEvent; +} + +/** + * RemoteStore - An interface to remotely stored data, basically providing a + * wrapper around the Datastore that is more reliable for the rest of the + * system. + * + * RemoteStore is responsible for maintaining the connection to the server. + * - maintaining a list of active listens. + * - reconnecting when the connection is dropped. + * - resuming all the active listens on reconnect. + * + * RemoteStore handles all incoming events from the Datastore. + * - listening to the watch stream and repackaging the events as RemoteEvents + * - notifying SyncEngine of any changes to the active listens. + * + * RemoteStore takes writes from other components and handles them reliably. + * - pulling pending mutations from LocalStore and sending them to Datastore. + * - retrying mutations that failed because of network problems. + * - acking mutations to the SyncEngine once they are accepted or rejected. + */ +declare interface RemoteStore { + /** + * SyncEngine to notify of watch and write events. This must be set + * immediately after construction. + */ + remoteSyncer: RemoteSyncer; +} + +/** + * An interface that describes the actions the RemoteStore needs to perform on + * a cooperating synchronization engine. + */ +declare interface RemoteSyncer { + /** + * Applies one remote event to the sync engine, notifying any views of the + * changes, and releasing any pending mutation batches that would become + * visible because of the snapshot version the remote event contains. + */ + applyRemoteEvent?(remoteEvent: RemoteEvent): Promise; + /** + * Rejects the listen for the given targetID. This can be triggered by the + * backend for any active target. + * + * @param targetId - The targetID corresponds to one previously initiated by + * the user as part of TargetData passed to listen() on RemoteStore. + * @param error - A description of the condition that has forced the rejection. + * Nearly always this will be an indication that the user is no longer + * authorized to see the data matching the target. + */ + rejectListen?(targetId: TargetId, error: FirestoreError): Promise; + /** + * Applies the result of a successful write of a mutation batch to the sync + * engine, emitting snapshots in any views that the mutation applies to, and + * removing the batch from the mutation queue. + */ + applySuccessfulWrite?(result: MutationBatchResult): Promise; + /** + * Rejects the batch, removing the batch from the mutation queue, recomputing + * the local view of any documents affected by the batch and then, emitting + * snapshots with the reverted value. + */ + rejectFailedWrite?(batchId: BatchId, error: FirestoreError): Promise; + /** + * Returns the set of remote document keys for the given target ID. This list + * includes the documents that were assigned to the target when we received + * the last snapshot. + */ + getRemoteKeysForTarget?(targetId: TargetId): DocumentKeySet; + /** + * Updates all local state to match the pending mutations for the given user. + * May be called repeatedly for the same user. + */ + handleCredentialChange?(user: User): Promise; +} + +declare type Resolver = (value?: T) => void; + +/** + * A slash-separated path for navigating resources (documents and collections) + * within Firestore. + */ +declare class ResourcePath extends BasePath { + protected construct( + segments: string[], + offset?: number, + length?: number + ): ResourcePath; + canonicalString(): string; + toString(): string; + /** + * Creates a resource path from the given slash-delimited string. If multiple + * arguments are provided, all components are combined. Leading and trailing + * slashes from all components are ignored. + */ + static fromString(...pathComponents: string[]): ResourcePath; + static emptyPath(): ResourcePath; +} + +/** + * Executes the given `updateFunction` and then attempts to commit the changes + * applied within the transaction. If any document read within the transaction + * has changed, Cloud Firestore retries the `updateFunction`. If it fails to + * commit after 5 attempts, the transaction fails. + * + * The maximum number of writes allowed in a single transaction is 500. + * + * @param firestore - A reference to the Firestore database to run this + * transaction against. + * @param updateFunction - The function to execute within the transaction + * context. + * @returns If the transaction completed successfully or was explicitly aborted + * (the `updateFunction` returned a failed promise), the promise returned by the + * `updateFunction `is returned here. Otherwise, if the transaction failed, a + * rejected promise with the corresponding failure error is returned. + */ +export declare function runTransaction( + firestore: FirebaseFirestore, + updateFunction: (transaction: Transaction) => Promise +): Promise; + +/** + * Returns a sentinel used with {@link setDoc} or {@link updateDoc} to + * include a server-generated timestamp in the written data. + */ +export declare function serverTimestamp(): FieldValue; + +declare type ServerTimestampBehavior = 'estimate' | 'previous' | 'none'; + +/** + * Writes to the document referred to by this `DocumentReference`. If the + * document does not yet exist, it will be created. + * + * @param reference - A reference to the document to write. + * @param data - A map of the fields and values for the document. + * @returns A Promise resolved once the data has been successfully written + * to the backend (note that it won't resolve while you're offline). + */ +export declare function setDoc( + reference: DocumentReference, + data: T +): Promise; + +/** + * Writes to the document referred to by the specified `DocumentReference`. If + * the document does not yet exist, it will be created. If you provide `merge` + * or `mergeFields`, the provided data can be merged into an existing document. + * + * @param reference - A reference to the document to write. + * @param data - A map of the fields and values for the document. + * @param options - An object to configure the set behavior. + * @returns A Promise resolved once the data has been successfully written + * to the backend (note that it won't resolve while you're offline). + */ +export declare function setDoc( + reference: DocumentReference, + data: Partial, + options: SetOptions +): Promise; + +/** + * Sets the verbosity of Cloud Firestore logs (debug, error, or silent). + * + * @param logLevel - The verbosity you set for activity and error logging. Can + * be any of the following values: + * + *
    + *
  • `debug` for the most verbose logging level, primarily for + * debugging.
  • + *
  • `error` to log errors only.
  • + *
  • `silent` to turn off logging.
  • + *
+ */ +export declare function setLogLevel(logLevel: LogLevel): void; + +/** + * An options object that configures the behavior of {@link setDoc}, {@link + * WriteBatch#set} and {@link Transaction#set} calls. These calls can be + * configured to perform granular merges instead of overwriting the target + * documents in their entirety by providing a `SetOptions` with `merge: true`. + * + * @param merge - Changes the behavior of a `setDoc()` call to only replace the + * values specified in its data argument. Fields omitted from the `setDoc()` + * call remain untouched. + * @param mergeFields - Changes the behavior of `setDoc()` calls to only replace + * the specified field paths. Any field path that is not specified is ignored + * and remains untouched. + */ +export declare type SetOptions = + | { + readonly merge?: boolean; + } + | { + readonly mergeFields?: Array; + }; + +export declare interface Settings extends Settings_2 { + cacheSizeBytes?: number; +} + +declare interface Settings_2 { + host?: string; + ssl?: boolean; + ignoreUndefinedProperties?: boolean; + cacheSizeBytes?: number; + experimentalForceLongPolling?: boolean; + experimentalAutoDetectLongPolling?: boolean; +} + +/** + * A `SharedClientState` keeps track of the global state of the mutations + * and query targets for all active clients with the same persistence key (i.e. + * project ID and FirebaseApp name). It relays local changes to other clients + * and updates its local state as new state is observed. + * + * `SharedClientState` is primarily used for synchronization in Multi-Tab + * environments. Each tab is responsible for registering its active query + * targets and mutations. `SharedClientState` will then notify the listener + * assigned to `.syncEngine` for updates to mutations and queries that + * originated in other clients. + * + * To receive notifications, `.syncEngine` and `.onlineStateHandler` has to be + * assigned before calling `start()`. + */ +declare interface SharedClientState { + onlineStateHandler: ((onlineState: OnlineState) => void) | null; + sequenceNumberHandler: + | ((sequenceNumber: ListenSequenceNumber) => void) + | null; + /** Registers the Mutation Batch ID of a newly pending mutation. */ + addPendingMutation(batchId: BatchId): void; + /** + * Records that a pending mutation has been acknowledged or rejected. + * Called by the primary client to notify secondary clients of mutation + * results as they come back from the backend. + */ + updateMutationState( + batchId: BatchId, + state: 'acknowledged' | 'rejected', + error?: FirestoreError + ): void; + /** + * Associates a new Query Target ID with the local Firestore client. Returns + * the new query state for the query (which can be 'current' if the query is + * already associated with another tab). + * + * If the target id is already associated with local client, the method simply + * returns its `QueryTargetState`. + */ + addLocalQueryTarget(targetId: TargetId): QueryTargetState; + /** Removes the Query Target ID association from the local client. */ + removeLocalQueryTarget(targetId: TargetId): void; + /** Checks whether the target is associated with the local client. */ + isLocalQueryTarget(targetId: TargetId): boolean; + /** + * Processes an update to a query target. + * + * Called by the primary client to notify secondary clients of document + * changes or state transitions that affect the provided query target. + */ + updateQueryState( + targetId: TargetId, + state: QueryTargetState, + error?: FirestoreError + ): void; + /** + * Removes the target's metadata entry. + * + * Called by the primary client when all clients stopped listening to a query + * target. + */ + clearQueryState(targetId: TargetId): void; + /** + * Gets the active Query Targets IDs for all active clients. + * + * The implementation for this may require O(n) runtime, where 'n' is the size + * of the result set. + */ + getAllActiveQueryTargets(): SortedSet; + /** + * Checks whether the provided target ID is currently being listened to by + * any of the active clients. + * + * The implementation may require O(n*log m) runtime, where 'n' is the number + * of clients and 'm' the number of targets. + */ + isActiveQueryTarget(targetId: TargetId): boolean; + /** + * Starts the SharedClientState, reads existing client data and registers + * listeners for updates to new and existing clients. + */ + start(): Promise; + /** Shuts down the `SharedClientState` and its listeners. */ + shutdown(): void; + /** + * Changes the active user and removes all existing user-specific data. The + * user change does not call back into SyncEngine (for example, no mutations + * will be marked as removed). + */ + handleUserChange( + user: User, + removedBatchIds: BatchId[], + addedBatchIds: BatchId[] + ): void; + /** Changes the shared online state of all clients. */ + setOnlineState(onlineState: OnlineState): void; + writeSequenceNumber(sequenceNumber: ListenSequenceNumber): void; + /** + * Notifies other clients when remote documents have changed due to loading + * a bundle. + */ + notifyBundleLoaded(): void; +} + +/** + * Returns true if the provided snapshots are equal. + * + * @param left - A snapshot to compare. + * @param right - A snapshot to compare. + * @returns true if the snapshots are equal. + */ +export declare function snapshotEqual( + left: DocumentSnapshot | QuerySnapshot, + right: DocumentSnapshot | QuerySnapshot +): boolean; + +/** + * An options object that can be passed to {@link onSnapshot} and {@link + * QuerySnapshot#docChanges} to control which types of changes to include in the + * result set. + */ +export declare interface SnapshotListenOptions { + /** + * Include a change even if only the metadata of the query or of a document + * changed. Default is false. + */ + readonly includeMetadataChanges?: boolean; +} + +/** + * Metadata about a snapshot, describing the state of the snapshot. + */ +export declare class SnapshotMetadata { + /** + * True if the snapshot contains the result of local writes (for example + * `set()` or `update()` calls) that have not yet been committed to the + * backend. If your listener has opted into metadata updates (via + * `SnapshotListenOptions`) you will receive another snapshot with + * `hasPendingWrites` equal to false once the writes have been committed to + * the backend. + */ + readonly hasPendingWrites: boolean; + /** + * True if the snapshot was created from cached data rather than guaranteed + * up-to-date server data. If your listener has opted into metadata updates + * (via `SnapshotListenOptions`) you will receive another snapshot with + * `fromCache` set to false once the client has received up-to-date data from + * the backend. + */ + readonly fromCache: boolean; + /** @hideconstructor */ + constructor(hasPendingWrites: boolean, fromCache: boolean); + /** + * Returns true if this `SnapshotMetadata` is equal to the provided one. + * + * @param other - The `SnapshotMetadata` to compare against. + * @returns true if this `SnapshotMetadata` is equal to the provided one. + */ + isEqual(other: SnapshotMetadata): boolean; +} + +/** + * Options that configure how data is retrieved from a `DocumentSnapshot` (for + * example the desired behavior for server timestamps that have not yet been set + * to their final value). + */ +export declare interface SnapshotOptions { + /** + * If set, controls the return value for server timestamps that have not yet + * been set to their final value. + * + * By specifying 'estimate', pending server timestamps return an estimate + * based on the local clock. This estimate will differ from the final value + * and cause these values to change once the server result becomes available. + * + * By specifying 'previous', pending timestamps will be ignored and return + * their previous value instead. + * + * If omitted or set to 'none', `null` will be returned by default until the + * server value becomes available. + */ + readonly serverTimestamps?: 'estimate' | 'previous' | 'none'; +} + +/** + * A version of a document in Firestore. This corresponds to the version + * timestamp, such as update_time or read_time. + */ +declare class SnapshotVersion { + private timestamp; + static fromTimestamp(value: Timestamp): SnapshotVersion; + static min(): SnapshotVersion; + private constructor(); + compareTo(other: SnapshotVersion): number; + isEqual(other: SnapshotVersion): boolean; + /** Returns a number representation of the version for use in spec tests. */ + toMicroseconds(): number; + toString(): string; + toTimestamp(): Timestamp; +} + +declare class SortedMap { + comparator: Comparator; + root: LLRBNode | LLRBEmptyNode; + constructor( + comparator: Comparator, + root?: LLRBNode | LLRBEmptyNode + ); + insert(key: K, value: V): SortedMap; + remove(key: K): SortedMap; + get(key: K): V | null; + indexOf(key: K): number; + isEmpty(): boolean; + get size(): number; + minKey(): K | null; + maxKey(): K | null; + inorderTraversal(action: (k: K, v: V) => T): T; + forEach(fn: (k: K, v: V) => void): void; + toString(): string; + reverseTraversal(action: (k: K, v: V) => T): T; + getIterator(): SortedMapIterator; + getIteratorFrom(key: K): SortedMapIterator; + getReverseIterator(): SortedMapIterator; + getReverseIteratorFrom(key: K): SortedMapIterator; +} + +declare class SortedMapIterator { + private isReverse; + private nodeStack; + constructor( + node: LLRBNode | LLRBEmptyNode, + startKey: K | null, + comparator: Comparator, + isReverse: boolean + ); + getNext(): Entry; + hasNext(): boolean; + peek(): Entry | null; +} + +/** + * SortedSet is an immutable (copy-on-write) collection that holds elements + * in order specified by the provided comparator. + * + * NOTE: if provided comparator returns 0 for two elements, we consider them to + * be equal! + */ +declare class SortedSet { + private comparator; + private data; + constructor(comparator: (left: T, right: T) => number); + has(elem: T): boolean; + first(): T | null; + last(): T | null; + get size(): number; + indexOf(elem: T): number; + /** Iterates elements in order defined by "comparator" */ + forEach(cb: (elem: T) => void): void; + /** Iterates over `elem`s such that: range[0] <= elem < range[1]. */ + forEachInRange(range: [T, T], cb: (elem: T) => void): void; + /** + * Iterates over `elem`s such that: start <= elem until false is returned. + */ + forEachWhile(cb: (elem: T) => boolean, start?: T): void; + /** Finds the least element greater than or equal to `elem`. */ + firstAfterOrEqual(elem: T): T | null; + getIterator(): SortedSetIterator; + getIteratorFrom(key: T): SortedSetIterator; + /** Inserts or updates an element */ + add(elem: T): SortedSet; + /** Deletes an element */ + delete(elem: T): SortedSet; + isEmpty(): boolean; + unionWith(other: SortedSet): SortedSet; + isEqual(other: SortedSet): boolean; + toArray(): T[]; + toString(): string; + private copy; +} + +declare class SortedSetIterator { + private iter; + constructor(iter: SortedMapIterator); + getNext(): T; + hasNext(): boolean; +} + +/** + * Creates a `QueryConstraint` 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. + * + * @param snapshot - The snapshot of the document to start after. + * @returns A `QueryConstraint` to pass to `query()` + */ +export declare function startAfter( + snapshot: DocumentSnapshot_2 +): QueryConstraint; + +/** + * Creates a `QueryConstraint` that modifies the result set to start after the + * provided fields relative to the order of the query. The order of the field + * values must match the order of the order by clauses of the query. + * + * @param fieldValues - The field values to start this query after, in order + * of the query's order by. + * @returns A `QueryConstraint` to pass to `query()` + */ +export declare function startAfter(...fieldValues: unknown[]): QueryConstraint; + +/** + * Creates a `QueryConstraint` 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. + * + * @param snapshot - The snapshot of the document to start at. + * @returns A `QueryConstraint` to pass to `query()`. + */ +export declare function startAt( + snapshot: DocumentSnapshot_2 +): QueryConstraint; + +/** + * Creates a `QueryConstraint` that modifies the result set to start at the + * provided fields relative to the order of the query. The order of the field + * values must match the order of the order by clauses of the query. + * + * @param fieldValues - The field values to start this query at, in order + * of the query's order by. + * @returns A `QueryConstraint` to pass to `query()`. + */ +export declare function startAt(...fieldValues: unknown[]): QueryConstraint; + +declare type StructuredQuery = firestoreV1ApiClientInterfaces.StructuredQuery; + +/** + * @license + * Copyright 2017 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. + */ +/** + * SyncEngine is the central controller in the client SDK architecture. It is + * the glue code between the EventManager, LocalStore, and RemoteStore. Some of + * SyncEngine's responsibilities include: + * 1. Coordinating client requests and remote events between the EventManager + * and the local and remote data stores. + * 2. Managing a View object for each query, providing the unified view between + * the local and remote data stores. + * 3. Notifying the RemoteStore when the LocalStore has new mutations in its + * queue that need sending to the backend. + * + * The SyncEngine’s methods should only ever be called by methods running in the + * global async queue. + * + * PORTING NOTE: On Web, SyncEngine does not have an explicit subscribe() + * function. Instead, it directly depends on EventManager's tree-shakeable API + * (via `ensureWatchStream()`). + */ +declare interface SyncEngine { + isPrimaryClient: boolean; +} + +/** + * A Target represents the WatchTarget representation of a Query, which is used + * by the LocalStore and the RemoteStore to keep track of and to execute + * backend queries. While a Query can represent multiple Targets, each Targets + * maps to a single WatchTarget in RemoteStore and a single TargetData entry + * in persistence. + */ +declare interface Target { + readonly path: ResourcePath; + readonly collectionGroup: string | null; + readonly orderBy: OrderBy[]; + readonly filters: Filter[]; + readonly limit: number | null; + readonly startAt: Bound | null; + readonly endAt: Bound | null; +} + +/** + * Represents cached targets received from the remote backend. + * + * The cache is keyed by `Target` and entries in the cache are `TargetData` + * instances. + */ +declare interface TargetCache { + /** + * A global snapshot version representing the last consistent snapshot we + * received from the backend. This is monotonically increasing and any + * snapshots received from the backend prior to this version (e.g. for targets + * resumed with a resume_token) should be suppressed (buffered) until the + * backend has caught up to this snapshot version again. This prevents our + * cache from ever going backwards in time. + * + * This is updated whenever our we get a TargetChange with a read_time and + * empty target_ids. + */ + getLastRemoteSnapshotVersion( + transaction: PersistenceTransaction + ): PersistencePromise; + /** + * @returns The highest sequence number observed, including any that might be + * persisted on-disk. + */ + getHighestSequenceNumber( + transaction: PersistenceTransaction + ): PersistencePromise; + /** + * Call provided function with each `TargetData` that we have cached. + */ + forEachTarget( + txn: PersistenceTransaction, + f: (q: TargetData) => void + ): PersistencePromise; + /** + * Set the highest listen sequence number and optionally updates the + * snapshot version of the last consistent snapshot received from the backend + * (see getLastRemoteSnapshotVersion() for more details). + * + * @param highestListenSequenceNumber - The new maximum listen sequence number. + * @param lastRemoteSnapshotVersion - The new snapshot version. Optional. + */ + setTargetsMetadata( + transaction: PersistenceTransaction, + highestListenSequenceNumber: number, + lastRemoteSnapshotVersion?: SnapshotVersion + ): PersistencePromise; + /** + * Adds an entry in the cache. + * + * The cache key is extracted from `targetData.target`. The key must not already + * exist in the cache. + * + * @param targetData - A TargetData instance to put in the cache. + */ + addTargetData( + transaction: PersistenceTransaction, + targetData: TargetData + ): PersistencePromise; + /** + * Updates an entry in the cache. + * + * The cache key is extracted from `targetData.target`. The entry must already + * exist in the cache, and it will be replaced. + * @param targetData - The TargetData to be replaced into the cache. + */ + updateTargetData( + transaction: PersistenceTransaction, + targetData: TargetData + ): PersistencePromise; + /** + * Removes the cached entry for the given target data. It is an error to remove + * a target data that does not exist. + * + * Multi-Tab Note: This operation should only be called by the primary client. + */ + removeTargetData( + transaction: PersistenceTransaction, + targetData: TargetData + ): PersistencePromise; + /** + * The number of targets currently in the cache. + */ + getTargetCount( + transaction: PersistenceTransaction + ): PersistencePromise; + /** + * Looks up a TargetData entry by target. + * + * @param target - The query target corresponding to the entry to look up. + * @returns The cached TargetData entry, or null if the cache has no entry for + * the target. + */ + getTargetData( + transaction: PersistenceTransaction, + target: Target + ): PersistencePromise; + /** + * Adds the given document keys to cached query results of the given target + * ID. + * + * Multi-Tab Note: This operation should only be called by the primary client. + */ + addMatchingKeys( + transaction: PersistenceTransaction, + keys: DocumentKeySet, + targetId: TargetId + ): PersistencePromise; + /** + * Removes the given document keys from the cached query results of the + * given target ID. + * + * Multi-Tab Note: This operation should only be called by the primary client. + */ + removeMatchingKeys( + transaction: PersistenceTransaction, + keys: DocumentKeySet, + targetId: TargetId + ): PersistencePromise; + /** + * Removes all the keys in the query results of the given target ID. + * + * Multi-Tab Note: This operation should only be called by the primary client. + */ + removeMatchingKeysForTargetId( + transaction: PersistenceTransaction, + targetId: TargetId + ): PersistencePromise; + /** + * Returns the document keys that match the provided target ID. + */ + getMatchingKeysForTargetId( + transaction: PersistenceTransaction, + targetId: TargetId + ): PersistencePromise; + /** + * Returns a new target ID that is higher than any query in the cache. If + * there are no queries in the cache, returns the first valid target ID. + * Allocated target IDs are persisted and `allocateTargetId()` will never + * return the same ID twice. + */ + allocateTargetId( + transaction: PersistenceTransaction + ): PersistencePromise; + containsKey( + transaction: PersistenceTransaction, + key: DocumentKey + ): PersistencePromise; +} + +/** + * A TargetChange specifies the set of changes for a specific target as part of + * a RemoteEvent. These changes track which documents are added, modified or + * removed, as well as the target's resume token and whether the target is + * marked CURRENT. + * The actual changes *to* documents are not part of the TargetChange since + * documents may be part of multiple targets. + */ +declare class TargetChange { + /** + * An opaque, server-assigned token that allows watching a query to be resumed + * after disconnecting without retransmitting all the data that matches the + * query. The resume token essentially identifies a point in time from which + * the server should resume sending results. + */ + readonly resumeToken: ByteString; + /** + * The "current" (synced) status of this target. Note that "current" + * has special meaning in the RPC protocol that implies that a target is + * both up-to-date and consistent with the rest of the watch stream. + */ + readonly current: boolean; + /** + * The set of documents that were newly assigned to this target as part of + * this remote event. + */ + readonly addedDocuments: DocumentKeySet; + /** + * The set of documents that were already assigned to this target but received + * an update during this remote event. + */ + readonly modifiedDocuments: DocumentKeySet; + /** + * The set of documents that were removed from this target as part of this + * remote event. + */ + readonly removedDocuments: DocumentKeySet; + constructor( + /** + * An opaque, server-assigned token that allows watching a query to be resumed + * after disconnecting without retransmitting all the data that matches the + * query. The resume token essentially identifies a point in time from which + * the server should resume sending results. + */ + resumeToken: ByteString, + /** + * The "current" (synced) status of this target. Note that "current" + * has special meaning in the RPC protocol that implies that a target is + * both up-to-date and consistent with the rest of the watch stream. + */ + current: boolean, + /** + * The set of documents that were newly assigned to this target as part of + * this remote event. + */ + addedDocuments: DocumentKeySet, + /** + * The set of documents that were already assigned to this target but received + * an update during this remote event. + */ + modifiedDocuments: DocumentKeySet, + /** + * The set of documents that were removed from this target as part of this + * remote event. + */ + removedDocuments: DocumentKeySet + ); + /** + * This method is used to create a synthesized TargetChanges that can be used to + * apply a CURRENT status change to a View (for queries executed in a different + * tab) or for new queries (to raise snapshots with correct CURRENT status). + */ + static createSynthesizedTargetChangeForCurrentChange( + targetId: TargetId, + current: boolean + ): TargetChange; +} + +declare type TargetChangeTargetChangeType = + | 'NO_CHANGE' + | 'ADD' + | 'REMOVE' + | 'CURRENT' + | 'RESET'; + +/** + * An immutable set of metadata that the local store tracks for each target. + */ +declare class TargetData { + /** The target being listened to. */ + readonly target: Target; + /** + * The target ID to which the target corresponds; Assigned by the + * LocalStore for user listens and by the SyncEngine for limbo watches. + */ + readonly targetId: TargetId; + /** The purpose of the target. */ + readonly purpose: TargetPurpose; + /** + * The sequence number of the last transaction during which this target data + * was modified. + */ + readonly sequenceNumber: ListenSequenceNumber; + /** The latest snapshot version seen for this target. */ + readonly snapshotVersion: SnapshotVersion; + /** + * The maximum snapshot version at which the associated view + * contained no limbo documents. + */ + readonly lastLimboFreeSnapshotVersion: SnapshotVersion; + /** + * An opaque, server-assigned token that allows watching a target to be + * resumed after disconnecting without retransmitting all the data that + * matches the target. The resume token essentially identifies a point in + * time from which the server should resume sending results. + */ + readonly resumeToken: ByteString; + constructor( + /** The target being listened to. */ + target: Target, + /** + * The target ID to which the target corresponds; Assigned by the + * LocalStore for user listens and by the SyncEngine for limbo watches. + */ + targetId: TargetId, + /** The purpose of the target. */ + purpose: TargetPurpose, + /** + * The sequence number of the last transaction during which this target data + * was modified. + */ + sequenceNumber: ListenSequenceNumber, + /** The latest snapshot version seen for this target. */ + snapshotVersion?: SnapshotVersion, + /** + * The maximum snapshot version at which the associated view + * contained no limbo documents. + */ + lastLimboFreeSnapshotVersion?: SnapshotVersion, + /** + * An opaque, server-assigned token that allows watching a target to be + * resumed after disconnecting without retransmitting all the data that + * matches the target. The resume token essentially identifies a point in + * time from which the server should resume sending results. + */ + resumeToken?: ByteString + ); + /** Creates a new target data instance with an updated sequence number. */ + withSequenceNumber(sequenceNumber: number): TargetData; + /** + * Creates a new target data instance with an updated resume token and + * snapshot version. + */ + withResumeToken( + resumeToken: ByteString, + snapshotVersion: SnapshotVersion + ): TargetData; + /** + * Creates a new target data instance with an updated last limbo free + * snapshot version number. + */ + withLastLimboFreeSnapshotVersion( + lastLimboFreeSnapshotVersion: SnapshotVersion + ): TargetData; +} + +/** + * A locally-assigned ID used to refer to a target being watched via the + * Watch service. + */ +declare type TargetId = number; + +/** An enumeration of the different purposes we have for targets. */ +declare const enum TargetPurpose { + /** A regular, normal query target. */ + Listen = 0, + /** + * The query target was used to refill a query after an existence filter mismatch. + */ + ExistenceFilterMismatch = 1, + /** The query target was used to resolve a limbo document. */ + LimboResolution = 2 +} + +/** + * Represents the state of bundle loading tasks. + * + * Both 'Error' and 'Success' are sinking state: task will abort or complete and there will + * be no more updates after they are reported. + */ +export declare type TaskState = 'Error' | 'Running' | 'Success'; + +/** + * Terminates the provided Firestore instance. + * + * After calling `terminate()` only the `clearIndexedDbPersistence()` function + * may be used. Any other function will throw a `FirestoreError`. + * + * To restart after termination, create a new instance of FirebaseFirestore with + * {@link getFirestore}. + * + * Termination does not cancel any pending writes, and any promises that are + * awaiting a response from the server will not be resolved. If you have + * persistence enabled, the next time you start this instance, it will resume + * sending these writes to the server. + * + * Note: Under normal circumstances, calling `terminate()` is not required. This + * function is useful only when you want to force this instance to release all + * of its resources or in combination with `clearIndexedDbPersistence()` to + * ensure that all local state is destroyed between test runs. + * + * @returns A promise that is resolved when the instance has been successfully + * terminated. + */ +export declare function terminate(firestore: FirebaseFirestore): Promise; + +/** + * Wellknown "timer" IDs used when scheduling delayed operations on the + * AsyncQueue. These IDs can then be used from tests to check for the presence + * of operations or to run them early. + * + * The string values are used when encoding these timer IDs in JSON spec tests. + */ +declare const enum TimerId { + /** All can be used with runDelayedOperationsEarly() to run all timers. */ + All = 'all', + /** + * The following 4 timers are used in persistent_stream.ts for the listen and + * write streams. The "Idle" timer is used to close the stream due to + * inactivity. The "ConnectionBackoff" timer is used to restart a stream once + * the appropriate backoff delay has elapsed. + */ + ListenStreamIdle = 'listen_stream_idle', + ListenStreamConnectionBackoff = 'listen_stream_connection_backoff', + WriteStreamIdle = 'write_stream_idle', + WriteStreamConnectionBackoff = 'write_stream_connection_backoff', + /** + * A timer used in online_state_tracker.ts to transition from + * OnlineState.Unknown to Offline after a set timeout, rather than waiting + * indefinitely for success or failure. + */ + OnlineStateTimeout = 'online_state_timeout', + /** + * A timer used to update the client metadata in IndexedDb, which is used + * to determine the primary leaseholder. + */ + ClientMetadataRefresh = 'client_metadata_refresh', + /** A timer used to periodically attempt LRU Garbage collection */ + LruGarbageCollection = 'lru_garbage_collection', + /** + * A timer used to retry transactions. Since there can be multiple concurrent + * transactions, multiple of these may be in the queue at a given time. + */ + TransactionRetry = 'transaction_retry', + /** + * A timer used to retry operations scheduled via retryable AsyncQueue + * operations. + */ + AsyncQueueRetry = 'async_queue_retry' +} + +/** + * @license + * Copyright 2017 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. + */ +/** + * 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 + * {@link https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto | Timestamp definition}. + */ +export declare class Timestamp { + readonly seconds: number; + readonly nanoseconds: number; + /** + * Creates a new timestamp with the current date, with millisecond precision. + * + * @returns a new timestamp representing the current date. + */ + static now(): Timestamp; + /** + * Creates a new timestamp from the given date. + * + * @param date - The date to initialize the `Timestamp` from. + * @returns A new `Timestamp` representing the same point in time as the given + * date. + */ + static fromDate(date: Date): Timestamp; + /** + * Creates a new timestamp from the given number of milliseconds. + * + * @param milliseconds - Number of milliseconds since Unix epoch + * 1970-01-01T00:00:00Z. + * @returns A new `Timestamp` representing the same point in time as the given + * number of milliseconds. + */ + static fromMillis(milliseconds: number): Timestamp; + /** + * Creates a new timestamp. + * + * @param seconds - The number of seconds of UTC time since Unix epoch + * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to + * 9999-12-31T23:59:59Z inclusive. + * @param nanoseconds - The non-negative fractions of a second at nanosecond + * resolution. Negative second values with fractions must still have + * non-negative nanoseconds values that count forward in time. Must be + * from 0 to 999,999,999 inclusive. + */ + constructor(seconds: number, nanoseconds: number); + /** + * Converts a `Timestamp` to a JavaScript `Date` object. This conversion causes + * a loss of precision since `Date` objects only support millisecond precision. + * + * @returns JavaScript `Date` object representing the same point in time as + * this `Timestamp`, with millisecond precision. + */ + toDate(): Date; + /** + * Converts a `Timestamp` to a numeric timestamp (in milliseconds since + * epoch). This operation causes a loss of precision. + * + * @returns The point in time corresponding to this timestamp, represented as + * the number of milliseconds since Unix epoch 1970-01-01T00:00:00Z. + */ + toMillis(): number; + _compareTo(other: Timestamp): number; + /** + * Returns true if this `Timestamp` is equal to the provided one. + * + * @param other - The `Timestamp` to compare against. + * @returns true if this `Timestamp` is equal to the provided one. + */ + isEqual(other: Timestamp): boolean; + toString(): string; + toJSON(): { + seconds: number; + nanoseconds: number; + }; + /** + * Converts this object to a primitive string, which allows Timestamp objects to be compared + * using the `>`, `<=`, `>=` and `>` operators. + */ + valueOf(): string; +} + +declare type Timestamp_2 = + | string + | { + seconds?: string | number; + nanos?: number; + }; + +declare interface Token { + /** Type of token. */ + type: TokenType; + /** + * The user with which the token is associated (used for persisting user + * state on disk, etc.). + */ + user: User; + /** Extra header values to be passed along with a request */ + authHeaders: { + [header: string]: string; + }; +} + +declare type TokenType = 'OAuth' | 'FirstParty'; + +/** + * 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 + * {@link runTransaction}. + */ +export declare class Transaction extends Transaction_2 { + protected readonly _firestore: FirebaseFirestore; + /** @hideconstructor */ + constructor(_firestore: FirebaseFirestore, _transaction: Transaction_3); + /** + * Reads the document referenced by the provided {@link DocumentReference}. + * + * @param documentRef - A reference to the document to be read. + * @returns A `DocumentSnapshot` with the read data. + */ + get(documentRef: DocumentReference): Promise>; +} + +/** + * 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 + * {@link runTransaction}. + */ +declare class Transaction_2 { + protected readonly _firestore: FirebaseFirestore_2; + private readonly _transaction; + private readonly _dataReader; + /** @hideconstructor */ + constructor(_firestore: FirebaseFirestore_2, _transaction: Transaction_3); + /** + * Reads the document referenced by the provided {@link DocumentReference}. + * + * @param documentRef - A reference to the document to be read. + * @returns A `DocumentSnapshot` with the read data. + */ + get(documentRef: DocumentReference): Promise>; + /** + * Writes to the document referred to by the provided {@link + * DocumentReference}. If the document does not exist yet, it will be created. + * + * @param documentRef - A reference to the document to be set. + * @param data - An object of the fields and values for the document. + * @returns This `Transaction` instance. Used for chaining method calls. + */ + set(documentRef: DocumentReference, data: T): this; + /** + * Writes to the document referred to by the provided {@link + * DocumentReference}. If the document does not exist yet, it will be created. + * If you provide `merge` or `mergeFields`, the provided data can be merged + * into an existing document. + * + * @param documentRef - A reference to the document to be set. + * @param data - An object of the fields and values for the document. + * @param options - An object to configure the set behavior. + * @returns This `Transaction` instance. Used for chaining method calls. + */ + set( + documentRef: DocumentReference, + data: Partial, + options: SetOptions + ): this; + /** + * Updates fields in the document referred to by the provided {@link + * DocumentReference}. The update will fail if applied to a document that does + * not exist. + * + * @param documentRef - A reference to the document to be updated. + * @param data - An object containing the fields and values with which to + * update the document. Fields can contain dots to reference nested fields + * within the document. + * @returns This `Transaction` instance. Used for chaining method calls. + */ + update(documentRef: DocumentReference, data: UpdateData): this; + /** + * Updates fields in the document referred to by the provided {@link + * DocumentReference}. The update will fail if applied to a document that does + * not exist. + * + * Nested fields can be updated by providing dot-separated field path + * strings or by providing `FieldPath` objects. + * + * @param documentRef - A reference to the document to be updated. + * @param field - The first field to update. + * @param value - The first value. + * @param moreFieldsAndValues - Additional key/value pairs. + * @returns This `Transaction` instance. Used for chaining method calls. + */ + update( + documentRef: DocumentReference, + field: string | FieldPath, + value: unknown, + ...moreFieldsAndValues: unknown[] + ): this; + /** + * Deletes the document referred to by the provided {@link DocumentReference}. + * + * @param documentRef - A reference to the document to be deleted. + * @returns This `Transaction` instance. Used for chaining method calls. + */ + delete(documentRef: DocumentReference): this; +} + +/** + * Internal transaction object responsible for accumulating the mutations to + * perform and the base versions for any documents read. + */ +declare class Transaction_3 { + private datastore; + private readVersions; + private mutations; + private committed; + /** + * A deferred usage error that occurred previously in this transaction that + * will cause the transaction to fail once it actually commits. + */ + private lastWriteError; + /** + * Set of documents that have been written in the transaction. + * + * When there's more than one write to the same key in a transaction, any + * writes after the first are handled differently. + */ + private writtenDocs; + constructor(datastore: Datastore); + lookup(keys: DocumentKey[]): Promise; + set(key: DocumentKey, data: ParsedSetData): void; + update(key: DocumentKey, data: ParsedUpdateData): void; + delete(key: DocumentKey): void; + commit(): Promise; + private recordVersion; + /** + * Returns the version of this document when it was read in this transaction, + * as a precondition, or no precondition if it was not read. + */ + private precondition; + /** + * Returns the precondition for a document if the operation is an update. + */ + private preconditionForUpdate; + private write; + private ensureCommitNotCalled; +} + +/** Used to represent a field transform on a mutation. */ +declare class TransformOperation { + private _; +} + +declare type UnaryFilterOp = + | 'OPERATOR_UNSPECIFIED' + | 'IS_NAN' + | 'IS_NULL' + | 'IS_NOT_NAN' + | 'IS_NOT_NULL'; + +export declare interface Unsubscribe { + (): void; +} + +/** + * An untyped Firestore Data Converter interface that is shared between the + * lite, firestore-exp and classic SDK. + */ +declare interface UntypedFirestoreDataConverter { + toFirestore(modelObject: T): DocumentData_2; + toFirestore(modelObject: Partial, options: SetOptions_2): DocumentData_2; + fromFirestore(snapshot: unknown, options?: unknown): T; +} + +/** + * Update data (for use with {@link updateDoc}) consists of field paths (e.g. + * 'foo' or 'foo.baz') mapped to values. Fields that contain dots reference + * nested fields within the document. + */ +export declare interface UpdateData { + [fieldPath: string]: any; +} + +/** + * Updates fields in the document referred to by the specified + * `DocumentReference`. The update will fail if applied to a document that does + * not exist. + * + * @param reference - A reference to the document to update. + * @param data - An object containing the fields and values with which to + * update the document. Fields can contain dots to reference nested fields + * within the document. + * @returns A Promise resolved once the data has been successfully written + * to the backend (note that it won't resolve while you're offline). + */ +export declare function updateDoc( + reference: DocumentReference, + data: UpdateData +): Promise; + +/** + * Updates fields in the document referred to by the specified + * `DocumentReference` The update will fail if applied to a document that does + * not exist. + * + * Nested fields can be updated by providing dot-separated field path + * strings or by providing `FieldPath` objects. + * + * @param reference - A reference to the document to update. + * @param field - The first field to update. + * @param value - The first value. + * @param moreFieldsAndValues - Additional key value pairs. + * @returns A Promise resolved once the data has been successfully written + * to the backend (note that it won't resolve while you're offline). + */ +export declare function updateDoc( + reference: DocumentReference, + field: string | FieldPath, + value: unknown, + ...moreFieldsAndValues: unknown[] +): Promise; + +/** + * Modify this instance to communicate with the Cloud Firestore emulator. + * + * Note: This must be called before this instance has been used to do any + * operations. + * + * @param firestore - The Firestore instance to configure to connect to the + * emulator. + * @param host - the emulator host (ex: localhost). + * @param port - the emulator port (ex: 9000). + */ +export declare function useFirestoreEmulator( + firestore: FirebaseFirestore_2, + host: string, + port: number +): void; + +/** + * @license + * Copyright 2017 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. + */ +/** + * Simple wrapper around a nullable UID. Mostly exists to make code more + * readable. + */ +declare class User { + readonly uid: string | null; + /** A user with a null UID. */ + static readonly UNAUTHENTICATED: User; + static readonly GOOGLE_CREDENTIALS: User; + static readonly FIRST_PARTY: User; + constructor(uid: string | null); + isAuthenticated(): boolean; + /** + * Returns a key representing this user, suitable for inclusion in a + * dictionary. + */ + toKey(): string; + isEqual(otherUser: User): boolean; +} + +declare type Value = firestoreV1ApiClientInterfaces.Value; + +declare type ValueNullValue = 'NULL_VALUE'; + +declare class ViewSnapshot { + readonly query: Query_2; + readonly docs: DocumentSet; + readonly oldDocs: DocumentSet; + readonly docChanges: DocumentViewChange[]; + readonly mutatedKeys: DocumentKeySet; + readonly fromCache: boolean; + readonly syncStateChanged: boolean; + readonly excludesMetadataChanges: boolean; + constructor( + query: Query_2, + docs: DocumentSet, + oldDocs: DocumentSet, + docChanges: DocumentViewChange[], + mutatedKeys: DocumentKeySet, + fromCache: boolean, + syncStateChanged: boolean, + excludesMetadataChanges: boolean + ); + /** Returns a view snapshot as if all documents in the snapshot were added. */ + static fromInitialDocuments( + query: Query_2, + documents: DocumentSet, + mutatedKeys: DocumentKeySet, + fromCache: boolean + ): ViewSnapshot; + get hasPendingWrites(): boolean; + isEqual(other: ViewSnapshot): boolean; +} + +/** + * Waits until all currently pending writes for the active user have been + * acknowledged by the backend. + * + * The returned Promise resolves immediately if there are no outstanding writes. + * Otherwise, the Promise waits for all previously issued writes (including + * those written in a previous app session), but it does not wait for writes + * that were added after the function is called. If you want to wait for + * additional writes, call `waitForPendingWrites()` again. + * + * Any outstanding `waitForPendingWrites()` Promises are rejected during user + * changes. + * + * @returns A Promise which resolves when all currently pending writes have been + * acknowledged by the backend. + */ +export declare function waitForPendingWrites( + firestore: FirebaseFirestore +): Promise; + +/** + * Creates a `QueryConstraint` that enforces that documents must contain the + * specified field and that the value should satisfy the relation constraint + * provided. + * + * @param fieldPath - The path to compare + * @param opStr - The operation string (e.g "<", "<=", "==", "<", + * "<=", "!="). + * @param value - The value for comparison + * @returns The created `Query`. + */ +export declare function where( + fieldPath: string | FieldPath, + opStr: WhereFilterOp, + value: unknown +): QueryConstraint; + +/** + * Filter conditions in a {@link where} clause are specified using the + * strings '<', '<=', '==', '!=', '>=', '>', 'array-contains', 'in', + * 'array-contains-any', and 'not-in'. + */ +export declare type WhereFilterOp = + | '<' + | '<=' + | '==' + | '!=' + | '>=' + | '>' + | 'array-contains' + | 'in' + | 'array-contains-any' + | 'not-in'; + +/** + * A write batch, used to perform multiple writes as a single atomic unit. + * + * A `WriteBatch` object can be acquired by calling {@link writeBatch}. It + * provides methods for adding writes to the write batch. None of the writes + * will be committed (or visible locally) until {@link WriteBatch#commit} is + * called. + */ +export declare class WriteBatch { + private readonly _firestore; + private readonly _commitHandler; + private readonly _dataReader; + private _mutations; + private _committed; + /** @hideconstructor */ + constructor( + _firestore: FirebaseFirestore_2, + _commitHandler: (m: Mutation[]) => Promise + ); + /** + * Writes to the document referred to by the provided {@link + * DocumentReference}. If the document does not exist yet, it will be created. + * + * @param documentRef - A reference to the document to be set. + * @param data - An object of the fields and values for the document. + * @returns This `WriteBatch` instance. Used for chaining method calls. + */ + set(documentRef: DocumentReference, data: T): WriteBatch; + /** + * Writes to the document referred to by the provided {@link + * DocumentReference}. If the document does not exist yet, it will be created. + * If you provide `merge` or `mergeFields`, the provided data can be merged + * into an existing document. + * + * @param documentRef - A reference to the document to be set. + * @param data - An object of the fields and values for the document. + * @param options - An object to configure the set behavior. + * @returns This `WriteBatch` instance. Used for chaining method calls. + */ + set( + documentRef: DocumentReference, + data: Partial, + options: SetOptions + ): WriteBatch; + /** + * Updates fields in the document referred to by the provided {@link + * DocumentReference}. The update will fail if applied to a document that does + * not exist. + * + * @param documentRef - A reference to the document to be updated. + * @param data - An object containing the fields and values with which to + * update the document. Fields can contain dots to reference nested fields + * within the document. + * @returns This `WriteBatch` instance. Used for chaining method calls. + */ + update(documentRef: DocumentReference, data: UpdateData): WriteBatch; + /** + * Updates fields in the document referred to by this {@link + * DocumentReference}. The update will fail if applied to a document that does + * not exist. + * + * Nested fields can be update by providing dot-separated field path strings + * or by providing `FieldPath` objects. + * + * @param documentRef - A reference to the document to be updated. + * @param field - The first field to update. + * @param value - The first value. + * @param moreFieldsAndValues - Additional key value pairs. + * @returns This `WriteBatch` instance. Used for chaining method calls. + */ + update( + documentRef: DocumentReference, + field: string | FieldPath, + value: unknown, + ...moreFieldsAndValues: unknown[] + ): WriteBatch; + /** + * Deletes the document referred to by the provided {@link DocumentReference}. + * + * @param documentRef - A reference to the document to be deleted. + * @returns This `WriteBatch` instance. Used for chaining method calls. + */ + delete(documentRef: DocumentReference): WriteBatch; + /** + * Commits all of the writes in this write batch as a single atomic unit. + * + * The result of these writes will only be reflected in document reads that + * occur after the returned Promise resolves. If the client is offline, the + * write fails. If you would like to see local modifications or buffer writes + * until the client is online, use the full Firestore SDK. + * + * @returns A Promise resolved once all of the writes in the batch have been + * successfully written to the backend as an atomic unit (note that it won't + * resolve while you're offline). + */ + commit(): Promise; + private _verifyNotCommitted; +} + +/** + * Creates a write batch, used for performing multiple writes as a single + * atomic operation. The maximum number of writes allowed in a single WriteBatch + * is 500. + * + * Unlike transactions, write batches are persisted offline and therefore are + * preferable when you don't need to condition your writes on read data. + * + * @returns A `WriteBatch` that can be used to atomically execute multiple + * writes. + */ +export declare function writeBatch(firestore: FirebaseFirestore): WriteBatch; + +export {}; diff --git a/repo-scripts/prune-dts/tests/firestore.output.d.ts b/repo-scripts/prune-dts/tests/firestore.output.d.ts new file mode 100644 index 00000000000..58411538953 --- /dev/null +++ b/repo-scripts/prune-dts/tests/firestore.output.d.ts @@ -0,0 +1,2078 @@ +/** + * @license + * Copyright 2021 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 { DocumentData as DocumentData_2 } from '@firebase/firestore-types'; +import { FirebaseApp } from '@firebase/app'; +import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; +import { _FirebaseService } from '@firebase/app'; +import { LogLevelString as LogLevel } from '@firebase/logger'; +import { Provider } from '@firebase/component'; +import { SetOptions as SetOptions_2 } from '@firebase/firestore-types'; +/** + * Add a new document to specified `CollectionReference` with the given data, + * assigning it a document ID automatically. + * + * @param reference - A reference to the collection to add this document to. + * @param data - An Object containing the data for the new document. + * @returns A Promise resolved with a `DocumentReference` pointing to the + * newly created document after it has been written to the backend (Note that it + * won't resolve while you're offline). + */ +export declare function addDoc( + reference: CollectionReference, + data: T +): Promise>; +/** + * Returns a special value that can be used with {@link (setDoc:1)} or {@link + * updateDoc} that tells the server to remove the given elements from any + * array value that already exists on the server. All instances of each element + * specified will be removed from the array. If the field being modified is not + * already an array it will be overwritten with an empty array. + * + * @param elements - The elements to remove from the array. + * @returns The `FieldValue` sentinel for use in a call to `setDoc()` or + * `updateDoc()` + */ +export declare function arrayRemove(...elements: unknown[]): FieldValue; +/** + * Returns a special value that can be used with {@link setDoc} or {@link + * updateDoc} that tells the server to union the given elements with any array + * value that already exists on the server. Each specified element that doesn't + * already exist in the array will be added to the end. If the field being + * modified is not already an array it will be overwritten with an array + * containing exactly the specified elements. + * + * @param elements - The elements to union into the array. + * @returns The `FieldValue` sentinel for use in a call to `setDoc()` or + * `updateDoc()`. + */ +export declare function arrayUnion(...elements: unknown[]): FieldValue; +/** + * An immutable object representing an array of bytes. + */ +export declare class Bytes { + private constructor(); + /** + * Creates a new `Bytes` object from the given Base64 string, converting it to + * bytes. + * + * @param base64 - The Base64 string used to create the `Bytes` object. + */ + static fromBase64String(base64: string): Bytes; + /** + * Creates a new `Bytes` object from the given Uint8Array. + * + * @param array - The Uint8Array used to create the `Bytes` object. + */ + static fromUint8Array(array: Uint8Array): Bytes; + /** + * Returns the underlying bytes as a Base64-encoded string. + * + * @returns The Base64-encoded string created from the `Bytes` object. + */ + toBase64(): string; + /** + * Returns the underlying bytes in a new `Uint8Array`. + * + * @returns The Uint8Array created from the `Bytes` object. + */ + toUint8Array(): Uint8Array; + /** + * Returns a string representation of the `Bytes` object. + * + * @returns A string representation of the `Bytes` object. + */ + toString(): string; + /** + * Returns true if this `Bytes` object is equal to the provided one. + * + * @param other - The `Bytes` object to compare against. + * @returns true if this `Bytes` object is equal to the provided one. + */ + isEqual(other: Bytes): boolean; +} +/** + * Constant used to indicate the LRU garbage collection should be disabled. + * Set this value as the `cacheSizeBytes` on the settings passed to the + * `Firestore` instance. + */ +export declare const CACHE_SIZE_UNLIMITED = -1; +/** + * Clears the persistent storage. This includes pending writes and cached + * documents. + * + * Must be called while the `Firestore` instance is not started (after the app is + * terminated or when the app is first initialized). On startup, this function + * must be called before other functions (other than {@link + * initializeFirestore} or {@link getFirestore})). If the `Firestore` + * instance is still running, the promise will be rejected with the error code + * of `failed-precondition`. + * + * Note: `clearIndexedDbPersistence()` is primarily intended to help write + * reliable tests that use Cloud Firestore. It uses an efficient mechanism for + * dropping existing data but does not attempt to securely overwrite or + * otherwise make cached data unrecoverable. For applications that are sensitive + * to the disclosure of cached data in between user sessions, we strongly + * recommend not enabling persistence at all. + * + * @param firestore - The `Firestore` instance to clear persistence for. + * @returns A promise that is resolved when the persistent storage is + * cleared. Otherwise, the promise is rejected with an error. + */ +export declare function clearIndexedDbPersistence( + firestore: FirebaseFirestore +): Promise; +/** + * Gets a `CollectionReference` instance that refers to the collection at + * the specified absolute path. + * + * @param firestore - A reference to the root Firestore instance. + * @param path - A slash-separated path to a collection. + * @param pathSegments - Additional path segments to apply relative to the first + * argument. + * @throws If the final path has an even number of segments and does not point + * to a collection. + * @returns The `CollectionReference` instance. + */ +export declare function collection( + firestore: FirebaseFirestore, + path: string, + ...pathSegments: string[] +): CollectionReference; +/** + * Gets a `CollectionReference` instance that refers to a subcollection of + * `reference` at the specified relative path. + * + * @param reference - A reference to a collection. + * @param path - A slash-separated path to a collection. + * @param pathSegments - Additional path segments to apply relative to the first + * argument. + * @throws If the final path has an even number of segments and does not point + * to a collection. + * @returns The `CollectionReference` instance. + */ +export declare function collection( + reference: CollectionReference, + path: string, + ...pathSegments: string[] +): CollectionReference; +/** + * Gets a `CollectionReference` instance that refers to a subcollection of + * `reference` at the specified relative path. + * + * @param reference - A reference to a Firestore document. + * @param path - A slash-separated path to a collection. + * @param pathSegments - Additional path segments that will be applied relative + * to the first argument. + * @throws If the final path has an even number of segments and does not point + * to a collection. + * @returns The `CollectionReference` instance. + */ +export declare function collection( + reference: DocumentReference, + path: string, + ...pathSegments: string[] +): CollectionReference; +/** + * Creates and returns a new `Query` instance that includes all documents in the + * database that are contained in a collection or subcollection with the + * given `collectionId`. + * + * @param firestore - A reference to the root Firestore instance. + * @param collectionId - Identifies the collections to query over. Every + * collection or subcollection with this ID as the last segment of its path + * will be included. Cannot contain a slash. + * @returns The created `Query`. + */ +export declare function collectionGroup( + firestore: FirebaseFirestore, + collectionId: string +): Query; +/** + * A `CollectionReference` object can be used for adding documents, getting + * document references, and querying for documents (using {@link query}). + */ +export declare class CollectionReference extends Query { + readonly firestore: FirebaseFirestore; + readonly type = 'collection'; + private constructor(); + /** The collection's identifier. */ + get id(): string; + /** + * A string representing the path of the referenced collection (relative + * to the root of the database). + */ + get path(): string; + /** + * A reference to the containing `DocumentReference` if this is a + * subcollection. If this isn't a subcollection, the reference is null. + */ + get parent(): DocumentReference | null; + /** + * Applies a custom data converter to this CollectionReference, allowing you + * to use your own custom model objects with Firestore. When you call {@link + * addDoc} with the returned `CollectionReference` instance, the provided + * converter will convert between Firestore data and your custom type `U`. + * + * @param converter - Converts objects to and from Firestore. + * @returns A `CollectionReference` that uses the provided converter. + */ + withConverter( + converter: FirestoreDataConverter + ): CollectionReference; +} +/** + * Deletes the document referred to by the specified `DocumentReference`. + * + * @param reference - A reference to the document to delete. + * @returns A Promise resolved once the document has been successfully + * deleted from the backend (note that it won't resolve while you're offline). + */ +export declare function deleteDoc( + reference: DocumentReference +): Promise; +/** + * Returns a sentinel for use with {@link updateDoc} or + * {@link setDoc} with `{merge: true}` to mark a field for deletion. + */ +export declare function deleteField(): FieldValue; +/** + * Disables network usage for this instance. It can be re-enabled via {@link + * enableNetwork}. While the network is disabled, any snapshot listeners, + * `getDoc()` or `getDocs()` calls will return results from cache, and any write + * operations will be queued until the network is restored. + * + * @returns A promise that is resolved once the network has been disabled. + */ +export declare function disableNetwork( + firestore: FirebaseFirestore +): Promise; +/** + * Gets a `DocumentReference` instance that refers to the document at the + * specified absolute path. + * + * @param firestore - A reference to the root Firestore instance. + * @param path - A slash-separated path to a document. + * @param pathSegments - Additional path segments that will be applied relative + * to the first argument. + * @throws If the final path has an odd number of segments and does not point to + * a document. + * @returns The `DocumentReference` instance. + */ +export declare function doc( + firestore: FirebaseFirestore, + path: string, + ...pathSegments: string[] +): DocumentReference; +/** + * Gets a `DocumentReference` instance that refers to a document within + * `reference` at the specified relative path. If no path is specified, an + * automatically-generated unique ID will be used for the returned + * `DocumentReference`. + * + * @param reference - A reference to a collection. + * @param path - A slash-separated path to a document. Has to be omitted to use + * auto-generated IDs. + * @param pathSegments - Additional path segments that will be applied relative + * to the first argument. + * @throws If the final path has an odd number of segments and does not point to + * a document. + * @returns The `DocumentReference` instance. + */ +export declare function doc( + reference: CollectionReference, + path?: string, + ...pathSegments: string[] +): DocumentReference; +/** + * Gets a `DocumentReference` instance that refers to a document within + * `reference` at the specified relative path. + * + * @param reference - A reference to a Firestore document. + * @param path - A slash-separated path to a document. + * @param pathSegments - Additional path segments that will be applied relative + * to the first argument. + * @throws If the final path has an odd number of segments and does not point to + * a document. + * @returns The `DocumentReference` instance. + */ +export declare function doc( + reference: DocumentReference, + path: string, + ...pathSegments: string[] +): DocumentReference; +/** + * A `DocumentChange` represents a change to the documents matching a query. + * It contains the document affected and the type of change that occurred. + */ +export declare interface DocumentChange { + /** The type of change ('added', 'modified', or 'removed'). */ + readonly type: DocumentChangeType; + /** The document affected by this change. */ + readonly doc: QueryDocumentSnapshot; + /** + * The index of the changed document in the result set immediately prior to + * this `DocumentChange` (i.e. supposing that all prior `DocumentChange` objects + * have been applied). Is `-1` for 'added' events. + */ + readonly oldIndex: number; + /** + * The index of the changed document in the result set immediately after + * this `DocumentChange` (i.e. supposing that all prior `DocumentChange` + * objects and the current `DocumentChange` object have been applied). + * Is -1 for 'removed' events. + */ + readonly newIndex: number; +} +/** + * The type of a `DocumentChange` may be 'added', 'removed', or 'modified'. + */ +export declare type DocumentChangeType = 'added' | 'removed' | 'modified'; +/** + * Document data (for use with {@link setDoc}) consists of fields mapped to + * values. + */ +export declare interface DocumentData { + [field: string]: any; +} +/** + * Returns a special sentinel `FieldPath` to refer to the ID of a document. + * It can be used in queries to sort or filter by the document ID. + */ +export declare function documentId(): FieldPath; +/** + * A `DocumentReference` refers to a document location in a Firestore database + * and can be used to write, read, or listen to the location. The document at + * the referenced location may or may not exist. + */ +export declare class DocumentReference { + /** The type of this Firestore reference. */ + readonly type = 'document'; + /** + * The {@link FirebaseFirestore} the document is in. + * This is useful for performing transactions, for example. + */ + readonly firestore: FirebaseFirestore; + private constructor(); + /** + * The document's identifier within its collection. + */ + get id(): string; + /** + * A string representing the path of the referenced document (relative + * to the root of the database). + */ + get path(): string; + /** + * The collection this `DocumentReference` belongs to. + */ + get parent(): CollectionReference; + /** + * Applies a custom data converter to this `DocumentReference`, allowing you + * to use your own custom model objects with Firestore. When you call {@link + * setDoc}, {@link getDoc}, etc. with the returned `DocumentReference` + * instance, the provided converter will convert between Firestore data and + * your custom type `U`. + * + * @param converter - Converts objects to and from Firestore. + * @returns A `DocumentReference` that uses the provided converter. + */ + withConverter(converter: FirestoreDataConverter): DocumentReference; +} +/** + * A `DocumentSnapshot` contains data read from a document in your Firestore + * database. The data can be extracted with `.data()` or `.get()` to + * get a specific field. + * + * For a `DocumentSnapshot` that points to a non-existing document, any data + * access will return 'undefined'. You can use the `exists()` method to + * explicitly verify a document's existence. + */ +export declare class DocumentSnapshot { + /** + * Metadata about the `DocumentSnapshot`, including information about its + * source and local modifications. + */ + readonly metadata: SnapshotMetadata; + protected constructor(); + /** + * Property of the `DocumentSnapshot` that signals whether or not the data + * exists. True if the document exists. + */ + exists(): this is QueryDocumentSnapshot; + /** + * Retrieves all fields in the document as an `Object`. Returns `undefined` if + * the document doesn't exist. + * + * By default, `FieldValue.serverTimestamp()` values that have not yet been + * set to their final value will be returned as `null`. You can override + * this by passing an options object. + * + * @param options - An options object to configure how data is retrieved from + * the snapshot (for example the desired behavior for server timestamps that + * have not yet been set to their final value). + * @returns An `Object` containing all fields in the document or `undefined` if + * the document doesn't exist. + */ + data(options?: SnapshotOptions): T | undefined; + /** + * Retrieves the field specified by `fieldPath`. Returns `undefined` if the + * document or field doesn't exist. + * + * By default, a `FieldValue.serverTimestamp()` that has not yet been set to + * its final value will be returned as `null`. You can override this by + * passing an options object. + * + * @param fieldPath - The path (for example 'foo' or 'foo.bar') to a specific + * field. + * @param options - An options object to configure how the field is retrieved + * from the snapshot (for example the desired behavior for server timestamps + * that have not yet been set to their final value). + * @returns The data at the specified field location or undefined if no such + * field exists in the document. + */ + get(fieldPath: string | FieldPath, options?: SnapshotOptions): any; + /** + * Property of the `DocumentSnapshot` that provides the document's ID. + */ + get id(): string; + /** + * The `DocumentReference` for the document included in the `DocumentSnapshot`. + */ + get ref(): DocumentReference; +} +/** + * Attempts to enable persistent storage, if possible. + * + * Must be called before any other functions (other than + * {@link initializeFirestore}, {@link getFirestore} or + * {@link clearIndexedDbPersistence}. + * + * If this fails, `enableIndexedDbPersistence()` will reject the promise it + * returns. Note that even after this failure, the `Firestore` instance will + * remain usable, however offline persistence will be disabled. + * + * There are several reasons why this can fail, which can be identified by + * the `code` on the error. + * + * * failed-precondition: The app is already open in another browser tab. + * * unimplemented: The browser is incompatible with the offline + * persistence implementation. + * + * @param firestore - The `Firestore` instance to enable persistence for. + * @param persistenceSettings - Optional settings object to configure + * persistence. + * @returns A promise that represents successfully enabling persistent storage. + */ +export declare function enableIndexedDbPersistence( + firestore: FirebaseFirestore, + persistenceSettings?: PersistenceSettings +): Promise; +/** + * Attempts to enable multi-tab persistent storage, if possible. If enabled + * across all tabs, all operations share access to local persistence, including + * shared execution of queries and latency-compensated local document updates + * across all connected instances. + * + * If this fails, `enableMultiTabIndexedDbPersistence()` will reject the promise + * it returns. Note that even after this failure, the `Firestore` instance will + * remain usable, however offline persistence will be disabled. + * + * There are several reasons why this can fail, which can be identified by + * the `code` on the error. + * + * * failed-precondition: The app is already open in another browser tab and + * multi-tab is not enabled. + * * unimplemented: The browser is incompatible with the offline + * persistence implementation. + * + * @param firestore - The `Firestore` instance to enable persistence for. + * @returns A promise that represents successfully enabling persistent + * storage. + */ +export declare function enableMultiTabIndexedDbPersistence( + firestore: FirebaseFirestore +): Promise; +/** + * Re-enables use of the network for this Firestore instance after a prior + * call to {@link disableNetwork}. + * + * @returns A promise that is resolved once the network has been enabled. + */ +export declare function enableNetwork( + firestore: FirebaseFirestore +): Promise; +/** + * Creates a `QueryConstraint` that modifies the result set to end at the + * provided document (inclusive). 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. + * + * @param snapshot - The snapshot of the document to end at. + * @returns A `QueryConstraint` to pass to `query()` + */ +export declare function endAt( + snapshot: DocumentSnapshot +): QueryConstraint; +/** + * Creates a `QueryConstraint` that modifies the result set to end at the + * provided fields relative to the order of the query. The order of the field + * values must match the order of the order by clauses of the query. + * + * @param fieldValues - The field values to end this query at, in order + * of the query's order by. + * @returns A `QueryConstraint` to pass to `query()` + */ +export declare function endAt(...fieldValues: unknown[]): QueryConstraint; +/** + * Creates a `QueryConstraint` 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. + * + * @param snapshot - The snapshot of the document to end before. + * @returns A `QueryConstraint` to pass to `query()` + */ +export declare function endBefore( + snapshot: DocumentSnapshot +): QueryConstraint; +/** + * Creates a `QueryConstraint` that modifies the result set to end before the + * provided fields relative to the order of the query. The order of the field + * values must match the order of the order by clauses of the query. + * + * @param fieldValues - The field values to end this query before, in order + * of the query's order by. + * @returns A `QueryConstraint` to pass to `query()` + */ +export declare function endBefore(...fieldValues: unknown[]): QueryConstraint; +/** + * A `FieldPath` refers to a field in a document. The path may consist of a + * single field name (referring to a top-level field in the document), or a + * list of field names (referring to a nested field in the document). + * + * Create a `FieldPath` by providing field names. If more than one field + * name is provided, the path will point to a nested field in a document. + */ +export declare class FieldPath { + /** + * Creates a FieldPath from the provided field names. If more than one field + * name is provided, the path will point to a nested field in a document. + * + * @param fieldNames - A list of field names. + */ + constructor(...fieldNames: string[]); + /** + * Returns true if this `FieldPath` is equal to the provided one. + * + * @param other - The `FieldPath` to compare against. + * @returns true if this `FieldPath` is equal to the provided one. + */ + isEqual(other: FieldPath): boolean; +} +/** + * Sentinel values that can be used when writing document fields with `set()` + * or `update()`. + */ +export declare abstract class FieldValue { + /** + * @param _methodName - The public API endpoint that returns this class. + */ + constructor(_methodName: string); + abstract isEqual(other: FieldValue): boolean; +} +/** + * The Cloud Firestore service interface. + * + * Do not call this constructor directly. Instead, use {@link getFirestore}. + */ +export declare class FirebaseFirestore { + private constructor(); + /** + * The {@link FirebaseApp} associated with this `Firestore` service + * instance. + */ + get app(): FirebaseApp; + toJSON(): object; +} +/** + * Converter used by `withConverter()` to transform user objects of type `T` + * into Firestore data. + * + * Using the converter allows you to specify generic type arguments when + * storing and retrieving objects from Firestore. + * + * @example + * ```typescript + * class Post { + * constructor(readonly title: string, readonly author: string) {} + * + * toString(): string { + * return this.title + ', by ' + this.author; + * } + * } + * + * const postConverter = { + * toFirestore(post: Post): firebase.firestore.DocumentData { + * return {title: post.title, author: post.author}; + * }, + * fromFirestore( + * snapshot: firebase.firestore.QueryDocumentSnapshot, + * options: firebase.firestore.SnapshotOptions + * ): Post { + * const data = snapshot.data(options)!; + * return new Post(data.title, data.author); + * } + * }; + * + * const postSnap = await firebase.firestore() + * .collection('posts') + * .withConverter(postConverter) + * .doc().get(); + * const post = postSnap.data(); + * if (post !== undefined) { + * post.title; // string + * post.toString(); // Should be defined + * post.someNonExistentProperty; // TS error + * } + * ``` + */ +export declare interface FirestoreDataConverter { + /** + * Called by the Firestore SDK to convert a custom model object of type `T` + * into a plain JavaScript object (suitable for writing directly to the + * Firestore database). To use `set()` with `merge` and `mergeFields`, + * `toFirestore()` must be defined with `Partial`. + */ + toFirestore(modelObject: T): DocumentData; + /** + * Called by the Firestore SDK to convert a custom model object of type `T` + * into a plain JavaScript object (suitable for writing directly to the + * Firestore database). Used with {@link setData}, {@link WriteBatch#set} + * and {@link Transaction#set} with `merge:true` or `mergeFields`. + */ + toFirestore(modelObject: Partial, options: SetOptions): DocumentData; + /** + * Called by the Firestore SDK to convert Firestore data into an object of + * type T. You can access your data by calling: `snapshot.data(options)`. + * + * @param snapshot - A `QueryDocumentSnapshot` containing your data and metadata. + * @param options - The `SnapshotOptions` from the initial call to `data()`. + */ + fromFirestore( + snapshot: QueryDocumentSnapshot, + options?: SnapshotOptions + ): T; +} +/** An error returned by a Firestore operation. */ +export declare class FirestoreError extends Error { + readonly code: FirestoreErrorCode; + readonly message: string; + readonly name: string; + readonly stack?: string; + private constructor(); +} +/** + * @license + * Copyright 2017 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. + */ +/** + * The set of Firestore status codes. The codes are the same at the ones + * exposed by gRPC here: + * https://github.com/grpc/grpc/blob/master/doc/statuscodes.md + * + * Possible values: + * - 'cancelled': The operation was cancelled (typically by the caller). + * - 'unknown': Unknown error or an error from a different error domain. + * - 'invalid-argument': Client specified an invalid argument. Note that this + * differs from 'failed-precondition'. 'invalid-argument' indicates + * arguments that are problematic regardless of the state of the system + * (e.g. an invalid field name). + * - 'deadline-exceeded': Deadline expired before operation could complete. + * For operations that change the state of the system, this error may be + * returned even if the operation has completed successfully. For example, + * a successful response from a server could have been delayed long enough + * for the deadline to expire. + * - 'not-found': Some requested document was not found. + * - 'already-exists': Some document that we attempted to create already + * exists. + * - 'permission-denied': The caller does not have permission to execute the + * specified operation. + * - 'resource-exhausted': Some resource has been exhausted, perhaps a + * per-user quota, or perhaps the entire file system is out of space. + * - 'failed-precondition': Operation was rejected because the system is not + * in a state required for the operation's execution. + * - 'aborted': The operation was aborted, typically due to a concurrency + * issue like transaction aborts, etc. + * - 'out-of-range': Operation was attempted past the valid range. + * - 'unimplemented': Operation is not implemented or not supported/enabled. + * - 'internal': Internal errors. Means some invariants expected by + * underlying system has been broken. If you see one of these errors, + * something is very broken. + * - 'unavailable': The service is currently unavailable. This is most likely + * a transient condition and may be corrected by retrying with a backoff. + * - 'data-loss': Unrecoverable data loss or corruption. + * - 'unauthenticated': The request does not have valid authentication + * credentials for the operation. + */ +export declare type FirestoreErrorCode = + | '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'; +/** + * @license + * Copyright 2017 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. + */ +/** + * An immutable object representing a geographic location in Firestore. The + * location is represented as latitude/longitude pair. + * + * Latitude values are in the range of [-90, 90]. + * Longitude values are in the range of [-180, 180]. + */ +export declare class GeoPoint { + /** + * Creates a new immutable `GeoPoint` object with the provided latitude and + * longitude values. + * @param latitude - The latitude as number between -90 and 90. + * @param longitude - The longitude as number between -180 and 180. + */ + constructor(latitude: number, longitude: number); + /** + * The latitude of this `GeoPoint` instance. + */ + get latitude(): number; + /** + * The longitude of this `GeoPoint` instance. + */ + get longitude(): number; + /** + * Returns true if this `GeoPoint` is equal to the provided one. + * + * @param other - The `GeoPoint` to compare against. + * @returns true if this `GeoPoint` is equal to the provided one. + */ + isEqual(other: GeoPoint): boolean; + toJSON(): { + latitude: number; + longitude: number; + }; +} +/** + * Reads the document referred to by this `DocumentReference`. + * + * Note: `getDoc()` attempts to provide up-to-date data when possible by waiting + * for data from the server, but it may return cached data or fail if you are + * offline and the server cannot be reached. To specify this behavior, invoke + * {@link getDocFromCache} or {@link getDocFromServer}. + * + * @param reference - The reference of the document to fetch. + * @returns A Promise resolved with a `DocumentSnapshot` containing the + * current document contents. + */ +export declare function getDoc( + reference: DocumentReference +): Promise>; +/** + * Reads the document referred to by this `DocumentReference` from cache. + * Returns an error if the document is not currently cached. + * + * @returns A Promise resolved with a `DocumentSnapshot` containing the + * current document contents. + */ +export declare function getDocFromCache( + reference: DocumentReference +): Promise>; +/** + * Reads the document referred to by this `DocumentReference` from the server. + * Returns an error if the network is not available. + * + * @returns A Promise resolved with a `DocumentSnapshot` containing the + * current document contents. + */ +export declare function getDocFromServer( + reference: DocumentReference +): Promise>; +/** + * Executes the query and returns the results as a `QuerySnapshot`. + * + * Note: `getDocs()` attempts to provide up-to-date data when possible by + * waiting for data from the server, but it may return cached data or fail if + * you are offline and the server cannot be reached. To specify this behavior, + * invoke {@link getDocsFromCache} or {@link getDocsFromServer}. + * + * @returns A Promise that will be resolved with the results of the query. + */ +export declare function getDocs(query: Query): Promise>; +/** + * Executes the query and returns the results as a `QuerySnapshot` from cache. + * Returns an error if the document is not currently cached. + * + * @returns A Promise that will be resolved with the results of the query. + */ +export declare function getDocsFromCache( + query: Query +): Promise>; +/** + * Executes the query and returns the results as a `QuerySnapshot` from the + * server. Returns an error if the network is not available. + * + * @returns A Promise that will be resolved with the results of the query. + */ +export declare function getDocsFromServer( + query: Query +): Promise>; +/** + * Returns the existing instance of Firestore that is associated with the + * provided {@link FirebaseApp}. If no instance exists, initializes a new + * instance with default settings. + * + * @param app - The {@link FirebaseApp} instance that the returned Firestore + * instance is associated with. + * @returns The `Firestore` instance of the provided app. + */ +export declare function getFirestore(app: FirebaseApp): FirebaseFirestore; +/** + * Returns a special value that can be used with {@link setDoc} or {@link + * updateDoc} that tells the server to increment the field's current value by + * the given value. + * + * If either the operand or the current field value uses floating point + * precision, all arithmetic follows IEEE 754 semantics. If both values are + * integers, values outside of JavaScript's safe number range + * (`Number.MIN_SAFE_INTEGER` to `Number.MAX_SAFE_INTEGER`) are also subject to + * precision loss. Furthermore, once processed by the Firestore backend, all + * integer operations are capped between -2^63 and 2^63-1. + * + * If the current field value is not of type `number`, or if the field does not + * yet exist, the transformation sets the field to the given value. + * + * @param n - The value to increment by. + * @returns The `FieldValue` sentinel for use in a call to `setDoc()` or + * `updateDoc()` + */ +export declare function increment(n: number): FieldValue; +/** + * Initializes a new instance of Cloud Firestore with the provided settings. + * Can only be called before any other function, including + * {@link getFirestore}. If the custom settings are empty, this function is + * equivalent to calling {@link getFirestore}. + * + * @param app - The {@link FirebaseApp} with which the `Firestore` instance will + * be associated. + * @param settings - A settings object to configure the `Firestore` instance. + * @returns A newly initialized `Firestore` instance. + */ +export declare function initializeFirestore( + app: FirebaseApp, + settings: Settings +): FirebaseFirestore; +/** + * Creates a `QueryConstraint` that only returns the first matching documents. + * + * @param limit - The maximum number of items to return. + * @returns The created `Query`. + */ +export declare function limit(limit: number): QueryConstraint; +/** + * Creates a `QueryConstraint` that only returns the last matching documents. + * + * You must specify at least one `orderBy` clause for `limitToLast` queries, + * otherwise an exception will be thrown during execution. + * + * @param limit - The maximum number of items to return. + * @returns The created `Query`. + */ +export declare function limitToLast(limit: number): QueryConstraint; +/** + * Loads a Firestore bundle into the local cache. + * + * @param firestore - The `Firestore` instance to load bundles for for. + * @param bundleData - An object representing the bundle to be loaded. Valid objects are + * `ArrayBuffer`, `ReadableStream` or `string`. + * + * @return + * A `LoadBundleTask` object, which notifies callers with progress updates, and completion + * or error events. It can be used as a `Promise`. + */ +export declare function loadBundle( + firestore: FirebaseFirestore, + bundleData: ReadableStream | ArrayBuffer | string +): LoadBundleTask; +/** + * Represents the task of loading a Firestore bundle. It provides progress of bundle + * loading, as well as task completion and error events. + * + * The API is compatible with `Promise`. + */ +export declare class LoadBundleTask + implements PromiseLike +{ + /** + * Registers functions to listen to bundle loading progress events. + * @param next - Called when there is a progress update from bundle loading. Typically `next` calls occur + * each time a Firestore document is loaded from the bundle. + * @param error - Called when an error occurs during bundle loading. The task aborts after reporting the + * error, and there should be no more updates after this. + * @param complete - Called when the loading task is complete. + */ + onProgress( + next?: (progress: LoadBundleTaskProgress) => unknown, + error?: (err: Error) => unknown, + complete?: () => void + ): void; + /** + * Implements the `Promise.catch` interface. + * + * @param onRejected - Called when an error occurs during bundle loading. + */ + catch( + onRejected: (a: Error) => R | PromiseLike + ): Promise; + /** + * Implements the `Promise.then` interface. + * + * @param onFulfilled - Called on the completion of the loading task with a final `LoadBundleTaskProgress` update. + * The update will always have its `taskState` set to `"Success"`. + * @param onRejected - Called when an error occurs during bundle loading. + */ + then( + onFulfilled?: (a: LoadBundleTaskProgress) => T | PromiseLike, + onRejected?: (a: Error) => R | PromiseLike + ): Promise; +} +/** + * Represents a progress update or a final state from loading bundles. + */ +export declare interface LoadBundleTaskProgress { + /** How many documents have been loaded. */ + documentsLoaded: number; + /** How many documents are in the bundle being loaded. */ + totalDocuments: number; + /** How many bytes have been loaded. */ + bytesLoaded: number; + /** How many bytes are in the bundle being loaded. */ + totalBytes: number; + /** Current task state. */ + taskState: TaskState; +} +export { LogLevel }; +/** + * Reads a Firestore `Query` from local cache, identified by the given name. + * + * The named queries are packaged into bundles on the server side (along + * with resulting documents), and loaded to local cache using `loadBundle`. Once in local + * cache, use this method to extract a `Query` by name. + */ +export declare function namedQuery( + firestore: FirebaseFirestore, + name: string +): Promise; +/** + * Attaches a listener for `DocumentSnapshot` events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param reference - A reference to the document to listen to. + * @param observer - A single object containing `next` and `error` callbacks. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export declare function onSnapshot( + reference: DocumentReference, + observer: { + next?: (snapshot: DocumentSnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; + } +): Unsubscribe; +/** + * Attaches a listener for `DocumentSnapshot` events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param reference - A reference to the document to listen to. + * @param options - Options controlling the listen behavior. + * @param observer - A single object containing `next` and `error` callbacks. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export declare function onSnapshot( + reference: DocumentReference, + options: SnapshotListenOptions, + observer: { + next?: (snapshot: DocumentSnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; + } +): Unsubscribe; +/** + * Attaches a listener for `DocumentSnapshot` events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param reference - A reference to the document to listen to. + * @param onNext - A callback to be called every time a new `DocumentSnapshot` + * is available. + * @param onError - A callback to be called if the listen fails or is + * cancelled. No further callbacks will occur. + * @param onCompletion - Can be provided, but will not be called since streams are + * never ending. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export declare function onSnapshot( + reference: DocumentReference, + onNext: (snapshot: DocumentSnapshot) => void, + onError?: (error: FirestoreError) => void, + onCompletion?: () => void +): Unsubscribe; +/** + * Attaches a listener for `DocumentSnapshot` events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param reference - A reference to the document to listen to. + * @param options - Options controlling the listen behavior. + * @param onNext - A callback to be called every time a new `DocumentSnapshot` + * is available. + * @param onError - A callback to be called if the listen fails or is + * cancelled. No further callbacks will occur. + * @param onCompletion - Can be provided, but will not be called since streams are + * never ending. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export declare function onSnapshot( + reference: DocumentReference, + options: SnapshotListenOptions, + onNext: (snapshot: DocumentSnapshot) => void, + onError?: (error: FirestoreError) => void, + onCompletion?: () => void +): Unsubscribe; +/** + * Attaches a listener for `QuerySnapshot` events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. The listener can be cancelled by + * calling the function that is returned when `onSnapshot` is called. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param query - The query to listen to. + * @param observer - A single object containing `next` and `error` callbacks. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export declare function onSnapshot( + query: Query, + observer: { + next?: (snapshot: QuerySnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; + } +): Unsubscribe; +/** + * Attaches a listener for `QuerySnapshot` events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. The listener can be cancelled by + * calling the function that is returned when `onSnapshot` is called. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param query - The query to listen to. + * @param options - Options controlling the listen behavior. + * @param observer - A single object containing `next` and `error` callbacks. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export declare function onSnapshot( + query: Query, + options: SnapshotListenOptions, + observer: { + next?: (snapshot: QuerySnapshot) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; + } +): Unsubscribe; +/** + * Attaches a listener for `QuerySnapshot` events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. The listener can be cancelled by + * calling the function that is returned when `onSnapshot` is called. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param query - The query to listen to. + * @param onNext - A callback to be called every time a new `QuerySnapshot` + * is available. + * @param onCompletion - Can be provided, but will not be called since streams are + * never ending. + * @param onError - A callback to be called if the listen fails or is + * cancelled. No further callbacks will occur. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export declare function onSnapshot( + query: Query, + onNext: (snapshot: QuerySnapshot) => void, + onError?: (error: FirestoreError) => void, + onCompletion?: () => void +): Unsubscribe; +/** + * Attaches a listener for `QuerySnapshot` events. You may either pass + * individual `onNext` and `onError` callbacks or pass a single observer + * object with `next` and `error` callbacks. The listener can be cancelled by + * calling the function that is returned when `onSnapshot` is called. + * + * NOTE: Although an `onCompletion` callback can be provided, it will + * never be called because the snapshot stream is never-ending. + * + * @param query - The query to listen to. + * @param options - Options controlling the listen behavior. + * @param onNext - A callback to be called every time a new `QuerySnapshot` + * is available. + * @param onCompletion - Can be provided, but will not be called since streams are + * never ending. + * @param onError - A callback to be called if the listen fails or is + * cancelled. No further callbacks will occur. + * @returns An unsubscribe function that can be called to cancel + * the snapshot listener. + */ +export declare function onSnapshot( + query: Query, + options: SnapshotListenOptions, + onNext: (snapshot: QuerySnapshot) => void, + onError?: (error: FirestoreError) => void, + onCompletion?: () => void +): Unsubscribe; +/** + * Attaches a listener for a snapshots-in-sync event. The snapshots-in-sync + * event indicates that all listeners affected by a given change have fired, + * even if a single server-generated change affects multiple listeners. + * + * NOTE: The snapshots-in-sync event only indicates that listeners are in sync + * with each other, but does not relate to whether those snapshots are in sync + * with the server. Use SnapshotMetadata in the individual listeners to + * determine if a snapshot is from the cache or the server. + * + * @param firestore - The instance of Firestore for synchronizing snapshots. + * @param observer - A single object containing `next` and `error` callbacks. + * @returns An unsubscribe function that can be called to cancel the snapshot + * listener. + */ +export declare function onSnapshotsInSync( + firestore: FirebaseFirestore, + observer: { + next?: (value: void) => void; + error?: (error: FirestoreError) => void; + complete?: () => void; + } +): Unsubscribe; +/** + * Attaches a listener for a snapshots-in-sync event. The snapshots-in-sync + * event indicates that all listeners affected by a given change have fired, + * even if a single server-generated change affects multiple listeners. + * + * NOTE: The snapshots-in-sync event only indicates that listeners are in sync + * with each other, but does not relate to whether those snapshots are in sync + * with the server. Use SnapshotMetadata in the individual listeners to + * determine if a snapshot is from the cache or the server. + * + * @param firestore - The instance of Firestore for synchronizing snapshots. + * @param onSync - A callback to be called every time all snapshot listeners are + * in sync with each other. + * @returns An unsubscribe function that can be called to cancel the snapshot + * listener. + */ +export declare function onSnapshotsInSync( + firestore: FirebaseFirestore, + onSync: () => void +): Unsubscribe; +/** + * Creates a `QueryConstraint` that sorts the query result by the + * specified field, optionally in descending order instead of ascending. + * + * @param fieldPath - The field to sort by. + * @param directionStr - Optional direction to sort by ('asc' or 'desc'). If + * not specified, order will be ascending. + * @returns The created `Query`. + */ +export declare function orderBy( + fieldPath: string | FieldPath, + directionStr?: OrderByDirection +): QueryConstraint; +/** + * The direction of a {@link orderBy} clause is specified as 'desc' or 'asc' + * (descending or ascending). + */ +export declare type OrderByDirection = 'desc' | 'asc'; +export declare interface PersistenceSettings { + forceOwnership?: boolean; +} +/** + * A `Query` refers to a Query which you can read or listen to. You can also + * construct refined `Query` objects by adding filters and ordering. + */ +export declare class Query { + /** The type of this Firestore reference. */ + readonly type: 'query' | 'collection'; + /** + * The `FirebaseFirestore` for the Firestore database (useful for performing + * transactions, etc.). + */ + readonly firestore: FirebaseFirestore; + protected constructor(); + /** + * Applies a custom data converter to this query, allowing you to use your own + * custom model objects with Firestore. When you call {@link getDocs} with + * the returned query, the provided converter will convert between Firestore + * data and your custom type `U`. + * + * @param converter - Converts objects to and from Firestore. + * @returns A `Query` that uses the provided converter. + */ + withConverter(converter: FirestoreDataConverter): Query; +} +/** + * Creates a new immutable instance of `query` that is extended to also include + * additional query constraints. + * + * @param query - The query instance to use as a base for the new constraints. + * @param queryConstraints - The list of `QueryConstraint`s to apply. + * @throws if any of the provided query constraints cannot be combined with the + * existing or new constraints. + */ +export declare function query( + query: Query, + ...queryConstraints: QueryConstraint[] +): Query; +/** + * A `QueryConstraint` is used to narrow the set of documents returned by a + * Firestore query. `QueryConstraint`s are created by invoking {@link where}, + * {@link orderBy}, {@link startAt}, {@link startAfter}, {@link + * endBefore}, {@link endAt}, {@link limit} or {@link limitToLast} and + * can then be passed to {@link query} to create a new query instance that + * also contains this `QueryConstraint`. + */ +export declare abstract class QueryConstraint { + /** The type of this query constraints */ + abstract readonly type: QueryConstraintType; +} +/** Describes the different query constraints available in this SDK. */ +export declare type QueryConstraintType = + | 'where' + | 'orderBy' + | 'limit' + | 'limitToLast' + | 'startAt' + | 'startAfter' + | 'endAt' + | 'endBefore'; +/** + * A `QueryDocumentSnapshot` contains data read from a document in your + * Firestore database as part of a query. The document is guaranteed to exist + * and its data can be extracted with `.data()` or `.get()` to get a + * specific field. + * + * A `QueryDocumentSnapshot` offers the same API surface as a + * `DocumentSnapshot`. Since query results contain only existing documents, the + * `exists` property will always be true and `data()` will never return + * 'undefined'. + */ +export declare class QueryDocumentSnapshot< + T = DocumentData +> extends DocumentSnapshot { + /** + * Retrieves all fields in the document as an `Object`. + * + * By default, `FieldValue.serverTimestamp()` values that have not yet been + * set to their final value will be returned as `null`. You can override + * this by passing an options object. + * + * @override + * @param options - An options object to configure how data is retrieved from + * the snapshot (for example the desired behavior for server timestamps that + * have not yet been set to their final value). + * @returns An `Object` containing all fields in the document. + */ + data(options?: SnapshotOptions): T; +} +/** + * Returns true if the provided queries point to the same collection and apply + * the same constraints. + * + * @param left - A `Query` to compare. + * @param right - A `Query` to compare. + * @returns true if the references point to the same location in the same + * Firestore database. + */ +export declare function queryEqual(left: Query, right: Query): boolean; +/** + * A `QuerySnapshot` contains zero or more `DocumentSnapshot` objects + * representing the results of a query. The documents can be accessed as an + * array via the `docs` property or enumerated using the `forEach` method. The + * number of documents can be determined via the `empty` and `size` + * properties. + */ +export declare class QuerySnapshot { + /** + * Metadata about this snapshot, concerning its source and if it has local + * modifications. + */ + readonly metadata: SnapshotMetadata; + /** + * The query on which you called `get` or `onSnapshot` in order to get this + * `QuerySnapshot`. + */ + readonly query: Query; + private constructor(); + /** An array of all the documents in the `QuerySnapshot`. */ + get docs(): Array>; + /** The number of documents in the `QuerySnapshot`. */ + get size(): number; + /** True if there are no documents in the `QuerySnapshot`. */ + get empty(): boolean; + /** + * Enumerates all of the documents in the `QuerySnapshot`. + * + * @param callback - A callback to be called with a `QueryDocumentSnapshot` for + * each document in the snapshot. + * @param thisArg - The `this` binding for the callback. + */ + forEach( + callback: (result: QueryDocumentSnapshot) => void, + thisArg?: unknown + ): void; + /** + * Returns an array of the documents changes since the last snapshot. If this + * is the first snapshot, all documents will be in the list as 'added' + * changes. + * + * @param options - `SnapshotListenOptions` that control whether metadata-only + * changes (i.e. only `DocumentSnapshot.metadata` changed) should trigger + * snapshot events. + */ + docChanges(options?: SnapshotListenOptions): Array>; +} +/** + * Returns true if the provided references are equal. + * + * @param left - A reference to compare. + * @param right - A reference to compare. + * @returns true if the references point to the same location in the same + * Firestore database. + */ +export declare function refEqual( + left: DocumentReference | CollectionReference, + right: DocumentReference | CollectionReference +): boolean; +/** + * Executes the given `updateFunction` and then attempts to commit the changes + * applied within the transaction. If any document read within the transaction + * has changed, Cloud Firestore retries the `updateFunction`. If it fails to + * commit after 5 attempts, the transaction fails. + * + * The maximum number of writes allowed in a single transaction is 500. + * + * @param firestore - A reference to the Firestore database to run this + * transaction against. + * @param updateFunction - The function to execute within the transaction + * context. + * @returns If the transaction completed successfully or was explicitly aborted + * (the `updateFunction` returned a failed promise), the promise returned by the + * `updateFunction `is returned here. Otherwise, if the transaction failed, a + * rejected promise with the corresponding failure error is returned. + */ +export declare function runTransaction( + firestore: FirebaseFirestore, + updateFunction: (transaction: Transaction) => Promise +): Promise; +/** + * Returns a sentinel used with {@link setDoc} or {@link updateDoc} to + * include a server-generated timestamp in the written data. + */ +export declare function serverTimestamp(): FieldValue; +/** + * Writes to the document referred to by this `DocumentReference`. If the + * document does not yet exist, it will be created. + * + * @param reference - A reference to the document to write. + * @param data - A map of the fields and values for the document. + * @returns A Promise resolved once the data has been successfully written + * to the backend (note that it won't resolve while you're offline). + */ +export declare function setDoc( + reference: DocumentReference, + data: T +): Promise; +/** + * Writes to the document referred to by the specified `DocumentReference`. If + * the document does not yet exist, it will be created. If you provide `merge` + * or `mergeFields`, the provided data can be merged into an existing document. + * + * @param reference - A reference to the document to write. + * @param data - A map of the fields and values for the document. + * @param options - An object to configure the set behavior. + * @returns A Promise resolved once the data has been successfully written + * to the backend (note that it won't resolve while you're offline). + */ +export declare function setDoc( + reference: DocumentReference, + data: Partial, + options: SetOptions +): Promise; +/** + * Sets the verbosity of Cloud Firestore logs (debug, error, or silent). + * + * @param logLevel - The verbosity you set for activity and error logging. Can + * be any of the following values: + * + *
    + *
  • `debug` for the most verbose logging level, primarily for + * debugging.
  • + *
  • `error` to log errors only.
  • + *
  • `silent` to turn off logging.
  • + *
+ */ +export declare function setLogLevel(logLevel: LogLevel): void; +/** + * An options object that configures the behavior of {@link setDoc}, {@link + * WriteBatch#set} and {@link Transaction#set} calls. These calls can be + * configured to perform granular merges instead of overwriting the target + * documents in their entirety by providing a `SetOptions` with `merge: true`. + * + * @param merge - Changes the behavior of a `setDoc()` call to only replace the + * values specified in its data argument. Fields omitted from the `setDoc()` + * call remain untouched. + * @param mergeFields - Changes the behavior of `setDoc()` calls to only replace + * the specified field paths. Any field path that is not specified is ignored + * and remains untouched. + */ +export declare type SetOptions = + | { + readonly merge?: boolean; + } + | { + readonly mergeFields?: Array; + }; +export declare interface Settings { + cacheSizeBytes?: number; + host?: string; + ssl?: boolean; + ignoreUndefinedProperties?: boolean; + experimentalForceLongPolling?: boolean; + experimentalAutoDetectLongPolling?: boolean; +} +/** + * Returns true if the provided snapshots are equal. + * + * @param left - A snapshot to compare. + * @param right - A snapshot to compare. + * @returns true if the snapshots are equal. + */ +export declare function snapshotEqual( + left: DocumentSnapshot | QuerySnapshot, + right: DocumentSnapshot | QuerySnapshot +): boolean; +/** + * An options object that can be passed to {@link onSnapshot} and {@link + * QuerySnapshot#docChanges} to control which types of changes to include in the + * result set. + */ +export declare interface SnapshotListenOptions { + /** + * Include a change even if only the metadata of the query or of a document + * changed. Default is false. + */ + readonly includeMetadataChanges?: boolean; +} +/** + * Metadata about a snapshot, describing the state of the snapshot. + */ +export declare class SnapshotMetadata { + /** + * True if the snapshot contains the result of local writes (for example + * `set()` or `update()` calls) that have not yet been committed to the + * backend. If your listener has opted into metadata updates (via + * `SnapshotListenOptions`) you will receive another snapshot with + * `hasPendingWrites` equal to false once the writes have been committed to + * the backend. + */ + readonly hasPendingWrites: boolean; + /** + * True if the snapshot was created from cached data rather than guaranteed + * up-to-date server data. If your listener has opted into metadata updates + * (via `SnapshotListenOptions`) you will receive another snapshot with + * `fromCache` set to false once the client has received up-to-date data from + * the backend. + */ + readonly fromCache: boolean; + private constructor(); + /** + * Returns true if this `SnapshotMetadata` is equal to the provided one. + * + * @param other - The `SnapshotMetadata` to compare against. + * @returns true if this `SnapshotMetadata` is equal to the provided one. + */ + isEqual(other: SnapshotMetadata): boolean; +} +/** + * Options that configure how data is retrieved from a `DocumentSnapshot` (for + * example the desired behavior for server timestamps that have not yet been set + * to their final value). + */ +export declare interface SnapshotOptions { + /** + * If set, controls the return value for server timestamps that have not yet + * been set to their final value. + * + * By specifying 'estimate', pending server timestamps return an estimate + * based on the local clock. This estimate will differ from the final value + * and cause these values to change once the server result becomes available. + * + * By specifying 'previous', pending timestamps will be ignored and return + * their previous value instead. + * + * If omitted or set to 'none', `null` will be returned by default until the + * server value becomes available. + */ + readonly serverTimestamps?: 'estimate' | 'previous' | 'none'; +} +/** + * Creates a `QueryConstraint` 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. + * + * @param snapshot - The snapshot of the document to start after. + * @returns A `QueryConstraint` to pass to `query()` + */ +export declare function startAfter( + snapshot: DocumentSnapshot +): QueryConstraint; +/** + * Creates a `QueryConstraint` that modifies the result set to start after the + * provided fields relative to the order of the query. The order of the field + * values must match the order of the order by clauses of the query. + * + * @param fieldValues - The field values to start this query after, in order + * of the query's order by. + * @returns A `QueryConstraint` to pass to `query()` + */ +export declare function startAfter(...fieldValues: unknown[]): QueryConstraint; +/** + * Creates a `QueryConstraint` 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. + * + * @param snapshot - The snapshot of the document to start at. + * @returns A `QueryConstraint` to pass to `query()`. + */ +export declare function startAt( + snapshot: DocumentSnapshot +): QueryConstraint; +/** + * Creates a `QueryConstraint` that modifies the result set to start at the + * provided fields relative to the order of the query. The order of the field + * values must match the order of the order by clauses of the query. + * + * @param fieldValues - The field values to start this query at, in order + * of the query's order by. + * @returns A `QueryConstraint` to pass to `query()`. + */ +export declare function startAt(...fieldValues: unknown[]): QueryConstraint; +/** + * Represents the state of bundle loading tasks. + * + * Both 'Error' and 'Success' are sinking state: task will abort or complete and there will + * be no more updates after they are reported. + */ +export declare type TaskState = 'Error' | 'Running' | 'Success'; +/** + * Terminates the provided Firestore instance. + * + * After calling `terminate()` only the `clearIndexedDbPersistence()` function + * may be used. Any other function will throw a `FirestoreError`. + * + * To restart after termination, create a new instance of FirebaseFirestore with + * {@link getFirestore}. + * + * Termination does not cancel any pending writes, and any promises that are + * awaiting a response from the server will not be resolved. If you have + * persistence enabled, the next time you start this instance, it will resume + * sending these writes to the server. + * + * Note: Under normal circumstances, calling `terminate()` is not required. This + * function is useful only when you want to force this instance to release all + * of its resources or in combination with `clearIndexedDbPersistence()` to + * ensure that all local state is destroyed between test runs. + * + * @returns A promise that is resolved when the instance has been successfully + * terminated. + */ +export declare function terminate(firestore: FirebaseFirestore): Promise; +/** + * @license + * Copyright 2017 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. + */ +/** + * 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 + * {@link https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto | Timestamp definition}. + */ +export declare class Timestamp { + readonly seconds: number; + readonly nanoseconds: number; + /** + * Creates a new timestamp with the current date, with millisecond precision. + * + * @returns a new timestamp representing the current date. + */ + static now(): Timestamp; + /** + * Creates a new timestamp from the given date. + * + * @param date - The date to initialize the `Timestamp` from. + * @returns A new `Timestamp` representing the same point in time as the given + * date. + */ + static fromDate(date: Date): Timestamp; + /** + * Creates a new timestamp from the given number of milliseconds. + * + * @param milliseconds - Number of milliseconds since Unix epoch + * 1970-01-01T00:00:00Z. + * @returns A new `Timestamp` representing the same point in time as the given + * number of milliseconds. + */ + static fromMillis(milliseconds: number): Timestamp; + /** + * Creates a new timestamp. + * + * @param seconds - The number of seconds of UTC time since Unix epoch + * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to + * 9999-12-31T23:59:59Z inclusive. + * @param nanoseconds - The non-negative fractions of a second at nanosecond + * resolution. Negative second values with fractions must still have + * non-negative nanoseconds values that count forward in time. Must be + * from 0 to 999,999,999 inclusive. + */ + constructor(seconds: number, nanoseconds: number); + /** + * Converts a `Timestamp` to a JavaScript `Date` object. This conversion causes + * a loss of precision since `Date` objects only support millisecond precision. + * + * @returns JavaScript `Date` object representing the same point in time as + * this `Timestamp`, with millisecond precision. + */ + toDate(): Date; + /** + * Converts a `Timestamp` to a numeric timestamp (in milliseconds since + * epoch). This operation causes a loss of precision. + * + * @returns The point in time corresponding to this timestamp, represented as + * the number of milliseconds since Unix epoch 1970-01-01T00:00:00Z. + */ + toMillis(): number; + /** + * Returns true if this `Timestamp` is equal to the provided one. + * + * @param other - The `Timestamp` to compare against. + * @returns true if this `Timestamp` is equal to the provided one. + */ + isEqual(other: Timestamp): boolean; + toString(): string; + toJSON(): { + seconds: number; + nanoseconds: number; + }; + /** + * Converts this object to a primitive string, which allows Timestamp objects to be compared + * using the `>`, `<=`, `>=` and `>` operators. + */ + valueOf(): string; +} +/** + * 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 + * {@link runTransaction}. + */ +export declare class Transaction { + private constructor(); + /** + * Reads the document referenced by the provided {@link DocumentReference}. + * + * @param documentRef - A reference to the document to be read. + * @returns A `DocumentSnapshot` with the read data. + */ + get(documentRef: DocumentReference): Promise>; + /** + * Writes to the document referred to by the provided {@link + * DocumentReference}. If the document does not exist yet, it will be created. + * + * @param documentRef - A reference to the document to be set. + * @param data - An object of the fields and values for the document. + * @returns This `Transaction` instance. Used for chaining method calls. + */ + set(documentRef: DocumentReference, data: T): this; + /** + * Writes to the document referred to by the provided {@link + * DocumentReference}. If the document does not exist yet, it will be created. + * If you provide `merge` or `mergeFields`, the provided data can be merged + * into an existing document. + * + * @param documentRef - A reference to the document to be set. + * @param data - An object of the fields and values for the document. + * @param options - An object to configure the set behavior. + * @returns This `Transaction` instance. Used for chaining method calls. + */ + set( + documentRef: DocumentReference, + data: Partial, + options: SetOptions + ): this; + /** + * Updates fields in the document referred to by the provided {@link + * DocumentReference}. The update will fail if applied to a document that does + * not exist. + * + * @param documentRef - A reference to the document to be updated. + * @param data - An object containing the fields and values with which to +update the document. Fields can contain dots to reference nested fields +within the document. + * @returns This `Transaction` instance. Used for chaining method calls. + */ + update(documentRef: DocumentReference, data: UpdateData): this; + /** + * Updates fields in the document referred to by the provided {@link + * DocumentReference}. The update will fail if applied to a document that does + * not exist. + * + * Nested fields can be updated by providing dot-separated field path + * strings or by providing `FieldPath` objects. + * + * @param documentRef - A reference to the document to be updated. + * @param field - The first field to update. + * @param value - The first value. + * @param moreFieldsAndValues - Additional key/value pairs. + * @returns This `Transaction` instance. Used for chaining method calls. + */ + update( + documentRef: DocumentReference, + field: string | FieldPath, + value: unknown, + ...moreFieldsAndValues: unknown[] + ): this; + /** + * Deletes the document referred to by the provided {@link DocumentReference}. + * + * @param documentRef - A reference to the document to be deleted. + * @returns This `Transaction` instance. Used for chaining method calls. + */ + delete(documentRef: DocumentReference): this; +} +export declare interface Unsubscribe { + (): void; +} +/** + * Update data (for use with {@link updateDoc}) consists of field paths (e.g. + * 'foo' or 'foo.baz') mapped to values. Fields that contain dots reference + * nested fields within the document. + */ +export declare interface UpdateData { + [fieldPath: string]: any; +} +/** + * Updates fields in the document referred to by the specified + * `DocumentReference`. The update will fail if applied to a document that does + * not exist. + * + * @param reference - A reference to the document to update. + * @param data - An object containing the fields and values with which to + * update the document. Fields can contain dots to reference nested fields + * within the document. + * @returns A Promise resolved once the data has been successfully written + * to the backend (note that it won't resolve while you're offline). + */ +export declare function updateDoc( + reference: DocumentReference, + data: UpdateData +): Promise; +/** + * Updates fields in the document referred to by the specified + * `DocumentReference` The update will fail if applied to a document that does + * not exist. + * + * Nested fields can be updated by providing dot-separated field path + * strings or by providing `FieldPath` objects. + * + * @param reference - A reference to the document to update. + * @param field - The first field to update. + * @param value - The first value. + * @param moreFieldsAndValues - Additional key value pairs. + * @returns A Promise resolved once the data has been successfully written + * to the backend (note that it won't resolve while you're offline). + */ +export declare function updateDoc( + reference: DocumentReference, + field: string | FieldPath, + value: unknown, + ...moreFieldsAndValues: unknown[] +): Promise; +/** + * Modify this instance to communicate with the Cloud Firestore emulator. + * + * Note: This must be called before this instance has been used to do any + * operations. + * + * @param firestore - The Firestore instance to configure to connect to the + * emulator. + * @param host - the emulator host (ex: localhost). + * @param port - the emulator port (ex: 9000). + */ +export declare function useFirestoreEmulator( + firestore: FirebaseFirestore, + host: string, + port: number +): void; +/** + * Waits until all currently pending writes for the active user have been + * acknowledged by the backend. + * + * The returned Promise resolves immediately if there are no outstanding writes. + * Otherwise, the Promise waits for all previously issued writes (including + * those written in a previous app session), but it does not wait for writes + * that were added after the function is called. If you want to wait for + * additional writes, call `waitForPendingWrites()` again. + * + * Any outstanding `waitForPendingWrites()` Promises are rejected during user + * changes. + * + * @returns A Promise which resolves when all currently pending writes have been + * acknowledged by the backend. + */ +export declare function waitForPendingWrites( + firestore: FirebaseFirestore +): Promise; +/** + * Creates a `QueryConstraint` that enforces that documents must contain the + * specified field and that the value should satisfy the relation constraint + * provided. + * + * @param fieldPath - The path to compare + * @param opStr - The operation string (e.g "<", "<=", "==", "<", + * "<=", "!="). + * @param value - The value for comparison + * @returns The created `Query`. + */ +export declare function where( + fieldPath: string | FieldPath, + opStr: WhereFilterOp, + value: unknown +): QueryConstraint; +/** + * Filter conditions in a {@link where} clause are specified using the + * strings '<', '<=', '==', '!=', '>=', '>', 'array-contains', 'in', + * 'array-contains-any', and 'not-in'. + */ +export declare type WhereFilterOp = + | '<' + | '<=' + | '==' + | '!=' + | '>=' + | '>' + | 'array-contains' + | 'in' + | 'array-contains-any' + | 'not-in'; +/** + * A write batch, used to perform multiple writes as a single atomic unit. + * + * A `WriteBatch` object can be acquired by calling {@link writeBatch}. It + * provides methods for adding writes to the write batch. None of the writes + * will be committed (or visible locally) until {@link WriteBatch#commit} is + * called. + */ +export declare class WriteBatch { + private constructor(); + /** + * Writes to the document referred to by the provided {@link + * DocumentReference}. If the document does not exist yet, it will be created. + * + * @param documentRef - A reference to the document to be set. + * @param data - An object of the fields and values for the document. + * @returns This `WriteBatch` instance. Used for chaining method calls. + */ + set(documentRef: DocumentReference, data: T): WriteBatch; + /** + * Writes to the document referred to by the provided {@link + * DocumentReference}. If the document does not exist yet, it will be created. + * If you provide `merge` or `mergeFields`, the provided data can be merged + * into an existing document. + * + * @param documentRef - A reference to the document to be set. + * @param data - An object of the fields and values for the document. + * @param options - An object to configure the set behavior. + * @returns This `WriteBatch` instance. Used for chaining method calls. + */ + set( + documentRef: DocumentReference, + data: Partial, + options: SetOptions + ): WriteBatch; + /** + * Updates fields in the document referred to by the provided {@link + * DocumentReference}. The update will fail if applied to a document that does + * not exist. + * + * @param documentRef - A reference to the document to be updated. + * @param data - An object containing the fields and values with which to + * update the document. Fields can contain dots to reference nested fields + * within the document. + * @returns This `WriteBatch` instance. Used for chaining method calls. + */ + update(documentRef: DocumentReference, data: UpdateData): WriteBatch; + /** + * Updates fields in the document referred to by this {@link + * DocumentReference}. The update will fail if applied to a document that does + * not exist. + * + * Nested fields can be update by providing dot-separated field path strings + * or by providing `FieldPath` objects. + * + * @param documentRef - A reference to the document to be updated. + * @param field - The first field to update. + * @param value - The first value. + * @param moreFieldsAndValues - Additional key value pairs. + * @returns This `WriteBatch` instance. Used for chaining method calls. + */ + update( + documentRef: DocumentReference, + field: string | FieldPath, + value: unknown, + ...moreFieldsAndValues: unknown[] + ): WriteBatch; + /** + * Deletes the document referred to by the provided {@link DocumentReference}. + * + * @param documentRef - A reference to the document to be deleted. + * @returns This `WriteBatch` instance. Used for chaining method calls. + */ + delete(documentRef: DocumentReference): WriteBatch; + /** + * Commits all of the writes in this write batch as a single atomic unit. + * + * The result of these writes will only be reflected in document reads that + * occur after the returned Promise resolves. If the client is offline, the + * write fails. If you would like to see local modifications or buffer writes + * until the client is online, use the full Firestore SDK. + * + * @returns A Promise resolved once all of the writes in the batch have been + * successfully written to the backend as an atomic unit (note that it won't + * resolve while you're offline). + */ + commit(): Promise; +} +/** + * Creates a write batch, used for performing multiple writes as a single + * atomic operation. The maximum number of writes allowed in a single WriteBatch + * is 500. + * + * Unlike transactions, write batches are persisted offline and therefore are + * preferable when you don't need to condition your writes on read data. + * + * @returns A `WriteBatch` that can be used to atomically execute multiple + * writes. + */ +export declare function writeBatch(firestore: FirebaseFirestore): WriteBatch; +export {}; diff --git a/repo-scripts/prune-dts/tests/hide-constructor.input.d.ts b/repo-scripts/prune-dts/tests/hide-constructor.input.d.ts new file mode 100644 index 00000000000..e69af1bd2c0 --- /dev/null +++ b/repo-scripts/prune-dts/tests/hide-constructor.input.d.ts @@ -0,0 +1,22 @@ +/** + * @license + * Copyright 2020 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. + */ + +export class A { + /** @hideconstructor */ + constructor(b: string); +} +export {}; diff --git a/repo-scripts/prune-dts/tests/hide-constructor.output.d.ts b/repo-scripts/prune-dts/tests/hide-constructor.output.d.ts new file mode 100644 index 00000000000..352ba1ffda7 --- /dev/null +++ b/repo-scripts/prune-dts/tests/hide-constructor.output.d.ts @@ -0,0 +1,20 @@ +/** + * @license + * Copyright 2020 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. + */ +export class A { + private constructor(); +} +export {}; diff --git a/repo-scripts/prune-dts/tests/inherits-non-exported-members.input.d.ts b/repo-scripts/prune-dts/tests/inherits-non-exported-members.input.d.ts new file mode 100644 index 00000000000..b51730a18aa --- /dev/null +++ b/repo-scripts/prune-dts/tests/inherits-non-exported-members.input.d.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2020 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. + */ + +declare class B { + b: string; +} +export class A extends B { + a: string; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/inherits-non-exported-members.output.d.ts b/repo-scripts/prune-dts/tests/inherits-non-exported-members.output.d.ts new file mode 100644 index 00000000000..59f57065189 --- /dev/null +++ b/repo-scripts/prune-dts/tests/inherits-non-exported-members.output.d.ts @@ -0,0 +1,21 @@ +/** + * @license + * Copyright 2020 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. + */ +export class A { + a: string; + b: string; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/keeps-inheritance.input.d.ts b/repo-scripts/prune-dts/tests/keeps-inheritance.input.d.ts new file mode 100644 index 00000000000..5d41bf41c37 --- /dev/null +++ b/repo-scripts/prune-dts/tests/keeps-inheritance.input.d.ts @@ -0,0 +1,27 @@ +/** + * @license + * Copyright 2020 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. + */ + +declare class C { + get c(): T; +} +export declare class B extends C { + get b(): T; +} +export declare class A extends B { + get a(): T; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/keeps-inheritance.output.d.ts b/repo-scripts/prune-dts/tests/keeps-inheritance.output.d.ts new file mode 100644 index 00000000000..0b9a9da6d4f --- /dev/null +++ b/repo-scripts/prune-dts/tests/keeps-inheritance.output.d.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2020 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. + */ +export declare class B { + get b(): T; + get c(): T; +} +export declare class A extends B { + get a(): T; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/only-modifies-non-exported-types.input.d.ts b/repo-scripts/prune-dts/tests/only-modifies-non-exported-types.input.d.ts new file mode 100644 index 00000000000..6e854a0d717 --- /dev/null +++ b/repo-scripts/prune-dts/tests/only-modifies-non-exported-types.input.d.ts @@ -0,0 +1,25 @@ +/** + * @license + * Copyright 2020 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. + */ + +declare class A { + a: string; +} +export class C extends A { + c: number; +} +export class B extends A {} +export {}; diff --git a/repo-scripts/prune-dts/tests/only-modifies-non-exported-types.output.d.ts b/repo-scripts/prune-dts/tests/only-modifies-non-exported-types.output.d.ts new file mode 100644 index 00000000000..f25cc9216b4 --- /dev/null +++ b/repo-scripts/prune-dts/tests/only-modifies-non-exported-types.output.d.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2020 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. + */ +export class C { + c: number; + a: string; +} +export class B { + a: string; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/private-interface.input.d.ts b/repo-scripts/prune-dts/tests/private-interface.input.d.ts new file mode 100644 index 00000000000..925bb41e3e4 --- /dev/null +++ b/repo-scripts/prune-dts/tests/private-interface.input.d.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2020 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. + */ + +declare interface A { + a: string; +} +export class B implements A { + a: string; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/private-interface.output.d.ts b/repo-scripts/prune-dts/tests/private-interface.output.d.ts new file mode 100644 index 00000000000..9d7aaf0f1e6 --- /dev/null +++ b/repo-scripts/prune-dts/tests/private-interface.output.d.ts @@ -0,0 +1,20 @@ +/** + * @license + * Copyright 2020 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. + */ +export class B { + a: string; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/propagates-comments.input.d.ts b/repo-scripts/prune-dts/tests/propagates-comments.input.d.ts new file mode 100644 index 00000000000..6460b68a3e1 --- /dev/null +++ b/repo-scripts/prune-dts/tests/propagates-comments.input.d.ts @@ -0,0 +1,41 @@ +/** + * @license + * Copyright 2020 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. + */ + +declare class A { + /** a */ + a: string; + /** + * b1 first line + * + * b1 second line + * + * @param b1 b1 param + * @returns b1 return + */ + b(b1: string): string; + /** + * b2 first line + * + * b2 second line + * + * @param b2 b2 param + * @returns b2 return + */ + b(b2: string): string; +} +export class B extends A {} +export {}; diff --git a/repo-scripts/prune-dts/tests/propagates-comments.output.d.ts b/repo-scripts/prune-dts/tests/propagates-comments.output.d.ts new file mode 100644 index 00000000000..1f7ba0a2cca --- /dev/null +++ b/repo-scripts/prune-dts/tests/propagates-comments.output.d.ts @@ -0,0 +1,41 @@ +/** + * @license + * Copyright 2020 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. + */ +export class B { + /** + * a + */ + a: string; + /** + * b1 first line + * + * b1 second line + * + * @param b1 b1 param + * @returns b1 return + */ + b(b1: string): string; + /** + * b2 first line + * + * b2 second line + * + * @param b2 b2 param + * @returns b2 return + */ + b(b2: string): string; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/propagates-members.input.d.ts b/repo-scripts/prune-dts/tests/propagates-members.input.d.ts new file mode 100644 index 00000000000..8870cbe22e2 --- /dev/null +++ b/repo-scripts/prune-dts/tests/propagates-members.input.d.ts @@ -0,0 +1,27 @@ +/** + * @license + * Copyright 2020 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. + */ + +declare class A { + a: string; +} +export class B extends A { + b: string; +} +export class C extends B { + c: string; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/propagates-members.output.d.ts b/repo-scripts/prune-dts/tests/propagates-members.output.d.ts new file mode 100644 index 00000000000..07f579f865f --- /dev/null +++ b/repo-scripts/prune-dts/tests/propagates-members.output.d.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2020 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. + */ +export class B { + b: string; + a: string; +} +export class C extends B { + c: string; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/prunes-non-exported-types.input.d.ts b/repo-scripts/prune-dts/tests/prunes-non-exported-types.input.d.ts new file mode 100644 index 00000000000..54cd7c31224 --- /dev/null +++ b/repo-scripts/prune-dts/tests/prunes-non-exported-types.input.d.ts @@ -0,0 +1,22 @@ +/** + * @license + * Copyright 2020 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. + */ + +export function a(): void; +declare function b(): void; +export class A {} +declare class B {} +export {}; diff --git a/repo-scripts/prune-dts/tests/prunes-non-exported-types.output.d.ts b/repo-scripts/prune-dts/tests/prunes-non-exported-types.output.d.ts new file mode 100644 index 00000000000..67d55413794 --- /dev/null +++ b/repo-scripts/prune-dts/tests/prunes-non-exported-types.output.d.ts @@ -0,0 +1,19 @@ +/** + * @license + * Copyright 2020 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. + */ +export function a(): void; +export class A {} +export {}; diff --git a/repo-scripts/prune-dts/tests/prunes-underscores.input.d.ts b/repo-scripts/prune-dts/tests/prunes-underscores.input.d.ts new file mode 100644 index 00000000000..a390ad2f8c5 --- /dev/null +++ b/repo-scripts/prune-dts/tests/prunes-underscores.input.d.ts @@ -0,0 +1,25 @@ +/** + * @license + * Copyright 2020 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. + */ + +export class A { + a: string; + _b: string; + _bMethod(): string; + get _bGetter(): string; + readonly _bProperty: string; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/prunes-underscores.output.d.ts b/repo-scripts/prune-dts/tests/prunes-underscores.output.d.ts new file mode 100644 index 00000000000..c8e8d7963f0 --- /dev/null +++ b/repo-scripts/prune-dts/tests/prunes-underscores.output.d.ts @@ -0,0 +1,20 @@ +/** + * @license + * Copyright 2020 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. + */ +export class A { + a: string; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/references-public-interfaces.input.d.ts b/repo-scripts/prune-dts/tests/references-public-interfaces.input.d.ts new file mode 100644 index 00000000000..1cf7bac1055 --- /dev/null +++ b/repo-scripts/prune-dts/tests/references-public-interfaces.input.d.ts @@ -0,0 +1,23 @@ +/** + * @license + * Copyright 2020 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. + */ + +export interface A { + a: T; +} +declare interface B extends A {} +export function c(a: B): void; +export {}; diff --git a/repo-scripts/prune-dts/tests/references-public-interfaces.output.d.ts b/repo-scripts/prune-dts/tests/references-public-interfaces.output.d.ts new file mode 100644 index 00000000000..901c0113fae --- /dev/null +++ b/repo-scripts/prune-dts/tests/references-public-interfaces.output.d.ts @@ -0,0 +1,21 @@ +/** + * @license + * Copyright 2020 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. + */ +export interface A { + a: T; +} +export function c(a: A): void; +export {}; diff --git a/repo-scripts/prune-dts/tests/references-public-types.input.d.ts b/repo-scripts/prune-dts/tests/references-public-types.input.d.ts new file mode 100644 index 00000000000..a37b3567796 --- /dev/null +++ b/repo-scripts/prune-dts/tests/references-public-types.input.d.ts @@ -0,0 +1,28 @@ +/** + * @license + * Copyright 2020 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. + */ + +declare class B {} +export class A extends B {} +export function a(b: B): B; +export class Ab { + constructor(b: B); + b: B; + readonly bProperty: B; + get bGetter(): B; + bMethod(b: B): B; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/references-public-types.output.d.ts b/repo-scripts/prune-dts/tests/references-public-types.output.d.ts new file mode 100644 index 00000000000..bdd0de28bfe --- /dev/null +++ b/repo-scripts/prune-dts/tests/references-public-types.output.d.ts @@ -0,0 +1,26 @@ +/** + * @license + * Copyright 2020 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. + */ +export class A {} +export function a(b: A): A; +export class Ab { + constructor(b: A); + b: A; + readonly bProperty: A; + get bGetter(): A; + bMethod(b: A): A; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/resolves-generics-different-name.input.d.ts b/repo-scripts/prune-dts/tests/resolves-generics-different-name.input.d.ts new file mode 100644 index 00000000000..4389960f78b --- /dev/null +++ b/repo-scripts/prune-dts/tests/resolves-generics-different-name.input.d.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2020 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. + */ + +declare class A { + a: T; +} +export class B extends A { + b: K; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/resolves-generics-different-name.output.d.ts b/repo-scripts/prune-dts/tests/resolves-generics-different-name.output.d.ts new file mode 100644 index 00000000000..2926cd75565 --- /dev/null +++ b/repo-scripts/prune-dts/tests/resolves-generics-different-name.output.d.ts @@ -0,0 +1,21 @@ +/** + * @license + * Copyright 2020 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. + */ +export class B { + b: K; + a: K; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/resolves-generics-through-inheritance.input.d.ts b/repo-scripts/prune-dts/tests/resolves-generics-through-inheritance.input.d.ts new file mode 100644 index 00000000000..7208f2cf528 --- /dev/null +++ b/repo-scripts/prune-dts/tests/resolves-generics-through-inheritance.input.d.ts @@ -0,0 +1,25 @@ +/** + * @license + * Copyright 2020 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. + */ + +declare class A { + a: T; +} +export class B extends A { + b: T; +} +export class C extends A {} +export {}; diff --git a/repo-scripts/prune-dts/tests/resolves-generics-through-inheritence.output.d.ts b/repo-scripts/prune-dts/tests/resolves-generics-through-inheritence.output.d.ts new file mode 100644 index 00000000000..3dca92f7d56 --- /dev/null +++ b/repo-scripts/prune-dts/tests/resolves-generics-through-inheritence.output.d.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2020 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. + */ +export class B { + b: T; + a: string; +} +export class C { + a: T; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/resolves-generics.input.d.ts b/repo-scripts/prune-dts/tests/resolves-generics.input.d.ts new file mode 100644 index 00000000000..8c840eafffc --- /dev/null +++ b/repo-scripts/prune-dts/tests/resolves-generics.input.d.ts @@ -0,0 +1,22 @@ +/** + * @license + * Copyright 2020 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. + */ + +declare class B { + b: T; +} +export class A extends B {} +export {}; diff --git a/repo-scripts/prune-dts/tests/resolves-generics.output.d.ts b/repo-scripts/prune-dts/tests/resolves-generics.output.d.ts new file mode 100644 index 00000000000..d97cf28da79 --- /dev/null +++ b/repo-scripts/prune-dts/tests/resolves-generics.output.d.ts @@ -0,0 +1,20 @@ +/** + * @license + * Copyright 2020 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. + */ +export class A { + b: string; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/swaps-generics.input.d.ts b/repo-scripts/prune-dts/tests/swaps-generics.input.d.ts new file mode 100644 index 00000000000..43f9d6a004c --- /dev/null +++ b/repo-scripts/prune-dts/tests/swaps-generics.input.d.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2020 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. + */ + +declare class B { + b: T; +} +export class A extends B { + a: T; +} +export {}; diff --git a/repo-scripts/prune-dts/tests/swaps-generics.output.d.ts b/repo-scripts/prune-dts/tests/swaps-generics.output.d.ts new file mode 100644 index 00000000000..32e47ed96b9 --- /dev/null +++ b/repo-scripts/prune-dts/tests/swaps-generics.output.d.ts @@ -0,0 +1,21 @@ +/** + * @license + * Copyright 2020 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. + */ +export class A { + a: T; + b: string; +} +export {}; diff --git a/repo-scripts/prune-dts/tsconfig.eslint.json b/repo-scripts/prune-dts/tsconfig.eslint.json new file mode 100644 index 00000000000..814c97c2f85 --- /dev/null +++ b/repo-scripts/prune-dts/tsconfig.eslint.json @@ -0,0 +1,4 @@ +{ + "extends": "../../config/tsconfig.base.json", + "include": ["../../packages/*/dist/**/*.d.ts"] +} diff --git a/repo-scripts/prune-dts/tsconfig.json b/repo-scripts/prune-dts/tsconfig.json new file mode 100644 index 00000000000..236dfb5b9d8 --- /dev/null +++ b/repo-scripts/prune-dts/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "outDir": "dist", + "importHelpers": true, + "module": "commonjs", + "moduleResolution": "node", + "resolveJsonModule": true, + "target": "es2017", + "esModuleInterop": true, + "declaration": true, + "strict": true + }, + "exclude": ["dist/**/*"] +} diff --git a/repo-scripts/size-analysis/.eslintrc.js b/repo-scripts/size-analysis/.eslintrc.js new file mode 100644 index 00000000000..ca80aa0f69a --- /dev/null +++ b/repo-scripts/size-analysis/.eslintrc.js @@ -0,0 +1,26 @@ +/** + * @license + * Copyright 2020 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. + */ + +module.exports = { + extends: '../../config/.eslintrc.js', + parserOptions: { + project: 'tsconfig.json', + // to make vscode-eslint work with monorepo + // https://github.com/typescript-eslint/typescript-eslint/issues/251#issuecomment-463943250 + tsconfigRootDir: __dirname + } +}; diff --git a/repo-scripts/size-analysis/README.md b/repo-scripts/size-analysis/README.md new file mode 100644 index 00000000000..6f81162b789 --- /dev/null +++ b/repo-scripts/size-analysis/README.md @@ -0,0 +1,117 @@ +# Modular Export Binary Size Calculator + +The library supports two main features: +- Analyze the size of individual exports from a given package. +- Analyze the size of predefined bundle definitions. A bundle definition is a group of imports from different Firebase packages to support a certain use case. For example, to support Google signin and read from Firestore once. + +## Analyze the size of individual exports +### CLI Usage + +Flags + +- `--version` Show version number [boolean] +- `--inputModule, --im` The name of the module(s) to be analyzed. example: --inputModule "@firebase/functions" "firebase/auth" [array] +- `--inputDtsFile, --if` Support for adhoc analysis. requires a path to dts file [string] +- `--inputBundleFile, --ib` Support for adhoc analysis. requires a path to a bundle file [string] +- `--output, -o` The location where report(s) will be generated, a directory path if module(s) are analyzed; a file path if ad hoc analysis is to be performed [string] +- `--help ` Show help [boolean] + +#### To Do Analysis On One or Multiple Firebase Packages + +$firebase-js-sdk/repo-scripts/size-analysis `ts-node-script cli.ts --im "@firebase/app" "@firebase/auth" -o `. + +#### To Do Analysis On All Firebase Packages + +$firebase-js-sdk/repo-scripts/size-analysis `ts-node-script cli.ts -o ` +#### Adhoc Support + +$firebase-js-sdk/repo-scripts/size-analysis `ts-node-script cli.ts --if --ib -o ` + + +### Use the Tool Programmatically +### `async generateReportForModule(moduleLocation: string): Promise` +#### This function generates size analysis report for the given module specified by the `moduleLocation` argument. +#### `@param moduleLocation: an absolute path to location of a firebase module` +``` +try { + const moduleLocation: string = "absolute/path/to/firebase/module"; + const report: Report = await generateReportForModule(moduleLocation); + console.log(report); + + +}catch (error) { + + + console.log(error); +} + + +``` + +### `async generateReportForModules(moduleLocations: string[]): Promise` +#### This function recursively generates a size analysis report for every module (and its submodules) listed in moduleLocations array +#### `@param moduleLocations: an array of strings where each is a path to location of a firebase module` + +``` +try { + const moduleLocations: string[] = ['...module1', '...module2']; + const reports: Report[] = await generateReportForModules(moduleLocations); + console.log(reports); + + +}catch (error) { + + + console.log(error); +} + + +``` + +### `async generateReport(name: string, dtsFile: string, bundleFile: string): Promise` +#### Use this function for adhoc analysis. This function generates a size analysis report from the given definition file. +#### `@param name: name to be displayed on the report` +#### `@param: dtsFile: absolute path to a definition file of interest.` +#### `@param bundleFile: absolute path to the bundle file of given definition file` + + +``` +try { + const name: string = "adhoc"; + const dtsFile: string = '.../index.d.ts'; + const bundleFile: string = '.../index.esm2017.js'; + const report: Report = await generateReport(name, dtsFile, bundleFile); + console.log(report); + + +}catch (error) { + + + console.log(error); +} + + +``` + + +## Analyze the Size of Bundles + +A bundle is defined by a bundle definition. A bundle definition is a group of imports from different Firebase packages to support a certain use case. + +You can find some sample bundle definitions in the `/bundle-definitions` folder. + +### CLI Usage + +To analyze bundles, you would use the command `bundle`: +$firebase-js-sdk/repo-scripts/size-analysis `ts-node-script cli.ts bundle [FLAGs]`. + +Flags + +- `--input`, or `-i` Path to the bundle definition file' + +- `--mode`, or `-m` Possible values are `npm` and `local` + - `npm`: analyze packages published to npm + - `local`: ignore the version defined in bundle definition and analyze the corresponding local packages +- `--bundler`, or `-b`. Possible values are `rollup`, `webpack`, `both`. Decides which bundlers to be used to create bundles. +- `--output`, or `-o` The output file location of the bundle analysis result. +- `--debug`, or `-d` Enables the debug mode which output additional data in the bundle analysis result. diff --git a/repo-scripts/size-analysis/analysis-helper.ts b/repo-scripts/size-analysis/analysis-helper.ts new file mode 100644 index 00000000000..9507bfe253c --- /dev/null +++ b/repo-scripts/size-analysis/analysis-helper.ts @@ -0,0 +1,670 @@ +/** + * @license + * Copyright 2020 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 * as tmp from 'tmp'; +import * as path from 'path'; +import * as fs from 'fs'; +import * as rollup from 'rollup'; +import * as terser from 'terser'; +import * as ts from 'typescript'; +import resolve from '@rollup/plugin-node-resolve'; +import commonjs from '@rollup/plugin-commonjs'; +import { projectRoot } from '../../scripts/utils'; + +export const enum ErrorCode { + INVALID_FLAG_COMBINATION = 'Invalid command flag combinations!', + BUNDLE_FILE_DOES_NOT_EXIST = 'Module does not have a bundle file!', + DTS_FILE_DOES_NOT_EXIST = 'Module does not have a dts file!', + OUTPUT_DIRECTORY_REQUIRED = 'An output directory is required but a file given!', + OUTPUT_FILE_REQUIRED = 'An output file is required but a directory given!', + INPUT_FILE_DOES_NOT_EXIST = 'Input file does not exist!', + INPUT_DTS_FILE_DOES_NOT_EXIST = 'Input dts file does not exist!', + INPUT_BUNDLE_FILE_DOES_NOT_EXIST = 'Input bundle file does not exist!', + FILE_PARSING_ERROR = 'Failed to parse js file!', + PKG_JSON_DOES_NOT_EXIST = 'Module does not have a package.json file!', + TYPINGS_FIELD_NOT_DEFINED = 'Module does not have typings field defined in its package.json!' +} + +/** Contains a list of members by type. */ +export interface MemberList { + classes: string[]; + functions: string[]; + variables: string[]; + enums: string[]; + unknown: string[]; +} +/** Contains the dependencies and the size of their code for a single export. */ +export interface ExportData { + name: string; + classes: string[]; + functions: string[]; + variables: string[]; + enums: string[]; + unknown: string[]; + externals: { [key: string]: string[] }; + size: number; + sizeWithExtDeps: number; +} + +export interface Report { + name: string; + symbols: ExportData[]; +} +/** + * Helper for extractDependencies that extracts the dependencies and the size + * of the minified build. + */ +export async function extractDependenciesAndSize( + exportName: string, + jsBundle: string +): Promise { + const input = tmp.fileSync().name + '.js'; + const externalDepsResolvedOutput = tmp.fileSync().name + '.js'; + const externalDepsNotResolvedOutput = tmp.fileSync().name + '.js'; + + const exportStatement = `export { ${exportName} } from '${path.resolve( + jsBundle + )}';`; + fs.writeFileSync(input, exportStatement); + + // Run Rollup on the JavaScript above to produce a tree-shaken build + const externalDepsResolvedBundle = await rollup.rollup({ + input, + plugins: [ + resolve({ + mainFields: ['esm2017', 'module', 'main'] + }), + commonjs() + ] + }); + await externalDepsResolvedBundle.write({ + file: externalDepsResolvedOutput, + format: 'es' + }); + const externalDepsNotResolvedBundle = await rollup.rollup({ + input, + // exclude all firebase dependencies and tslib + external: id => id.startsWith('@firebase') || id === 'tslib' + }); + await externalDepsNotResolvedBundle.write({ + file: externalDepsNotResolvedOutput, + format: 'es' + }); + const dependencies: MemberList = extractAllTopLevelSymbols( + externalDepsNotResolvedOutput + ); + + const externalDepsResolvedOutputContent = fs.readFileSync( + externalDepsResolvedOutput, + 'utf-8' + ); + // Extract size of minified build + const externalDepsNotResolvedOutputContent = fs.readFileSync( + externalDepsNotResolvedOutput, + 'utf-8' + ); + const externalDepsResolvedOutputContentMinimized = await terser.minify( + externalDepsResolvedOutputContent, + { + format: { + comments: false + }, + mangle: { toplevel: true }, + compress: false + } + ); + const externalDepsNotResolvedOutputContentMinimized = await terser.minify( + externalDepsNotResolvedOutputContent, + { + format: { + comments: false + }, + mangle: { toplevel: true }, + compress: false + } + ); + const exportData: ExportData = { + name: '', + classes: [], + functions: [], + variables: [], + enums: [], + unknown: [], + externals: {}, + size: 0, + sizeWithExtDeps: 0 + }; + exportData.name = exportName; + for (const key of Object.keys(dependencies) as Array) { + exportData[key] = dependencies[key]; + } + + exportData.externals = extractExternalDependencies( + externalDepsNotResolvedOutput + ); + exportData.size = Buffer.byteLength( + externalDepsNotResolvedOutputContentMinimized.code!, + 'utf-8' + ); + exportData.sizeWithExtDeps = Buffer.byteLength( + externalDepsResolvedOutputContentMinimized.code!, + 'utf-8' + ); + fs.unlinkSync(input); + fs.unlinkSync(externalDepsNotResolvedOutput); + fs.unlinkSync(externalDepsResolvedOutput); + return exportData; +} + +/** + * Check what symbols are being pulled into a bundle + */ +export function extractAllTopLevelSymbols(filePath: string): MemberList { + const program = ts.createProgram([filePath], { allowJs: true }); + const sourceFile = program.getSourceFile(filePath); + if (!sourceFile) { + throw new Error(`${ErrorCode.FILE_PARSING_ERROR} ${filePath}`); + } + + const declarations: MemberList = { + functions: [], + classes: [], + variables: [], + enums: [], + unknown: [] + }; + + ts.forEachChild(sourceFile, node => { + if (ts.isFunctionDeclaration(node)) { + declarations.functions.push(node.name!.text); + } else if (ts.isClassDeclaration(node)) { + declarations.classes.push(node.name!.text); + } else if (ts.isVariableDeclaration(node)) { + declarations.variables.push(node.name!.getText()); + } else if (ts.isEnumDeclaration(node)) { + // `const enum`s should not be analyzed. They do not add to bundle size and + // creating a file that imports them causes an error during the rollup step. + if ( + // Identifies if this enum had a "const" modifier attached. + !node.modifiers?.some(mod => mod.kind === ts.SyntaxKind.ConstKeyword) + ) { + declarations.enums.push(node.name.escapedText.toString()); + } + } else if (ts.isVariableStatement(node)) { + const variableDeclarations = node.declarationList.declarations; + + variableDeclarations.forEach(variableDeclaration => { + //variableDeclaration.name could be of Identifier type or of BindingPattern type + // Identifier Example: export const a: string = "aString"; + if (ts.isIdentifier(variableDeclaration.name)) { + declarations.variables.push( + variableDeclaration.name.getText(sourceFile) + ); + } + // Binding Pattern Example: export const {a, b} = {a: 1, b: 1}; + else { + const elements = variableDeclaration.name + .elements as ts.NodeArray; + elements.forEach((node: ts.BindingElement) => { + declarations.variables.push(node.name.getText(sourceFile)); + }); + } + }); + } + }); + + //Sort to ensure stable output + Object.values(declarations).forEach(each => { + each.sort(); + }); + return declarations; +} + +/** + * Extract exports of a module + */ +export function extractExports(filePath: string): MemberList { + const exportDeclarations: MemberList = { + functions: [], + classes: [], + variables: [], + enums: [], + unknown: [] + }; + + const program = ts.createProgram([filePath], { + allowJs: true, + baseUrl: path.resolve(`${projectRoot}/node_modules`) + }); + const checker = program.getTypeChecker(); + const sourceFile = program.getSourceFile(filePath)!; + const module = checker.getSymbolAtLocation(sourceFile); + // no export from the file + if (!module) { + return exportDeclarations; + } + + const exports = checker.getExportsOfModule(module); + + for (const expt of exports) { + // get the source declaration where we can determine the type of the export. e.g. class vs function + let sourceSymbol = expt; + if (sourceSymbol.declarations?.[0].kind === ts.SyntaxKind.ExportSpecifier) { + sourceSymbol = checker.getAliasedSymbol(expt); + } + + if (!sourceSymbol.declarations || sourceSymbol.declarations.length === 0) { + console.log('Could not find the source symbol for ', expt.name); + continue; + } + const sourceDeclaration = sourceSymbol.declarations[0]; + + if (ts.isFunctionDeclaration(sourceDeclaration)) { + exportDeclarations.functions.push(expt.name); + } else if (ts.isClassDeclaration(sourceDeclaration)) { + exportDeclarations.classes.push(expt.name); + } else if (ts.isVariableDeclaration(sourceDeclaration)) { + exportDeclarations.variables.push(expt.name); + } else if (ts.isEnumDeclaration(sourceDeclaration)) { + // `const enum`s should not be analyzed. They do not add to bundle size and + // creating a file that imports them causes an error during the rollup step. + if ( + // Identifies if this enum had a "const" modifier attached. + !sourceDeclaration.modifiers?.some( + mod => mod.kind === ts.SyntaxKind.ConstKeyword + ) + ) { + exportDeclarations.enums.push(expt.name); + } + } else { + console.log(`export of unknown type: ${expt.name}`); + exportDeclarations.unknown.push(expt.name); + } + } + + Object.values(exportDeclarations).forEach(each => { + each.sort(); + }); + + return exportDeclarations; +} + +/** + * To Make sure symbols of every category are unique. + */ +export function dedup(memberList: MemberList): MemberList { + for (const key of Object.keys(memberList) as Array) { + const set: Set = new Set(memberList[key]); + memberList[key] = Array.from(set); + } + return memberList; +} + +export function mapSymbolToType( + map: Map, + memberList: MemberList +): MemberList { + const newMemberList: MemberList = { + functions: [], + classes: [], + variables: [], + enums: [], + unknown: [] + }; + + for (const key of Object.keys(memberList) as Array) { + memberList[key].forEach((element: string) => { + if (map.has(element)) { + newMemberList[map.get(element)! as keyof MemberList].push(element); + } else { + newMemberList[key].push(element); + } + }); + } + return newMemberList; +} + +export function replaceAll( + memberList: MemberList, + original: string, + current: string +): void { + for (const key of Object.keys(memberList) as Array) { + memberList[key] = replaceWith(memberList[key], original, current); + } +} + +function replaceWith( + arr: string[], + original: string, + current: string +): string[] { + const rv: string[] = []; + for (const each of arr) { + if (each.localeCompare(original) === 0) { + rv.push(current); + } else { + rv.push(each); + } + } + return rv; +} + +/** + * + * This functions writes generated json report(s) to a file + */ +export function writeReportToFile(report: Report, outputFile: string): void { + if (fs.existsSync(outputFile) && !fs.lstatSync(outputFile).isFile()) { + throw new Error(ErrorCode.OUTPUT_FILE_REQUIRED); + } + const directoryPath = path.dirname(outputFile); + //for output file path like ./dir/dir1/dir2/file, we need to make sure parent dirs exist. + if (!fs.existsSync(directoryPath)) { + fs.mkdirSync(directoryPath, { recursive: true }); + } + fs.writeFileSync(outputFile, JSON.stringify(report, null, 4)); +} +/** + * + * This functions writes generated json report(s) to a file of given directory + */ +export function writeReportToDirectory( + report: Report, + fileName: string, + directoryPath: string +): void { + if ( + fs.existsSync(directoryPath) && + !fs.lstatSync(directoryPath).isDirectory() + ) { + throw new Error(ErrorCode.OUTPUT_DIRECTORY_REQUIRED); + } + writeReportToFile(report, `${directoryPath}/${fileName}`); +} + +/** + * This function extract unresolved external module symbols from bundle file import statements. + * + */ +export function extractExternalDependencies(minimizedBundleFile: string): { + [key: string]: string[]; +} { + const program = ts.createProgram([minimizedBundleFile], { allowJs: true }); + + const sourceFile = program.getSourceFile(minimizedBundleFile); + if (!sourceFile) { + throw new Error(`${ErrorCode.FILE_PARSING_ERROR} ${minimizedBundleFile}`); + } + + const externalsMap: Map = new Map(); + ts.forEachChild(sourceFile, node => { + if (ts.isImportDeclaration(node) && node.importClause) { + const moduleName: string = node.moduleSpecifier.getText(sourceFile); + if (!externalsMap.has(moduleName)) { + externalsMap.set(moduleName, []); + } + + //import {a, b } from '@firebase/dummy-exp'; + // import {a as c, b } from '@firebase/dummy-exp'; + if ( + node.importClause.namedBindings && + ts.isNamedImports(node.importClause.namedBindings) + ) { + node.importClause.namedBindings.elements.forEach(each => { + // if imported symbol is renamed, we want its original name which is stored in propertyName + if (each.propertyName) { + externalsMap + .get(moduleName)! + .push(each.propertyName.getText(sourceFile)); + } else { + externalsMap.get(moduleName)!.push(each.name.getText(sourceFile)); + } + }); + // import * as fs from 'fs' + } else if ( + node.importClause.namedBindings && + ts.isNamespaceImport(node.importClause.namedBindings) + ) { + externalsMap.get(moduleName)!.push('*'); + // import a from '@firebase/dummy-exp' + } else if ( + node.importClause.name && + ts.isIdentifier(node.importClause.name) + ) { + externalsMap.get(moduleName)!.push('default export'); + } + } + }); + const externals: { [key: string]: string[] } = {}; + externalsMap.forEach((value, key) => { + externals[key.replace(/'/g, '')] = value; + }); + return externals; +} + +/** + * This function generates a binary size report for the given module specified by the moduleLocation argument. + * @param moduleLocation a path to location of a firebase module + */ +export async function generateReportForModule( + moduleLocation: string +): Promise { + const packageJsonPath = `${moduleLocation}/package.json`; + if (!fs.existsSync(packageJsonPath)) { + throw new Error( + `Firebase Module locates at ${moduleLocation}: ${ErrorCode.PKG_JSON_DOES_NOT_EXIST}` + ); + } + const packageJson = JSON.parse( + fs.readFileSync(packageJsonPath, { encoding: 'utf-8' }) + ); + // to exclude -types modules + const TYPINGS: string = 'typings'; + if (packageJson[TYPINGS]) { + const dtsFile = `${moduleLocation}/${packageJson[TYPINGS]}`; + const bundleLocation: string = retrieveBundleFileLocation(packageJson); + if (!bundleLocation) { + throw new Error(ErrorCode.BUNDLE_FILE_DOES_NOT_EXIST); + } + const bundleFile = `${moduleLocation}/${bundleLocation}`; + const jsonReport: Report = await generateReport( + packageJson.name, + dtsFile, + bundleFile + ); + + return jsonReport; + } + throw new Error( + `Firebase Module locates at: ${moduleLocation}: ${ErrorCode.TYPINGS_FIELD_NOT_DEFINED}` + ); +} +/** + * + * @param pkgJson package.json of the module. + * + * This function implements a fallback of locating module's bundle file. + * It first looks at esm2017 field of package.json, then module field. Main + * field at the last. + * + */ +function retrieveBundleFileLocation(pkgJson: { + [key: string]: string; +}): string { + if (pkgJson['esm2017']) { + return pkgJson['esm2017']; + } + if (pkgJson['module']) { + return pkgJson['module']; + } + if (pkgJson['main']) { + return pkgJson['main']; + } + return ''; +} + +/** + * A recursive function that locates and generates reports for sub-modules + */ +async function traverseDirs( + moduleLocation: string, + // eslint-disable-next-line @typescript-eslint/ban-types + executor: Function, + level: number, + levelLimit: number +): Promise { + if (level > levelLimit) { + return []; + } + + const reports: Report[] = []; + const report: Report = await executor(moduleLocation); + if (report != null) { + reports.push(report); + } + + for (const name of fs.readdirSync(moduleLocation)) { + const p = `${moduleLocation}/${name}`; + const generateSizeAnalysisReportPkgJsonField: string = + 'generate-size-analysis-report'; + // submodules of a firebase module should set generate-size-analysis-report field of package.json to true + // in order to be analyzed + if ( + fs.lstatSync(p).isDirectory() && + fs.existsSync(`${p}/package.json`) && + JSON.parse(fs.readFileSync(`${p}/package.json`, { encoding: 'utf-8' }))[ + generateSizeAnalysisReportPkgJsonField + ] + ) { + const subModuleReports: Report[] = await traverseDirs( + p, + executor, + level + 1, + levelLimit + ); + if (subModuleReports !== null && subModuleReports.length !== 0) { + reports.push(...subModuleReports); + } + } + } + return reports; +} + +/** + * + * This functions generates the final json report for the module. + * @param publicApi all symbols extracted from the input dts file. + * @param jsFile a bundle file generated by rollup according to the input dts file. + * @param map maps every symbol listed in publicApi to its type. eg: aVariable -> variable. + */ +export async function buildJsonReport( + moduleName: string, + publicApi: MemberList, + jsFile: string +): Promise { + const result: Report = { + name: moduleName, + symbols: [] + }; + for (const exp of publicApi.classes) { + try { + result.symbols.push(await extractDependenciesAndSize(exp, jsFile)); + } catch (e) { + console.log(e); + } + } + + for (const exp of publicApi.functions) { + try { + result.symbols.push(await extractDependenciesAndSize(exp, jsFile)); + } catch (e) { + console.log(e); + } + } + for (const exp of publicApi.variables) { + try { + result.symbols.push(await extractDependenciesAndSize(exp, jsFile)); + } catch (e) { + console.log(e); + } + } + + for (const exp of publicApi.enums) { + try { + result.symbols.push(await extractDependenciesAndSize(exp, jsFile)); + } catch (e) { + console.log(e); + } + } + return result; +} +/** + * + * This function generates a report from given dts file. + * @param name a name to be displayed on the report. a module name if for a firebase module; a random name if for adhoc analysis. + * @param dtsFile absolute path to the definition file of interest. + * @param bundleFile absolute path to the bundle file of the given definition file. + */ +export async function generateReport( + name: string, + dtsFile: string, + bundleFile: string +): Promise { + const resolvedDtsFile = path.resolve(dtsFile); + const resolvedBundleFile = path.resolve(bundleFile); + if (!fs.existsSync(resolvedDtsFile)) { + throw new Error(ErrorCode.INPUT_DTS_FILE_DOES_NOT_EXIST); + } + if (!fs.existsSync(resolvedBundleFile)) { + throw new Error(ErrorCode.INPUT_BUNDLE_FILE_DOES_NOT_EXIST); + } + + console.log('generating report for ', name); + const publicAPI = extractExports(resolvedBundleFile); + return buildJsonReport(name, publicAPI, bundleFile); +} + +/** + * This function recursively generates a binary size report for every module listed in moduleLocations array. + * + * @param moduleLocations an array of strings where each is a path to location of a firebase module + * + */ +export async function generateReportForModules( + moduleLocations: string[] +): Promise { + const reportCollection: Report[] = []; + + for (const moduleLocation of moduleLocations) { + // we traverse the dir in order to include binaries for submodules, e.g. @firebase/firestore/memory + // Currently we only traverse 1 level deep because we don't have any submodule deeper than that. + const reportsForModuleAndItsSubModule: Report[] = await traverseDirs( + moduleLocation, + generateReportForModule, + 0, + 1 + ); + if ( + reportsForModuleAndItsSubModule !== null && + reportsForModuleAndItsSubModule.length !== 0 + ) { + reportCollection.push(...reportsForModuleAndItsSubModule); + } + } + return reportCollection; +} diff --git a/repo-scripts/size-analysis/analyze-all-bundles.ts b/repo-scripts/size-analysis/analyze-all-bundles.ts new file mode 100644 index 00000000000..74390732980 --- /dev/null +++ b/repo-scripts/size-analysis/analyze-all-bundles.ts @@ -0,0 +1,117 @@ +/** + * @license + * Copyright 2021 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 * as fs from 'fs'; +import * as path from 'path'; +import * as tmp from 'tmp'; + +import { Bundler, Mode, run as runBundleAnalysis } from './bundle-analysis'; +import { Report } from '../../scripts/size_report/report_binary_size'; + +/** + * Runs bundle analysis for all bundle definition files under: + * + * `firebase-js-sdk/repo-scripts/size-analysis/bundle-definitions` + * + * The method accepts an optional parameter `version`: + * 1. when presented (for example, `version` = '9.0.0'), this method measures the bundle size by + * building test bundles with dependencies at that specific version downloaded from npm + * 2. when omitted (in this case, `version` = null), this method measures the bundle size by + * building test bundles with dependencies from local artifacts (e.g. produced by `yarn build`) + * + * #1 is intended only for manual runs for the purpose of back-filling historical size data. #2 is + * intended for CI runs that measure size for the current commit. + * + * More details on how a test bundle is built can be found in `bundle-analysis.ts`. + * + * @param version - If present, the SDK version to run measurement against + * @returns A list of bundle size measurements + */ +export async function generateReportForBundles( + version?: string +): Promise { + const definitionDir = `${__dirname}/bundle-definitions`; + const outputDir = tmp.dirSync().name; + console.log(`Bundle definitions are located at "${definitionDir}".`); + console.log(`Analysis output are located at "${outputDir}".`); + + const bundles = fs.readdirSync(definitionDir); + const results: Report[] = []; + for (const bundle of bundles) { + const product = path.basename(bundle, '.json'); + const output = `${outputDir}/${product}.analysis.json`; + if (version) { + overwriteVersion(definitionDir, bundle, outputDir, version); + } + const option = { + input: version ? `${outputDir}/${bundle}` : `${definitionDir}/${bundle}`, + bundler: Bundler.Rollup, + mode: version ? Mode.Npm : Mode.Local, + output, + debug: true + }; + console.log(`Running for bundle "${bundle}" with mode "${option.mode}".`); + await runBundleAnalysis(option); + const measurements = parseAnalysisOutput(product, output); + results.push(...measurements); + } + console.log(results); + return results; +} + +function overwriteVersion( + definitionDir: string, + bundle: string, + temp: string, + version: string +): void { + const definitions = JSON.parse( + fs.readFileSync(`${definitionDir}/${bundle}`, { encoding: 'utf-8' }) + ); + for (const definition of definitions) { + const dependencies = definition.dependencies; + for (const dependency of dependencies) { + dependency.versionOrTag = version; + } + } + fs.writeFileSync(`${temp}/${bundle}`, JSON.stringify(definitions, null, 2), { + encoding: 'utf-8' + }); +} + +function parseAnalysisOutput(product: string, output: string): Report[] { + const analyses = JSON.parse(fs.readFileSync(output, { encoding: 'utf-8' })); + const results: Report[] = []; + for (const analysis of analyses) { + // The API of the backend for persisting size measurements currently requires data to be + // organized strictly in the below json format: + // + // { + // sdk: , + // type: , + // value: + // } + // + // We are reusing this API here, although its semantics does not make sense in the context of + // bundle-analysis. + const sdk = 'bundle'; // to accommodate above API syntax, can be any string + const value = analysis.results[0].size; + const type = `${product} (${analysis.name})`; + results.push({ sdk, type, value }); + } + return results; +} diff --git a/repo-scripts/size-analysis/bundle-analysis.ts b/repo-scripts/size-analysis/bundle-analysis.ts new file mode 100644 index 00000000000..98505ab6859 --- /dev/null +++ b/repo-scripts/size-analysis/bundle-analysis.ts @@ -0,0 +1,500 @@ +/** + * @license + * Copyright 2020 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 * as tmp from 'tmp'; +import { existsSync, lstatSync, readFileSync, writeFileSync } from 'fs'; +import { spawn } from 'child-process-promise'; +import { ordinal } from '@firebase/util'; +import { bundleWithRollup } from './bundle/rollup'; +import { bundleWithWebpack } from './bundle/webpack'; +import { calculateContentSize } from './util'; +import { minify } from './bundle/minify'; +import { extractAllTopLevelSymbols, MemberList } from './analysis-helper'; + +interface BundleAnalysisArgs { + input: string; + bundler: 'webpack' | 'rollup' | 'both'; + mode: 'npm' | 'local'; + output: string; + debug: boolean; +} + +interface BundleAnalysisOptions { + bundleDefinitions: BundleDefinition[]; + bundler: Bundler; + mode: Mode; + output: string; + debug: boolean; +} + +interface DebugOptions { + output: string; // output folder for debug files +} + +interface BundleDefinition { + name: string; + description?: string; + dependencies: BundleDependency[]; +} + +interface BundleDependency { + packageName: string; + /** + * npm version or tag + */ + versionOrTag: string; + imports: string | SubModuleImport[]; +} + +interface SubModuleImport { + path: string; + imports: string[]; +} + +export enum Bundler { + Rollup = 'rollup', + Webpack = 'webpack', + Both = 'both' +} + +export enum Mode { + Npm = 'npm', + Local = 'local' +} + +enum SpecialImport { + Default = 'default import', + Sizeeffect = 'side effect import', + Namespace = 'namespace import' +} + +export async function run({ + input, + bundler, + mode, + output, + debug +}: BundleAnalysisArgs): Promise { + const options = { + bundleDefinitions: loadBundleDefinitions(input), + bundler: toBundlerEnum(bundler), + mode: toModeEnum(mode), + output, + debug + }; + + return analyze(options); +} + +function loadBundleDefinitions(path: string): BundleDefinition[] { + if (!existsSync(path)) { + throw new Error( + `${path} doesn't exist. Please provide a valid path to the bundle definition file.` + ); + } + + if (lstatSync(path).isDirectory()) { + throw new Error( + `Expecting a file, but ${path} is a directory. Please provide a valid path to the bundle definition file.` + ); + } + + const def = parseBundleDefinition(readFileSync(path, { encoding: 'utf-8' })); + + return def; +} + +function toBundlerEnum(bundler: 'webpack' | 'rollup' | 'both'): Bundler { + switch (bundler) { + case 'rollup': + return Bundler.Rollup; + case 'webpack': + return Bundler.Webpack; + case 'both': + return Bundler.Both; + default: + throw new Error('impossible!'); + } +} + +function toModeEnum(mode: 'npm' | 'local'): Mode { + switch (mode) { + case 'npm': + return Mode.Npm; + case 'local': + return Mode.Local; + default: + throw new Error('impossible'); + } +} + +/** + * + * @param input + * @returns - an array of error messages. Empty if the bundle definition is valid + */ +function parseBundleDefinition(input: string): BundleDefinition[] { + const bundleDefinitions: BundleDefinition[] = JSON.parse(input); + + const errorMessages = []; + if (!Array.isArray(bundleDefinitions)) { + throw new Error('Bundle definition must be defined in an array'); + } + + for (let i = 0; i < bundleDefinitions.length; i++) { + const bundleDefinition = bundleDefinitions[i]; + if (!bundleDefinition.name) { + errorMessages.push( + `Missing field 'name' in the ${ordinal(i + 1)} bundle definition` + ); + } + + if (!bundleDefinition.dependencies) { + errorMessages.push( + `Missing field 'dependencies' in the ${ordinal( + i + 1 + )} bundle definition` + ); + } + + if (!Array.isArray(bundleDefinition.dependencies)) { + errorMessages.push( + `Expecting an array for field 'dependencies', but it is not an array in the ${ordinal( + i + 1 + )} bundle definition` + ); + } + + for (let j = 0; j < bundleDefinition.dependencies.length; j++) { + const dependency = bundleDefinition.dependencies[j]; + + if (!dependency.packageName) { + errorMessages.push( + `Missing field 'packageName' in the ${ordinal( + j + 1 + )} dependency of the ${ordinal(i + 1)} bundle definition` + ); + } + + if (!dependency.imports) { + errorMessages.push( + `Missing field 'imports' in the ${ordinal( + j + 1 + )} dependency of the ${ordinal(i + 1)} bundle definition` + ); + } + + if (!Array.isArray(dependency.imports)) { + errorMessages.push( + `Expecting an array for field 'imports', but it is not an array in the ${ordinal( + j + 1 + )} dependency of the ${ordinal(i + 1)} bundle definition` + ); + } + + if (!dependency.versionOrTag) { + dependency.versionOrTag = 'latest'; + } + } + } + + if (errorMessages.length > 0) { + throw new Error(errorMessages.join('\n')); + } + + return bundleDefinitions; +} + +async function analyze({ + bundleDefinitions, + bundler, + output, + mode, + debug +}: BundleAnalysisOptions): Promise { + const analyses: BundleAnalysis[] = []; + + let debugOptions: DebugOptions | undefined; + if (debug) { + const tmpDir = tmp.dirSync(); + debugOptions = { + output: tmpDir.name + }; + } + + for (const bundleDefinition of bundleDefinitions) { + analyses.push( + await analyzeBundle(bundleDefinition, bundler, mode, debugOptions) + ); + } + + writeFileSync(output, JSON.stringify(analyses, null, 2), { + encoding: 'utf-8' + }); +} + +async function analyzeBundle( + bundleDefinition: BundleDefinition, + bundler: Bundler, + mode: Mode, + debugOptions?: DebugOptions +): Promise { + const analysis: BundleAnalysis = { + name: bundleDefinition.name, + description: bundleDefinition.description ?? '', + results: [], + dependencies: bundleDefinition.dependencies + }; + + let moduleDirectory: string | undefined; + let tmpDir: tmp.DirResult | undefined; + if (mode === Mode.Npm) { + tmpDir = await setupTempProject(bundleDefinition); + moduleDirectory = `${tmpDir.name}/node_modules`; + } + + const entryFileContent = createEntryFileContent(bundleDefinition); + + switch (bundler) { + case Bundler.Rollup: + case Bundler.Webpack: + analysis.results.push( + await analyzeBundleWithBundler( + bundleDefinition.name, + entryFileContent, + bundler, + moduleDirectory, + debugOptions + ) + ); + break; + case Bundler.Both: + analysis.results.push( + await analyzeBundleWithBundler( + bundleDefinition.name, + entryFileContent, + Bundler.Rollup, + moduleDirectory, + debugOptions + ) + ); + analysis.results.push( + await analyzeBundleWithBundler( + bundleDefinition.name, + entryFileContent, + Bundler.Webpack, + moduleDirectory, + debugOptions + ) + ); + break; + default: + throw new Error('impossible!'); + } + + if (tmpDir) { + tmpDir.removeCallback(); + } + + return analysis; +} + +/** + * Create a temp project and install dependencies the bundleDefinition defines + * @returns - the path to the temp project + */ +async function setupTempProject( + bundleDefinition: BundleDefinition +): Promise { + /// set up a temporary project to install dependencies + const tmpDir = tmp.dirSync({ unsafeCleanup: true }); + console.log(tmpDir.name); + // create package.json + const pkgJson: { + name: string; + version: string; + dependencies: Record; + } = { + name: 'size-analysis', + version: '0.0.0', + dependencies: {} + }; + + for (const dep of bundleDefinition.dependencies) { + pkgJson.dependencies[dep.packageName] = dep.versionOrTag; + } + + writeFileSync( + `${tmpDir.name}/package.json`, + `${JSON.stringify(pkgJson, null, 2)}\n`, + { encoding: 'utf-8' } + ); + + // install dependencies + await spawn('npm', ['install'], { + cwd: tmpDir.name, + stdio: 'inherit' + }); + + return tmpDir; +} + +async function analyzeBundleWithBundler( + bundleName: string, + entryFileContent: string, + bundler: Exclude, + moduleDirectory?: string, + debugOptions?: DebugOptions +): Promise { + let bundledContent = ''; + + // bundle using bundlers + if (bundler === Bundler.Rollup) { + bundledContent = await bundleWithRollup(entryFileContent, moduleDirectory); + } else { + bundledContent = await bundleWithWebpack(entryFileContent, moduleDirectory); + } + + const minifiedBundle = await minify(bundledContent); + const { size, gzipSize } = calculateContentSize(minifiedBundle); + + const analysisResult: BundleAnalysisResult = { + bundler, + size, + gzipSize + }; + + if (debugOptions) { + const bundleFilePath = `${debugOptions.output}/${bundleName.replace( + / +/g, + '-' + )}.${bundler}.js`; + const minifiedBundleFilePath = `${debugOptions.output}/${bundleName.replace( + / +/g, + '-' + )}.${bundler}.minified.js`; + writeFileSync(bundleFilePath, bundledContent, { encoding: 'utf8' }); + writeFileSync(minifiedBundleFilePath, minifiedBundle, { encoding: 'utf8' }); + + analysisResult.debugInfo = { + pathToBundle: bundleFilePath, + pathToMinifiedBundle: minifiedBundleFilePath, + dependencies: extractAllTopLevelSymbols(bundleFilePath) + }; + } + + return analysisResult; +} + +function createEntryFileContent(bundleDefinition: BundleDefinition): string { + const contentArray = []; + // cache used symbols. Used to avoid symbol collision when multiple modules export symbols with the same name. + const symbolsCache = new Set(); + for (const dep of bundleDefinition.dependencies) { + for (const imp of dep.imports) { + if (typeof imp === 'string') { + contentArray.push( + ...createImportExport(imp, dep.packageName, symbolsCache) + ); + } else { + // submodule imports + for (const subImp of imp.imports) { + contentArray.push( + ...createImportExport( + subImp, + `${dep.packageName}/${imp.path}`, + symbolsCache + ) + ); + } + } + } + } + + return contentArray.join('\n'); +} + +function createImportExport( + symbol: string, + modulePath: string, + symbolsCache: Set +): string[] { + const contentArray = []; + + switch (symbol) { + case SpecialImport.Default: { + const nameToUse = createSymbolName('default_import', symbolsCache); + contentArray.push(`import ${nameToUse} from '${modulePath}';`); + contentArray.push(`console.log(${nameToUse})`); // prevent import from being tree shaken + break; + } + case SpecialImport.Namespace: { + const nameToUse = createSymbolName('namespace', symbolsCache); + contentArray.push(`import * as ${nameToUse} from '${modulePath}';`); + contentArray.push(`console.log(${nameToUse})`); // prevent import from being tree shaken + break; + } + case SpecialImport.Sizeeffect: + contentArray.push(`import '${modulePath}';`); + break; + default: + // named imports + const nameToUse = createSymbolName(symbol, symbolsCache); + + if (nameToUse !== symbol) { + contentArray.push( + `export {${symbol} as ${nameToUse}} from '${modulePath}';` + ); + } else { + contentArray.push(`export {${symbol}} from '${modulePath}';`); + } + } + + return contentArray; +} + +/** + * In case a symbol with the same name is already imported from another module, we need to give this symbol another name + * using "originalname as anothername" syntax, otherwise it returns the original symbol name. + */ +function createSymbolName(symbol: string, symbolsCache: Set): string { + let nameToUse = symbol; + const max = 100; + while (symbolsCache.has(nameToUse)) { + nameToUse = `${symbol}_${Math.floor(Math.random() * max)}`; + } + + symbolsCache.add(nameToUse); + return nameToUse; +} + +interface BundleAnalysis { + name: string; // the bundle name defined in the bundle definition + description: string; + dependencies: BundleDependency[]; + results: BundleAnalysisResult[]; +} + +interface BundleAnalysisResult { + bundler: 'rollup' | 'webpack'; + size: number; + gzipSize: number; + debugInfo?: { + pathToBundle?: string; + pathToMinifiedBundle?: string; + dependencies?: MemberList; + }; +} diff --git a/repo-scripts/size-analysis/bundle-definitions/analytics.json b/repo-scripts/size-analysis/bundle-definitions/analytics.json new file mode 100644 index 00000000000..4f7ae74d700 --- /dev/null +++ b/repo-scripts/size-analysis/bundle-definitions/analytics.json @@ -0,0 +1,32 @@ +[ + { + "name": "logEvent", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "analytics", + "imports": [ + "getAnalytics", + "logEvent" + ] + } + ] + } + ] + } +] diff --git a/repo-scripts/size-analysis/bundle-definitions/app-check.json b/repo-scripts/size-analysis/bundle-definitions/app-check.json new file mode 100644 index 00000000000..b9b2cada716 --- /dev/null +++ b/repo-scripts/size-analysis/bundle-definitions/app-check.json @@ -0,0 +1,95 @@ +[ + { + "name": "ReCaptchaV3Provider", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app-check", + "imports": [ + "initializeAppCheck", + "ReCaptchaV3Provider", + "getToken" + ] + } + ] + } + ] + }, + { + "name": "ReCaptchaEnterpriseProvider", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app-check", + "imports": [ + "initializeAppCheck", + "ReCaptchaEnterpriseProvider", + "getToken" + ] + } + ] + } + ] + }, + { + "name": "CustomProvider", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app-check", + "imports": [ + "initializeAppCheck", + "CustomProvider", + "getToken" + ] + } + ] + } + ] + } +] diff --git a/repo-scripts/size-analysis/bundle-definitions/auth.json b/repo-scripts/size-analysis/bundle-definitions/auth.json new file mode 100644 index 00000000000..c777109a7fe --- /dev/null +++ b/repo-scripts/size-analysis/bundle-definitions/auth.json @@ -0,0 +1,201 @@ +[ + { + "name": "EmailAndPassword", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "auth", + "imports": [ + "initializeAuth", + "indexedDBLocalPersistence", + "signInWithEmailAndPassword", + "onAuthStateChanged" + ] + } + ] + } + ] + }, + { + "name": "GooglePopup", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "auth", + "imports": [ + "initializeAuth", + "indexedDBLocalPersistence", + "GoogleAuthProvider", + "signInWithPopup", + "browserPopupRedirectResolver", + "onAuthStateChanged" + ] + } + ] + } + ] + }, + { + "name": "GoogleFBTwitterGitHubPopup", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "auth", + "imports": [ + "getAuth", + "GoogleAuthProvider", + "FacebookAuthProvider", + "TwitterAuthProvider", + "GithubAuthProvider", + "signInWithPopup", + "onAuthStateChanged" + ] + } + ] + } + ] + }, + { + "name": "GoogleRedirect", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "auth", + "imports": [ + "initializeAuth", + "indexedDBLocalPersistence", + "GoogleAuthProvider", + "getRedirectResult", + "browserPopupRedirectResolver", + "signInWithRedirect", + "onAuthStateChanged" + ] + } + ] + } + ] + }, + { + "name": "Phone", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "auth", + "imports": [ + "initializeAuth", + "indexedDBLocalPersistence", + "RecaptchaVerifier", + "signInWithPhoneNumber" + ] + } + ] + } + ] + }, + { + "name": "Anonymous", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "auth", + "imports": [ + "initializeAuth", + "indexedDBLocalPersistence", + "signInAnonymously" + ] + } + ] + } + ] + } +] diff --git a/repo-scripts/size-analysis/bundle-definitions/database.json b/repo-scripts/size-analysis/bundle-definitions/database.json new file mode 100644 index 00000000000..b4366ec3dff --- /dev/null +++ b/repo-scripts/size-analysis/bundle-definitions/database.json @@ -0,0 +1,288 @@ +[ + { + "name": "Read data once", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "database", + "imports": [ + "getDatabase", + "ref", + "child", + "get" + ] + } + ] + } + ] + }, + { + "name": "Write data", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "database", + "imports": [ + "getDatabase", + "ref", + "set" + ] + } + ] + } + ] + }, + { + "name": "Save data as transactions", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "database", + "imports": [ + "getDatabase", + "ref", + "runTransaction" + ] + } + ] + } + ] + }, + { + "name": "Append to a list of data", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "database", + "imports": [ + "getDatabase", + "ref", + "push", + "set" + ] + } + ] + } + ] + }, + { + "name": "Listen for child events", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "database", + "imports": [ + "getDatabase", + "ref", + "onChildAdded", + "onChildChanged", + "onChildRemoved" + ] + } + ] + } + ] + }, + { + "name": "Listen for value events", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "database", + "imports": [ + "getDatabase", + "ref", + "onValue" + ] + } + ] + } + ] + }, + { + "name": "Listen for value events + Detach listeners", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "database", + "imports": [ + "getDatabase", + "ref", + "onValue", + "off" + ] + } + ] + } + ] + }, + { + "name": "Sort data", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "database", + "imports": [ + "getDatabase", + "ref", + "query", + "orderByChild" + ] + } + ] + } + ] + }, + { + "name": "Filtering data", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "database", + "imports": [ + "getDatabase", + "ref", + "query", + "limitToLast" + ] + } + ] + } + ] + } +] diff --git a/repo-scripts/size-analysis/bundle-definitions/firestore-lite.json b/repo-scripts/size-analysis/bundle-definitions/firestore-lite.json new file mode 100644 index 00000000000..0313cc7af77 --- /dev/null +++ b/repo-scripts/size-analysis/bundle-definitions/firestore-lite.json @@ -0,0 +1,166 @@ +[ + { + "name": "Read data once", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "firestore/lite", + "imports": [ + "getFirestore", + "doc", + "getDoc" + ] + } + ] + } + ] + }, + { + "name": "Write data", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "firestore/lite", + "imports": [ + "getFirestore", + "collection", + "doc", + "setDoc" + ] + } + ] + } + ] + }, + { + "name": "Query", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "firestore/lite", + "imports": [ + "getFirestore", + "collection", + "query", + "where", + "orderBy", + "getDocs" + ] + } + ] + } + ] + }, + { + "name": "Query Cursors", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "firestore/lite", + "imports": [ + "getFirestore", + "collection", + "doc", + "getDocs", + "query", + "orderBy", + "startAt", + "endBefore" + ] + } + ] + } + ] + }, + { + "name": "Transaction", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "firestore/lite", + "imports": [ + "getFirestore", + "doc", + "runTransaction" + ] + } + ] + } + ] + } +] diff --git a/repo-scripts/size-analysis/bundle-definitions/firestore.json b/repo-scripts/size-analysis/bundle-definitions/firestore.json new file mode 100644 index 00000000000..f5ddafd167c --- /dev/null +++ b/repo-scripts/size-analysis/bundle-definitions/firestore.json @@ -0,0 +1,328 @@ +[ + { + "name": "Read data once", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "firestore", + "imports": [ + "getFirestore", + "doc", + "getDoc" + ] + } + ] + } + ] + }, + { + "name": "Write data", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "firestore", + "imports": [ + "getFirestore", + "collection", + "doc", + "setDoc" + ] + } + ] + } + ] + }, + { + "name": "Realtime updates", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "firestore", + "imports": [ + "getFirestore", + "doc", + "onSnapshot" + ] + } + ] + } + ] + }, + { + "name": "Query", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "firestore", + "imports": [ + "getFirestore", + "onSnapshot", + "collection", + "query", + "where", + "orderBy", + "getDocs" + ] + } + ] + } + ] + }, + { + "name": "Query Cursors", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "firestore", + "imports": [ + "getFirestore", + "collection", + "doc", + "getDocs", + "query", + "orderBy", + "startAt", + "endBefore" + ] + } + ] + } + ] + }, + { + "name": "Persistence", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "firestore", + "imports": [ + "getFirestore", + "enableMultiTabIndexedDbPersistence" + ] + } + ] + } + ] + }, + { + "name": "Read Write w Persistence", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "firestore", + "imports": [ + "collection", + "doc", + "enableMultiTabIndexedDbPersistence", + "getDoc", + "getFirestore", + "setDoc" + ] + } + ] + } + ] + }, + { + "name": "Transaction", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "firestore", + "imports": [ + "getFirestore", + "doc", + "runTransaction" + ] + } + ] + } + ] + }, + { + "name": "CSI Auto Indexing Enable", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "firestore", + "imports": [ + "deleteAllPersistentCacheIndexes", + "enableIndexedDbPersistence", + "enablePersistentCacheIndexAutoCreation", + "getFirestore", + "getPersistentCacheIndexManager" + ] + } + ] + } + ] + }, + { + "name": "CSI Auto Indexing Disable and Delete", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "firestore", + "imports": [ + "deleteAllPersistentCacheIndexes", + "disablePersistentCacheIndexAutoCreation", + "enableIndexedDbPersistence", + "getFirestore", + "getPersistentCacheIndexManager" + ] + } + ] + } + ] + } +] diff --git a/repo-scripts/size-analysis/bundle-definitions/functions.json b/repo-scripts/size-analysis/bundle-definitions/functions.json new file mode 100644 index 00000000000..6ec4c259dd6 --- /dev/null +++ b/repo-scripts/size-analysis/bundle-definitions/functions.json @@ -0,0 +1,32 @@ +[ + { + "name": "call", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "functions", + "imports": [ + "getFunctions", + "httpsCallable" + ] + } + ] + } + ] + } +] diff --git a/repo-scripts/size-analysis/bundle-definitions/messaging.json b/repo-scripts/size-analysis/bundle-definitions/messaging.json new file mode 100644 index 00000000000..03f930d44d7 --- /dev/null +++ b/repo-scripts/size-analysis/bundle-definitions/messaging.json @@ -0,0 +1,33 @@ +[ + { + "name": "send + receive", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "messaging", + "imports": [ + "getMessaging", + "getToken", + "onMessage" + ] + } + ] + } + ] + } +] diff --git a/repo-scripts/size-analysis/bundle-definitions/performance.json b/repo-scripts/size-analysis/bundle-definitions/performance.json new file mode 100644 index 00000000000..6803a424540 --- /dev/null +++ b/repo-scripts/size-analysis/bundle-definitions/performance.json @@ -0,0 +1,32 @@ +[ + { + "name": "trace", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "performance", + "imports": [ + "getPerformance", + "trace" + ] + } + ] + } + ] + } +] diff --git a/repo-scripts/size-analysis/bundle-definitions/remote-config.json b/repo-scripts/size-analysis/bundle-definitions/remote-config.json new file mode 100644 index 00000000000..9a5f28cf2b6 --- /dev/null +++ b/repo-scripts/size-analysis/bundle-definitions/remote-config.json @@ -0,0 +1,33 @@ +[ + { + "name": "getAndFetch", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "remote-config", + "imports": [ + "getRemoteConfig", + "getValue", + "fetchAndActivate" + ] + } + ] + } + ] + } +] diff --git a/repo-scripts/size-analysis/bundle-definitions/storage.json b/repo-scripts/size-analysis/bundle-definitions/storage.json new file mode 100644 index 00000000000..8b847eec197 --- /dev/null +++ b/repo-scripts/size-analysis/bundle-definitions/storage.json @@ -0,0 +1,251 @@ +[ + { + "name": "uploadBytes", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "storage", + "imports": [ + "getStorage", + "ref", + "uploadBytes" + ] + } + ] + } + ] + }, + { + "name": "uploadString", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "storage", + "imports": [ + "getStorage", + "ref", + "uploadString" + ] + } + ] + } + ] + }, + { + "name": "uploadBytesResumable", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "storage", + "imports": [ + "getStorage", + "ref", + "uploadBytesResumable" + ] + } + ] + } + ] + }, + { + "name": "getDownloadURL", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "storage", + "imports": [ + "getStorage", + "ref", + "getDownloadURL" + ] + } + ] + } + ] + }, + { + "name": "list + listAll", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "storage", + "imports": [ + "getStorage", + "ref", + "list", + "listAll" + ] + } + ] + } + ] + }, + { + "name": "getMetadata", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "storage", + "imports": [ + "getStorage", + "ref", + "getMetadata" + ] + } + ] + } + ] + }, + { + "name": "updateMetadata", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "storage", + "imports": [ + "getStorage", + "ref", + "updateMetadata" + ] + } + ] + } + ] + }, + { + "name": "getBytes", + "dependencies": [ + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "app", + "imports": [ + "initializeApp" + ] + } + ] + }, + { + "packageName": "firebase", + "versionOrTag": "latest", + "imports": [ + { + "path": "storage", + "imports": [ + "getStorage", + "ref", + "getBytes" + ] + } + ] + } + ] + } +] diff --git a/repo-scripts/size-analysis/bundle/minify.ts b/repo-scripts/size-analysis/bundle/minify.ts new file mode 100644 index 00000000000..60597001a5e --- /dev/null +++ b/repo-scripts/size-analysis/bundle/minify.ts @@ -0,0 +1,30 @@ +/** + * @license + * Copyright 2020 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 * as terser from 'terser'; + +export async function minify(content: string): Promise { + const minified = await terser.minify(content, { + format: { + comments: false + }, + mangle: { toplevel: true }, + compress: false + }); + + return minified.code ?? ''; +} diff --git a/repo-scripts/size-analysis/bundle/rollup.ts b/repo-scripts/size-analysis/bundle/rollup.ts new file mode 100644 index 00000000000..272cd934d08 --- /dev/null +++ b/repo-scripts/size-analysis/bundle/rollup.ts @@ -0,0 +1,57 @@ +/** + * @license + * Copyright 2020 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 * as rollup from 'rollup'; +import resolve, { RollupNodeResolveOptions } from '@rollup/plugin-node-resolve'; +import commonjs from '@rollup/plugin-commonjs'; +// @ts-ignore +import virtual from '@rollup/plugin-virtual'; + +/** + * + * @param fileContent + * @param moduleDirectory - the path to the node_modules folder of the temporary project in npm mode. + * undefined in local mode + */ +export async function bundleWithRollup( + fileContent: string, + moduleDirectory?: string +): Promise { + const resolveOptions: RollupNodeResolveOptions = { + mainFields: ['esm2017', 'module', 'main'] + }; + + if (moduleDirectory) { + resolveOptions.moduleDirectories = [moduleDirectory]; + } + + const bundle = await rollup.rollup({ + input: 'entry', + plugins: [ + virtual({ + entry: fileContent + }), + resolve(resolveOptions), + commonjs() + ] + }); + + const { output } = await bundle.generate({ + format: 'es' + }); + return output[0].code; +} diff --git a/repo-scripts/size-analysis/bundle/webpack.ts b/repo-scripts/size-analysis/bundle/webpack.ts new file mode 100644 index 00000000000..93f8523c37b --- /dev/null +++ b/repo-scripts/size-analysis/bundle/webpack.ts @@ -0,0 +1,85 @@ +/** + * @license + * Copyright 2020 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 webpack from 'webpack'; +// @ts-ignore +import virtualModulesPlugin from 'webpack-virtual-modules'; +import { createFsFromVolume, IFs, Volume } from 'memfs'; +import path from 'path'; +import { projectRoot } from '../util'; + +/** + * + * @param fileContent + * @param moduleDirectory - the path to the node_modules folder of the temporary project in npm mode. + * undefined in local mode + */ +export async function bundleWithWebpack( + fileContent: string, + moduleDirectory?: string +): Promise { + const entryFileName = '/virtual_path_to_in_memory_file/index.js'; + const outputFileName = 'o.js'; + + const resolveConfig: webpack.ResolveOptions = { + mainFields: ['esm2017', 'module', 'main'] + }; + + if (moduleDirectory) { + resolveConfig.modules = [moduleDirectory]; + } else { + // local mode + resolveConfig.modules = [`${projectRoot}/node_modules`]; + } + + const compiler = webpack({ + entry: entryFileName, + output: { + filename: outputFileName + }, + resolve: resolveConfig, + plugins: [ + new virtualModulesPlugin({ + [entryFileName]: fileContent + }) + ], + mode: 'production' + }); + + // use virtual file system for output to avoid I/O + compiler.outputFileSystem = getMemoryFileSystem(); + + return new Promise((res, rej) => { + compiler.run((err, stats) => { + if (err) { + rej(err); + return; + } + + // Hack to get string output without reading the output file using an internal API from webpack + // eslint-disable-next-line @typescript-eslint/no-explicit-any + res((stats!.compilation.assets[outputFileName] as any)['_value']); + }); + }); +} + +function getMemoryFileSystem(): IFs { + const fs = createFsFromVolume(new Volume()); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (fs as any).join = path.join.bind(path); + return fs; +} diff --git a/repo-scripts/size-analysis/cli.ts b/repo-scripts/size-analysis/cli.ts new file mode 100644 index 00000000000..e9d6d9653f3 --- /dev/null +++ b/repo-scripts/size-analysis/cli.ts @@ -0,0 +1,92 @@ +/** + * @license + * Copyright 2020 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 * as yargs from 'yargs'; +import { run as runBundleAnalysis } from './bundle-analysis'; +import { analyzePackageSize } from './package-analysis'; + +// eslint-disable-next-line no-unused-expressions +yargs + .command( + '$0', + 'Analyze the size of individual exports from packages', + { + inputModule: { + type: 'array', + alias: 'im', + desc: 'The name of the module(s) to be analyzed. example: --inputModule "@firebase/functions" "firebase/auth"' + }, + inputDtsFile: { + type: 'string', + alias: 'if', + desc: 'support for adhoc analysis. requires a path to a d.ts file' + }, + inputBundleFile: { + type: 'string', + alias: 'ib', + desc: 'support for adhoc analysis. requires a path to a bundle file' + }, + output: { + type: 'string', + alias: 'o', + required: true, + desc: 'The location where report(s) will be generated, a directory path if module(s) are analyzed; a file path if ad hoc analysis is to be performed' + } + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + args => analyzePackageSize(args as any).catch(e => console.log(e)) + ) + .command( + 'bundle', + 'Analyze bundle size', + { + input: { + type: 'string', + alias: 'i', + required: true, + desc: 'Path to the JSON file that describes the bundles to be analyzed' + }, + mode: { + choices: ['npm', 'local'], + alias: 'm', + default: 'npm', + desc: 'Use Firebase packages from npm or the local repo' + }, + bundler: { + choices: ['rollup', 'webpack', 'both'], + alias: 'b', + default: 'rollup', + desc: 'The bundler(s) to be used' + }, + output: { + type: 'string', + alias: 'o', + default: './size-analysis-bundles.json', + desc: 'The output location' + }, + debug: { + type: 'boolean', + alias: 'd', + default: false, + desc: 'debug mode' + } + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + argv => runBundleAnalysis(argv as any) + ) + .help() + .parseSync(); diff --git a/repo-scripts/size-analysis/package-analysis.ts b/repo-scripts/size-analysis/package-analysis.ts new file mode 100644 index 00000000000..e8d179e19fc --- /dev/null +++ b/repo-scripts/size-analysis/package-analysis.ts @@ -0,0 +1,134 @@ +/** + * @license + * Copyright 2020 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 { resolve, basename, dirname } from 'path'; +import { + generateReport, + generateReportForModules, + writeReportToFile, + Report, + ErrorCode, + writeReportToDirectory +} from './analysis-helper'; +import glob from 'glob'; +import * as fs from 'fs'; + +const projectRoot = dirname(resolve(__dirname, '../../package.json')); +/** + * Support Command Line Options + * -- inputModule (optional) : can be left unspecified which results in running analysis on all exp modules. + * can specify one to many module names separated by space. + * eg: --inputModule "@firebase/functions-exp" "firebase/auth-exp" + * + * -- inputDtsFile (optional) : adhoc support. Specify a path to dts file. Must enable -- inputBundleFile if this flag is specified. + * + * -- inputBundleFile (optional): adhoc support. Specify a path to bundle file. Must enable -- inputDtsFile if this flag is specified. + * + * --output (required): output directory or file where reports will be generated. + * specify a directory if module(s) are analyzed + * specify a file path if ad hoc analysis is to be performed + * + */ +interface PackageAnalysisOptions { + inputModule: string[]; + inputDtsFile: string; + inputBundleFile: string; + output: string; +} +/** + * Entry Point of the Tool. + * The function first checks if it's an adhoc run (by checking whether --inputDtsFile and --inputBundle are both enabled) + * The function then checks whether --inputModule flag is specified; Run analysis on all modules if not, run analysis on selected modules if enabled. + * Throw INVALID_FLAG_COMBINATION error if neither case fulfill. + */ +export async function analyzePackageSize( + argv: PackageAnalysisOptions +): Promise { + // check if it's an adhoc run + // adhoc run report can only be redirected to files + if (argv.inputDtsFile && argv.inputBundleFile && argv.output) { + const jsonReport: Report = await generateReport( + 'adhoc', + argv.inputDtsFile, + argv.inputBundleFile + ); + writeReportToFile(jsonReport, resolve(argv.output)); + } else if (!argv.inputDtsFile && !argv.inputBundleFile) { + // retrieve All Module Names + let allModulesLocation = await mapWorkspaceToPackages([ + `${projectRoot}/packages/*` + ]); + allModulesLocation = allModulesLocation.filter(path => { + const pkgJsonPath = `${path}/package.json`; + if (!fs.existsSync(pkgJsonPath)) { + return false; + } + + const json = JSON.parse( + fs.readFileSync(`${path}/package.json`, { encoding: 'utf-8' }) + ); + return ( + json.name.startsWith('@firebase') && + !json.name.includes('-compat') && + !json.name.includes('-types') + ); + }); + if (argv.inputModule) { + allModulesLocation = allModulesLocation.filter(path => { + const json = JSON.parse( + fs.readFileSync(`${path}/package.json`, { encoding: 'utf-8' }) + ); + return argv.inputModule.includes(json.name); + }); + } + let writeFiles: boolean = false; + if (argv.output) { + writeFiles = true; + } + + const reports: Report[] = await generateReportForModules( + allModulesLocation + ); + if (writeFiles) { + for (const report of reports) { + writeReportToDirectory( + report, + `${basename(report.name)}-dependencies.json`, + resolve(argv.output) + ); + } + } + } else { + throw new Error(ErrorCode.INVALID_FLAG_COMBINATION); + } +} + +function mapWorkspaceToPackages(workspaces: string[]): Promise { + return Promise.all>( + workspaces.map>( + workspace => + new Promise(resolve => { + glob(workspace, (err, paths) => { + if (err) { + throw err; + } + resolve(paths); + }); + }) + ) + ).then(paths => paths.reduce((arr, val) => arr.concat(val), [])); +} diff --git a/repo-scripts/size-analysis/package.json b/repo-scripts/size-analysis/package.json new file mode 100644 index 00000000000..5ed11ae4c9b --- /dev/null +++ b/repo-scripts/size-analysis/package.json @@ -0,0 +1,61 @@ +{ + "name": "firebase-size-analysis", + "version": "0.1.0", + "private": true, + "description": "A template package for new firebase packages", + "author": "Firebase (https://firebase.google.com/)", + "main": "dist/index.cjs.js", + "esm2017": "dist/index.esm2017.js", + "files": [ + "dist" + ], + "scripts": { + "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "test": "run-p --npm-path npm lint test:node", + "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:node", + "pretest:node": "tsc -p test/test-inputs && rollup -c", + "test:node": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha **/*.test.ts --config ../../config/mocharc.node.js --timeout 60000", + "build": "rollup -c" + }, + "dependencies": { + "rollup": "2.79.1", + "@rollup/plugin-commonjs": "21.1.0", + "@rollup/plugin-json": "4.1.0", + "@rollup/plugin-node-resolve": "13.3.0", + "rollup-plugin-replace": "2.2.0", + "rollup-plugin-typescript2": "0.31.2", + "@rollup/plugin-virtual": "2.1.0", + "webpack": "5.76.0", + "@types/webpack": "5.28.5", + "webpack-virtual-modules": "0.5.0", + "child-process-promise": "2.2.1", + "memfs": "3.5.3", + "tmp": "0.2.1", + "typescript": "4.7.4", + "terser": "5.16.1", + "yargs": "17.7.2", + "@firebase/util": "1.9.7", + "gzip-size": "6.0.0", + "glob": "7.2.3" + }, + "license": "Apache-2.0", + "devDependencies": { + "@firebase/logger": "0.4.2", + "@firebase/app": "0.10.7" + }, + "repository": { + "directory": "repo-scripts/size-analysis", + "type": "git", + "url": "git+https://github.com/firebase/firebase-js-sdk.git" + }, + "bugs": { + "url": "https://github.com/firebase/firebase-js-sdk/issues" + }, + "nyc": { + "extension": [ + ".ts" + ], + "reportDir": "./coverage/node" + } +} diff --git a/repo-scripts/size-analysis/rollup.config.js b/repo-scripts/size-analysis/rollup.config.js new file mode 100644 index 00000000000..505596e3d2e --- /dev/null +++ b/repo-scripts/size-analysis/rollup.config.js @@ -0,0 +1,83 @@ +/** + * @license + * Copyright 2019 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 typescriptPlugin from 'rollup-plugin-typescript2'; +import typescript from 'typescript'; +import pkg from './package.json'; +import json from '@rollup/plugin-json'; + +const deps = Object.keys( + Object.assign({}, pkg.peerDependencies, pkg.dependencies) +); + +const nodeInternals = ['fs', 'path', 'util']; + +export default [ + { + input: 'test/test-inputs/subsetExports.ts', + output: [ + { + file: 'test/test-inputs/dist/subsetExportsBundle.js', + format: 'es', + sourcemap: false + } + ], + plugins: [ + typescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + target: 'es2017', + module: 'es2015' + } + } + }), + json({ + preferConst: true + }) + ], + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + }, + { + input: 'cli.ts', + output: [ + { + file: 'dist/cli.js', + format: 'cjs', + sourcemap: false + } + ], + plugins: [ + typescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + target: 'es2017', + module: 'es2015' + } + } + }), + json({ + preferConst: true + }) + ], + external: id => + [...deps, ...nodeInternals].some( + dep => id === dep || id.startsWith(`${dep}/`) + ) + } +]; diff --git a/repo-scripts/size-analysis/test/size-analysis.test.ts b/repo-scripts/size-analysis/test/size-analysis.test.ts new file mode 100644 index 00000000000..d3d32ce1fae --- /dev/null +++ b/repo-scripts/size-analysis/test/size-analysis.test.ts @@ -0,0 +1,489 @@ +/** + * @license + * Copyright 2017 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 { expect } from 'chai'; + +import { + MemberList, + dedup, + mapSymbolToType, + replaceAll, + writeReportToFile, + ErrorCode, + writeReportToDirectory, + extractExternalDependencies, + Report, + extractExports, + extractAllTopLevelSymbols +} from '../analysis-helper'; + +import { + getTestModuleDtsFilePath, + getAssortedImportsJsFilePath, + getSubsetExportsBundleFilePath +} from './utils'; +import * as fs from 'fs'; +import { resolve } from 'path'; + +describe('extractExports', () => { + let testModuleDtsFile: string; + let extractedDeclarations: MemberList; + before(() => { + const start = Date.now(); + testModuleDtsFile = getTestModuleDtsFilePath(); + extractedDeclarations = extractExports(testModuleDtsFile); + console.log('extractExports took ', Date.now() - start); + }); + // export {tar as tarr, tar1 as tarr1} from '..' + it('test export rename', () => { + expect(extractedDeclarations.functions).to.include.members([ + 'tarr', + 'tarr1' + ]); + }); + // function foo() { } + // export { foo as foo2 }; + it('test declare then export', () => { + expect(extractedDeclarations.functions).to.include.members(['foo2']); + expect(extractedDeclarations.classes).to.include.members(['Foo1']); + }); + + it('test basic variable extractions', () => { + expect(extractedDeclarations.variables).to.include.members([ + 'basicVarDeclarationExport', + 'basicVarStatementExport', + 'reExportVarStatementExport' + ]); + }); + it('test re-exported variable extractions from same module - named re-exports', () => { + expect(extractedDeclarations.variables).to.include.members([ + 'basicVarDeclarationExportFar', + 'basicVarStatementExportFar', + 'reExportVarStatementExportFar' + ]); + }); + it('test re-exported variable extractions from same module - * re-exports', () => { + expect(extractedDeclarations.variables).to.include.members([ + 'basicVarDeclarationExportBar', + 'basicVarStatementExportBar', + 'reExportVarStatementExportBar' + ]); + }); + + it('test basic function extractions', () => { + expect(extractedDeclarations.functions).to.include.members([ + 'basicFuncExportNoDependencies', + 'basicFuncExportVarDependencies', + 'basicFuncExportFuncDependencies', + 'basicFuncExportEnumDependencies', + 'basicFuncExternalDependencies' + ]); + }); + it('test basic function de-duplication ', () => { + expect(extractedDeclarations.functions).include.members([ + 'basicUniqueFunc' + ]); + + expect( + extractedDeclarations.functions.filter( + each => each.localeCompare('basicUniqueFunc') === 0 + ).length + ).to.equal(1); + }); + + it('test re-exported function extractions from same module - named re-exports', () => { + expect(extractedDeclarations.functions).to.include.members([ + 'basicFuncExportNoDependenciesFar', + 'basicFuncExportVarDependenciesFar', + 'basicFuncExportFuncDependenciesFar', + 'basicFuncExportEnumDependenciesFar', + 'basicFuncExternalDependenciesFar' + ]); + }); + + it('test re-exported function extractions from same module - * re-exports', () => { + expect(extractedDeclarations.functions).to.include.members([ + 'basicFuncExportNoDependenciesBar', + 'basicFuncExportVarDependenciesBar', + 'basicFuncExportFuncDependenciesBar', + 'basicFuncExportEnumDependenciesBar', + 'basicFuncExternalDependenciesBar' + ]); + }); + + it('test re-exported function de-duplication from same module ', () => { + expect(extractedDeclarations.functions).include.members([ + 'basicUniqueFuncFar' + ]); + + expect( + extractedDeclarations.functions.filter( + each => each.localeCompare('basicUniqueFuncFar') === 0 + ).length + ).to.equal(1); + }); + + it('test basic class extractions', () => { + expect(extractedDeclarations.classes).to.include.members([ + 'BasicClassExport' + ]); + }); + + it('test re-exported class extractions from same module - named re-exports', () => { + expect(extractedDeclarations.classes).to.include.members([ + 'BasicClassExportFar' + ]); + }); + it('test re-exported class extractions from same module - * re-exports', () => { + expect(extractedDeclarations.classes).to.include.members([ + 'BasicClassExportBar' + ]); + }); + + it('test basic enum extractions', () => { + expect(extractedDeclarations.enums).to.include.members(['BasicEnumExport']); + }); + + it('test re-exported enum extractions from same module - named re-exports', () => { + expect(extractedDeclarations.enums).to.include.members([ + 'BasicEnumExportFar' + ]); + }); + it('test re-exported enum extractions from same module - * re-exports', () => { + expect(extractedDeclarations.enums).to.include.members([ + 'BasicEnumExportBar' + ]); + }); + // import {LogLevel as LogLevel1} from '@firebase/logger'; + // export {LogLevel1 as LogLevel2}; + it('test renamed import then renamed export', () => { + expect(extractedDeclarations.enums).to.include.members(['LogLevel2']); + }); + + //import { Logger } from "@firebase/logger"; + // export { Logger as Logger1 }; + it('test import then renamed export', () => { + expect(extractedDeclarations.classes).to.include.members(['Logger1']); + }); + + //import { setLogLevel } from "@firebase/logger"; + // export { setLogLevel }; + it('test import then export', () => { + expect(extractedDeclarations.functions).to.include.members(['setLogLevel']); + }); + + // import * as fs from 'fs' + // export { fs as fs1 }; + it('test namespace export', () => { + expect(extractedDeclarations.unknown).to.include.members(['fs1']); + }); +}); + +describe('extractAllTopLevelSymbols', () => { + let subsetExportsBundleFile: string; + let extractedDeclarations: MemberList; + before(() => { + const start = Date.now(); + subsetExportsBundleFile = getSubsetExportsBundleFilePath(); + extractedDeclarations = extractAllTopLevelSymbols(subsetExportsBundleFile); + console.log( + 'extractDeclarations on js bundle file took ', + Date.now() - start + ); + }); + it('test variable extractions', () => { + const variablesArray = ['aVar']; + variablesArray.sort(); + expect(extractedDeclarations.variables).to.include.members(variablesArray); + }); + + it('test functions extractions', () => { + const functionsArray = [ + 'tar', + 'tar1', + 'basicFuncExportEnumDependencies', + 'd1', + 'd2', + 'd3', + 'basicFuncExportFuncDependenciesBar' + ]; + functionsArray.sort(); + expect(extractedDeclarations.functions).to.have.members(functionsArray); + }); + + it('test enums extractions', () => { + const enumsArray = [ + 'BasicEnumExport', + 'BasicEnumExportBar', + 'BasicEnumExportFar' + ]; + enumsArray.sort(); + expect(extractedDeclarations.variables).to.include.members(enumsArray); + }); + + it('test classes extractions', () => { + const classesArray = ['BasicClassExport']; + classesArray.sort(); + expect(extractedDeclarations.classes).to.have.members(classesArray); + }); +}); + +describe('test dedup helper function', () => { + it('test dedup with non-empty entries', () => { + let memberList: MemberList = { + functions: ['aFunc', 'aFunc', 'bFunc', 'cFunc'], + classes: ['aClass', 'bClass', 'aClass', 'cClass'], + variables: ['aVar', 'bVar', 'cVar', 'aVar'], + enums: ['aEnum', 'bEnum', 'cEnum', 'dEnum'], + unknown: [] + }; + memberList = dedup(memberList); + + expect(memberList.functions).to.have.length(3); + expect(memberList.classes).to.have.length(3); + expect(memberList.variables).to.have.length(3); + expect(memberList.enums).to.have.length(4); + expect( + memberList.functions.filter(each => each.localeCompare('aFunc') === 0) + .length + ).to.equal(1); + expect( + memberList.classes.filter(each => each.localeCompare('aClass') === 0) + .length + ).to.equal(1); + expect( + memberList.variables.filter(each => each.localeCompare('aVar') === 0) + .length + ).to.equal(1); + expect( + memberList.enums.filter(each => each.localeCompare('aEnum') === 0).length + ).to.equal(1); + }); + + it('test dedup with empty entries', () => { + let memberList: MemberList = { + functions: [], + classes: [], + variables: ['aVar', 'bVar', 'cVar', 'aVar'], + enums: [], + unknown: [] + }; + memberList = dedup(memberList); + expect(memberList.functions).to.have.length(0); + expect(memberList.classes).to.have.length(0); + expect(memberList.enums).to.have.length(0); + expect(memberList.variables).to.have.length(3); + + expect( + memberList.variables.filter(each => each.localeCompare('aVar') === 0) + .length + ).to.equal(1); + }); +}); + +describe('test replaceAll helper function', () => { + it('test replaceAll with multiple occurrences of an element', () => { + const memberList: MemberList = { + functions: ['aFunc', 'aFunc', 'bFunc', 'cFunc'], + classes: ['aClass', 'bClass', 'aClass', 'cClass'], + variables: ['aVar', 'bVar', 'cVar', 'aVar'], + enums: ['aEnum', 'bEnum', 'cEnum', 'dEnum'], + unknown: [] + }; + const original: string = 'aFunc'; + const replaceTo: string = 'replacedFunc'; + replaceAll(memberList, original, replaceTo); + expect(memberList.functions).to.not.include.members([original]); + expect(memberList.functions).to.include.members([replaceTo]); + expect(memberList.functions).to.have.length(4); + expect( + memberList.functions.filter(each => each.localeCompare(original) === 0) + .length + ).to.equal(0); + expect( + memberList.functions.filter(each => each.localeCompare(replaceTo) === 0) + .length + ).to.equal(2); + }); + + it('test replaceAll with single occurrence of an element', () => { + const memberList: MemberList = { + functions: ['aFunc', 'aFunc', 'bFunc', 'cFunc'], + classes: ['aClass', 'bClass', 'aClass', 'cClass'], + variables: ['aVar', 'bVar', 'cVar', 'aVar'], + enums: ['aEnum', 'bEnum', 'cEnum', 'dEnum'], + unknown: [] + }; + const replaceTo: string = 'replacedClass'; + const original: string = 'bClass'; + replaceAll(memberList, original, replaceTo); + expect(memberList.classes).to.not.include.members([original]); + expect(memberList.classes).to.include.members([replaceTo]); + expect(memberList.classes).to.have.length(4); + expect( + memberList.classes.filter(each => each.localeCompare(original) === 0) + .length + ).to.equal(0); + expect( + memberList.classes.filter(each => each.localeCompare(replaceTo) === 0) + .length + ).to.equal(1); + }); + + it('test replaceAll with zero occurrence of an element', () => { + const memberList: MemberList = { + functions: ['aFunc', 'aFunc', 'bFunc', 'cFunc'], + classes: ['aClass', 'bClass', 'aClass', 'cClass'], + variables: ['aVar', 'bVar', 'cVar', 'aVar'], + enums: ['aEnum', 'bEnum', 'cEnum', 'dEnum'], + unknown: [] + }; + const replaceTo: string = 'replacedEnum'; + const original: string = 'eEnum'; + replaceAll(memberList, original, replaceTo); + expect(memberList.enums).to.not.include.members([original, replaceTo]); + expect(memberList.enums).to.have.length(4); + expect( + memberList.enums.filter(each => each.localeCompare(original) === 0).length + ).to.equal(0); + expect( + memberList.enums.filter(each => each.localeCompare(replaceTo) === 0) + .length + ).to.equal(0); + }); +}); + +describe('test mapSymbolToType helper function', () => { + it('test if function correctly categorizes symbols that are misplaced', () => { + let memberList: MemberList = { + functions: ['aVar', 'bFunc', 'cFunc'], + classes: ['bClass', 'cClass'], + variables: ['aClass', 'bVar', 'cVar', 'aEnum'], + enums: ['bEnum', 'cEnum', 'dEnum', 'aFunc'], + unknown: [] + }; + + const map: Map = new Map([ + ['aFunc', 'functions'], + ['bFunc', 'functions'], + ['aClass', 'classes'], + ['bClass', 'classes'], + ['aVar', 'variables'], + ['bVar', 'variables'], + ['aEnum', 'enums'] + ]); + + memberList = mapSymbolToType(map, memberList); + + expect(memberList.functions).to.have.members(['aFunc', 'bFunc', 'cFunc']); + expect(memberList.functions).to.not.include.members(['aVar']); + expect(memberList.classes).to.have.members(['aClass', 'bClass', 'cClass']); + expect(memberList.variables).to.not.include.members(['aClass', 'aEnum']); + expect(memberList.variables).to.have.members(['aVar', 'bVar', 'cVar']); + expect(memberList.enums).to.have.members([ + 'aEnum', + 'bEnum', + 'cEnum', + 'dEnum' + ]); + expect(memberList.enums).to.not.include.members(['aFunc']); + + expect(memberList.functions).to.have.length(3); + expect(memberList.classes).to.have.length(3); + expect(memberList.variables).to.have.length(3); + expect(memberList.enums).to.have.length(4); + }); +}); + +describe('test writeReportToFile helper function', () => { + let fileContent: Report; + + before(() => { + fileContent = { + name: 'name', + symbols: [] + }; + }); + it('should throw error when given path exists and points to directory', () => { + const aDir = resolve('./a-dir/a-sub-dir'); + fs.mkdirSync(aDir, { recursive: true }); + expect(() => writeReportToFile(fileContent, aDir)).to.throw( + ErrorCode.OUTPUT_FILE_REQUIRED + ); + }); + + it('should not throw error when given path does not pre-exist', () => { + const aPathToFile = resolve('./a-dir/a-sub-dir/a-file'); + expect(() => writeReportToFile(fileContent, aPathToFile)).to.not.throw(); + fs.unlinkSync(aPathToFile); + }); + after(() => { + fs.rmdirSync('a-dir/a-sub-dir'); + fs.rmdirSync('a-dir', { recursive: true }); + }); +}); + +describe('test writeReportToDirectory helper function', () => { + let fileContent: Report; + + before(() => { + fileContent = { + name: 'name', + symbols: [] + }; + }); + it('should throw error when given path exists and points to a file', () => { + const aDir = resolve('./a-dir/a-sub-dir'); + fs.mkdirSync(aDir, { recursive: true }); + const aFile = `a-file`; + const aPathToFile = `${aDir}/${aFile}`; + fs.writeFileSync(aPathToFile, JSON.stringify(fileContent)); + expect(() => + writeReportToDirectory(fileContent, aFile, aPathToFile) + ).to.throw(ErrorCode.OUTPUT_DIRECTORY_REQUIRED); + }); + + it('should not throw error when given path does not pre-exist', () => { + const aDir = resolve('./a-dir/a-sub-dir'); + const aFile = `a-file`; + expect(() => + writeReportToDirectory(fileContent, aFile, aDir) + ).to.not.throw(); + }); + after(() => { + fs.unlinkSync(`${resolve('./a-dir/a-sub-dir')}/a-file`); + fs.rmdirSync('a-dir/a-sub-dir'); + fs.rmdirSync('a-dir', { recursive: true }); + }); +}); + +describe('test extractExternalDependencies helper function', () => { + it('should correctly extract all symbols listed in import statements', () => { + const assortedImports: string = getAssortedImportsJsFilePath(); + const externals: { [key: string]: string[] } = + extractExternalDependencies(assortedImports); + + expect(externals['./bar']).to.have.members([ + 'basicFuncExternalDependenciesBar', + 'basicFuncExportEnumDependenciesBar', + 'BasicClassExportBar' // extract original name if renamed + ]); + expect(externals['@firebase/logger']).to.be.undefined; + expect(externals['fs']).to.have.members(['*']); // namespace export + // expect(externals['@firebase/app']).to.have.members(['default export']); // default export + }); +}); diff --git a/repo-scripts/size-analysis/test/test-inputs/assortedImports.ts b/repo-scripts/size-analysis/test/test-inputs/assortedImports.ts new file mode 100644 index 00000000000..390936c22c7 --- /dev/null +++ b/repo-scripts/size-analysis/test/test-inputs/assortedImports.ts @@ -0,0 +1,32 @@ +/** + * @license + * Copyright 2020 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 '@firebase/logger'; +import * as fs from 'fs'; +import { + basicFuncExportEnumDependenciesBar, + basicFuncExternalDependenciesBar, + BasicClassExportBar as BasicClassExportBarRenamed +} from './bar'; +// import defaultExport from '@firebase/app'; +console.log( + fs, + basicFuncExportEnumDependenciesBar, + basicFuncExternalDependenciesBar, + BasicClassExportBarRenamed + // defaultExport +); diff --git a/repo-scripts/size-analysis/test/test-inputs/bar.ts b/repo-scripts/size-analysis/test/test-inputs/bar.ts new file mode 100644 index 00000000000..534f820d685 --- /dev/null +++ b/repo-scripts/size-analysis/test/test-inputs/bar.ts @@ -0,0 +1,60 @@ +/** + * @license + * Copyright 2020 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 { LogLevel } from '@firebase/logger'; + +export let basicVarDeclarationExportBar: string; +export const basicVarStatementExportBar = 'basicVarStatementExportBar'; +export const reExportVarStatementExportBar = LogLevel; + +export enum BasicEnumExportBar { + DEBUG = 0, + VERBOSE = 1, + INFO = 2, + WARN = 3, + ERROR = 4, + SILENT = 5 +} + +export class BasicClassExportBar {} + +export function basicFuncExportNoDependenciesBar(): string { + return 'basicFuncExportNoDependenciesBar'; +} +export function basicFuncExportVarDependenciesBar(): string { + return basicVarStatementExportBar; +} + +function d1(): string { + return 'd1'; +} +function d2(): string { + return 'd2'; +} +function d3(): string { + return d1() + d2(); +} +export function basicFuncExportFuncDependenciesBar(): string { + return d3(); +} + +export function basicFuncExportEnumDependenciesBar(): BasicEnumExportBar { + return BasicEnumExportBar.DEBUG; +} + +export function basicFuncExternalDependenciesBar(): LogLevel { + return LogLevel.ERROR; +} diff --git a/repo-scripts/size-analysis/test/test-inputs/far.ts b/repo-scripts/size-analysis/test/test-inputs/far.ts new file mode 100644 index 00000000000..7339c51e4e4 --- /dev/null +++ b/repo-scripts/size-analysis/test/test-inputs/far.ts @@ -0,0 +1,77 @@ +/** + * @license + * Copyright 2020 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 { LogLevel } from '@firebase/logger'; + +export let basicVarDeclarationExportFar: string; +export const basicVarStatementExportFar = 'basicVarStatementExportFar'; +export const reExportVarStatementExportFar = name; + +export enum BasicEnumExportFar { + DEBUG = 0, + VERBOSE = 1, + INFO = 2, + WARN = 3, + ERROR = 4, + SILENT = 5 +} + +export class BasicClassExportFar {} + +export function basicFuncExportNoDependenciesFar(): string { + return 'basicFuncExportNoDependenciesFar'; +} +export function basicFuncExportVarDependenciesFar(): string { + return basicVarStatementExportFar; +} + +function d1(): string { + return 'd1'; +} +function d2(): string { + return 'd2'; +} +function d3(): string { + return d1() + d2(); +} +export function basicFuncExportFuncDependenciesFar(): string { + return d3(); +} + +export function basicFuncExportEnumDependenciesFar(): BasicEnumExportFar { + return BasicEnumExportFar.DEBUG; +} + +export function basicFuncExternalDependenciesFar(): LogLevel { + return LogLevel.ERROR; +} + +export function basicUniqueFuncFar( + x: Array<{ suit: string; card: number }> +): number; +export function basicUniqueFuncFar(x: number): { suit: string; card: number }; +export function basicUniqueFuncFar( + x: number | Array<{ suit: string; card: number }> +): number | { suit: string; card: number } { + if (typeof x === 'object') { + const pickedCard = Math.floor(Math.random() * x.length); + return pickedCard; + } + // Otherwise just let them pick the card + else { + return { suit: 'a', card: x % 13 }; + } +} diff --git a/repo-scripts/size-analysis/test/test-inputs/index.ts b/repo-scripts/size-analysis/test/test-inputs/index.ts new file mode 100644 index 00000000000..f071a54fbd0 --- /dev/null +++ b/repo-scripts/size-analysis/test/test-inputs/index.ts @@ -0,0 +1,117 @@ +/** + * @license + * Copyright 2020 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 { LogLevel as LogLevel1, Logger, setLogLevel } from '@firebase/logger'; +import * as fs from 'fs'; +export { fs as fs1 }; +import * as tmp from 'tmp'; +export const aVar: tmp.FileOptions = {}; +// wildcard export +export * from './bar'; +// named export +export { + BasicEnumExportFar, + BasicClassExportFar, + basicFuncExportNoDependenciesFar, + basicFuncExportVarDependenciesFar, + basicFuncExportFuncDependenciesFar, + basicFuncExportEnumDependenciesFar, + basicFuncExternalDependenciesFar, + basicUniqueFuncFar, + basicVarDeclarationExportFar, + basicVarStatementExportFar, + reExportVarStatementExportFar +} from './far'; +export let basicVarDeclarationExport: string; +export const basicVarStatementExport = 'basicVarStatementExport'; +export const reExportVarStatementExport = LogLevel1; + +export enum BasicEnumExport { + DEBUG = 0, + VERBOSE = 1, + INFO = 2, + WARN = 3, + ERROR = 4, + SILENT = 5 +} + +export class BasicClassExport {} + +export function basicFuncExportNoDependencies(): string { + return 'basicFuncExportNoDependencies'; +} +export function basicFuncExportVarDependencies(): string { + return basicVarStatementExport; +} + +function d1(): string { + return 'd1'; +} +function d2(): string { + return 'd2'; +} +function d3(): string { + return d1() + d2(); +} +export function basicFuncExportFuncDependencies(): string { + return d3(); +} + +export function basicFuncExportEnumDependencies(): BasicEnumExport { + return BasicEnumExport.DEBUG; +} + +export function basicFuncExternalDependencies(): LogLevel1 { + return LogLevel1.WARN; +} + +export function basicUniqueFunc( + x: Array<{ suit: string; card: number }> +): number; +export function basicUniqueFunc(x: number): { suit: string; card: number }; +export function basicUniqueFunc( + x: number | Array<{ suit: string; card: number }> +): number | object { + if (typeof x === 'object') { + const pickedCard = Math.floor(Math.random() * x.length); + return pickedCard; + } + // Otherwise just let them pick the card + else { + return { suit: 'a', card: x % 13 }; + } +} + +const apps: Map = new Map(); +export { apps }; + +class Foo {} +export { Foo as Foo1 }; + +function foo(x: string): string { + return x; +} +export { foo as foo2 }; + +export {}; + +export { tar as tarr, tar1 as tarr1 } from './tar'; +// re-export from firebase external module +export { LogLevel1 as LogLevel2 }; + +export { Logger as Logger1 }; + +export { setLogLevel }; diff --git a/repo-scripts/size-analysis/test/test-inputs/subsetExports.ts b/repo-scripts/size-analysis/test/test-inputs/subsetExports.ts new file mode 100644 index 00000000000..dac88c80ea2 --- /dev/null +++ b/repo-scripts/size-analysis/test/test-inputs/subsetExports.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2020 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. + */ +export { + aVar, + LogLevel2, + tarr1, + basicFuncExportEnumDependencies, + basicFuncExportFuncDependenciesBar, + BasicClassExport +} from './index'; diff --git a/repo-scripts/size-analysis/test/test-inputs/tar.ts b/repo-scripts/size-analysis/test/test-inputs/tar.ts new file mode 100644 index 00000000000..d9660e44f3e --- /dev/null +++ b/repo-scripts/size-analysis/test/test-inputs/tar.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2020 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. + */ + +export function tar(name: string): string { + return name; +} + +export function tar1(name: string): string { + return tar(name); +} diff --git a/repo-scripts/size-analysis/test/test-inputs/tsconfig.json b/repo-scripts/size-analysis/test/test-inputs/tsconfig.json new file mode 100644 index 00000000000..82f14ae86e1 --- /dev/null +++ b/repo-scripts/size-analysis/test/test-inputs/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "outDir": "dist", + "importHelpers": true, + "module": "es6", + "moduleResolution": "node", + "resolveJsonModule": true, + "target": "es6", + "declaration": true + }, + "exclude": [ + "dist/**/*" + ] + } \ No newline at end of file diff --git a/repo-scripts/size-analysis/test/utils.ts b/repo-scripts/size-analysis/test/utils.ts new file mode 100644 index 00000000000..8af8a36dd58 --- /dev/null +++ b/repo-scripts/size-analysis/test/utils.ts @@ -0,0 +1,26 @@ +/** + * @license + * Copyright 2020 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. + */ + +export function getTestModuleDtsFilePath(): string { + return `${__dirname}/test-inputs/dist/index.d.ts`; +} +export function getSubsetExportsBundleFilePath(): string { + return `${__dirname}/test-inputs/dist/subsetExportsBundle.js`; +} +export function getAssortedImportsJsFilePath(): string { + return `${__dirname}/test-inputs/dist/assortedImports.js`; +} diff --git a/repo-scripts/size-analysis/tsconfig.json b/repo-scripts/size-analysis/tsconfig.json new file mode 100644 index 00000000000..326e95a0fa6 --- /dev/null +++ b/repo-scripts/size-analysis/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "outDir": "dist", + "importHelpers": true, + "module": "commonjs", + "moduleResolution": "node", + "resolveJsonModule": true, + "target": "es2017", + "esModuleInterop": true, + "declaration": true, + "strict": true + }, + "exclude": [ + "dist/**/*" + ] +} \ No newline at end of file diff --git a/repo-scripts/size-analysis/util.ts b/repo-scripts/size-analysis/util.ts new file mode 100644 index 00000000000..bd56e98a6e7 --- /dev/null +++ b/repo-scripts/size-analysis/util.ts @@ -0,0 +1,34 @@ +/** + * @license + * Copyright 2020 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 calculateGzipSize from 'gzip-size'; +import { dirname, resolve } from 'path'; + +interface ContentSize { + size: number; + gzipSize: number; +} + +export function calculateContentSize(content: string): ContentSize { + const size = Buffer.byteLength(content, 'utf-8'); + const gzipSize = calculateGzipSize.sync(content); + return { + size, + gzipSize + }; +} + +export const projectRoot = dirname(resolve(__dirname, '../../package.json')); diff --git a/tools/config.js b/tools/config.js new file mode 100644 index 00000000000..7de821095e5 --- /dev/null +++ b/tools/config.js @@ -0,0 +1,103 @@ +/** + * @license + * Copyright 2017 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. + */ + +const { argv } = require('yargs'); +const { exec } = require('child-process-promise'); +const { homedir } = require('os'); +const firebaseTools = require('firebase-tools'); +const inquirer = require('inquirer'); +const fs = require('mz/fs'); +const path = require('path'); + +// Command Line Arguments +const userToken = argv.token; +const projectId = argv.projectId; + +let cachedToken; + +try { + const config = require(path.resolve( + homedir(), + '.config/configstore/firebase-tools.json' + )); + cachedToken = config.tokens.refresh_token; +} catch (err) {} + +Promise.resolve(userToken || cachedToken) + // Log in to firebase-tools + .then(async userToken => { + if (userToken) return userToken; + const { + tokens: { refresh_token: freshToken } + } = await firebaseTools.login.ci(); + return freshToken; + }) + // Capture the firebase test project + .then(async token => { + const project = await (async () => { + if (projectId) return projectId; + + const projects = await firebaseTools.projects.list({ token }); + const response = await inquirer.prompt([ + { + type: 'list', + name: 'projectId', + message: 'Which project would you like to use to test?', + choices: projects + .sort(project => + project.name.toLowerCase().includes('jscore') ? -1 : 1 + ) + .map(project => ({ + name: `${project.displayName} (${project.projectId})`, + value: project + })) + } + ]); + + return response.projectId.projectId; + })(); + + // Write config to top-level config directory + await firebaseTools.apps + .sdkconfig('web', undefined, { project, token }) + .then(config => + fs.writeFile( + path.resolve(__dirname, '../config/project.json'), + JSON.stringify(config.sdkConfig, null, 2) + ) + ); + + // npm install the dependencies for functions + await exec('npm install', { + cwd: path.resolve(__dirname, '../config/functions') + }); + + // Deploy database rules + await firebaseTools.deploy({ + project, + token, + cwd: path.resolve(__dirname, '../config') + }); + }) + .then(() => { + console.log('Success! Exiting...'); + process.exit(); + }) + .catch(err => { + console.error(err); + process.exit(1); + }); diff --git a/tools/pretest.js b/tools/pretest.js new file mode 100644 index 00000000000..298455f43f3 --- /dev/null +++ b/tools/pretest.js @@ -0,0 +1,98 @@ +/** + * @license + * Copyright 2017 Google Inc. + * + * 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. + */ + +const { resolve } = require('path'); +const { spawn } = require('child-process-promise'); +const chalk = require('chalk'); +const fs = require('mz/fs'); +const semver = require('semver'); + +// CONSTANTS +const root = resolve(__dirname, '..'); +const REQ_YARN_VERSION = '1.0.0'; + +/** + * Potential Problem #1: + * Developer has not yet specified a valid test project + */ +function checkTestConfigExists() { + if (!fs.existsSync(resolve(root, 'config/project.json'))) { + throw chalk` +{red You have not yet specified a Firebase project to use for testing.} + +To create a test project, please visit {underline https://console.firebase.google.com/}. +After doing so, or if you already have a test project, please run the following command +at the root of this package: + +$ npm run test:setup +`; + } +} + +/** + * Potential Problem #2: + * Developer is not using a valid version of `yarn` + */ +async function validateCompatibleYarnVersion() { + const promise = spawn('yarn', ['-v']); + const { childProcess } = promise; + let version = ''; + + childProcess.stdout.on('data', data => { + version += data.toString(); + }); + + await promise; + + if (semver.lt(version, REQ_YARN_VERSION)) { + throw chalk` +{red Your globally installed version of yarn is not compatible} + +We use yarn workspaces to manage this project and your version of yarn is not +compatible. Please visit {underline https://yarnpkg.com/lang/en/docs/install/} +for upgrade/installation instructions +`; + } +} + +/** + * Potential Problem #3: + * Developers yarn setup was misconfigured + */ +async function validateYarnInstall() { + try { + await spawn('yarn', ['check', '--integrity']); + } catch (err) { + throw chalk` +{red Your yarn workspace didn't pass the integrity check} + +To fix the integrity of your test environment please run the following at the +root of this package: + +$ yarn install +`; + } +} + +Promise.resolve() + .then(() => checkTestConfigExists()) + .then(() => validateCompatibleYarnVersion()) + .then(() => validateYarnInstall()) + .catch(err => { + console.error(err); + return process.exit(1); + }); diff --git a/tools/repl.js b/tools/repl.js new file mode 100644 index 00000000000..f3c5266810e --- /dev/null +++ b/tools/repl.js @@ -0,0 +1,61 @@ +/** + * @license + * Copyright 2017 Google Inc. + * + * 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. + */ + +const repl = require('repl'); +const firebase = require('../packages/firebase'); + +function clearTerminal() { + return process.stdout.write('\033c'); +} + +function giveContext() { + console.log(`Welcome to the firebase REPL! + +The current firebase build is has been assigned to the \`firebase\` variable. + +Utility Commands Available: + +.clear - Resets the current REPL +.exit - exits the REPL +.help - prints this message +`); +} + +function addFirebaseToContext(repl) { + Object.defineProperty(repl.context, 'firebase', { + configurable: false, + enumerable: true, + value: firebase + }); +} + +clearTerminal(); +giveContext(); + +const replInst = repl.start('> '); +replInst.on('reset', () => { + clearTerminal(); + giveContext(); + addFirebaseToContext(replInst); +}); + +addFirebaseToContext(replInst); + +replInst.defineCommand('help', () => { + giveContext(); + replInst.displayPrompt(); +}); From 74ad85b481106f111289afb03d3d84db95e907e0 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Tue, 3 Sep 2024 20:14:27 -0700 Subject: [PATCH 36/74] Fixed appcheck implementation --- common/api-review/data-connect.api.md | 16 ++++++++-- packages/data-connect/src/api/index.ts | 1 + .../src/network/transport/rest.ts | 32 +++++++++---------- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/common/api-review/data-connect.api.md b/common/api-review/data-connect.api.md index 41f9cb4bb60..fce3c921e2e 100644 --- a/common/api-review/data-connect.api.md +++ b/common/api-review/data-connect.api.md @@ -4,6 +4,9 @@ ```ts +import { AppCheckInternalComponentName } from '@firebase/app-check-interop-types'; +import { AppCheckTokenListener } from '@firebase/app-check-interop-types'; +import { AppCheckTokenResult } from '@firebase/app-check-interop-types'; import { FirebaseApp } from '@firebase/app'; import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; import { FirebaseAuthTokenData } from '@firebase/auth-interop-types'; @@ -12,6 +15,15 @@ import { FirebaseOptions } from '@firebase/app-types'; import { LogLevelString } from '@firebase/logger'; import { Provider } from '@firebase/component'; +// @public +export class AppCheckTokenProvider { + constructor(appName_: string, appCheckProvider?: Provider); + // (undocumented) + addTokenChangeListener(listener: AppCheckTokenListener): void; + // (undocumented) + getToken(forceRefresh?: boolean): Promise; +} + // @public (undocumented) export type AuthTokenListener = (token: string | null) => void; @@ -46,7 +58,7 @@ export interface ConnectorConfig { // @public export class DataConnect { - constructor(app: FirebaseApp, dataConnectOptions: DataConnectOptions, _authProvider: Provider); + constructor(app: FirebaseApp, dataConnectOptions: DataConnectOptions, _authProvider: Provider, _appCheckProvider: Provider); // (undocumented) readonly app: FirebaseApp; // (undocumented) @@ -283,7 +295,7 @@ export function terminate(dataConnect: DataConnect): Promise; export function toQueryRef(serializedRef: SerializedRef): QueryRef; // @public (undocumented) -export type TransportClass = new (options: DataConnectOptions, apiKey?: string, authProvider?: AuthTokenProvider, transportOptions?: TransportOptions, _isUsingGen?: boolean) => DataConnectTransport; +export type TransportClass = new (options: DataConnectOptions, apiKey?: string, authProvider?: AuthTokenProvider, appCheckProvider?: AppCheckTokenProvider, transportOptions?: TransportOptions, _isUsingGen?: boolean) => DataConnectTransport; // @public export interface TransportOptions { diff --git a/packages/data-connect/src/api/index.ts b/packages/data-connect/src/api/index.ts index 885dac5a923..223b7387ddb 100644 --- a/packages/data-connect/src/api/index.ts +++ b/packages/data-connect/src/api/index.ts @@ -20,5 +20,6 @@ export * from './DataConnect'; export * from './Reference'; export * from './Mutation'; export * from './query'; +export * from '../core/AppCheckTokenProvider'; export { setLogLevel } from '../logger'; export { validateArgs } from '../util/validateArgs'; diff --git a/packages/data-connect/src/network/transport/rest.ts b/packages/data-connect/src/network/transport/rest.ts index 3a84c0c8507..88ceb39a93a 100644 --- a/packages/data-connect/src/network/transport/rest.ts +++ b/packages/data-connect/src/network/transport/rest.ts @@ -35,7 +35,6 @@ export class RESTTransport implements DataConnectTransport { private _serviceName: string; private _accessToken: string | null = null; private _appCheckToken: string | null = null; - private _authInitialized = false; private _lastToken: string | null = null; constructor( options: DataConnectOptions, @@ -103,24 +102,25 @@ export class RESTTransport implements DataConnectTransport { this._accessToken = newToken; } - getWithAuth(forceToken = false): Promise { + async getWithAuth(forceToken = false): Promise { let starterPromise: Promise = new Promise(resolve => resolve(this._accessToken) ); - if (!this._authInitialized) { - if (this.authProvider) { - starterPromise = this.authProvider - .getToken(/*forceToken=*/ forceToken) - .then(data => { - if (!data) { - return null; - } - this._accessToken = data.accessToken; - return this._accessToken; - }); - } else { - starterPromise = new Promise(resolve => resolve('')); - } + if (this.appCheckProvider) { + this._appCheckToken = (await this.appCheckProvider.getToken()).token; + } + if (this.authProvider) { + starterPromise = this.authProvider + .getToken(/*forceToken=*/ forceToken) + .then(data => { + if (!data) { + return null; + } + this._accessToken = data.accessToken; + return this._accessToken; + }); + } else { + starterPromise = new Promise(resolve => resolve('')); } return starterPromise; } From dc15ecc9ba2db5ea7b3578900c99a476af20c95f Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Wed, 4 Sep 2024 16:44:15 -0700 Subject: [PATCH 37/74] Updated formatting --- packages/data-connect/src/network/transport/rest.ts | 2 +- packages/data-connect/test/unit/fetch.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/data-connect/src/network/transport/rest.ts b/packages/data-connect/src/network/transport/rest.ts index 88ceb39a93a..5d8794da4ff 100644 --- a/packages/data-connect/src/network/transport/rest.ts +++ b/packages/data-connect/src/network/transport/rest.ts @@ -42,7 +42,7 @@ export class RESTTransport implements DataConnectTransport { private authProvider?: AuthTokenProvider | undefined, private appCheckProvider?: AppCheckTokenProvider | undefined, transportOptions?: TransportOptions | undefined, - private _isUsingGen = false, + private _isUsingGen = false ) { if (transportOptions) { if (typeof transportOptions.port === 'number') { diff --git a/packages/data-connect/test/unit/fetch.test.ts b/packages/data-connect/test/unit/fetch.test.ts index 8010b08b790..f0d7f38ee8c 100644 --- a/packages/data-connect/test/unit/fetch.test.ts +++ b/packages/data-connect/test/unit/fetch.test.ts @@ -40,7 +40,7 @@ describe('fetch', () => { message }); await expect( - dcFetch('http://localhost', {}, {} as AbortController, null, false) + dcFetch('http://localhost', {}, {} as AbortController, null, null, false) ).to.eventually.be.rejectedWith(message); }); it('should throw a stringified message when the server responds with an error without a message property in the body', async () => { @@ -51,7 +51,7 @@ describe('fetch', () => { }; mockFetch(json); await expect( - dcFetch('http://localhost', {}, {} as AbortController, null, false) + dcFetch('http://localhost', {}, {} as AbortController, null, null, false) ).to.eventually.be.rejectedWith(JSON.stringify(json)); }); }); From 0c46097931a9278a640277aa157583c0d2907a3f Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Wed, 4 Sep 2024 16:52:40 -0700 Subject: [PATCH 38/74] Updated yarn file --- .github/workflows/canary-deploy.yml | 1 + packages/data-connect/src/network/transport/rest.ts | 1 + yarn.lock | 11 +++++++++++ 3 files changed, 13 insertions(+) diff --git a/.github/workflows/canary-deploy.yml b/.github/workflows/canary-deploy.yml index 74364b49cd5..cd720955640 100644 --- a/.github/workflows/canary-deploy.yml +++ b/.github/workflows/canary-deploy.yml @@ -52,6 +52,7 @@ jobs: NPM_TOKEN_AUTH_INTEROP_TYPES: ${{secrets.NPM_TOKEN_AUTH_INTEROP_TYPES}} NPM_TOKEN_AUTH_TYPES: ${{secrets.NPM_TOKEN_AUTH_TYPES}} NPM_TOKEN_COMPONENT: ${{secrets.NPM_TOKEN_COMPONENT}} + NPM_TOKEN_DATA_CONNECT: ${{secrets.NPM_TOKEN_DATA_CONNECT}} NPM_TOKEN_DATABASE: ${{secrets.NPM_TOKEN_DATABASE}} NPM_TOKEN_DATABASE_TYPES: ${{secrets.NPM_TOKEN_DATABASE_TYPES}} NPM_TOKEN_FIRESTORE: ${{secrets.NPM_TOKEN_FIRESTORE}} diff --git a/packages/data-connect/src/network/transport/rest.ts b/packages/data-connect/src/network/transport/rest.ts index 5d8794da4ff..db1f50f4563 100644 --- a/packages/data-connect/src/network/transport/rest.ts +++ b/packages/data-connect/src/network/transport/rest.ts @@ -73,6 +73,7 @@ export class RESTTransport implements DataConnectTransport { this._accessToken = token; }); this.appCheckProvider?.addTokenChangeListener(result => { + console.log('getting token'); const { token } = result; logDebug(`New App Check Token Available: ${token}`); this._appCheckToken = token; diff --git a/yarn.lock b/yarn.lock index 92372aa94a3..c611eb3ab06 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1639,6 +1639,17 @@ resolved "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz#ef20350fec605a7f7035a01764731b2de0f3782b" integrity sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A== +"@firebase/app@0.10.7": + version "0.10.7" + resolved "https://registry.npmjs.org/@firebase/app/-/app-0.10.7.tgz#31cba1486c21b5a0cadc0036bfc0cec68ff53c8e" + integrity sha512-7OCd53B+wnk/onbMLn/vM10pDjw97zzWUD8m3swtLYKJIrL+gDZ7HZ4xcbBLw7OB8ikzu8k1ORNjRe2itgAy4g== + dependencies: + "@firebase/component" "0.6.8" + "@firebase/logger" "0.4.2" + "@firebase/util" "1.9.7" + idb "7.1.1" + tslib "^2.1.0" + "@gar/promisify@^1.0.1": version "1.1.2" resolved "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.2.tgz" From f45f6b2b928c381251c66d77038f356c442c6266 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Wed, 4 Sep 2024 17:23:31 -0700 Subject: [PATCH 39/74] Fixed tests --- packages/data-connect/package.json | 4 ++-- packages/data-connect/src/index.ts | 1 - packages/data-connect/src/network/transport/rest.ts | 3 +-- packages/data-connect/test/unit/userAgent.test.ts | 8 ++++++-- yarn.lock | 11 ----------- 5 files changed, 9 insertions(+), 18 deletions(-) diff --git a/packages/data-connect/package.json b/packages/data-connect/package.json index 699d9c5f32e..3792e047344 100644 --- a/packages/data-connect/package.json +++ b/packages/data-connect/package.json @@ -35,7 +35,7 @@ "dev": "rollup -c -w", "test": "run-p --npm-path npm test:emulator", "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:emulator", - "test:all": "npm run test:node", + "test:all": "npm run test:unit", "test:browser": "karma start --single-run", "test:node": "TS_NODE_FILES=true TS_NODE_CACHE=NO TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha 'test/{,!(browser)/**/}*.test.ts' --file src/index.node.ts --config ../../config/mocharc.node.js", "test:unit": "TS_NODE_FILES=true TS_NODE_CACHE=NO TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha 'test/unit/**/*.test.ts' --file src/index.node.ts --config ../../config/mocharc.node.js", @@ -57,7 +57,7 @@ "tslib": "^2.1.0" }, "devDependencies": { - "@firebase/app": "0.10.7", + "@firebase/app": "0.10.10", "rollup": "2.79.1", "rollup-plugin-typescript2": "0.31.2", "typescript": "4.7.4" diff --git a/packages/data-connect/src/index.ts b/packages/data-connect/src/index.ts index 946c72c38d5..6963618400c 100644 --- a/packages/data-connect/src/index.ts +++ b/packages/data-connect/src/index.ts @@ -22,7 +22,6 @@ */ import { DataConnect } from './api/DataConnect'; import { registerDataConnect } from './register'; -// import CryptoJS from 'crypto-js/hmac-sha512'; export * from './api'; export * from './api.browser'; diff --git a/packages/data-connect/src/network/transport/rest.ts b/packages/data-connect/src/network/transport/rest.ts index db1f50f4563..55eac51761b 100644 --- a/packages/data-connect/src/network/transport/rest.ts +++ b/packages/data-connect/src/network/transport/rest.ts @@ -73,7 +73,6 @@ export class RESTTransport implements DataConnectTransport { this._accessToken = token; }); this.appCheckProvider?.addTokenChangeListener(result => { - console.log('getting token'); const { token } = result; logDebug(`New App Check Token Available: ${token}`); this._appCheckToken = token; @@ -108,7 +107,7 @@ export class RESTTransport implements DataConnectTransport { resolve(this._accessToken) ); if (this.appCheckProvider) { - this._appCheckToken = (await this.appCheckProvider.getToken()).token; + this._appCheckToken = (await this.appCheckProvider.getToken())?.token; } if (this.authProvider) { starterPromise = this.authProvider diff --git a/packages/data-connect/test/unit/userAgent.test.ts b/packages/data-connect/test/unit/userAgent.test.ts index 696f0bf60e9..98675bbe61f 100644 --- a/packages/data-connect/test/unit/userAgent.test.ts +++ b/packages/data-connect/test/unit/userAgent.test.ts @@ -22,6 +22,7 @@ import sinonChai from 'sinon-chai'; import { DataConnect, executeQuery, getDataConnect, queryRef } from '../../src'; import { SDK_VERSION } from '../../src/core/version'; import { initializeFetch } from '../../src/network/fetch'; +import { deleteApp, initializeApp, FirebaseApp } from '@firebase/app'; use(sinonChai); const json = { @@ -38,12 +39,15 @@ const fakeFetchImpl = sinon.stub().returns( describe('User Agent Tests', () => { let dc: DataConnect; + let app: FirebaseApp; beforeEach(() => { initializeFetch(fakeFetchImpl); - dc = getDataConnect({ connector: 'c', location: 'l', service: 's' }); + app = initializeApp({projectId: 'p'}, 'abc'); // TODO(mtewani): Replace with util function + dc = getDataConnect(app, { connector: 'c', location: 'l', service: 's' }); }); afterEach(async () => { await dc._delete(); + await deleteApp(app); }); it('should send a request with the corresponding user agent if using the generated SDK', async () => { dc._useGeneratedSdk(); @@ -58,7 +62,7 @@ describe('User Agent Tests', () => { } ); }); - it('should send a request with the corresponding user agent if using the generated SDK', async () => { + it('should send a request with the corresponding user agent if not using the generated SDK', async () => { // @ts-ignore await executeQuery(queryRef(dc, '')).catch(() => {}); expect(fakeFetchImpl).to.be.calledWithMatch( diff --git a/yarn.lock b/yarn.lock index c611eb3ab06..92372aa94a3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1639,17 +1639,6 @@ resolved "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz#ef20350fec605a7f7035a01764731b2de0f3782b" integrity sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A== -"@firebase/app@0.10.7": - version "0.10.7" - resolved "https://registry.npmjs.org/@firebase/app/-/app-0.10.7.tgz#31cba1486c21b5a0cadc0036bfc0cec68ff53c8e" - integrity sha512-7OCd53B+wnk/onbMLn/vM10pDjw97zzWUD8m3swtLYKJIrL+gDZ7HZ4xcbBLw7OB8ikzu8k1ORNjRe2itgAy4g== - dependencies: - "@firebase/component" "0.6.8" - "@firebase/logger" "0.4.2" - "@firebase/util" "1.9.7" - idb "7.1.1" - tslib "^2.1.0" - "@gar/promisify@^1.0.1": version "1.1.2" resolved "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.2.tgz" From 4d279f7c51ccc8c6f67e1c28de759b092bd34bba Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Thu, 5 Sep 2024 16:03:25 -0700 Subject: [PATCH 40/74] Added new docs --- docs-devsite/_toc.yaml | 2 + .../data-connect.appchecktokenprovider.md | 86 +++++++++++++++++++ docs-devsite/data-connect.dataconnect.md | 5 +- docs-devsite/data-connect.md | 3 +- packages/auth/api-extractor.json | 2 +- .../src/core/AppCheckTokenProvider.ts | 10 ++- .../data-connect/test/unit/userAgent.test.ts | 2 +- 7 files changed, 102 insertions(+), 8 deletions(-) create mode 100644 docs-devsite/data-connect.appchecktokenprovider.md diff --git a/docs-devsite/_toc.yaml b/docs-devsite/_toc.yaml index a2b526e4c99..7dc5131af52 100644 --- a/docs-devsite/_toc.yaml +++ b/docs-devsite/_toc.yaml @@ -176,6 +176,8 @@ toc: - title: data-connect path: /docs/reference/js/data-connect.md section: + - title: AppCheckTokenProvider + path: /docs/reference/js/data-connect.appchecktokenprovider.md - title: AuthTokenProvider path: /docs/reference/js/data-connect.authtokenprovider.md - title: CancellableOperation diff --git a/docs-devsite/data-connect.appchecktokenprovider.md b/docs-devsite/data-connect.appchecktokenprovider.md new file mode 100644 index 00000000000..e98a6ba47d3 --- /dev/null +++ b/docs-devsite/data-connect.appchecktokenprovider.md @@ -0,0 +1,86 @@ +Project: /docs/reference/js/_project.yaml +Book: /docs/reference/_book.yaml +page_type: reference + +{% comment %} +DO NOT EDIT THIS FILE! +This is generated by the JS SDK team, and any local changes will be +overwritten. Changes should be made in the source code at +https://github.com/firebase/firebase-js-sdk +{% endcomment %} + +# AppCheckTokenProvider class +Abstraction around AppCheck's token fetching capabilities. + +Signature: + +```typescript +export declare class AppCheckTokenProvider +``` + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)(appName\_, appCheckProvider)](./data-connect.appchecktokenprovider.md#appchecktokenproviderconstructor) | | Constructs a new instance of the AppCheckTokenProvider class | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [addTokenChangeListener(listener)](./data-connect.appchecktokenprovider.md#appchecktokenprovideraddtokenchangelistener) | | | +| [getToken(forceRefresh)](./data-connect.appchecktokenprovider.md#appchecktokenprovidergettoken) | | | + +## AppCheckTokenProvider.(constructor) + +Constructs a new instance of the `AppCheckTokenProvider` class + +Signature: + +```typescript +constructor(appName_: string, appCheckProvider?: Provider); +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| appName\_ | string | | +| appCheckProvider | Provider<AppCheckInternalComponentName> | | + +## AppCheckTokenProvider.addTokenChangeListener() + +Signature: + +```typescript +addTokenChangeListener(listener: AppCheckTokenListener): void; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| listener | AppCheckTokenListener | | + +Returns: + +void + +## AppCheckTokenProvider.getToken() + +Signature: + +```typescript +getToken(forceRefresh?: boolean): Promise; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| forceRefresh | boolean | | + +Returns: + +Promise<AppCheckTokenResult> + diff --git a/docs-devsite/data-connect.dataconnect.md b/docs-devsite/data-connect.dataconnect.md index 3b72473eae8..8b0abdbadb6 100644 --- a/docs-devsite/data-connect.dataconnect.md +++ b/docs-devsite/data-connect.dataconnect.md @@ -22,7 +22,7 @@ export declare class DataConnect | Constructor | Modifiers | Description | | --- | --- | --- | -| [(constructor)(app, dataConnectOptions, \_authProvider)](./data-connect.dataconnect.md#dataconnectconstructor) | | Constructs a new instance of the DataConnect class | +| [(constructor)(app, dataConnectOptions, \_authProvider, \_appCheckProvider)](./data-connect.dataconnect.md#dataconnectconstructor) | | Constructs a new instance of the DataConnect class | ## Properties @@ -47,7 +47,7 @@ Constructs a new instance of the `DataConnect` class Signature: ```typescript -constructor(app: FirebaseApp, dataConnectOptions: DataConnectOptions, _authProvider: Provider); +constructor(app: FirebaseApp, dataConnectOptions: DataConnectOptions, _authProvider: Provider, _appCheckProvider: Provider); ``` #### Parameters @@ -57,6 +57,7 @@ constructor(app: FirebaseApp, dataConnectOptions: DataConnectOptions, _authProvi | app | [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) | | | dataConnectOptions | [DataConnectOptions](./data-connect.dataconnectoptions.md#dataconnectoptions_interface) | | | \_authProvider | Provider<FirebaseAuthInternalName> | | +| \_appCheckProvider | Provider<AppCheckInternalComponentName> | | ## DataConnect.app diff --git a/docs-devsite/data-connect.md b/docs-devsite/data-connect.md index cbd8b16d370..08aef0ad6ca 100644 --- a/docs-devsite/data-connect.md +++ b/docs-devsite/data-connect.md @@ -45,6 +45,7 @@ Firebase Data Connect | Class | Description | | --- | --- | +| [AppCheckTokenProvider](./data-connect.appchecktokenprovider.md#appchecktokenprovider_class) | Abstraction around AppCheck's token fetching capabilities. | | [DataConnect](./data-connect.dataconnect.md#dataconnect_class) | Class representing Firebase Data Connect | | [FirebaseAuthProvider](./data-connect.firebaseauthprovider.md#firebaseauthprovider_class) | | @@ -532,5 +533,5 @@ export declare type ReferenceType = typeof QUERY_STR | typeof MUTATION_STR; Signature: ```typescript -export declare type TransportClass = new (options: DataConnectOptions, apiKey?: string, authProvider?: AuthTokenProvider, transportOptions?: TransportOptions, _isUsingGen?: boolean) => DataConnectTransport; +export declare type TransportClass = new (options: DataConnectOptions, apiKey?: string, authProvider?: AuthTokenProvider, appCheckProvider?: AppCheckTokenProvider, transportOptions?: TransportOptions, _isUsingGen?: boolean) => DataConnectTransport; ``` diff --git a/packages/auth/api-extractor.json b/packages/auth/api-extractor.json index ca7e1ee6983..325fe12d507 100644 --- a/packages/auth/api-extractor.json +++ b/packages/auth/api-extractor.json @@ -1,6 +1,6 @@ { "extends": "../../config/api-extractor.json", - "mainEntryPointFilePath": "/dist/esm5/index.d.ts", + "mainEntryPointFilePath": "/dist/esm5/index.doc.d.ts", "dtsRollup": { "enabled": true, "untrimmedFilePath": "/dist/.d.ts", diff --git a/packages/data-connect/src/core/AppCheckTokenProvider.ts b/packages/data-connect/src/core/AppCheckTokenProvider.ts index 1497ae53daa..b67ace214f2 100644 --- a/packages/data-connect/src/core/AppCheckTokenProvider.ts +++ b/packages/data-connect/src/core/AppCheckTokenProvider.ts @@ -34,7 +34,10 @@ export class AppCheckTokenProvider { ) { this.appCheck = appCheckProvider?.getImmediate({ optional: true }); if (!this.appCheck) { - appCheckProvider?.get().then(appCheck => (this.appCheck = appCheck)); + appCheckProvider + ?.get() + .then(appCheck => (this.appCheck = appCheck)) + .catch(); } } @@ -57,10 +60,11 @@ export class AppCheckTokenProvider { return this.appCheck.getToken(forceRefresh); } - addTokenChangeListener(listener: AppCheckTokenListener) { + addTokenChangeListener(listener: AppCheckTokenListener): void { this.appCheckProvider ?.get() - .then(appCheck => appCheck.addTokenListener(listener)); + .then(appCheck => appCheck.addTokenListener(listener)) + .catch(); } // Not currently used at the moment. Will update if needed. diff --git a/packages/data-connect/test/unit/userAgent.test.ts b/packages/data-connect/test/unit/userAgent.test.ts index 98675bbe61f..1310f68020f 100644 --- a/packages/data-connect/test/unit/userAgent.test.ts +++ b/packages/data-connect/test/unit/userAgent.test.ts @@ -42,7 +42,7 @@ describe('User Agent Tests', () => { let app: FirebaseApp; beforeEach(() => { initializeFetch(fakeFetchImpl); - app = initializeApp({projectId: 'p'}, 'abc'); // TODO(mtewani): Replace with util function + app = initializeApp({ projectId: 'p' }, 'abc'); // TODO(mtewani): Replace with util function dc = getDataConnect(app, { connector: 'c', location: 'l', service: 's' }); }); afterEach(async () => { From e5e3acd87050439a445cdb6a58445154a0751239 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Thu, 5 Sep 2024 16:13:20 -0700 Subject: [PATCH 41/74] Fix linter --- packages/data-connect/src/core/AppCheckTokenProvider.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/data-connect/src/core/AppCheckTokenProvider.ts b/packages/data-connect/src/core/AppCheckTokenProvider.ts index b67ace214f2..11b001c2618 100644 --- a/packages/data-connect/src/core/AppCheckTokenProvider.ts +++ b/packages/data-connect/src/core/AppCheckTokenProvider.ts @@ -34,7 +34,7 @@ export class AppCheckTokenProvider { ) { this.appCheck = appCheckProvider?.getImmediate({ optional: true }); if (!this.appCheck) { - appCheckProvider + void appCheckProvider ?.get() .then(appCheck => (this.appCheck = appCheck)) .catch(); @@ -61,10 +61,9 @@ export class AppCheckTokenProvider { } addTokenChangeListener(listener: AppCheckTokenListener): void { - this.appCheckProvider + void this.appCheckProvider ?.get() .then(appCheck => appCheck.addTokenListener(listener)) - .catch(); } // Not currently used at the moment. Will update if needed. From 0e8cdfb5849e9b84a0e1b6d554dffaed9aa40808 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Thu, 5 Sep 2024 16:14:37 -0700 Subject: [PATCH 42/74] Fixed linting --- packages/data-connect/src/api/DataConnect.ts | 9 ++++----- packages/data-connect/src/core/AppCheckTokenProvider.ts | 2 +- packages/data-connect/src/network/transport/rest.ts | 2 +- packages/data-connect/test/unit/userAgent.test.ts | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/data-connect/src/api/DataConnect.ts b/packages/data-connect/src/api/DataConnect.ts index 6b4f44088e1..bdc965e0e4d 100644 --- a/packages/data-connect/src/api/DataConnect.ts +++ b/packages/data-connect/src/api/DataConnect.ts @@ -21,9 +21,13 @@ import { _removeServiceInstance, getApp } from '@firebase/app'; +import { + AppCheckInternalComponentName +} from '@firebase/app-check-interop-types'; import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; import { Provider } from '@firebase/component'; +import { AppCheckTokenProvider } from '../core/AppCheckTokenProvider'; import { Code, DataConnectError } from '../core/error'; import { AuthTokenProvider, @@ -35,11 +39,6 @@ import { DataConnectTransport, TransportClass } from '../network'; import { RESTTransport } from '../network/transport/rest'; import { MutationManager } from './Mutation'; -import { AppCheckTokenProvider } from '../core/AppCheckTokenProvider'; -import { - AppCheckInternalComponentName, - FirebaseAppCheckInternal -} from '@firebase/app-check-interop-types'; /** * Connector Config for calling Data Connect backend. diff --git a/packages/data-connect/src/core/AppCheckTokenProvider.ts b/packages/data-connect/src/core/AppCheckTokenProvider.ts index 11b001c2618..9d3a0c2cffd 100644 --- a/packages/data-connect/src/core/AppCheckTokenProvider.ts +++ b/packages/data-connect/src/core/AppCheckTokenProvider.ts @@ -63,7 +63,7 @@ export class AppCheckTokenProvider { addTokenChangeListener(listener: AppCheckTokenListener): void { void this.appCheckProvider ?.get() - .then(appCheck => appCheck.addTokenListener(listener)) + .then(appCheck => appCheck.addTokenListener(listener)); } // Not currently used at the moment. Will update if needed. diff --git a/packages/data-connect/src/network/transport/rest.ts b/packages/data-connect/src/network/transport/rest.ts index 55eac51761b..aaaf22abd64 100644 --- a/packages/data-connect/src/network/transport/rest.ts +++ b/packages/data-connect/src/network/transport/rest.ts @@ -16,6 +16,7 @@ */ import { DataConnectOptions, TransportOptions } from '../../api/DataConnect'; +import { AppCheckTokenProvider } from '../../core/AppCheckTokenProvider'; import { DataConnectError, Code } from '../../core/error'; import { AuthTokenProvider } from '../../core/FirebaseAuthProvider'; import { logDebug } from '../../logger'; @@ -23,7 +24,6 @@ import { addToken, urlBuilder } from '../../util/url'; import { dcFetch } from '../fetch'; import { DataConnectTransport } from '.'; -import { AppCheckTokenProvider } from '../../core/AppCheckTokenProvider'; export class RESTTransport implements DataConnectTransport { private _host = ''; diff --git a/packages/data-connect/test/unit/userAgent.test.ts b/packages/data-connect/test/unit/userAgent.test.ts index 1310f68020f..a42fd06f817 100644 --- a/packages/data-connect/test/unit/userAgent.test.ts +++ b/packages/data-connect/test/unit/userAgent.test.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import { deleteApp, initializeApp, FirebaseApp } from '@firebase/app'; import { expect, use } from 'chai'; import * as sinon from 'sinon'; import sinonChai from 'sinon-chai'; @@ -22,7 +23,6 @@ import sinonChai from 'sinon-chai'; import { DataConnect, executeQuery, getDataConnect, queryRef } from '../../src'; import { SDK_VERSION } from '../../src/core/version'; import { initializeFetch } from '../../src/network/fetch'; -import { deleteApp, initializeApp, FirebaseApp } from '@firebase/app'; use(sinonChai); const json = { From fb4220039c9fe2df9074d6b6c1d4c57fdd10f630 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Thu, 5 Sep 2024 16:18:38 -0700 Subject: [PATCH 43/74] Create cold-chairs-fold.md --- .changeset/cold-chairs-fold.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/cold-chairs-fold.md diff --git a/.changeset/cold-chairs-fold.md b/.changeset/cold-chairs-fold.md new file mode 100644 index 00000000000..61df53a3cdd --- /dev/null +++ b/.changeset/cold-chairs-fold.md @@ -0,0 +1,5 @@ +--- +"@firebase/data-connect": minor +--- + +Included Data Connect product. From 6660f9895d44a50b5319b7bce8c8b63d1fec2034 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Thu, 5 Sep 2024 16:21:31 -0700 Subject: [PATCH 44/74] Addressed comments --- .vscode/settings.json | 3 +-- config/karma.saucelabs.js | 2 +- config/mocharc.node.js | 2 +- packages/util/src/fetch_provider.ts | 1 + 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 279965399ab..706e611f5d5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,6 +11,5 @@ }, "typescript.tsdk": "node_modules/typescript/lib", "files.associations": { "*.json": "jsonc" }, - "eslint.workingDirectories": [{ "mode": "auto" }], - "editor.wordWrap": "off", + "eslint.workingDirectories": [{ "mode": "auto" }] } diff --git a/config/karma.saucelabs.js b/config/karma.saucelabs.js index a6b92a6a21b..b6572d9eace 100644 --- a/config/karma.saucelabs.js +++ b/config/karma.saucelabs.js @@ -210,7 +210,7 @@ module.exports = function (config) { port: 9876, - retryLimit: 0, + retryLimit: 3, // concurrency: 10, diff --git a/config/mocharc.node.js b/config/mocharc.node.js index 219256f9370..e493fce4d52 100644 --- a/config/mocharc.node.js +++ b/config/mocharc.node.js @@ -25,7 +25,7 @@ const config = { require: 'ts-node/register', timeout: 5000, - // retries: 5, + retries: 5, exit: true }; diff --git a/packages/util/src/fetch_provider.ts b/packages/util/src/fetch_provider.ts index c6eff767cd6..0f01f0a6080 100644 --- a/packages/util/src/fetch_provider.ts +++ b/packages/util/src/fetch_provider.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +// @internal export class FetchProvider { private static fetchImpl: typeof fetch | null; private static headersImpl: typeof Headers | null; From 97194aec37baa94f80670655db86c5c696b05c09 Mon Sep 17 00:00:00 2001 From: maneesht Date: Thu, 5 Sep 2024 23:36:19 +0000 Subject: [PATCH 45/74] Update API reports --- common/api-review/auth.api.md | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/common/api-review/auth.api.md b/common/api-review/auth.api.md index 8e915daf731..89a71e26b7b 100644 --- a/common/api-review/auth.api.md +++ b/common/api-review/auth.api.md @@ -104,14 +104,14 @@ export class AuthCredential { protected constructor( providerId: string, signInMethod: string); - // Warning: (ae-forgotten-export) The symbol "AuthInternal" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "PhoneOrOauthTokenResponse" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "AuthInternal" needs to be exported by the entry point index.doc.d.ts + // Warning: (ae-forgotten-export) The symbol "PhoneOrOauthTokenResponse" needs to be exported by the entry point index.doc.d.ts // // @internal (undocumented) _getIdTokenResponse(_auth: AuthInternal): Promise; // @internal (undocumented) _getReauthenticationResolver(_auth: AuthInternal): Promise; - // Warning: (ae-forgotten-export) The symbol "IdTokenResponse" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "IdTokenResponse" needs to be exported by the entry point index.doc.d.ts // // @internal (undocumented) _linkToIdToken(_auth: AuthInternal, _idToken: string): Promise; @@ -293,6 +293,9 @@ export function connectAuthEmulator(auth: Auth, url: string, options?: { disableWarnings: boolean; }): void; +// @public +export const cordovaPopupRedirectResolver: PopupRedirectResolver; + // @public export function createUserWithEmailAndPassword(auth: Auth, email: string, password: string): Promise; @@ -356,7 +359,7 @@ export interface EmulatorConfig { export { ErrorFn } -// Warning: (ae-forgotten-export) The symbol "BaseOAuthProvider" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "BaseOAuthProvider" needs to be exported by the entry point index.doc.d.ts // // @public export class FacebookAuthProvider extends BaseOAuthProvider { @@ -392,6 +395,9 @@ export function getIdTokenResult(user: User, forceRefresh?: boolean): Promise; @@ -504,7 +510,7 @@ export type NextOrObserver = NextFn | Observer; export class OAuthCredential extends AuthCredential { accessToken?: string; static fromJSON(json: string | object): OAuthCredential | null; - // Warning: (ae-forgotten-export) The symbol "OAuthCredentialParams" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "OAuthCredentialParams" needs to be exported by the entry point index.doc.d.ts // // @internal (undocumented) static _fromParams(params: OAuthCredentialParams): OAuthCredential; @@ -609,7 +615,7 @@ export class PhoneAuthCredential extends AuthCredential { _getReauthenticationResolver(auth: AuthInternal): Promise; // @internal (undocumented) _linkToIdToken(auth: AuthInternal, idToken: string): Promise; - // Warning: (ae-forgotten-export) The symbol "SignInWithPhoneNumberRequest" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "SignInWithPhoneNumberRequest" needs to be exported by the entry point index.doc.d.ts // // @internal (undocumented) _makeVerificationRequest(): SignInWithPhoneNumberRequest; @@ -706,13 +712,13 @@ export interface RecaptchaParameters { [key: string]: any; } -// Warning: (ae-forgotten-export) The symbol "ApplicationVerifierInternal" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "ApplicationVerifierInternal" needs to be exported by the entry point index.doc.d.ts // // @public export class RecaptchaVerifier implements ApplicationVerifierInternal { constructor(authExtern: Auth, containerOrId: HTMLElement | string, parameters?: RecaptchaParameters); clear(): void; - // Warning: (ae-forgotten-export) The symbol "ReCaptchaLoader" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "ReCaptchaLoader" needs to be exported by the entry point index.doc.d.ts // // @internal (undocumented) readonly _recaptchaLoader: ReCaptchaLoader; @@ -729,7 +735,7 @@ export function reload(user: User): Promise; // @public export function revokeAccessToken(auth: Auth, token: string): Promise; -// Warning: (ae-forgotten-export) The symbol "FederatedAuthProvider" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "FederatedAuthProvider" needs to be exported by the entry point index.doc.d.ts // // @public export class SAMLAuthProvider extends FederatedAuthProvider { @@ -810,13 +816,13 @@ export class TotpSecret { readonly codeIntervalSeconds: number; readonly codeLength: number; readonly enrollmentCompletionDeadline: string; - // Warning: (ae-forgotten-export) The symbol "StartTotpMfaEnrollmentResponse" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "StartTotpMfaEnrollmentResponse" needs to be exported by the entry point index.doc.d.ts // // @internal (undocumented) static _fromStartTotpMfaEnrollmentResponse(response: StartTotpMfaEnrollmentResponse, auth: AuthInternal): TotpSecret; generateQrCodeUrl(accountName?: string, issuer?: string): string; readonly hashingAlgorithm: string; - // Warning: (ae-forgotten-export) The symbol "TotpVerificationInfo" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "TotpVerificationInfo" needs to be exported by the entry point index.doc.d.ts // // @internal (undocumented) _makeTotpVerificationInfo(otp: string): TotpVerificationInfo; From a65a247c7ac311bdaaeca9f064b9d96135fb61e4 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Thu, 5 Sep 2024 16:47:53 -0700 Subject: [PATCH 46/74] Removed unnecessary exports --- common/api-review/data-connect.api.md | 77 ------------------- packages/data-connect/src/api/DataConnect.ts | 18 +++-- packages/data-connect/src/api/index.ts | 1 - .../src/core/AppCheckTokenProvider.ts | 1 + .../src/core/FirebaseAuthProvider.ts | 2 + .../src/network/transport/index.ts | 20 ++--- .../database/src/core/AuthTokenProvider.ts | 1 + 7 files changed, 20 insertions(+), 100 deletions(-) diff --git a/common/api-review/data-connect.api.md b/common/api-review/data-connect.api.md index fce3c921e2e..b2c6fb01931 100644 --- a/common/api-review/data-connect.api.md +++ b/common/api-review/data-connect.api.md @@ -5,36 +5,12 @@ ```ts import { AppCheckInternalComponentName } from '@firebase/app-check-interop-types'; -import { AppCheckTokenListener } from '@firebase/app-check-interop-types'; -import { AppCheckTokenResult } from '@firebase/app-check-interop-types'; import { FirebaseApp } from '@firebase/app'; import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; -import { FirebaseAuthTokenData } from '@firebase/auth-interop-types'; import { FirebaseError } from '@firebase/util'; -import { FirebaseOptions } from '@firebase/app-types'; import { LogLevelString } from '@firebase/logger'; import { Provider } from '@firebase/component'; -// @public -export class AppCheckTokenProvider { - constructor(appName_: string, appCheckProvider?: Provider); - // (undocumented) - addTokenChangeListener(listener: AppCheckTokenListener): void; - // (undocumented) - getToken(forceRefresh?: boolean): Promise; -} - -// @public (undocumented) -export type AuthTokenListener = (token: string | null) => void; - -// @public (undocumented) -export interface AuthTokenProvider { - // (undocumented) - addTokenChangeListener(listener: AuthTokenListener): void; - // (undocumented) - getToken(forceRefresh: boolean): Promise; -} - // @public (undocumented) export interface CancellableOperation extends PromiseLike<{ data: T; @@ -66,8 +42,6 @@ export class DataConnect { // (undocumented) getSettings(): ConnectorConfig; // (undocumented) - initialized: boolean; - // (undocumented) isEmulator: boolean; // (undocumented) setInitialized(): void; @@ -95,24 +69,6 @@ export interface DataConnectSubscription { userCallback: OnResultSubscription; } -// @public (undocumented) -export interface DataConnectTransport { - // (undocumented) - invokeMutation(queryName: string, body?: U): PromiseLike<{ - data: T; - errors: Error[]; - }>; - // (undocumented) - invokeQuery(queryName: string, body?: U): PromiseLike<{ - data: T; - errors: Error[]; - }>; - // (undocumented) - onTokenChanged: (token: string | null) => void; - // (undocumented) - useEmulator(host: string, port?: number, sslEnabled?: boolean): void; -} - // @public (undocumented) export type DataSource = typeof SOURCE_CACHE | typeof SOURCE_SERVER; @@ -122,20 +78,6 @@ export function executeMutation(mutationRef: MutationRef(queryRef: QueryRef): QueryPromise; -// @public (undocumented) -export const FIREBASE_DATA_CONNECT_EMULATOR_HOST_VAR = "FIREBASE_DATA_CONNECT_EMULATOR_HOST"; - -// @public (undocumented) -export class FirebaseAuthProvider implements AuthTokenProvider { - constructor(_appName: string, _options: FirebaseOptions, _authProvider: Provider); - // (undocumented) - addTokenChangeListener(listener: AuthTokenListener): void; - // (undocumented) - getToken(forceRefresh: boolean): Promise; - // (undocumented) - removeTokenChangeListener(listener: (token: string | null) => void): void; -} - // @public export function getDataConnect(options: ConnectorConfig): DataConnect; @@ -161,10 +103,6 @@ export function mutationRef(dcInstance: DataConnect, mutationName: string) // @public (undocumented) export function mutationRef(dcInstance: DataConnect, mutationName: string, variables: Variables): MutationRef; -// @public (undocumented) -export interface MutationResponse extends CancellableOperation { -} - // @public export interface MutationResult extends DataConnectResult { // (undocumented) @@ -221,10 +159,6 @@ export function queryRef(dcInstance: DataConnect, queryName: string): Quer // @public export function queryRef(dcInstance: DataConnect, queryName: string, variables: Variables): QueryRef; -// @public (undocumented) -export interface QueryResponse extends CancellableOperation { -} - // @public export interface QueryResult extends DataConnectResult { // (undocumented) @@ -249,14 +183,6 @@ export interface RefInfo { variables: Variables; } -// @public (undocumented) -export interface Sender { - // (undocumented) - abort: () => void; - // (undocumented) - send: () => Promise; -} - // @public export interface SerializedRef extends OpResult { // (undocumented) @@ -294,9 +220,6 @@ export function terminate(dataConnect: DataConnect): Promise; // @public export function toQueryRef(serializedRef: SerializedRef): QueryRef; -// @public (undocumented) -export type TransportClass = new (options: DataConnectOptions, apiKey?: string, authProvider?: AuthTokenProvider, appCheckProvider?: AppCheckTokenProvider, transportOptions?: TransportOptions, _isUsingGen?: boolean) => DataConnectTransport; - // @public export interface TransportOptions { // (undocumented) diff --git a/packages/data-connect/src/api/DataConnect.ts b/packages/data-connect/src/api/DataConnect.ts index bdc965e0e4d..552d074d6c1 100644 --- a/packages/data-connect/src/api/DataConnect.ts +++ b/packages/data-connect/src/api/DataConnect.ts @@ -58,7 +58,7 @@ export interface TransportOptions { port?: number; } -export const FIREBASE_DATA_CONNECT_EMULATOR_HOST_VAR = +const FIREBASE_DATA_CONNECT_EMULATOR_HOST_VAR = 'FIREBASE_DATA_CONNECT_EMULATOR_HOST'; /** @@ -88,13 +88,14 @@ export class DataConnect { _queryManager!: QueryManager; _mutationManager!: MutationManager; isEmulator = false; - initialized = false; + _initialized = false; private _transport!: DataConnectTransport; private _transportClass: TransportClass | undefined; private _transportOptions?: TransportOptions; private _authTokenProvider?: AuthTokenProvider; _isUsingGeneratedSdk: boolean = false; private _appCheckTokenProvider?: AppCheckTokenProvider; + // @internal constructor( public readonly app: FirebaseApp, // TODO(mtewani): Replace with _dataConnectOptions in the future @@ -111,9 +112,7 @@ export class DataConnect { } } } - /* - @internal - */ + // @internal _useGeneratedSdk(): void { if (!this._isUsingGeneratedSdk) { this._isUsingGeneratedSdk = true; @@ -128,14 +127,16 @@ export class DataConnect { return Promise.resolve(); } + // @internal getSettings(): ConnectorConfig { const copy = JSON.parse(JSON.stringify(this.dataConnectOptions)); delete copy.projectId; return copy; } + // @internal setInitialized(): void { - if (this.initialized) { + if (this._initialized) { return; } if (this._transportClass === undefined) { @@ -157,7 +158,7 @@ export class DataConnect { ); } - this.initialized = true; + this._initialized = true; this._transport = new this._transportClass( this.dataConnectOptions, this.app.options.apiKey, @@ -177,8 +178,9 @@ export class DataConnect { this._mutationManager = new MutationManager(this._transport); } + // @internal enableEmulator(transportOptions: TransportOptions): void { - if (this.initialized) { + if (this._initialized) { logError('enableEmulator called after initialization'); throw new DataConnectError( Code.ALREADY_INITIALIZED, diff --git a/packages/data-connect/src/api/index.ts b/packages/data-connect/src/api/index.ts index 223b7387ddb..885dac5a923 100644 --- a/packages/data-connect/src/api/index.ts +++ b/packages/data-connect/src/api/index.ts @@ -20,6 +20,5 @@ export * from './DataConnect'; export * from './Reference'; export * from './Mutation'; export * from './query'; -export * from '../core/AppCheckTokenProvider'; export { setLogLevel } from '../logger'; export { validateArgs } from '../util/validateArgs'; diff --git a/packages/data-connect/src/core/AppCheckTokenProvider.ts b/packages/data-connect/src/core/AppCheckTokenProvider.ts index 9d3a0c2cffd..dd80d0bea56 100644 --- a/packages/data-connect/src/core/AppCheckTokenProvider.ts +++ b/packages/data-connect/src/core/AppCheckTokenProvider.ts @@ -24,6 +24,7 @@ import { import { Provider } from '@firebase/component'; /** + @internal * Abstraction around AppCheck's token fetching capabilities. */ export class AppCheckTokenProvider { diff --git a/packages/data-connect/src/core/FirebaseAuthProvider.ts b/packages/data-connect/src/core/FirebaseAuthProvider.ts index bf7c9b300a2..a19b8a46d6c 100644 --- a/packages/data-connect/src/core/FirebaseAuthProvider.ts +++ b/packages/data-connect/src/core/FirebaseAuthProvider.ts @@ -25,12 +25,14 @@ import { Provider } from '@firebase/component'; import { logDebug, logError } from '../logger'; +// @internal export interface AuthTokenProvider { getToken(forceRefresh: boolean): Promise; addTokenChangeListener(listener: AuthTokenListener): void; } export type AuthTokenListener = (token: string | null) => void; +// @internal export class FirebaseAuthProvider implements AuthTokenProvider { private _auth: FirebaseAuthInternal; constructor( diff --git a/packages/data-connect/src/network/transport/index.ts b/packages/data-connect/src/network/transport/index.ts index c6674746c4c..8ccbc88f435 100644 --- a/packages/data-connect/src/network/transport/index.ts +++ b/packages/data-connect/src/network/transport/index.ts @@ -19,7 +19,9 @@ import { DataConnectOptions, TransportOptions } from '../../api/DataConnect'; import { AppCheckTokenProvider } from '../../core/AppCheckTokenProvider'; import { AuthTokenProvider } from '../../core/FirebaseAuthProvider'; -// Change this to only specify specific args. +/** + * @internal + */ export interface DataConnectTransport { invokeQuery( queryName: string, @@ -37,18 +39,9 @@ export interface CancellableOperation extends PromiseLike<{ data: T }> { cancel: () => void; } -export interface QueryResponse extends CancellableOperation {} -// export type QueryResponse = { -// // Type '{ data: T; }' is not assignable to type 'T'. -// then: (a: (data: T) => void) => void; -// } -export interface MutationResponse extends CancellableOperation {} - -export interface Sender { - abort: () => void; - send: () => Promise; -} - +/** + * @internal + */ export type TransportClass = new ( options: DataConnectOptions, apiKey?: string, @@ -57,4 +50,3 @@ export type TransportClass = new ( transportOptions?: TransportOptions, _isUsingGen?: boolean ) => DataConnectTransport; -export * from '../../core/FirebaseAuthProvider'; diff --git a/packages/database/src/core/AuthTokenProvider.ts b/packages/database/src/core/AuthTokenProvider.ts index af3a0ed4b83..40f90a41c37 100644 --- a/packages/database/src/core/AuthTokenProvider.ts +++ b/packages/database/src/core/AuthTokenProvider.ts @@ -24,6 +24,7 @@ import { Provider } from '@firebase/component'; import { log, warn } from './util/util'; +// @internal export interface AuthTokenProvider { getToken(forceRefresh: boolean): Promise; addTokenChangeListener(listener: (token: string | null) => void): void; From 1cc1f2a35f64a0685dfcc0a81e37c11ac01e1f05 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Thu, 5 Sep 2024 16:52:47 -0700 Subject: [PATCH 47/74] Updated license years --- packages/data-connect/.eslintrc.js | 2 +- packages/data-connect/src/core/AppCheckTokenProvider.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/data-connect/.eslintrc.js b/packages/data-connect/.eslintrc.js index 5dd443333d9..faef63a0395 100644 --- a/packages/data-connect/.eslintrc.js +++ b/packages/data-connect/.eslintrc.js @@ -1,6 +1,6 @@ /** * @license - * Copyright 2020 Google LLC + * 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. diff --git a/packages/data-connect/src/core/AppCheckTokenProvider.ts b/packages/data-connect/src/core/AppCheckTokenProvider.ts index dd80d0bea56..cc215d32b2b 100644 --- a/packages/data-connect/src/core/AppCheckTokenProvider.ts +++ b/packages/data-connect/src/core/AppCheckTokenProvider.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2021 Google LLC + * 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. From f8b585703704faf455818dbf49778db261ea3f9e Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Thu, 5 Sep 2024 16:56:13 -0700 Subject: [PATCH 48/74] removed internal --- packages/database/src/core/AuthTokenProvider.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/database/src/core/AuthTokenProvider.ts b/packages/database/src/core/AuthTokenProvider.ts index 40f90a41c37..af3a0ed4b83 100644 --- a/packages/database/src/core/AuthTokenProvider.ts +++ b/packages/database/src/core/AuthTokenProvider.ts @@ -24,7 +24,6 @@ import { Provider } from '@firebase/component'; import { log, warn } from './util/util'; -// @internal export interface AuthTokenProvider { getToken(forceRefresh: boolean): Promise; addTokenChangeListener(listener: (token: string | null) => void): void; From cedd6519a1762397684e98d8d22efafdbd818c2e Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Thu, 5 Sep 2024 16:57:14 -0700 Subject: [PATCH 49/74] Replaced minor with major --- .changeset/cold-chairs-fold.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/cold-chairs-fold.md b/.changeset/cold-chairs-fold.md index 61df53a3cdd..adf2b915214 100644 --- a/.changeset/cold-chairs-fold.md +++ b/.changeset/cold-chairs-fold.md @@ -1,5 +1,5 @@ --- -"@firebase/data-connect": minor +"@firebase/data-connect": major --- Included Data Connect product. From 64eff34565377beb8e0f970ba8611cba767ec4c1 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Thu, 5 Sep 2024 16:58:13 -0700 Subject: [PATCH 50/74] Removed auth changes --- packages/auth/api-extractor.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/auth/api-extractor.json b/packages/auth/api-extractor.json index 325fe12d507..ca7e1ee6983 100644 --- a/packages/auth/api-extractor.json +++ b/packages/auth/api-extractor.json @@ -1,6 +1,6 @@ { "extends": "../../config/api-extractor.json", - "mainEntryPointFilePath": "/dist/esm5/index.doc.d.ts", + "mainEntryPointFilePath": "/dist/esm5/index.d.ts", "dtsRollup": { "enabled": true, "untrimmedFilePath": "/dist/.d.ts", From 629d70b8ecdd7d788ed487ebcebad163cbf9fd16 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Thu, 5 Sep 2024 16:58:53 -0700 Subject: [PATCH 51/74] Removed repo-scripts changes --- .../src/documenters/IConfigFile.ts | 2 +- repo-scripts/changelog-generator/package.json | 2 +- repo-scripts/prune-dts/extract-public-api.ts | 12 ++++----- repo-scripts/prune-dts/package.json | 4 +-- repo-scripts/prune-dts/prune-dts.ts | 10 ++++---- .../prune-dts/tests/firestore.input.d.ts | 12 ++++----- .../prune-dts/tests/firestore.output.d.ts | 8 +++--- ...es-generics-through-inheritence.input.d.ts | 25 +++++++++++++++++++ repo-scripts/size-analysis/README.md | 4 +-- repo-scripts/size-analysis/bundle-analysis.ts | 2 +- .../size-analysis/package-analysis.ts | 2 +- repo-scripts/size-analysis/package.json | 4 +-- .../size-analysis/test/size-analysis.test.ts | 12 ++++----- .../size-analysis/test/test-inputs/bar.ts | 2 +- .../size-analysis/test/test-inputs/far.ts | 2 +- .../size-analysis/test/test-inputs/index.ts | 4 +-- 16 files changed, 65 insertions(+), 42 deletions(-) create mode 100644 repo-scripts/prune-dts/tests/resolves-generics-through-inheritence.input.d.ts diff --git a/repo-scripts/api-documenter/src/documenters/IConfigFile.ts b/repo-scripts/api-documenter/src/documenters/IConfigFile.ts index 2a7bf8ab358..fd95a719dd6 100644 --- a/repo-scripts/api-documenter/src/documenters/IConfigFile.ts +++ b/repo-scripts/api-documenter/src/documenters/IConfigFile.ts @@ -19,7 +19,7 @@ // See LICENSE in the project root for license information. /** - * TypeScript interface describing the config schema for toc.yml file format. + * Typescript interface describing the config schema for toc.yml file format. */ export interface IConfigTableOfContents { /** diff --git a/repo-scripts/changelog-generator/package.json b/repo-scripts/changelog-generator/package.json index f4afc131d1f..e04bb6f2ecf 100644 --- a/repo-scripts/changelog-generator/package.json +++ b/repo-scripts/changelog-generator/package.json @@ -20,7 +20,7 @@ "@changesets/types": "3.3.0", "@changesets/get-github-info": "0.5.2", "@types/node": "20.8.10", - "undici": "6.19.7" + "undici": "5.28.4" }, "license": "Apache-2.0", "devDependencies": { diff --git a/repo-scripts/prune-dts/extract-public-api.ts b/repo-scripts/prune-dts/extract-public-api.ts index c5c202f0734..c6ca172a70f 100644 --- a/repo-scripts/prune-dts/extract-public-api.ts +++ b/repo-scripts/prune-dts/extract-public-api.ts @@ -38,7 +38,7 @@ const baseApiExtractorConfigFile: string = path.resolve( const reportFolder = path.resolve(__dirname, '../../common/api-review'); const tmpDir = tmp.dirSync().name; -function writeTypeScriptConfig(packageRoot: string): void { +function writeTypescriptConfig(packageRoot: string): void { const tsConfigJson = { extends: path.resolve(packageRoot, './tsconfig.json'), include: [path.resolve(packageRoot, './src')], @@ -125,7 +125,7 @@ function loadApiExtractorConfig( * @param packageName - The name of the Firebase package (e.g. "database" or * "firestore-lite") * @param packageRoot - The root path of the package - * @param typescriptDtsPath - The .d.ts file generated by the TypeScript + * @param typescriptDtsPath - The .d.ts file generated by the Typescript * compiler as we transpile our sources * @param rollupDtsPath - A "bundled" version of our d.ts files that includes * all public and private types @@ -144,7 +144,7 @@ export async function generateApi( publicDtsPath: string ): Promise { console.log(`Configuring API Extractor for ${packageName}`); - writeTypeScriptConfig(packageRoot); + writeTypescriptConfig(packageRoot); writePackageJson(packageName); let extractorConfig = loadApiExtractorConfig( @@ -156,9 +156,7 @@ export async function generateApi( /* apiReportEnabled= */ false ); Extractor.invoke(extractorConfig, { - localBuild: true, - showDiagnostics: true, - showVerboseMessages: true + localBuild: true }); console.log('Generated rollup DTS'); @@ -198,7 +196,7 @@ const argv = yargs typescriptDts: { type: 'string', desc: - 'The .d.ts file generated by the TypeScript compiler as we transpile ' + + 'The .d.ts file generated by the Typescript compiler as we transpile ' + 'our sources', require: true }, diff --git a/repo-scripts/prune-dts/package.json b/repo-scripts/prune-dts/package.json index d8d4ab6391b..f48f530f38a 100644 --- a/repo-scripts/prune-dts/package.json +++ b/repo-scripts/prune-dts/package.json @@ -13,8 +13,8 @@ }, "license": "Apache-2.0", "dependencies": { - "eslint": "8.56.0", - "eslint-plugin-unused-imports": "3.2.0", + "eslint": "7.32.0", + "eslint-plugin-unused-imports": "2.0.0", "prettier": "2.8.7" }, "repository": { diff --git a/repo-scripts/prune-dts/prune-dts.ts b/repo-scripts/prune-dts/prune-dts.ts index dfd83a62a91..46ffecc19ec 100644 --- a/repo-scripts/prune-dts/prune-dts.ts +++ b/repo-scripts/prune-dts/prune-dts.ts @@ -108,7 +108,7 @@ export async function removeUnusedImports( /** Determines whether the provided identifier should be hidden. */ function hasPrivatePrefix(name: ts.Identifier): boolean { - // Identifiers that are prefixed with an underscore are not included in + // Identifiers that are prefixed with an underscore are not not included in // the public API. return !!name.escapedText?.toString().startsWith('_'); } @@ -123,11 +123,11 @@ function isExported( typeChecker.getSymbolAtLocation(name)?.declarations ?? []; // Check is this is a public symbol (e.g. part of the DOM library) - const isTypeScriptType = declarations.find( + const isTypescriptType = declarations.find( d => d.getSourceFile().fileName.indexOf('typescript/lib') != -1 ); const isImported = declarations.find(d => ts.isImportSpecifier(d)); - if (isTypeScriptType || isImported) { + if (isTypescriptType || isImported) { return true; } @@ -141,8 +141,8 @@ function isExported( /** * Replaces an existing constructor implementation if the constructor is marked * with the JSDod tag `@hideconstructor`. The replaced constructor can either - * have `private` visibility` or `protected`. To generate a protected - * constructor, specify `@hideconstructor protected`. + * have `private` visibility` or `proctected`. To generate a protected + * constructor, specify `@hideconstructor proctected`. * * Returns either the modified constructor or the existing constructor if no * modification was needed. diff --git a/repo-scripts/prune-dts/tests/firestore.input.d.ts b/repo-scripts/prune-dts/tests/firestore.input.d.ts index aa71ccffaf0..73e938a1b17 100644 --- a/repo-scripts/prune-dts/tests/firestore.input.d.ts +++ b/repo-scripts/prune-dts/tests/firestore.input.d.ts @@ -464,7 +464,7 @@ export declare function collection( /** * Gets a `CollectionReference` instance that refers to a subcollection of - * `reference` at the specified relative path. + * `reference` at the the specified relative path. * * @param reference - A reference to a collection. * @param path - A slash-separated path to a collection. @@ -482,7 +482,7 @@ export declare function collection( /** * Gets a `CollectionReference` instance that refers to a subcollection of - * `reference` at the specified relative path. + * `reference` at the the specified relative path. * * @param reference - A reference to a Firestore document. * @param path - A slash-separated path to a collection. @@ -794,7 +794,7 @@ export declare function disableNetwork( /** * Gets a `DocumentReference` instance that refers to the document at the - * specified absolute path. + * specified abosulute path. * * @param firestore - A reference to the root Firestore instance. * @param path - A slash-separated path to a document. @@ -818,7 +818,7 @@ export declare function doc( * * @param reference - A reference to a collection. * @param path - A slash-separated path to a document. Has to be omitted to use - * auto-generated IDs. + * auto-genrated IDs. * @param pathSegments - Additional path segments that will be applied relative * to the first argument. * @throws If the final path has an odd number of segments and does not point to @@ -1643,14 +1643,14 @@ export declare interface FirestoreDataConverter declare interface FirestoreDataConverter_2 { /** * Called by the Firestore SDK to convert a custom model object of type `T` - * into a plain JavaScript object (suitable for writing directly to the + * into a plain Javascript object (suitable for writing directly to the * Firestore database). Used with {@link setData}, {@link WriteBatch#set} * and {@link Transaction#set}. */ toFirestore(modelObject: T): DocumentData; /** * Called by the Firestore SDK to convert a custom model object of type `T` - * into a plain JavaScript object (suitable for writing directly to the + * into a plain Javascript object (suitable for writing directly to the * Firestore database). Used with {@link setData}, {@link WriteBatch#set} * and {@link Transaction#set} with `merge:true` or `mergeFields`. */ diff --git a/repo-scripts/prune-dts/tests/firestore.output.d.ts b/repo-scripts/prune-dts/tests/firestore.output.d.ts index 58411538953..5b4945c9b27 100644 --- a/repo-scripts/prune-dts/tests/firestore.output.d.ts +++ b/repo-scripts/prune-dts/tests/firestore.output.d.ts @@ -154,7 +154,7 @@ export declare function collection( ): CollectionReference; /** * Gets a `CollectionReference` instance that refers to a subcollection of - * `reference` at the specified relative path. + * `reference` at the the specified relative path. * * @param reference - A reference to a collection. * @param path - A slash-separated path to a collection. @@ -171,7 +171,7 @@ export declare function collection( ): CollectionReference; /** * Gets a `CollectionReference` instance that refers to a subcollection of - * `reference` at the specified relative path. + * `reference` at the the specified relative path. * * @param reference - A reference to a Firestore document. * @param path - A slash-separated path to a collection. @@ -262,7 +262,7 @@ export declare function disableNetwork( ): Promise; /** * Gets a `DocumentReference` instance that refers to the document at the - * specified absolute path. + * specified abosulute path. * * @param firestore - A reference to the root Firestore instance. * @param path - A slash-separated path to a document. @@ -285,7 +285,7 @@ export declare function doc( * * @param reference - A reference to a collection. * @param path - A slash-separated path to a document. Has to be omitted to use - * auto-generated IDs. + * auto-genrated IDs. * @param pathSegments - Additional path segments that will be applied relative * to the first argument. * @throws If the final path has an odd number of segments and does not point to diff --git a/repo-scripts/prune-dts/tests/resolves-generics-through-inheritence.input.d.ts b/repo-scripts/prune-dts/tests/resolves-generics-through-inheritence.input.d.ts new file mode 100644 index 00000000000..7208f2cf528 --- /dev/null +++ b/repo-scripts/prune-dts/tests/resolves-generics-through-inheritence.input.d.ts @@ -0,0 +1,25 @@ +/** + * @license + * Copyright 2020 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. + */ + +declare class A { + a: T; +} +export class B extends A { + b: T; +} +export class C extends A {} +export {}; diff --git a/repo-scripts/size-analysis/README.md b/repo-scripts/size-analysis/README.md index 6f81162b789..d29eb882ee4 100644 --- a/repo-scripts/size-analysis/README.md +++ b/repo-scripts/size-analysis/README.md @@ -28,13 +28,13 @@ $firebase-js-sdk/repo-scripts/size-analysis `ts-node-script cli.ts -o --ib -o ` -### Use the Tool Programmatically +### Use the Tool Programatically ### `async generateReportForModule(moduleLocation: string): Promise` #### This function generates size analysis report for the given module specified by the `moduleLocation` argument. #### `@param moduleLocation: an absolute path to location of a firebase module` ``` try { - const moduleLocation: string = "absolute/path/to/firebase/module"; + const moduleLocation: string = "absoulte/path/to/firebase/module"; const report: Report = await generateReportForModule(moduleLocation); console.log(report); diff --git a/repo-scripts/size-analysis/bundle-analysis.ts b/repo-scripts/size-analysis/bundle-analysis.ts index 98505ab6859..4045b2fc886 100644 --- a/repo-scripts/size-analysis/bundle-analysis.ts +++ b/repo-scripts/size-analysis/bundle-analysis.ts @@ -102,7 +102,7 @@ export async function run({ function loadBundleDefinitions(path: string): BundleDefinition[] { if (!existsSync(path)) { throw new Error( - `${path} doesn't exist. Please provide a valid path to the bundle definition file.` + `${path} doesn't exist. Please provide a valid path to the bundle defintion file.` ); } diff --git a/repo-scripts/size-analysis/package-analysis.ts b/repo-scripts/size-analysis/package-analysis.ts index e8d179e19fc..67a973c31b1 100644 --- a/repo-scripts/size-analysis/package-analysis.ts +++ b/repo-scripts/size-analysis/package-analysis.ts @@ -31,7 +31,7 @@ const projectRoot = dirname(resolve(__dirname, '../../package.json')); /** * Support Command Line Options * -- inputModule (optional) : can be left unspecified which results in running analysis on all exp modules. - * can specify one to many module names separated by space. + * can specify one to many module names seperated by space. * eg: --inputModule "@firebase/functions-exp" "firebase/auth-exp" * * -- inputDtsFile (optional) : adhoc support. Specify a path to dts file. Must enable -- inputBundleFile if this flag is specified. diff --git a/repo-scripts/size-analysis/package.json b/repo-scripts/size-analysis/package.json index e374d6d9d9c..866a2868064 100644 --- a/repo-scripts/size-analysis/package.json +++ b/repo-scripts/size-analysis/package.json @@ -35,14 +35,14 @@ "typescript": "4.7.4", "terser": "5.16.1", "yargs": "17.7.2", - "@firebase/util": "1.9.7", + "@firebase/util": "1.9.6", "gzip-size": "6.0.0", "glob": "7.2.3" }, "license": "Apache-2.0", "devDependencies": { "@firebase/logger": "0.4.2", - "@firebase/app": "0.10.10" + "@firebase/app": "0.10.3" }, "repository": { "directory": "repo-scripts/size-analysis", diff --git a/repo-scripts/size-analysis/test/size-analysis.test.ts b/repo-scripts/size-analysis/test/size-analysis.test.ts index d3d32ce1fae..790211e96d2 100644 --- a/repo-scripts/size-analysis/test/size-analysis.test.ts +++ b/repo-scripts/size-analysis/test/size-analysis.test.ts @@ -66,21 +66,21 @@ describe('extractExports', () => { expect(extractedDeclarations.variables).to.include.members([ 'basicVarDeclarationExport', 'basicVarStatementExport', - 'reExportVarStatementExport' + 'reExportVarStatmentExport' ]); }); it('test re-exported variable extractions from same module - named re-exports', () => { expect(extractedDeclarations.variables).to.include.members([ 'basicVarDeclarationExportFar', 'basicVarStatementExportFar', - 'reExportVarStatementExportFar' + 'reExportVarStatmentExportFar' ]); }); it('test re-exported variable extractions from same module - * re-exports', () => { expect(extractedDeclarations.variables).to.include.members([ 'basicVarDeclarationExportBar', 'basicVarStatementExportBar', - 'reExportVarStatementExportBar' + 'reExportVarStatmentExportBar' ]); }); @@ -296,7 +296,7 @@ describe('test dedup helper function', () => { }); describe('test replaceAll helper function', () => { - it('test replaceAll with multiple occurrences of an element', () => { + it('test replaceAll with multiple occurences of an element', () => { const memberList: MemberList = { functions: ['aFunc', 'aFunc', 'bFunc', 'cFunc'], classes: ['aClass', 'bClass', 'aClass', 'cClass'], @@ -320,7 +320,7 @@ describe('test replaceAll helper function', () => { ).to.equal(2); }); - it('test replaceAll with single occurrence of an element', () => { + it('test replaceAll with single occurence of an element', () => { const memberList: MemberList = { functions: ['aFunc', 'aFunc', 'bFunc', 'cFunc'], classes: ['aClass', 'bClass', 'aClass', 'cClass'], @@ -344,7 +344,7 @@ describe('test replaceAll helper function', () => { ).to.equal(1); }); - it('test replaceAll with zero occurrence of an element', () => { + it('test replaceAll with zero occurence of an element', () => { const memberList: MemberList = { functions: ['aFunc', 'aFunc', 'bFunc', 'cFunc'], classes: ['aClass', 'bClass', 'aClass', 'cClass'], diff --git a/repo-scripts/size-analysis/test/test-inputs/bar.ts b/repo-scripts/size-analysis/test/test-inputs/bar.ts index 534f820d685..ee727e325ec 100644 --- a/repo-scripts/size-analysis/test/test-inputs/bar.ts +++ b/repo-scripts/size-analysis/test/test-inputs/bar.ts @@ -18,7 +18,7 @@ import { LogLevel } from '@firebase/logger'; export let basicVarDeclarationExportBar: string; export const basicVarStatementExportBar = 'basicVarStatementExportBar'; -export const reExportVarStatementExportBar = LogLevel; +export const reExportVarStatmentExportBar = LogLevel; export enum BasicEnumExportBar { DEBUG = 0, diff --git a/repo-scripts/size-analysis/test/test-inputs/far.ts b/repo-scripts/size-analysis/test/test-inputs/far.ts index 7339c51e4e4..7688eef785c 100644 --- a/repo-scripts/size-analysis/test/test-inputs/far.ts +++ b/repo-scripts/size-analysis/test/test-inputs/far.ts @@ -18,7 +18,7 @@ import { LogLevel } from '@firebase/logger'; export let basicVarDeclarationExportFar: string; export const basicVarStatementExportFar = 'basicVarStatementExportFar'; -export const reExportVarStatementExportFar = name; +export const reExportVarStatmentExportFar = name; export enum BasicEnumExportFar { DEBUG = 0, diff --git a/repo-scripts/size-analysis/test/test-inputs/index.ts b/repo-scripts/size-analysis/test/test-inputs/index.ts index f071a54fbd0..3a478abf165 100644 --- a/repo-scripts/size-analysis/test/test-inputs/index.ts +++ b/repo-scripts/size-analysis/test/test-inputs/index.ts @@ -33,11 +33,11 @@ export { basicUniqueFuncFar, basicVarDeclarationExportFar, basicVarStatementExportFar, - reExportVarStatementExportFar + reExportVarStatmentExportFar } from './far'; export let basicVarDeclarationExport: string; export const basicVarStatementExport = 'basicVarStatementExport'; -export const reExportVarStatementExport = LogLevel1; +export const reExportVarStatmentExport = LogLevel1; export enum BasicEnumExport { DEBUG = 0, From f1c1e64899529f866595d737907667893462d0e4 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Thu, 5 Sep 2024 17:08:20 -0700 Subject: [PATCH 52/74] Made emulator changes more scalable --- .../data-connect/test/unit/queries.test.ts | 3 +-- .../emulators/dataconnect-emulator.ts | 25 ++++++++++++++++--- .../emulator-testing/emulators/emulator.ts | 4 +-- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/packages/data-connect/test/unit/queries.test.ts b/packages/data-connect/test/unit/queries.test.ts index aa94428a6d7..5b1643707df 100644 --- a/packages/data-connect/test/unit/queries.test.ts +++ b/packages/data-connect/test/unit/queries.test.ts @@ -22,12 +22,11 @@ import chaiAsPromised from 'chai-as-promised'; import * as sinon from 'sinon'; import { - AuthTokenListener, - AuthTokenProvider, DataConnectOptions } from '../../src'; import { initializeFetch } from '../../src/network/fetch'; import { RESTTransport } from '../../src/network/transport/rest'; +import { AuthTokenListener, AuthTokenProvider } from '../../src/core/FirebaseAuthProvider'; chai.use(chaiAsPromised); const options: DataConnectOptions = { connector: 'c', diff --git a/scripts/emulator-testing/emulators/dataconnect-emulator.ts b/scripts/emulator-testing/emulators/dataconnect-emulator.ts index 487319e5ffd..823bfde6245 100644 --- a/scripts/emulator-testing/emulators/dataconnect-emulator.ts +++ b/scripts/emulator-testing/emulators/dataconnect-emulator.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import { platform } from 'os'; import { Emulator } from './emulator'; const DATABASE_EMULATOR_VERSION = '1.1.17'; @@ -22,16 +23,32 @@ const DATABASE_EMULATOR_VERSION = '1.1.17'; export class DataConnectEmulator extends Emulator { // namespace: string; - constructor(port = 3628, namespace = 'test-emulator') { + constructor(port = 3628) { + const os = platform(); + let urlString = ''; + switch(os) { + case 'darwin': + urlString = 'https://firebasestorage.googleapis.com/v0/b/firemat-preview-drop/o/emulator%2Fdataconnect-emulator-macos-v1.3.5?alt=media&token=52c3db6e-2a2a-4094-a482-a8c85ae67a88'; + break; + case 'linux': + urlString = 'https://firebasestorage.googleapis.com/v0/b/firemat-preview-drop/o/emulator%2Fdataconnect-emulator-linux-v1.3.5?alt=media&token=bafb1f81-2a27-4851-b655-59934985b492'; + break; + case 'win32': + urlString = 'https://firebasestorage.googleapis.com/v0/b/firemat-preview-drop/o/emulator%2Fdataconnect-emulator-windows-v1.3.5?alt=media&token=d3d04c57-992f-4a4b-931d-5c90efd54c5a'; + break; + default: + throw new Error(`We are unable to support your environment ${os} at this time.`); + + + } super( `cli-v${DATABASE_EMULATOR_VERSION}`, // Use locked version of emulator for test to be deterministic. // The latest version can be found from database emulator doc: // https://firebase.google.com/docs/database/security/test-rules-emulator - `https://firebasestorage.googleapis.com/v0/b/firemat-preview-drop/o/emulator%2Fdataconnect-emulator-macos-v1.1.17?alt=media&token=c5e758bc-aaad-4be6-bd41-bcc08f3944a7`, + urlString, port ); - this.isJar = false; - // this.namespace = namespace; + this.isDataConnect = true; } } diff --git a/scripts/emulator-testing/emulators/emulator.ts b/scripts/emulator-testing/emulators/emulator.ts index 48de98500f5..6f88c9769e4 100644 --- a/scripts/emulator-testing/emulators/emulator.ts +++ b/scripts/emulator-testing/emulators/emulator.ts @@ -36,7 +36,7 @@ export abstract class Emulator { cacheDirectory: string; cacheBinaryPath: string; - isJar = true; + isDataConnect = false; constructor( private binaryName: string, @@ -96,7 +96,7 @@ export abstract class Emulator { throw new Error('You must call download() before setUp()'); } let promise: ChildProcessPromise; - if (!this.isJar) { + if (this.isDataConnect) { promise = spawn(this.binaryPath, [ 'dev', '--local_connection_string', From 9a5b1b909e0e5f3b2faa50187fe338677f34023e Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Thu, 5 Sep 2024 17:09:15 -0700 Subject: [PATCH 53/74] Fixed formatting --- packages/data-connect/src/api/DataConnect.ts | 4 +--- .../data-connect/test/unit/queries.test.ts | 9 ++++---- .../emulators/dataconnect-emulator.ts | 23 +++++++++++-------- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/packages/data-connect/src/api/DataConnect.ts b/packages/data-connect/src/api/DataConnect.ts index 552d074d6c1..9393b9ef4e4 100644 --- a/packages/data-connect/src/api/DataConnect.ts +++ b/packages/data-connect/src/api/DataConnect.ts @@ -21,9 +21,7 @@ import { _removeServiceInstance, getApp } from '@firebase/app'; -import { - AppCheckInternalComponentName -} from '@firebase/app-check-interop-types'; +import { AppCheckInternalComponentName } from '@firebase/app-check-interop-types'; import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; import { Provider } from '@firebase/component'; diff --git a/packages/data-connect/test/unit/queries.test.ts b/packages/data-connect/test/unit/queries.test.ts index 5b1643707df..bc1dc1311c2 100644 --- a/packages/data-connect/test/unit/queries.test.ts +++ b/packages/data-connect/test/unit/queries.test.ts @@ -21,12 +21,13 @@ import * as chai from 'chai'; import chaiAsPromised from 'chai-as-promised'; import * as sinon from 'sinon'; -import { - DataConnectOptions -} from '../../src'; +import { DataConnectOptions } from '../../src'; import { initializeFetch } from '../../src/network/fetch'; import { RESTTransport } from '../../src/network/transport/rest'; -import { AuthTokenListener, AuthTokenProvider } from '../../src/core/FirebaseAuthProvider'; +import { + AuthTokenListener, + AuthTokenProvider +} from '../../src/core/FirebaseAuthProvider'; chai.use(chaiAsPromised); const options: DataConnectOptions = { connector: 'c', diff --git a/scripts/emulator-testing/emulators/dataconnect-emulator.ts b/scripts/emulator-testing/emulators/dataconnect-emulator.ts index 823bfde6245..012aca578ca 100644 --- a/scripts/emulator-testing/emulators/dataconnect-emulator.ts +++ b/scripts/emulator-testing/emulators/dataconnect-emulator.ts @@ -26,20 +26,23 @@ export class DataConnectEmulator extends Emulator { constructor(port = 3628) { const os = platform(); let urlString = ''; - switch(os) { + switch (os) { case 'darwin': - urlString = 'https://firebasestorage.googleapis.com/v0/b/firemat-preview-drop/o/emulator%2Fdataconnect-emulator-macos-v1.3.5?alt=media&token=52c3db6e-2a2a-4094-a482-a8c85ae67a88'; - break; + urlString = + 'https://firebasestorage.googleapis.com/v0/b/firemat-preview-drop/o/emulator%2Fdataconnect-emulator-macos-v1.3.5?alt=media&token=52c3db6e-2a2a-4094-a482-a8c85ae67a88'; + break; case 'linux': - urlString = 'https://firebasestorage.googleapis.com/v0/b/firemat-preview-drop/o/emulator%2Fdataconnect-emulator-linux-v1.3.5?alt=media&token=bafb1f81-2a27-4851-b655-59934985b492'; - break; + urlString = + 'https://firebasestorage.googleapis.com/v0/b/firemat-preview-drop/o/emulator%2Fdataconnect-emulator-linux-v1.3.5?alt=media&token=bafb1f81-2a27-4851-b655-59934985b492'; + break; case 'win32': - urlString = 'https://firebasestorage.googleapis.com/v0/b/firemat-preview-drop/o/emulator%2Fdataconnect-emulator-windows-v1.3.5?alt=media&token=d3d04c57-992f-4a4b-931d-5c90efd54c5a'; - break; + urlString = + 'https://firebasestorage.googleapis.com/v0/b/firemat-preview-drop/o/emulator%2Fdataconnect-emulator-windows-v1.3.5?alt=media&token=d3d04c57-992f-4a4b-931d-5c90efd54c5a'; + break; default: - throw new Error(`We are unable to support your environment ${os} at this time.`); - - + throw new Error( + `We are unable to support your environment ${os} at this time.` + ); } super( `cli-v${DATABASE_EMULATOR_VERSION}`, From 6aabfc4108ff3e723112256816d9fd9b688c8bbe Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Thu, 5 Sep 2024 17:11:20 -0700 Subject: [PATCH 54/74] Removed fetch provider --- packages/util/index.node.ts | 1 - packages/util/index.ts | 1 - packages/util/src/fetch_provider.ts | 73 ----------------------------- 3 files changed, 75 deletions(-) delete mode 100644 packages/util/src/fetch_provider.ts diff --git a/packages/util/index.node.ts b/packages/util/index.node.ts index c7ba551a368..9c3b54b1c86 100644 --- a/packages/util/index.node.ts +++ b/packages/util/index.node.ts @@ -43,4 +43,3 @@ export * from './src/exponential_backoff'; export * from './src/formatters'; export * from './src/compat'; export * from './src/global'; -export * from './src/fetch_provider'; diff --git a/packages/util/index.ts b/packages/util/index.ts index 7dc79a09ebb..38b944cd9b5 100644 --- a/packages/util/index.ts +++ b/packages/util/index.ts @@ -38,4 +38,3 @@ export * from './src/exponential_backoff'; export * from './src/formatters'; export * from './src/compat'; export * from './src/global'; -export * from './src/fetch_provider'; diff --git a/packages/util/src/fetch_provider.ts b/packages/util/src/fetch_provider.ts deleted file mode 100644 index 0f01f0a6080..00000000000 --- a/packages/util/src/fetch_provider.ts +++ /dev/null @@ -1,73 +0,0 @@ -/** - * @license - * Copyright 2023 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. - */ - -// @internal -export class FetchProvider { - private static fetchImpl: typeof fetch | null; - private static headersImpl: typeof Headers | null; - private static responseImpl: typeof Response | null; - - static initialize( - fetchImpl: typeof fetch, - headersImpl?: typeof Headers, - responseImpl?: typeof Response - ): void { - this.fetchImpl = fetchImpl; - if (headersImpl) { - this.headersImpl = headersImpl; - } - if (responseImpl) { - this.responseImpl = responseImpl; - } - } - - static fetch(): typeof fetch { - if (this.fetchImpl) { - return this.fetchImpl; - } - if (typeof self !== 'undefined' && 'fetch' in self) { - return self.fetch; - } - throw new Error( - 'Could not find fetch implementation, make sure you call FetchProvider.initialize() with an appropriate polyfill' - ); - } - - static headers(): typeof Headers { - if (this.headersImpl) { - return this.headersImpl; - } - if (typeof self !== 'undefined' && 'Headers' in self) { - return self.Headers; - } - throw new Error( - 'Could not find Headers implementation, make sure you call FetchProvider.initialize() with an appropriate polyfill' - ); - } - - static response(): typeof Response { - if (this.responseImpl) { - return this.responseImpl; - } - if (typeof self !== 'undefined' && 'Response' in self) { - return self.Response; - } - throw new Error( - 'Could not find Response implementation, make sure you call FetchProvider.initialize() with an appropriate polyfill' - ); - } -} From 310b23581e9cfd17d3797ee5d9f37b59f4d54487 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Thu, 5 Sep 2024 17:18:36 -0700 Subject: [PATCH 55/74] Removed repo-scripts changes --- .../api-documenter/src/documenters/IConfigFile.ts | 2 +- repo-scripts/changelog-generator/package.json | 2 +- repo-scripts/prune-dts/extract-public-api.ts | 8 ++++---- repo-scripts/prune-dts/package.json | 4 ++-- repo-scripts/prune-dts/prune-dts.ts | 10 +++++----- repo-scripts/prune-dts/tests/firestore.input.d.ts | 12 ++++++------ repo-scripts/prune-dts/tests/firestore.output.d.ts | 8 ++++---- repo-scripts/size-analysis/README.md | 4 ++-- repo-scripts/size-analysis/bundle-analysis.ts | 2 +- repo-scripts/size-analysis/package-analysis.ts | 2 +- repo-scripts/size-analysis/package.json | 4 ++-- .../size-analysis/test/size-analysis.test.ts | 12 ++++++------ repo-scripts/size-analysis/test/test-inputs/bar.ts | 2 +- repo-scripts/size-analysis/test/test-inputs/far.ts | 2 +- repo-scripts/size-analysis/test/test-inputs/index.ts | 4 ++-- 15 files changed, 39 insertions(+), 39 deletions(-) diff --git a/repo-scripts/api-documenter/src/documenters/IConfigFile.ts b/repo-scripts/api-documenter/src/documenters/IConfigFile.ts index fd95a719dd6..2a7bf8ab358 100644 --- a/repo-scripts/api-documenter/src/documenters/IConfigFile.ts +++ b/repo-scripts/api-documenter/src/documenters/IConfigFile.ts @@ -19,7 +19,7 @@ // See LICENSE in the project root for license information. /** - * Typescript interface describing the config schema for toc.yml file format. + * TypeScript interface describing the config schema for toc.yml file format. */ export interface IConfigTableOfContents { /** diff --git a/repo-scripts/changelog-generator/package.json b/repo-scripts/changelog-generator/package.json index e04bb6f2ecf..f4afc131d1f 100644 --- a/repo-scripts/changelog-generator/package.json +++ b/repo-scripts/changelog-generator/package.json @@ -20,7 +20,7 @@ "@changesets/types": "3.3.0", "@changesets/get-github-info": "0.5.2", "@types/node": "20.8.10", - "undici": "5.28.4" + "undici": "6.19.7" }, "license": "Apache-2.0", "devDependencies": { diff --git a/repo-scripts/prune-dts/extract-public-api.ts b/repo-scripts/prune-dts/extract-public-api.ts index c6ca172a70f..c7517399565 100644 --- a/repo-scripts/prune-dts/extract-public-api.ts +++ b/repo-scripts/prune-dts/extract-public-api.ts @@ -38,7 +38,7 @@ const baseApiExtractorConfigFile: string = path.resolve( const reportFolder = path.resolve(__dirname, '../../common/api-review'); const tmpDir = tmp.dirSync().name; -function writeTypescriptConfig(packageRoot: string): void { +function writeTypeScriptConfig(packageRoot: string): void { const tsConfigJson = { extends: path.resolve(packageRoot, './tsconfig.json'), include: [path.resolve(packageRoot, './src')], @@ -125,7 +125,7 @@ function loadApiExtractorConfig( * @param packageName - The name of the Firebase package (e.g. "database" or * "firestore-lite") * @param packageRoot - The root path of the package - * @param typescriptDtsPath - The .d.ts file generated by the Typescript + * @param typescriptDtsPath - The .d.ts file generated by the TypeScript * compiler as we transpile our sources * @param rollupDtsPath - A "bundled" version of our d.ts files that includes * all public and private types @@ -144,7 +144,7 @@ export async function generateApi( publicDtsPath: string ): Promise { console.log(`Configuring API Extractor for ${packageName}`); - writeTypescriptConfig(packageRoot); + writeTypeScriptConfig(packageRoot); writePackageJson(packageName); let extractorConfig = loadApiExtractorConfig( @@ -196,7 +196,7 @@ const argv = yargs typescriptDts: { type: 'string', desc: - 'The .d.ts file generated by the Typescript compiler as we transpile ' + + 'The .d.ts file generated by the TypeScript compiler as we transpile ' + 'our sources', require: true }, diff --git a/repo-scripts/prune-dts/package.json b/repo-scripts/prune-dts/package.json index f48f530f38a..d8d4ab6391b 100644 --- a/repo-scripts/prune-dts/package.json +++ b/repo-scripts/prune-dts/package.json @@ -13,8 +13,8 @@ }, "license": "Apache-2.0", "dependencies": { - "eslint": "7.32.0", - "eslint-plugin-unused-imports": "2.0.0", + "eslint": "8.56.0", + "eslint-plugin-unused-imports": "3.2.0", "prettier": "2.8.7" }, "repository": { diff --git a/repo-scripts/prune-dts/prune-dts.ts b/repo-scripts/prune-dts/prune-dts.ts index 46ffecc19ec..dfd83a62a91 100644 --- a/repo-scripts/prune-dts/prune-dts.ts +++ b/repo-scripts/prune-dts/prune-dts.ts @@ -108,7 +108,7 @@ export async function removeUnusedImports( /** Determines whether the provided identifier should be hidden. */ function hasPrivatePrefix(name: ts.Identifier): boolean { - // Identifiers that are prefixed with an underscore are not not included in + // Identifiers that are prefixed with an underscore are not included in // the public API. return !!name.escapedText?.toString().startsWith('_'); } @@ -123,11 +123,11 @@ function isExported( typeChecker.getSymbolAtLocation(name)?.declarations ?? []; // Check is this is a public symbol (e.g. part of the DOM library) - const isTypescriptType = declarations.find( + const isTypeScriptType = declarations.find( d => d.getSourceFile().fileName.indexOf('typescript/lib') != -1 ); const isImported = declarations.find(d => ts.isImportSpecifier(d)); - if (isTypescriptType || isImported) { + if (isTypeScriptType || isImported) { return true; } @@ -141,8 +141,8 @@ function isExported( /** * Replaces an existing constructor implementation if the constructor is marked * with the JSDod tag `@hideconstructor`. The replaced constructor can either - * have `private` visibility` or `proctected`. To generate a protected - * constructor, specify `@hideconstructor proctected`. + * have `private` visibility` or `protected`. To generate a protected + * constructor, specify `@hideconstructor protected`. * * Returns either the modified constructor or the existing constructor if no * modification was needed. diff --git a/repo-scripts/prune-dts/tests/firestore.input.d.ts b/repo-scripts/prune-dts/tests/firestore.input.d.ts index 73e938a1b17..aa71ccffaf0 100644 --- a/repo-scripts/prune-dts/tests/firestore.input.d.ts +++ b/repo-scripts/prune-dts/tests/firestore.input.d.ts @@ -464,7 +464,7 @@ export declare function collection( /** * Gets a `CollectionReference` instance that refers to a subcollection of - * `reference` at the the specified relative path. + * `reference` at the specified relative path. * * @param reference - A reference to a collection. * @param path - A slash-separated path to a collection. @@ -482,7 +482,7 @@ export declare function collection( /** * Gets a `CollectionReference` instance that refers to a subcollection of - * `reference` at the the specified relative path. + * `reference` at the specified relative path. * * @param reference - A reference to a Firestore document. * @param path - A slash-separated path to a collection. @@ -794,7 +794,7 @@ export declare function disableNetwork( /** * Gets a `DocumentReference` instance that refers to the document at the - * specified abosulute path. + * specified absolute path. * * @param firestore - A reference to the root Firestore instance. * @param path - A slash-separated path to a document. @@ -818,7 +818,7 @@ export declare function doc( * * @param reference - A reference to a collection. * @param path - A slash-separated path to a document. Has to be omitted to use - * auto-genrated IDs. + * auto-generated IDs. * @param pathSegments - Additional path segments that will be applied relative * to the first argument. * @throws If the final path has an odd number of segments and does not point to @@ -1643,14 +1643,14 @@ export declare interface FirestoreDataConverter declare interface FirestoreDataConverter_2 { /** * Called by the Firestore SDK to convert a custom model object of type `T` - * into a plain Javascript object (suitable for writing directly to the + * into a plain JavaScript object (suitable for writing directly to the * Firestore database). Used with {@link setData}, {@link WriteBatch#set} * and {@link Transaction#set}. */ toFirestore(modelObject: T): DocumentData; /** * Called by the Firestore SDK to convert a custom model object of type `T` - * into a plain Javascript object (suitable for writing directly to the + * into a plain JavaScript object (suitable for writing directly to the * Firestore database). Used with {@link setData}, {@link WriteBatch#set} * and {@link Transaction#set} with `merge:true` or `mergeFields`. */ diff --git a/repo-scripts/prune-dts/tests/firestore.output.d.ts b/repo-scripts/prune-dts/tests/firestore.output.d.ts index 5b4945c9b27..58411538953 100644 --- a/repo-scripts/prune-dts/tests/firestore.output.d.ts +++ b/repo-scripts/prune-dts/tests/firestore.output.d.ts @@ -154,7 +154,7 @@ export declare function collection( ): CollectionReference; /** * Gets a `CollectionReference` instance that refers to a subcollection of - * `reference` at the the specified relative path. + * `reference` at the specified relative path. * * @param reference - A reference to a collection. * @param path - A slash-separated path to a collection. @@ -171,7 +171,7 @@ export declare function collection( ): CollectionReference; /** * Gets a `CollectionReference` instance that refers to a subcollection of - * `reference` at the the specified relative path. + * `reference` at the specified relative path. * * @param reference - A reference to a Firestore document. * @param path - A slash-separated path to a collection. @@ -262,7 +262,7 @@ export declare function disableNetwork( ): Promise; /** * Gets a `DocumentReference` instance that refers to the document at the - * specified abosulute path. + * specified absolute path. * * @param firestore - A reference to the root Firestore instance. * @param path - A slash-separated path to a document. @@ -285,7 +285,7 @@ export declare function doc( * * @param reference - A reference to a collection. * @param path - A slash-separated path to a document. Has to be omitted to use - * auto-genrated IDs. + * auto-generated IDs. * @param pathSegments - Additional path segments that will be applied relative * to the first argument. * @throws If the final path has an odd number of segments and does not point to diff --git a/repo-scripts/size-analysis/README.md b/repo-scripts/size-analysis/README.md index d29eb882ee4..6f81162b789 100644 --- a/repo-scripts/size-analysis/README.md +++ b/repo-scripts/size-analysis/README.md @@ -28,13 +28,13 @@ $firebase-js-sdk/repo-scripts/size-analysis `ts-node-script cli.ts -o --ib -o ` -### Use the Tool Programatically +### Use the Tool Programmatically ### `async generateReportForModule(moduleLocation: string): Promise` #### This function generates size analysis report for the given module specified by the `moduleLocation` argument. #### `@param moduleLocation: an absolute path to location of a firebase module` ``` try { - const moduleLocation: string = "absoulte/path/to/firebase/module"; + const moduleLocation: string = "absolute/path/to/firebase/module"; const report: Report = await generateReportForModule(moduleLocation); console.log(report); diff --git a/repo-scripts/size-analysis/bundle-analysis.ts b/repo-scripts/size-analysis/bundle-analysis.ts index 4045b2fc886..98505ab6859 100644 --- a/repo-scripts/size-analysis/bundle-analysis.ts +++ b/repo-scripts/size-analysis/bundle-analysis.ts @@ -102,7 +102,7 @@ export async function run({ function loadBundleDefinitions(path: string): BundleDefinition[] { if (!existsSync(path)) { throw new Error( - `${path} doesn't exist. Please provide a valid path to the bundle defintion file.` + `${path} doesn't exist. Please provide a valid path to the bundle definition file.` ); } diff --git a/repo-scripts/size-analysis/package-analysis.ts b/repo-scripts/size-analysis/package-analysis.ts index 67a973c31b1..e8d179e19fc 100644 --- a/repo-scripts/size-analysis/package-analysis.ts +++ b/repo-scripts/size-analysis/package-analysis.ts @@ -31,7 +31,7 @@ const projectRoot = dirname(resolve(__dirname, '../../package.json')); /** * Support Command Line Options * -- inputModule (optional) : can be left unspecified which results in running analysis on all exp modules. - * can specify one to many module names seperated by space. + * can specify one to many module names separated by space. * eg: --inputModule "@firebase/functions-exp" "firebase/auth-exp" * * -- inputDtsFile (optional) : adhoc support. Specify a path to dts file. Must enable -- inputBundleFile if this flag is specified. diff --git a/repo-scripts/size-analysis/package.json b/repo-scripts/size-analysis/package.json index 866a2868064..e374d6d9d9c 100644 --- a/repo-scripts/size-analysis/package.json +++ b/repo-scripts/size-analysis/package.json @@ -35,14 +35,14 @@ "typescript": "4.7.4", "terser": "5.16.1", "yargs": "17.7.2", - "@firebase/util": "1.9.6", + "@firebase/util": "1.9.7", "gzip-size": "6.0.0", "glob": "7.2.3" }, "license": "Apache-2.0", "devDependencies": { "@firebase/logger": "0.4.2", - "@firebase/app": "0.10.3" + "@firebase/app": "0.10.10" }, "repository": { "directory": "repo-scripts/size-analysis", diff --git a/repo-scripts/size-analysis/test/size-analysis.test.ts b/repo-scripts/size-analysis/test/size-analysis.test.ts index 790211e96d2..d3d32ce1fae 100644 --- a/repo-scripts/size-analysis/test/size-analysis.test.ts +++ b/repo-scripts/size-analysis/test/size-analysis.test.ts @@ -66,21 +66,21 @@ describe('extractExports', () => { expect(extractedDeclarations.variables).to.include.members([ 'basicVarDeclarationExport', 'basicVarStatementExport', - 'reExportVarStatmentExport' + 'reExportVarStatementExport' ]); }); it('test re-exported variable extractions from same module - named re-exports', () => { expect(extractedDeclarations.variables).to.include.members([ 'basicVarDeclarationExportFar', 'basicVarStatementExportFar', - 'reExportVarStatmentExportFar' + 'reExportVarStatementExportFar' ]); }); it('test re-exported variable extractions from same module - * re-exports', () => { expect(extractedDeclarations.variables).to.include.members([ 'basicVarDeclarationExportBar', 'basicVarStatementExportBar', - 'reExportVarStatmentExportBar' + 'reExportVarStatementExportBar' ]); }); @@ -296,7 +296,7 @@ describe('test dedup helper function', () => { }); describe('test replaceAll helper function', () => { - it('test replaceAll with multiple occurences of an element', () => { + it('test replaceAll with multiple occurrences of an element', () => { const memberList: MemberList = { functions: ['aFunc', 'aFunc', 'bFunc', 'cFunc'], classes: ['aClass', 'bClass', 'aClass', 'cClass'], @@ -320,7 +320,7 @@ describe('test replaceAll helper function', () => { ).to.equal(2); }); - it('test replaceAll with single occurence of an element', () => { + it('test replaceAll with single occurrence of an element', () => { const memberList: MemberList = { functions: ['aFunc', 'aFunc', 'bFunc', 'cFunc'], classes: ['aClass', 'bClass', 'aClass', 'cClass'], @@ -344,7 +344,7 @@ describe('test replaceAll helper function', () => { ).to.equal(1); }); - it('test replaceAll with zero occurence of an element', () => { + it('test replaceAll with zero occurrence of an element', () => { const memberList: MemberList = { functions: ['aFunc', 'aFunc', 'bFunc', 'cFunc'], classes: ['aClass', 'bClass', 'aClass', 'cClass'], diff --git a/repo-scripts/size-analysis/test/test-inputs/bar.ts b/repo-scripts/size-analysis/test/test-inputs/bar.ts index ee727e325ec..534f820d685 100644 --- a/repo-scripts/size-analysis/test/test-inputs/bar.ts +++ b/repo-scripts/size-analysis/test/test-inputs/bar.ts @@ -18,7 +18,7 @@ import { LogLevel } from '@firebase/logger'; export let basicVarDeclarationExportBar: string; export const basicVarStatementExportBar = 'basicVarStatementExportBar'; -export const reExportVarStatmentExportBar = LogLevel; +export const reExportVarStatementExportBar = LogLevel; export enum BasicEnumExportBar { DEBUG = 0, diff --git a/repo-scripts/size-analysis/test/test-inputs/far.ts b/repo-scripts/size-analysis/test/test-inputs/far.ts index 7688eef785c..7339c51e4e4 100644 --- a/repo-scripts/size-analysis/test/test-inputs/far.ts +++ b/repo-scripts/size-analysis/test/test-inputs/far.ts @@ -18,7 +18,7 @@ import { LogLevel } from '@firebase/logger'; export let basicVarDeclarationExportFar: string; export const basicVarStatementExportFar = 'basicVarStatementExportFar'; -export const reExportVarStatmentExportFar = name; +export const reExportVarStatementExportFar = name; export enum BasicEnumExportFar { DEBUG = 0, diff --git a/repo-scripts/size-analysis/test/test-inputs/index.ts b/repo-scripts/size-analysis/test/test-inputs/index.ts index 3a478abf165..f071a54fbd0 100644 --- a/repo-scripts/size-analysis/test/test-inputs/index.ts +++ b/repo-scripts/size-analysis/test/test-inputs/index.ts @@ -33,11 +33,11 @@ export { basicUniqueFuncFar, basicVarDeclarationExportFar, basicVarStatementExportFar, - reExportVarStatmentExportFar + reExportVarStatementExportFar } from './far'; export let basicVarDeclarationExport: string; export const basicVarStatementExport = 'basicVarStatementExport'; -export const reExportVarStatmentExport = LogLevel1; +export const reExportVarStatementExport = LogLevel1; export enum BasicEnumExport { DEBUG = 0, From 81918594e791ab516855d8b8a71a2e3ccb9fc94d Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Thu, 5 Sep 2024 17:20:19 -0700 Subject: [PATCH 56/74] Updated data connect product --- .changeset/cold-chairs-fold.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.changeset/cold-chairs-fold.md b/.changeset/cold-chairs-fold.md index adf2b915214..532b7fb0453 100644 --- a/.changeset/cold-chairs-fold.md +++ b/.changeset/cold-chairs-fold.md @@ -1,5 +1,7 @@ --- -"@firebase/data-connect": major +"firebase": major +"@firebase/app": patch +"@firebase/data-connect": patch --- Included Data Connect product. From 86456bdfecdc336fa779dd8da10f46adf00b7422 Mon Sep 17 00:00:00 2001 From: maneesht Date: Fri, 6 Sep 2024 00:34:48 +0000 Subject: [PATCH 57/74] Update API reports --- common/api-review/auth.api.md | 28 +++++++++++----------------- common/api-review/util.api.md | 14 -------------- 2 files changed, 11 insertions(+), 31 deletions(-) diff --git a/common/api-review/auth.api.md b/common/api-review/auth.api.md index 89a71e26b7b..8e915daf731 100644 --- a/common/api-review/auth.api.md +++ b/common/api-review/auth.api.md @@ -104,14 +104,14 @@ export class AuthCredential { protected constructor( providerId: string, signInMethod: string); - // Warning: (ae-forgotten-export) The symbol "AuthInternal" needs to be exported by the entry point index.doc.d.ts - // Warning: (ae-forgotten-export) The symbol "PhoneOrOauthTokenResponse" needs to be exported by the entry point index.doc.d.ts + // Warning: (ae-forgotten-export) The symbol "AuthInternal" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "PhoneOrOauthTokenResponse" needs to be exported by the entry point index.d.ts // // @internal (undocumented) _getIdTokenResponse(_auth: AuthInternal): Promise; // @internal (undocumented) _getReauthenticationResolver(_auth: AuthInternal): Promise; - // Warning: (ae-forgotten-export) The symbol "IdTokenResponse" needs to be exported by the entry point index.doc.d.ts + // Warning: (ae-forgotten-export) The symbol "IdTokenResponse" needs to be exported by the entry point index.d.ts // // @internal (undocumented) _linkToIdToken(_auth: AuthInternal, _idToken: string): Promise; @@ -293,9 +293,6 @@ export function connectAuthEmulator(auth: Auth, url: string, options?: { disableWarnings: boolean; }): void; -// @public -export const cordovaPopupRedirectResolver: PopupRedirectResolver; - // @public export function createUserWithEmailAndPassword(auth: Auth, email: string, password: string): Promise; @@ -359,7 +356,7 @@ export interface EmulatorConfig { export { ErrorFn } -// Warning: (ae-forgotten-export) The symbol "BaseOAuthProvider" needs to be exported by the entry point index.doc.d.ts +// Warning: (ae-forgotten-export) The symbol "BaseOAuthProvider" needs to be exported by the entry point index.d.ts // // @public export class FacebookAuthProvider extends BaseOAuthProvider { @@ -395,9 +392,6 @@ export function getIdTokenResult(user: User, forceRefresh?: boolean): Promise; @@ -510,7 +504,7 @@ export type NextOrObserver = NextFn | Observer; export class OAuthCredential extends AuthCredential { accessToken?: string; static fromJSON(json: string | object): OAuthCredential | null; - // Warning: (ae-forgotten-export) The symbol "OAuthCredentialParams" needs to be exported by the entry point index.doc.d.ts + // Warning: (ae-forgotten-export) The symbol "OAuthCredentialParams" needs to be exported by the entry point index.d.ts // // @internal (undocumented) static _fromParams(params: OAuthCredentialParams): OAuthCredential; @@ -615,7 +609,7 @@ export class PhoneAuthCredential extends AuthCredential { _getReauthenticationResolver(auth: AuthInternal): Promise; // @internal (undocumented) _linkToIdToken(auth: AuthInternal, idToken: string): Promise; - // Warning: (ae-forgotten-export) The symbol "SignInWithPhoneNumberRequest" needs to be exported by the entry point index.doc.d.ts + // Warning: (ae-forgotten-export) The symbol "SignInWithPhoneNumberRequest" needs to be exported by the entry point index.d.ts // // @internal (undocumented) _makeVerificationRequest(): SignInWithPhoneNumberRequest; @@ -712,13 +706,13 @@ export interface RecaptchaParameters { [key: string]: any; } -// Warning: (ae-forgotten-export) The symbol "ApplicationVerifierInternal" needs to be exported by the entry point index.doc.d.ts +// Warning: (ae-forgotten-export) The symbol "ApplicationVerifierInternal" needs to be exported by the entry point index.d.ts // // @public export class RecaptchaVerifier implements ApplicationVerifierInternal { constructor(authExtern: Auth, containerOrId: HTMLElement | string, parameters?: RecaptchaParameters); clear(): void; - // Warning: (ae-forgotten-export) The symbol "ReCaptchaLoader" needs to be exported by the entry point index.doc.d.ts + // Warning: (ae-forgotten-export) The symbol "ReCaptchaLoader" needs to be exported by the entry point index.d.ts // // @internal (undocumented) readonly _recaptchaLoader: ReCaptchaLoader; @@ -735,7 +729,7 @@ export function reload(user: User): Promise; // @public export function revokeAccessToken(auth: Auth, token: string): Promise; -// Warning: (ae-forgotten-export) The symbol "FederatedAuthProvider" needs to be exported by the entry point index.doc.d.ts +// Warning: (ae-forgotten-export) The symbol "FederatedAuthProvider" needs to be exported by the entry point index.d.ts // // @public export class SAMLAuthProvider extends FederatedAuthProvider { @@ -816,13 +810,13 @@ export class TotpSecret { readonly codeIntervalSeconds: number; readonly codeLength: number; readonly enrollmentCompletionDeadline: string; - // Warning: (ae-forgotten-export) The symbol "StartTotpMfaEnrollmentResponse" needs to be exported by the entry point index.doc.d.ts + // Warning: (ae-forgotten-export) The symbol "StartTotpMfaEnrollmentResponse" needs to be exported by the entry point index.d.ts // // @internal (undocumented) static _fromStartTotpMfaEnrollmentResponse(response: StartTotpMfaEnrollmentResponse, auth: AuthInternal): TotpSecret; generateQrCodeUrl(accountName?: string, issuer?: string): string; readonly hashingAlgorithm: string; - // Warning: (ae-forgotten-export) The symbol "TotpVerificationInfo" needs to be exported by the entry point index.doc.d.ts + // Warning: (ae-forgotten-export) The symbol "TotpVerificationInfo" needs to be exported by the entry point index.d.ts // // @internal (undocumented) _makeTotpVerificationInfo(otp: string): TotpVerificationInfo; diff --git a/common/api-review/util.api.md b/common/api-review/util.api.md index c6100a740d1..09558e72ce8 100644 --- a/common/api-review/util.api.md +++ b/common/api-review/util.api.md @@ -189,20 +189,6 @@ export type ExperimentalKey = 'authTokenSyncURL' | 'authIdTokenMaxAge'; // @public export function extractQuerystring(url: string): string; -// Warning: (ae-missing-release-tag) "FetchProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public -export class FetchProvider { - // (undocumented) - static fetch(): typeof fetch; - // (undocumented) - static headers(): typeof Headers; - // (undocumented) - static initialize(fetchImpl: typeof fetch, headersImpl?: typeof Headers, responseImpl?: typeof Response): void; - // (undocumented) - static response(): typeof Response; - } - // @public export interface FirebaseDefaults { // (undocumented) From b482715c0046e0525e7feee72c27dad4f78453d0 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Fri, 6 Sep 2024 12:01:11 -0700 Subject: [PATCH 58/74] Trigger Build From a25716fab8be3edae0322c57cf5fae135f05da56 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Fri, 6 Sep 2024 13:11:59 -0700 Subject: [PATCH 59/74] Fixed linting issues --- packages/data-connect/src/core/AppCheckTokenProvider.ts | 2 +- packages/data-connect/test/unit/queries.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/data-connect/src/core/AppCheckTokenProvider.ts b/packages/data-connect/src/core/AppCheckTokenProvider.ts index cc215d32b2b..d4994efc5cd 100644 --- a/packages/data-connect/src/core/AppCheckTokenProvider.ts +++ b/packages/data-connect/src/core/AppCheckTokenProvider.ts @@ -24,7 +24,7 @@ import { import { Provider } from '@firebase/component'; /** - @internal + * @internal * Abstraction around AppCheck's token fetching capabilities. */ export class AppCheckTokenProvider { diff --git a/packages/data-connect/test/unit/queries.test.ts b/packages/data-connect/test/unit/queries.test.ts index bc1dc1311c2..444601bd5ea 100644 --- a/packages/data-connect/test/unit/queries.test.ts +++ b/packages/data-connect/test/unit/queries.test.ts @@ -22,12 +22,12 @@ import chaiAsPromised from 'chai-as-promised'; import * as sinon from 'sinon'; import { DataConnectOptions } from '../../src'; -import { initializeFetch } from '../../src/network/fetch'; -import { RESTTransport } from '../../src/network/transport/rest'; import { AuthTokenListener, AuthTokenProvider } from '../../src/core/FirebaseAuthProvider'; +import { initializeFetch } from '../../src/network/fetch'; +import { RESTTransport } from '../../src/network/transport/rest'; chai.use(chaiAsPromised); const options: DataConnectOptions = { connector: 'c', From a7b69225e43c0907235c861a3940f977005c01ed Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Fri, 6 Sep 2024 16:52:11 -0700 Subject: [PATCH 60/74] Updated docsite --- docs-devsite/_toc.yaml | 14 --- .../data-connect.appchecktokenprovider.md | 86 -------------- .../data-connect.authtokenprovider.md | 61 ---------- docs-devsite/data-connect.dataconnect.md | 9 -- .../data-connect.dataconnecttransport.md | 104 ----------------- .../data-connect.firebaseauthprovider.md | 105 ------------------ docs-devsite/data-connect.md | 34 ------ docs-devsite/data-connect.mutationresponse.md | 19 ---- docs-devsite/data-connect.queryresponse.md | 19 ---- docs-devsite/data-connect.sender.md | 40 ------- 10 files changed, 491 deletions(-) delete mode 100644 docs-devsite/data-connect.appchecktokenprovider.md delete mode 100644 docs-devsite/data-connect.authtokenprovider.md delete mode 100644 docs-devsite/data-connect.dataconnecttransport.md delete mode 100644 docs-devsite/data-connect.firebaseauthprovider.md delete mode 100644 docs-devsite/data-connect.mutationresponse.md delete mode 100644 docs-devsite/data-connect.queryresponse.md delete mode 100644 docs-devsite/data-connect.sender.md diff --git a/docs-devsite/_toc.yaml b/docs-devsite/_toc.yaml index 7dc5131af52..6a806bfa2b3 100644 --- a/docs-devsite/_toc.yaml +++ b/docs-devsite/_toc.yaml @@ -176,10 +176,6 @@ toc: - title: data-connect path: /docs/reference/js/data-connect.md section: - - title: AppCheckTokenProvider - path: /docs/reference/js/data-connect.appchecktokenprovider.md - - title: AuthTokenProvider - path: /docs/reference/js/data-connect.authtokenprovider.md - title: CancellableOperation path: /docs/reference/js/data-connect.cancellableoperation.md - title: ConnectorConfig @@ -192,16 +188,10 @@ toc: path: /docs/reference/js/data-connect.dataconnectresult.md - title: DataConnectSubscription path: /docs/reference/js/data-connect.dataconnectsubscription.md - - title: DataConnectTransport - path: /docs/reference/js/data-connect.dataconnecttransport.md - - title: FirebaseAuthProvider - path: /docs/reference/js/data-connect.firebaseauthprovider.md - title: MutationPromise path: /docs/reference/js/data-connect.mutationpromise.md - title: MutationRef path: /docs/reference/js/data-connect.mutationref.md - - title: MutationResponse - path: /docs/reference/js/data-connect.mutationresponse.md - title: MutationResult path: /docs/reference/js/data-connect.mutationresult.md - title: OperationRef @@ -212,14 +202,10 @@ toc: path: /docs/reference/js/data-connect.querypromise.md - title: QueryRef path: /docs/reference/js/data-connect.queryref.md - - title: QueryResponse - path: /docs/reference/js/data-connect.queryresponse.md - title: QueryResult path: /docs/reference/js/data-connect.queryresult.md - title: RefInfo path: /docs/reference/js/data-connect.refinfo.md - - title: Sender - path: /docs/reference/js/data-connect.sender.md - title: SerializedRef path: /docs/reference/js/data-connect.serializedref.md - title: SubscriptionOptions diff --git a/docs-devsite/data-connect.appchecktokenprovider.md b/docs-devsite/data-connect.appchecktokenprovider.md deleted file mode 100644 index e98a6ba47d3..00000000000 --- a/docs-devsite/data-connect.appchecktokenprovider.md +++ /dev/null @@ -1,86 +0,0 @@ -Project: /docs/reference/js/_project.yaml -Book: /docs/reference/_book.yaml -page_type: reference - -{% comment %} -DO NOT EDIT THIS FILE! -This is generated by the JS SDK team, and any local changes will be -overwritten. Changes should be made in the source code at -https://github.com/firebase/firebase-js-sdk -{% endcomment %} - -# AppCheckTokenProvider class -Abstraction around AppCheck's token fetching capabilities. - -Signature: - -```typescript -export declare class AppCheckTokenProvider -``` - -## Constructors - -| Constructor | Modifiers | Description | -| --- | --- | --- | -| [(constructor)(appName\_, appCheckProvider)](./data-connect.appchecktokenprovider.md#appchecktokenproviderconstructor) | | Constructs a new instance of the AppCheckTokenProvider class | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [addTokenChangeListener(listener)](./data-connect.appchecktokenprovider.md#appchecktokenprovideraddtokenchangelistener) | | | -| [getToken(forceRefresh)](./data-connect.appchecktokenprovider.md#appchecktokenprovidergettoken) | | | - -## AppCheckTokenProvider.(constructor) - -Constructs a new instance of the `AppCheckTokenProvider` class - -Signature: - -```typescript -constructor(appName_: string, appCheckProvider?: Provider); -``` - -#### Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| appName\_ | string | | -| appCheckProvider | Provider<AppCheckInternalComponentName> | | - -## AppCheckTokenProvider.addTokenChangeListener() - -Signature: - -```typescript -addTokenChangeListener(listener: AppCheckTokenListener): void; -``` - -#### Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| listener | AppCheckTokenListener | | - -Returns: - -void - -## AppCheckTokenProvider.getToken() - -Signature: - -```typescript -getToken(forceRefresh?: boolean): Promise; -``` - -#### Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| forceRefresh | boolean | | - -Returns: - -Promise<AppCheckTokenResult> - diff --git a/docs-devsite/data-connect.authtokenprovider.md b/docs-devsite/data-connect.authtokenprovider.md deleted file mode 100644 index 0dd0a7ae6f4..00000000000 --- a/docs-devsite/data-connect.authtokenprovider.md +++ /dev/null @@ -1,61 +0,0 @@ -Project: /docs/reference/js/_project.yaml -Book: /docs/reference/_book.yaml -page_type: reference - -{% comment %} -DO NOT EDIT THIS FILE! -This is generated by the JS SDK team, and any local changes will be -overwritten. Changes should be made in the source code at -https://github.com/firebase/firebase-js-sdk -{% endcomment %} - -# AuthTokenProvider interface -Signature: - -```typescript -export declare interface AuthTokenProvider -``` - -## Methods - -| Method | Description | -| --- | --- | -| [addTokenChangeListener(listener)](./data-connect.authtokenprovider.md#authtokenprovideraddtokenchangelistener) | | -| [getToken(forceRefresh)](./data-connect.authtokenprovider.md#authtokenprovidergettoken) | | - -## AuthTokenProvider.addTokenChangeListener() - -Signature: - -```typescript -addTokenChangeListener(listener: AuthTokenListener): void; -``` - -#### Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| listener | [AuthTokenListener](./data-connect.md#authtokenlistener) | | - -Returns: - -void - -## AuthTokenProvider.getToken() - -Signature: - -```typescript -getToken(forceRefresh: boolean): Promise; -``` - -#### Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| forceRefresh | boolean | | - -Returns: - -Promise<FirebaseAuthTokenData \| null> - diff --git a/docs-devsite/data-connect.dataconnect.md b/docs-devsite/data-connect.dataconnect.md index 8b0abdbadb6..bec53472e11 100644 --- a/docs-devsite/data-connect.dataconnect.md +++ b/docs-devsite/data-connect.dataconnect.md @@ -29,7 +29,6 @@ export declare class DataConnect | Property | Modifiers | Type | Description | | --- | --- | --- | --- | | [app](./data-connect.dataconnect.md#dataconnectapp) | | [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) | | -| [initialized](./data-connect.dataconnect.md#dataconnectinitialized) | | boolean | | | [isEmulator](./data-connect.dataconnect.md#dataconnectisemulator) | | boolean | | ## Methods @@ -67,14 +66,6 @@ constructor(app: FirebaseApp, dataConnectOptions: DataConnectOptions, _authProvi readonly app: FirebaseApp; ``` -## DataConnect.initialized - -Signature: - -```typescript -initialized: boolean; -``` - ## DataConnect.isEmulator Signature: diff --git a/docs-devsite/data-connect.dataconnecttransport.md b/docs-devsite/data-connect.dataconnecttransport.md deleted file mode 100644 index f84693e0c2f..00000000000 --- a/docs-devsite/data-connect.dataconnecttransport.md +++ /dev/null @@ -1,104 +0,0 @@ -Project: /docs/reference/js/_project.yaml -Book: /docs/reference/_book.yaml -page_type: reference - -{% comment %} -DO NOT EDIT THIS FILE! -This is generated by the JS SDK team, and any local changes will be -overwritten. Changes should be made in the source code at -https://github.com/firebase/firebase-js-sdk -{% endcomment %} - -# DataConnectTransport interface -Signature: - -```typescript -export declare interface DataConnectTransport -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [onTokenChanged](./data-connect.dataconnecttransport.md#dataconnecttransportontokenchanged) | (token: string \| null) => void | | - -## Methods - -| Method | Description | -| --- | --- | -| [invokeMutation(queryName, body)](./data-connect.dataconnecttransport.md#dataconnecttransportinvokemutation) | | -| [invokeQuery(queryName, body)](./data-connect.dataconnecttransport.md#dataconnecttransportinvokequery) | | -| [useEmulator(host, port, sslEnabled)](./data-connect.dataconnecttransport.md#dataconnecttransportuseemulator) | | - -## DataConnectTransport.onTokenChanged - -Signature: - -```typescript -onTokenChanged: (token: string | null) => void; -``` - -## DataConnectTransport.invokeMutation() - -Signature: - -```typescript -invokeMutation(queryName: string, body?: U): PromiseLike<{ - data: T; - errors: Error[]; - }>; -``` - -#### Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| queryName | string | | -| body | U | | - -Returns: - -PromiseLike<{ data: T; errors: Error\[\]; }> - -## DataConnectTransport.invokeQuery() - -Signature: - -```typescript -invokeQuery(queryName: string, body?: U): PromiseLike<{ - data: T; - errors: Error[]; - }>; -``` - -#### Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| queryName | string | | -| body | U | | - -Returns: - -PromiseLike<{ data: T; errors: Error\[\]; }> - -## DataConnectTransport.useEmulator() - -Signature: - -```typescript -useEmulator(host: string, port?: number, sslEnabled?: boolean): void; -``` - -#### Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| host | string | | -| port | number | | -| sslEnabled | boolean | | - -Returns: - -void - diff --git a/docs-devsite/data-connect.firebaseauthprovider.md b/docs-devsite/data-connect.firebaseauthprovider.md deleted file mode 100644 index 49580885def..00000000000 --- a/docs-devsite/data-connect.firebaseauthprovider.md +++ /dev/null @@ -1,105 +0,0 @@ -Project: /docs/reference/js/_project.yaml -Book: /docs/reference/_book.yaml -page_type: reference - -{% comment %} -DO NOT EDIT THIS FILE! -This is generated by the JS SDK team, and any local changes will be -overwritten. Changes should be made in the source code at -https://github.com/firebase/firebase-js-sdk -{% endcomment %} - -# FirebaseAuthProvider class -Signature: - -```typescript -export declare class FirebaseAuthProvider implements AuthTokenProvider -``` -Implements: [AuthTokenProvider](./data-connect.authtokenprovider.md#authtokenprovider_interface) - -## Constructors - -| Constructor | Modifiers | Description | -| --- | --- | --- | -| [(constructor)(\_appName, \_options, \_authProvider)](./data-connect.firebaseauthprovider.md#firebaseauthproviderconstructor) | | Constructs a new instance of the FirebaseAuthProvider class | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [addTokenChangeListener(listener)](./data-connect.firebaseauthprovider.md#firebaseauthprovideraddtokenchangelistener) | | | -| [getToken(forceRefresh)](./data-connect.firebaseauthprovider.md#firebaseauthprovidergettoken) | | | -| [removeTokenChangeListener(listener)](./data-connect.firebaseauthprovider.md#firebaseauthproviderremovetokenchangelistener) | | | - -## FirebaseAuthProvider.(constructor) - -Constructs a new instance of the `FirebaseAuthProvider` class - -Signature: - -```typescript -constructor(_appName: string, _options: FirebaseOptions, _authProvider: Provider); -``` - -#### Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| \_appName | string | | -| \_options | FirebaseOptions | | -| \_authProvider | Provider<FirebaseAuthInternalName> | | - -## FirebaseAuthProvider.addTokenChangeListener() - -Signature: - -```typescript -addTokenChangeListener(listener: AuthTokenListener): void; -``` - -#### Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| listener | [AuthTokenListener](./data-connect.md#authtokenlistener) | | - -Returns: - -void - -## FirebaseAuthProvider.getToken() - -Signature: - -```typescript -getToken(forceRefresh: boolean): Promise; -``` - -#### Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| forceRefresh | boolean | | - -Returns: - -Promise<FirebaseAuthTokenData \| null> - -## FirebaseAuthProvider.removeTokenChangeListener() - -Signature: - -```typescript -removeTokenChangeListener(listener: (token: string | null) => void): void; -``` - -#### Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| listener | (token: string \| null) => void | | - -Returns: - -void - diff --git a/docs-devsite/data-connect.md b/docs-devsite/data-connect.md index 08aef0ad6ca..a8a8699487a 100644 --- a/docs-devsite/data-connect.md +++ b/docs-devsite/data-connect.md @@ -45,33 +45,26 @@ Firebase Data Connect | Class | Description | | --- | --- | -| [AppCheckTokenProvider](./data-connect.appchecktokenprovider.md#appchecktokenprovider_class) | Abstraction around AppCheck's token fetching capabilities. | | [DataConnect](./data-connect.dataconnect.md#dataconnect_class) | Class representing Firebase Data Connect | -| [FirebaseAuthProvider](./data-connect.firebaseauthprovider.md#firebaseauthprovider_class) | | ## Interfaces | Interface | Description | | --- | --- | -| [AuthTokenProvider](./data-connect.authtokenprovider.md#authtokenprovider_interface) | | | [CancellableOperation](./data-connect.cancellableoperation.md#cancellableoperation_interface) | | | [ConnectorConfig](./data-connect.connectorconfig.md#connectorconfig_interface) | Connector Config for calling Data Connect backend. | | [DataConnectOptions](./data-connect.dataconnectoptions.md#dataconnectoptions_interface) | DataConnectOptions including project id | | [DataConnectResult](./data-connect.dataconnectresult.md#dataconnectresult_interface) | | | [DataConnectSubscription](./data-connect.dataconnectsubscription.md#dataconnectsubscription_interface) | Representation of user provided subscription options. | -| [DataConnectTransport](./data-connect.dataconnecttransport.md#dataconnecttransport_interface) | | | [MutationPromise](./data-connect.mutationpromise.md#mutationpromise_interface) | Mutation return value from executeMutation | | [MutationRef](./data-connect.mutationref.md#mutationref_interface) | | -| [MutationResponse](./data-connect.mutationresponse.md#mutationresponse_interface) | | | [MutationResult](./data-connect.mutationresult.md#mutationresult_interface) | Mutation Result from executeMutation | | [OperationRef](./data-connect.operationref.md#operationref_interface) | | | [OpResult](./data-connect.opresult.md#opresult_interface) | | | [QueryPromise](./data-connect.querypromise.md#querypromise_interface) | Promise returned from executeQuery | | [QueryRef](./data-connect.queryref.md#queryref_interface) | QueryRef object | -| [QueryResponse](./data-connect.queryresponse.md#queryresponse_interface) | | | [QueryResult](./data-connect.queryresult.md#queryresult_interface) | Result of executeQuery | | [RefInfo](./data-connect.refinfo.md#refinfo_interface) | Serialized RefInfo as a result of QueryResult.toJSON().refInfo | -| [Sender](./data-connect.sender.md#sender_interface) | | | [SerializedRef](./data-connect.serializedref.md#serializedref_interface) | Serialized Ref as a result of QueryResult.toJSON() | | [SubscriptionOptions](./data-connect.subscriptionoptions.md#subscriptionoptions_interface) | Representation of full observer options in subscribe | | [TransportOptions](./data-connect.transportoptions.md#transportoptions_interface) | Options to connect to emulator | @@ -80,7 +73,6 @@ Firebase Data Connect | Variable | Description | | --- | --- | -| [FIREBASE\_DATA\_CONNECT\_EMULATOR\_HOST\_VAR](./data-connect.md#firebase_data_connect_emulator_host_var) | | | [MUTATION\_STR](./data-connect.md#mutation_str) | | | [QUERY\_STR](./data-connect.md#query_str) | | | [SOURCE\_CACHE](./data-connect.md#source_cache) | | @@ -90,14 +82,12 @@ Firebase Data Connect | Type Alias | Description | | --- | --- | -| [AuthTokenListener](./data-connect.md#authtokenlistener) | | | [DataSource](./data-connect.md#datasource) | | | [OnCompleteSubscription](./data-connect.md#oncompletesubscription) | OnCompleteSubscription | | [OnErrorSubscription](./data-connect.md#onerrorsubscription) | Signature for OnErrorSubscription for subscribe | | [OnResultSubscription](./data-connect.md#onresultsubscription) | Signature for OnResultSubscription for subscribe | | [QueryUnsubscribe](./data-connect.md#queryunsubscribe) | Signature for unsubscribe from subscribe | | [ReferenceType](./data-connect.md#referencetype) | | -| [TransportClass](./data-connect.md#transportclass) | | ## function(app, ...) @@ -424,14 +414,6 @@ export declare function toQueryRef(serializedRef: SerializedRef `QueryRef` -## FIREBASE\_DATA\_CONNECT\_EMULATOR\_HOST\_VAR - -Signature: - -```typescript -FIREBASE_DATA_CONNECT_EMULATOR_HOST_VAR = "FIREBASE_DATA_CONNECT_EMULATOR_HOST" -``` - ## MUTATION\_STR Signature: @@ -464,14 +446,6 @@ SOURCE_CACHE = "CACHE" SOURCE_SERVER = "SERVER" ``` -## AuthTokenListener - -Signature: - -```typescript -export declare type AuthTokenListener = (token: string | null) => void; -``` - ## DataSource Signature: @@ -527,11 +501,3 @@ export declare type QueryUnsubscribe = () => void; ```typescript export declare type ReferenceType = typeof QUERY_STR | typeof MUTATION_STR; ``` - -## TransportClass - -Signature: - -```typescript -export declare type TransportClass = new (options: DataConnectOptions, apiKey?: string, authProvider?: AuthTokenProvider, appCheckProvider?: AppCheckTokenProvider, transportOptions?: TransportOptions, _isUsingGen?: boolean) => DataConnectTransport; -``` diff --git a/docs-devsite/data-connect.mutationresponse.md b/docs-devsite/data-connect.mutationresponse.md deleted file mode 100644 index 0b018d140ac..00000000000 --- a/docs-devsite/data-connect.mutationresponse.md +++ /dev/null @@ -1,19 +0,0 @@ -Project: /docs/reference/js/_project.yaml -Book: /docs/reference/_book.yaml -page_type: reference - -{% comment %} -DO NOT EDIT THIS FILE! -This is generated by the JS SDK team, and any local changes will be -overwritten. Changes should be made in the source code at -https://github.com/firebase/firebase-js-sdk -{% endcomment %} - -# MutationResponse interface -Signature: - -```typescript -export declare interface MutationResponse extends CancellableOperation -``` -Extends: [CancellableOperation](./data-connect.cancellableoperation.md#cancellableoperation_interface)<T> - diff --git a/docs-devsite/data-connect.queryresponse.md b/docs-devsite/data-connect.queryresponse.md deleted file mode 100644 index 0c1047339f6..00000000000 --- a/docs-devsite/data-connect.queryresponse.md +++ /dev/null @@ -1,19 +0,0 @@ -Project: /docs/reference/js/_project.yaml -Book: /docs/reference/_book.yaml -page_type: reference - -{% comment %} -DO NOT EDIT THIS FILE! -This is generated by the JS SDK team, and any local changes will be -overwritten. Changes should be made in the source code at -https://github.com/firebase/firebase-js-sdk -{% endcomment %} - -# QueryResponse interface -Signature: - -```typescript -export declare interface QueryResponse extends CancellableOperation -``` -Extends: [CancellableOperation](./data-connect.cancellableoperation.md#cancellableoperation_interface)<T> - diff --git a/docs-devsite/data-connect.sender.md b/docs-devsite/data-connect.sender.md deleted file mode 100644 index 64bab9156fe..00000000000 --- a/docs-devsite/data-connect.sender.md +++ /dev/null @@ -1,40 +0,0 @@ -Project: /docs/reference/js/_project.yaml -Book: /docs/reference/_book.yaml -page_type: reference - -{% comment %} -DO NOT EDIT THIS FILE! -This is generated by the JS SDK team, and any local changes will be -overwritten. Changes should be made in the source code at -https://github.com/firebase/firebase-js-sdk -{% endcomment %} - -# Sender interface -Signature: - -```typescript -export declare interface Sender -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [abort](./data-connect.sender.md#senderabort) | () => void | | -| [send](./data-connect.sender.md#sendersend) | () => Promise<T> | | - -## Sender.abort - -Signature: - -```typescript -abort: () => void; -``` - -## Sender.send - -Signature: - -```typescript -send: () => Promise; -``` From 87d8a1139079b56d6106288913b012327e33b11d Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Wed, 18 Sep 2024 05:05:03 -0700 Subject: [PATCH 61/74] Replaced node-fetch with fetch --- packages/data-connect/test/runTest.mjs | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/data-connect/test/runTest.mjs b/packages/data-connect/test/runTest.mjs index 1201bb48602..2ef752cf37d 100644 --- a/packages/data-connect/test/runTest.mjs +++ b/packages/data-connect/test/runTest.mjs @@ -1,5 +1,4 @@ import { uuidv4 } from '@firebase/util'; -import fetch from 'node-fetch'; // initializeApp({ projectId: 'p' }); // const dc = getDataConnect({ connector: 'c', location: 'l', service: 'l' }); From 44fa9c66f3db2cb5640489feff98fc90e6100a83 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Wed, 18 Sep 2024 05:09:47 -0700 Subject: [PATCH 62/74] Removed runtest --- packages/data-connect/test/runTest.mjs | 87 -------------------------- 1 file changed, 87 deletions(-) delete mode 100644 packages/data-connect/test/runTest.mjs diff --git a/packages/data-connect/test/runTest.mjs b/packages/data-connect/test/runTest.mjs deleted file mode 100644 index 2ef752cf37d..00000000000 --- a/packages/data-connect/test/runTest.mjs +++ /dev/null @@ -1,87 +0,0 @@ -import { uuidv4 } from '@firebase/util'; -// initializeApp({ projectId: 'p' }); -// const dc = getDataConnect({ connector: 'c', location: 'l', service: 'l' }); - -// connectDataConnectEmulator(dc, 'localhost', 3628); -// const ref = queryRef(dc, 'listPosts'); -// const res = executeQuery(ref); -// res.then(res => console.log(res.data)); -function listPosts() { - // perform Mutation of full data - const executeBody = { - name: 'projects/p/locations/l/services/l/connectors/c', - operationName: 'listPosts', - variables: undefined - }; - return fetch( - 'http://localhost:3628/v1alpha/projects/p/locations/l/services/l/connectors/c:executeQuery', - { - method: 'POST', - body: JSON.stringify(executeBody) - } - ); -} -const SEEDED_DATA = [ - { - id: uuidv4(), - content: 'task 1' - }, - { - id: uuidv4(), - content: 'task 2' - } -]; -async function seedDatabase() { - // perform Mutation of full data - for(let i = 0; i < SEEDED_DATA.length; i++) { - const data = SEEDED_DATA[i]; - const executeBody = { - name: 'projects/p/locations/l/services/l/connectors/c', - operationName: 'createPost', - variables: data - }; - await fetch( - 'http://localhost:3628/v1alpha/projects/p/locations/l/services/l/connectors/c:executeMutation', - { - method: 'POST', - body: JSON.stringify(executeBody) - } - ); - } -} -function removeData() { - // perform mutation of removing data -} -function setupSchema() { - const obj = { - "service_id": "l", - "schema": { - "files": [ - { - "path": "schema/post.gql", - "content": "type Post @table {content: String!}" - } - ] - }, - "connectors": { - "c": { - "files": [ - { - "path": "operations/post.gql", - "content": "query getPost($id: UUID!) @auth(level: PUBLIC) {post(id: $id) {content}} query listPosts @auth(level: PUBLIC) {posts {content}} mutation createPost($id: UUID!, $content: String!) @auth(level: PUBLIC) {post_insert(data: {id: $id, content: $content})} mutation deletePost($id: UUID!) @auth(level: PUBLIC) { post_delete(id: $id)}" - } - ] - } - } -}; -return fetch(`http://localhost:3628/setupSchema`, { - method: 'POST', - body: JSON.stringify(obj) - }); -} - -await setupSchema().then(res => res.json()); -const databaseSeeded = await seedDatabase() -const posts = await listPosts().then(res => res.json()); -console.log(posts); -// console.log(databaseSeeded); From 57ffcf6b7588cc80279d88fcecc2954861e83e83 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Thu, 19 Sep 2024 23:23:52 +0100 Subject: [PATCH 63/74] Changed firebase to minor release --- .changeset/cold-chairs-fold.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/cold-chairs-fold.md b/.changeset/cold-chairs-fold.md index 532b7fb0453..ee89d84984d 100644 --- a/.changeset/cold-chairs-fold.md +++ b/.changeset/cold-chairs-fold.md @@ -1,5 +1,5 @@ --- -"firebase": major +"firebase": minor "@firebase/app": patch "@firebase/data-connect": patch --- From c23f54e691c76cef478540ae06837fcdacc3ae46 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Thu, 19 Sep 2024 15:52:40 -0700 Subject: [PATCH 64/74] FDC: Implement GMPID (#8486) --- packages/data-connect/src/api/DataConnect.ts | 1 + packages/data-connect/src/network/fetch.ts | 4 + .../src/network/transport/index.ts | 1 + .../src/network/transport/rest.ts | 3 + packages/data-connect/test/emulatorSeeder.ts | 1 - packages/data-connect/test/unit/fetch.test.ts | 20 ++++- packages/data-connect/test/unit/gmpid.test.ts | 84 +++++++++++++++++++ .../data-connect/test/unit/queries.test.ts | 6 +- 8 files changed, 114 insertions(+), 6 deletions(-) create mode 100644 packages/data-connect/test/unit/gmpid.test.ts diff --git a/packages/data-connect/src/api/DataConnect.ts b/packages/data-connect/src/api/DataConnect.ts index 9393b9ef4e4..27ab83660fd 100644 --- a/packages/data-connect/src/api/DataConnect.ts +++ b/packages/data-connect/src/api/DataConnect.ts @@ -160,6 +160,7 @@ export class DataConnect { this._transport = new this._transportClass( this.dataConnectOptions, this.app.options.apiKey, + this.app.options.appId, this._authTokenProvider, this._appCheckTokenProvider, undefined, diff --git a/packages/data-connect/src/network/fetch.ts b/packages/data-connect/src/network/fetch.ts index fbd930977ab..928b9f873cf 100644 --- a/packages/data-connect/src/network/fetch.ts +++ b/packages/data-connect/src/network/fetch.ts @@ -34,6 +34,7 @@ export function dcFetch( url: string, body: U, { signal }: AbortController, + appId: string | null, accessToken: string | null, appCheckToken: string | null, _isUsingGen: boolean @@ -48,6 +49,9 @@ export function dcFetch( if (accessToken) { headers['X-Firebase-Auth-Token'] = accessToken; } + if (appId) { + headers['x-firebase-gmpid'] = appId; + } if (appCheckToken) { headers['X-Firebase-AppCheck'] = appCheckToken; } diff --git a/packages/data-connect/src/network/transport/index.ts b/packages/data-connect/src/network/transport/index.ts index 8ccbc88f435..5518faa0f95 100644 --- a/packages/data-connect/src/network/transport/index.ts +++ b/packages/data-connect/src/network/transport/index.ts @@ -45,6 +45,7 @@ export interface CancellableOperation extends PromiseLike<{ data: T }> { export type TransportClass = new ( options: DataConnectOptions, apiKey?: string, + appId?: string, authProvider?: AuthTokenProvider, appCheckProvider?: AppCheckTokenProvider, transportOptions?: TransportOptions, diff --git a/packages/data-connect/src/network/transport/rest.ts b/packages/data-connect/src/network/transport/rest.ts index aaaf22abd64..85847868c5d 100644 --- a/packages/data-connect/src/network/transport/rest.ts +++ b/packages/data-connect/src/network/transport/rest.ts @@ -39,6 +39,7 @@ export class RESTTransport implements DataConnectTransport { constructor( options: DataConnectOptions, private apiKey?: string | undefined, + private appId?: string, private authProvider?: AuthTokenProvider | undefined, private appCheckProvider?: AppCheckTokenProvider | undefined, transportOptions?: TransportOptions | undefined, @@ -175,6 +176,7 @@ export class RESTTransport implements DataConnectTransport { variables: body } as unknown as U, // TODO(mtewani): This is a patch, fix this. abortController, + this.appId, this._accessToken, this._appCheckToken, this._isUsingGen @@ -203,6 +205,7 @@ export class RESTTransport implements DataConnectTransport { variables: body } as unknown as U, abortController, + this.appId, this._accessToken, this._appCheckToken, this._isUsingGen diff --git a/packages/data-connect/test/emulatorSeeder.ts b/packages/data-connect/test/emulatorSeeder.ts index df7071a5868..36cdf691169 100644 --- a/packages/data-connect/test/emulatorSeeder.ts +++ b/packages/data-connect/test/emulatorSeeder.ts @@ -82,7 +82,6 @@ export async function setupQueries( connection_string: 'postgresql://postgres:secretpassword@localhost:5432/postgres?sslmode=disable' }; - fs.writeFileSync('./emulator.json', JSON.stringify(toWrite)); return fetch(`http://localhost:${EMULATOR_PORT}/setupSchema`, { method: 'POST', body: JSON.stringify(toWrite) diff --git a/packages/data-connect/test/unit/fetch.test.ts b/packages/data-connect/test/unit/fetch.test.ts index f0d7f38ee8c..a50ac188724 100644 --- a/packages/data-connect/test/unit/fetch.test.ts +++ b/packages/data-connect/test/unit/fetch.test.ts @@ -40,7 +40,15 @@ describe('fetch', () => { message }); await expect( - dcFetch('http://localhost', {}, {} as AbortController, null, null, false) + dcFetch( + 'http://localhost', + {}, + {} as AbortController, + null, + null, + null, + false + ) ).to.eventually.be.rejectedWith(message); }); it('should throw a stringified message when the server responds with an error without a message property in the body', async () => { @@ -51,7 +59,15 @@ describe('fetch', () => { }; mockFetch(json); await expect( - dcFetch('http://localhost', {}, {} as AbortController, null, null, false) + dcFetch( + 'http://localhost', + {}, + {} as AbortController, + null, + null, + null, + false + ) ).to.eventually.be.rejectedWith(JSON.stringify(json)); }); }); diff --git a/packages/data-connect/test/unit/gmpid.test.ts b/packages/data-connect/test/unit/gmpid.test.ts new file mode 100644 index 00000000000..c6679ca242d --- /dev/null +++ b/packages/data-connect/test/unit/gmpid.test.ts @@ -0,0 +1,84 @@ +/** + * @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 { deleteApp, initializeApp, FirebaseApp } from '@firebase/app'; +import { expect, use } from 'chai'; +import * as sinon from 'sinon'; +import sinonChai from 'sinon-chai'; + +import { DataConnect, executeQuery, getDataConnect, queryRef } from '../../src'; +import { initializeFetch } from '../../src/network/fetch'; + +use(sinonChai); +const json = { + message: 'unauthorized' +}; +const fakeFetchImpl = sinon.stub().returns( + Promise.resolve({ + json: () => { + return Promise.resolve(json); + }, + status: 401 + } as Response) +); + +describe('GMPID Tests', () => { + let dc: DataConnect; + let app: FirebaseApp; + const APPID = 'MYAPPID'; + beforeEach(() => { + initializeFetch(fakeFetchImpl); + app = initializeApp({ projectId: 'p', appId: APPID }, 'fdsasdf'); // TODO(mtewani): Replace with util function + dc = getDataConnect(app, { connector: 'c', location: 'l', service: 's' }); + }); + afterEach(async () => { + await dc._delete(); + await deleteApp(app); + }); + it('should send a request with the corresponding gmpid if using the app id is specified', async () => { + // @ts-ignore + await executeQuery(queryRef(dc, '')).catch(() => {}); + expect(fakeFetchImpl).to.be.calledWithMatch( + 'https://firebasedataconnect.googleapis.com/v1alpha/projects/p/locations/l/services/s/connectors/c:executeQuery', + { + headers: { + ['x-firebase-gmpid']: APPID + } + } + ); + }); + it('should send a request with no gmpid if using the app id is not specified', async () => { + const app2 = initializeApp({ projectId: 'p' }, 'def'); // TODO(mtewani): Replace with util function + const dc2 = getDataConnect(app2, { + connector: 'c', + location: 'l', + service: 's' + }); + // @ts-ignore + await executeQuery(queryRef(dc2, '')).catch(() => {}); + expect(fakeFetchImpl).to.be.calledWithMatch( + 'https://firebasedataconnect.googleapis.com/v1alpha/projects/p/locations/l/services/s/connectors/c:executeQuery', + { + headers: { + ['x-firebase-gmpid']: APPID + } + } + ); + await dc2._delete(); + await deleteApp(app2); + }); +}); diff --git a/packages/data-connect/test/unit/queries.test.ts b/packages/data-connect/test/unit/queries.test.ts index 444601bd5ea..68bd96268a6 100644 --- a/packages/data-connect/test/unit/queries.test.ts +++ b/packages/data-connect/test/unit/queries.test.ts @@ -68,7 +68,7 @@ describe('Queries', () => { it('[QUERY] should retry auth whenever the fetcher returns with unauthorized', async () => { initializeFetch(fakeFetchImpl); const authProvider = new FakeAuthProvider(); - const rt = new RESTTransport(options, undefined, authProvider); + const rt = new RESTTransport(options, undefined, undefined, authProvider); await expect(rt.invokeQuery('test', null)).to.eventually.be.rejectedWith( json.message ); @@ -77,7 +77,7 @@ describe('Queries', () => { it('[MUTATION] should retry auth whenever the fetcher returns with unauthorized', async () => { initializeFetch(fakeFetchImpl); const authProvider = new FakeAuthProvider(); - const rt = new RESTTransport(options, undefined, authProvider); + const rt = new RESTTransport(options, undefined, undefined, authProvider); await expect(rt.invokeMutation('test', null)).to.eventually.be.rejectedWith( json.message ); @@ -86,7 +86,7 @@ describe('Queries', () => { it("should not retry auth whenever the fetcher returns with unauthorized and the token doesn't change", async () => { initializeFetch(fakeFetchImpl); const authProvider = new FakeAuthProvider(); - const rt = new RESTTransport(options, undefined, authProvider); + const rt = new RESTTransport(options, undefined, undefined, authProvider); rt._setLastToken('initial token'); await expect( rt.invokeQuery('test', null) as Promise From ac13c8090ef3b36a7b5c68777211ebd8f0c72d27 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Fri, 20 Sep 2024 00:07:37 +0100 Subject: [PATCH 65/74] Fixed component error --- packages/data-connect/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/data-connect/package.json b/packages/data-connect/package.json index 9e9ecdbd403..ce7a98f167a 100644 --- a/packages/data-connect/package.json +++ b/packages/data-connect/package.json @@ -44,14 +44,14 @@ "api-report:api-json": "rm -rf temp && api-extractor run --local --verbose", "doc": "api-documenter markdown --input temp --output docs", "typings:public": "node ../../scripts/build/use_typings.js ./dist/public.d.ts" -}, + }, "license": "Apache-2.0", "peerDependencies": { "@firebase/app": "0.x" }, "dependencies": { "@firebase/auth-interop-types": "0.2.3", - "@firebase/component": "0.6.8", + "@firebase/component": "0.6.9", "@firebase/logger": "0.4.2", "@firebase/util": "1.10.0", "tslib": "^2.1.0" @@ -77,4 +77,4 @@ ], "reportDir": "./coverage/node" } -} +} \ No newline at end of file From 13abf2d92bb2ebce19a5dac44ab3c555fee8172a Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Thu, 19 Sep 2024 16:09:19 -0700 Subject: [PATCH 66/74] FDC: Upgrade to v1beta (#8485) --- packages/data-connect/package.json | 2 +- packages/data-connect/src/util/url.ts | 2 +- .../test/dataconnect/connector/connector.yaml | 2 +- .../test/dataconnect/dataconnect.yaml | 3 ++- packages/data-connect/test/unit/userAgent.test.ts | 4 ++-- .../emulators/dataconnect-emulator.ts | 8 ++++---- yarn.lock | 15 --------------- 7 files changed, 11 insertions(+), 25 deletions(-) diff --git a/packages/data-connect/package.json b/packages/data-connect/package.json index ce7a98f167a..ba697fb9a76 100644 --- a/packages/data-connect/package.json +++ b/packages/data-connect/package.json @@ -35,7 +35,7 @@ "dev": "rollup -c -w", "test": "run-p --npm-path npm test:emulator", "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:emulator", - "test:all": "npm run test:unit", + "test:all": "run-p --npm-path npm lint test:unit", "test:browser": "karma start --single-run", "test:node": "TS_NODE_FILES=true TS_NODE_CACHE=NO TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha 'test/{,!(browser)/**/}*.test.ts' --file src/index.node.ts --config ../../config/mocharc.node.js", "test:unit": "TS_NODE_FILES=true TS_NODE_CACHE=NO TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' nyc --reporter lcovonly -- mocha 'test/unit/**/*.test.ts' --file src/index.node.ts --config ../../config/mocharc.node.js", diff --git a/packages/data-connect/src/util/url.ts b/packages/data-connect/src/util/url.ts index 3cef50ae648..b979ec19eb5 100644 --- a/packages/data-connect/src/util/url.ts +++ b/packages/data-connect/src/util/url.ts @@ -37,7 +37,7 @@ export function urlBuilder( 'Incorrect type for port passed in!' ); } - return `${baseUrl}/v1alpha/projects/${project}/locations/${location}/services/${service}/connectors/${connector}`; + return `${baseUrl}/v1beta/projects/${project}/locations/${location}/services/${service}/connectors/${connector}`; } export function addToken(url: string, apiKey?: string): string { if (!apiKey) { diff --git a/packages/data-connect/test/dataconnect/connector/connector.yaml b/packages/data-connect/test/dataconnect/connector/connector.yaml index 3779c6a3700..064d9c2c184 100644 --- a/packages/data-connect/test/dataconnect/connector/connector.yaml +++ b/packages/data-connect/test/dataconnect/connector/connector.yaml @@ -3,4 +3,4 @@ authMode: "PUBLIC" generate: javascriptSdk: outputDir: "./gen/web" - jsPackageName: "@movie-app-ssr/movies" + package: "@movie-app-ssr/movies" diff --git a/packages/data-connect/test/dataconnect/dataconnect.yaml b/packages/data-connect/test/dataconnect/dataconnect.yaml index 1931e7eb963..442e98e5592 100644 --- a/packages/data-connect/test/dataconnect/dataconnect.yaml +++ b/packages/data-connect/test/dataconnect/dataconnect.yaml @@ -1,5 +1,6 @@ -specVersion: "v1alpha" +specVersion: "v1beta" serviceId: "dataconnect" +location: "us-west2" schema: source: "./schema" datasource: diff --git a/packages/data-connect/test/unit/userAgent.test.ts b/packages/data-connect/test/unit/userAgent.test.ts index a42fd06f817..d218969fb75 100644 --- a/packages/data-connect/test/unit/userAgent.test.ts +++ b/packages/data-connect/test/unit/userAgent.test.ts @@ -54,7 +54,7 @@ describe('User Agent Tests', () => { // @ts-ignore await executeQuery(queryRef(dc, '')).catch(() => {}); expect(fakeFetchImpl).to.be.calledWithMatch( - 'https://firebasedataconnect.googleapis.com/v1alpha/projects/p/locations/l/services/s/connectors/c:executeQuery', + 'https://firebasedataconnect.googleapis.com/v1beta/projects/p/locations/l/services/s/connectors/c:executeQuery', { headers: { ['X-Goog-Api-Client']: 'gl-js/ fire/' + SDK_VERSION + ' web/gen' @@ -66,7 +66,7 @@ describe('User Agent Tests', () => { // @ts-ignore await executeQuery(queryRef(dc, '')).catch(() => {}); expect(fakeFetchImpl).to.be.calledWithMatch( - 'https://firebasedataconnect.googleapis.com/v1alpha/projects/p/locations/l/services/s/connectors/c:executeQuery', + 'https://firebasedataconnect.googleapis.com/v1beta/projects/p/locations/l/services/s/connectors/c:executeQuery', { headers: { ['X-Goog-Api-Client']: 'gl-js/ fire/' + SDK_VERSION diff --git a/scripts/emulator-testing/emulators/dataconnect-emulator.ts b/scripts/emulator-testing/emulators/dataconnect-emulator.ts index 012aca578ca..efe5bdbe52c 100644 --- a/scripts/emulator-testing/emulators/dataconnect-emulator.ts +++ b/scripts/emulator-testing/emulators/dataconnect-emulator.ts @@ -18,7 +18,7 @@ import { platform } from 'os'; import { Emulator } from './emulator'; -const DATABASE_EMULATOR_VERSION = '1.1.17'; +const DATABASE_EMULATOR_VERSION = '1.3.7'; export class DataConnectEmulator extends Emulator { // namespace: string; @@ -29,15 +29,15 @@ export class DataConnectEmulator extends Emulator { switch (os) { case 'darwin': urlString = - 'https://firebasestorage.googleapis.com/v0/b/firemat-preview-drop/o/emulator%2Fdataconnect-emulator-macos-v1.3.5?alt=media&token=52c3db6e-2a2a-4094-a482-a8c85ae67a88'; + 'https://firebasestorage.googleapis.com/v0/b/firemat-preview-drop/o/emulator%2Fdataconnect-emulator-macos-v1.3.7?alt=media&token=2cf32435-d479-4929-b963-a97ae1ac3f0b'; break; case 'linux': urlString = - 'https://firebasestorage.googleapis.com/v0/b/firemat-preview-drop/o/emulator%2Fdataconnect-emulator-linux-v1.3.5?alt=media&token=bafb1f81-2a27-4851-b655-59934985b492'; + 'https://firebasestorage.googleapis.com/v0/b/firemat-preview-drop/o/emulator%2Fdataconnect-emulator-linux-v1.3.7?alt=media&token=fd33b4fc-2e27-4874-893a-2d1f0ecbf116'; break; case 'win32': urlString = - 'https://firebasestorage.googleapis.com/v0/b/firemat-preview-drop/o/emulator%2Fdataconnect-emulator-windows-v1.3.5?alt=media&token=d3d04c57-992f-4a4b-931d-5c90efd54c5a'; + 'https://firebasestorage.googleapis.com/v0/b/firemat-preview-drop/o/emulator%2Fdataconnect-emulator-windows-v1.3.7?alt=media&token=bd6e60b0-50b4-46db-aa6c-5fcc6e991f39'; break; default: throw new Error( diff --git a/yarn.lock b/yarn.lock index 70858a5db9c..92372aa94a3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1639,21 +1639,6 @@ resolved "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz#ef20350fec605a7f7035a01764731b2de0f3782b" integrity sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A== -"@firebase/component@0.6.8": - version "0.6.8" - resolved "https://registry.npmjs.org/@firebase/component/-/component-0.6.8.tgz#899b9318c0ce0586580e8cda7eaf61296f7fb43b" - integrity sha512-LcNvxGLLGjBwB0dJUsBGCej2fqAepWyBubs4jt1Tiuns7QLbXHuyObZ4aMeBjZjWx4m8g1LoVI9QFpSaq/k4/g== - dependencies: - "@firebase/util" "1.9.7" - tslib "^2.1.0" - -"@firebase/util@1.9.7": - version "1.9.7" - resolved "https://registry.npmjs.org/@firebase/util/-/util-1.9.7.tgz#c03b0ae065b3bba22800da0bd5314ef030848038" - integrity sha512-fBVNH/8bRbYjqlbIhZ+lBtdAAS4WqZumx03K06/u7fJSpz1TGjEMm1ImvKD47w+xaFKIP2ori6z8BrbakRfjJA== - dependencies: - tslib "^2.1.0" - "@gar/promisify@^1.0.1": version "1.1.2" resolved "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.2.tgz" From a96720a2b33637f24d0a295f894e4ede9e7fc476 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Fri, 20 Sep 2024 00:14:11 +0100 Subject: [PATCH 67/74] Excluded data connect --- scripts/docgen/docgen.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/docgen/docgen.ts b/scripts/docgen/docgen.ts index 8e9dfa87cab..cbe4b754908 100644 --- a/scripts/docgen/docgen.ts +++ b/scripts/docgen/docgen.ts @@ -37,7 +37,7 @@ https://github.com/firebase/firebase-js-sdk `; const tmpDir = `${projectRoot}/temp`; -const EXCLUDED_PACKAGES = ['app-compat', 'util', 'rules-unit-testing']; +const EXCLUDED_PACKAGES = ['app-compat', 'util', 'rules-unit-testing', 'data-connect']; /** * When ordering functions, will prioritize these first params at From 51d433c3d34b3ce870229a26c23db8e06cb63ffe Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Fri, 20 Sep 2024 00:21:44 +0100 Subject: [PATCH 68/74] Removed data connect reference docs --- docs-devsite/_toc.yaml | 39 -- .../data-connect.cancellableoperation.md | 43 -- docs-devsite/data-connect.connectorconfig.md | 51 -- docs-devsite/data-connect.dataconnect.md | 116 ---- .../data-connect.dataconnectoptions.md | 34 -- .../data-connect.dataconnectresult.md | 32 -- .../data-connect.dataconnectsubscription.md | 51 -- docs-devsite/data-connect.md | 503 ------------------ docs-devsite/data-connect.mutationpromise.md | 21 - docs-devsite/data-connect.mutationref.md | 32 -- docs-devsite/data-connect.mutationresult.md | 34 -- docs-devsite/data-connect.operationref.md | 58 -- docs-devsite/data-connect.opresult.md | 49 -- docs-devsite/data-connect.querypromise.md | 21 - docs-devsite/data-connect.queryref.md | 34 -- docs-devsite/data-connect.queryresult.md | 43 -- docs-devsite/data-connect.refinfo.md | 51 -- docs-devsite/data-connect.serializedref.md | 34 -- .../data-connect.subscriptionoptions.md | 51 -- docs-devsite/data-connect.transportoptions.md | 51 -- docs-devsite/index.md | 1 - 21 files changed, 1349 deletions(-) delete mode 100644 docs-devsite/data-connect.cancellableoperation.md delete mode 100644 docs-devsite/data-connect.connectorconfig.md delete mode 100644 docs-devsite/data-connect.dataconnect.md delete mode 100644 docs-devsite/data-connect.dataconnectoptions.md delete mode 100644 docs-devsite/data-connect.dataconnectresult.md delete mode 100644 docs-devsite/data-connect.dataconnectsubscription.md delete mode 100644 docs-devsite/data-connect.md delete mode 100644 docs-devsite/data-connect.mutationpromise.md delete mode 100644 docs-devsite/data-connect.mutationref.md delete mode 100644 docs-devsite/data-connect.mutationresult.md delete mode 100644 docs-devsite/data-connect.operationref.md delete mode 100644 docs-devsite/data-connect.opresult.md delete mode 100644 docs-devsite/data-connect.querypromise.md delete mode 100644 docs-devsite/data-connect.queryref.md delete mode 100644 docs-devsite/data-connect.queryresult.md delete mode 100644 docs-devsite/data-connect.refinfo.md delete mode 100644 docs-devsite/data-connect.serializedref.md delete mode 100644 docs-devsite/data-connect.subscriptionoptions.md delete mode 100644 docs-devsite/data-connect.transportoptions.md diff --git a/docs-devsite/_toc.yaml b/docs-devsite/_toc.yaml index 6a806bfa2b3..3a04923f8de 100644 --- a/docs-devsite/_toc.yaml +++ b/docs-devsite/_toc.yaml @@ -173,45 +173,6 @@ toc: path: /docs/reference/js/auth.userinfo.md - title: UserMetadata path: /docs/reference/js/auth.usermetadata.md - - title: data-connect - path: /docs/reference/js/data-connect.md - section: - - title: CancellableOperation - path: /docs/reference/js/data-connect.cancellableoperation.md - - title: ConnectorConfig - path: /docs/reference/js/data-connect.connectorconfig.md - - title: DataConnect - path: /docs/reference/js/data-connect.dataconnect.md - - title: DataConnectOptions - path: /docs/reference/js/data-connect.dataconnectoptions.md - - title: DataConnectResult - path: /docs/reference/js/data-connect.dataconnectresult.md - - title: DataConnectSubscription - path: /docs/reference/js/data-connect.dataconnectsubscription.md - - title: MutationPromise - path: /docs/reference/js/data-connect.mutationpromise.md - - title: MutationRef - path: /docs/reference/js/data-connect.mutationref.md - - title: MutationResult - path: /docs/reference/js/data-connect.mutationresult.md - - title: OperationRef - path: /docs/reference/js/data-connect.operationref.md - - title: OpResult - path: /docs/reference/js/data-connect.opresult.md - - title: QueryPromise - path: /docs/reference/js/data-connect.querypromise.md - - title: QueryRef - path: /docs/reference/js/data-connect.queryref.md - - title: QueryResult - path: /docs/reference/js/data-connect.queryresult.md - - title: RefInfo - path: /docs/reference/js/data-connect.refinfo.md - - title: SerializedRef - path: /docs/reference/js/data-connect.serializedref.md - - title: SubscriptionOptions - path: /docs/reference/js/data-connect.subscriptionoptions.md - - title: TransportOptions - path: /docs/reference/js/data-connect.transportoptions.md - title: database path: /docs/reference/js/database.md section: diff --git a/docs-devsite/data-connect.cancellableoperation.md b/docs-devsite/data-connect.cancellableoperation.md deleted file mode 100644 index 3c5e89a42c0..00000000000 --- a/docs-devsite/data-connect.cancellableoperation.md +++ /dev/null @@ -1,43 +0,0 @@ -Project: /docs/reference/js/_project.yaml -Book: /docs/reference/_book.yaml -page_type: reference - -{% comment %} -DO NOT EDIT THIS FILE! -This is generated by the JS SDK team, and any local changes will be -overwritten. Changes should be made in the source code at -https://github.com/firebase/firebase-js-sdk -{% endcomment %} - -# CancellableOperation interface -Signature: - -```typescript -export declare interface CancellableOperation extends PromiseLike<{ - data: T; -}> -``` -Extends: PromiseLike<{ data: T; }> - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [cancel](./data-connect.cancellableoperation.md#cancellableoperationcancel) | () => void | | -| [data](./data-connect.cancellableoperation.md#cancellableoperationdata) | T | | - -## CancellableOperation.cancel - -Signature: - -```typescript -cancel: () => void; -``` - -## CancellableOperation.data - -Signature: - -```typescript -data: T; -``` diff --git a/docs-devsite/data-connect.connectorconfig.md b/docs-devsite/data-connect.connectorconfig.md deleted file mode 100644 index b4998e93a14..00000000000 --- a/docs-devsite/data-connect.connectorconfig.md +++ /dev/null @@ -1,51 +0,0 @@ -Project: /docs/reference/js/_project.yaml -Book: /docs/reference/_book.yaml -page_type: reference - -{% comment %} -DO NOT EDIT THIS FILE! -This is generated by the JS SDK team, and any local changes will be -overwritten. Changes should be made in the source code at -https://github.com/firebase/firebase-js-sdk -{% endcomment %} - -# ConnectorConfig interface -Connector Config for calling Data Connect backend. - -Signature: - -```typescript -export declare interface ConnectorConfig -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [connector](./data-connect.connectorconfig.md#connectorconfigconnector) | string | | -| [location](./data-connect.connectorconfig.md#connectorconfiglocation) | string | | -| [service](./data-connect.connectorconfig.md#connectorconfigservice) | string | | - -## ConnectorConfig.connector - -Signature: - -```typescript -connector: string; -``` - -## ConnectorConfig.location - -Signature: - -```typescript -location: string; -``` - -## ConnectorConfig.service - -Signature: - -```typescript -service: string; -``` diff --git a/docs-devsite/data-connect.dataconnect.md b/docs-devsite/data-connect.dataconnect.md deleted file mode 100644 index bec53472e11..00000000000 --- a/docs-devsite/data-connect.dataconnect.md +++ /dev/null @@ -1,116 +0,0 @@ -Project: /docs/reference/js/_project.yaml -Book: /docs/reference/_book.yaml -page_type: reference - -{% comment %} -DO NOT EDIT THIS FILE! -This is generated by the JS SDK team, and any local changes will be -overwritten. Changes should be made in the source code at -https://github.com/firebase/firebase-js-sdk -{% endcomment %} - -# DataConnect class -Class representing Firebase Data Connect - -Signature: - -```typescript -export declare class DataConnect -``` - -## Constructors - -| Constructor | Modifiers | Description | -| --- | --- | --- | -| [(constructor)(app, dataConnectOptions, \_authProvider, \_appCheckProvider)](./data-connect.dataconnect.md#dataconnectconstructor) | | Constructs a new instance of the DataConnect class | - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [app](./data-connect.dataconnect.md#dataconnectapp) | | [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) | | -| [isEmulator](./data-connect.dataconnect.md#dataconnectisemulator) | | boolean | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [enableEmulator(transportOptions)](./data-connect.dataconnect.md#dataconnectenableemulator) | | | -| [getSettings()](./data-connect.dataconnect.md#dataconnectgetsettings) | | | -| [setInitialized()](./data-connect.dataconnect.md#dataconnectsetinitialized) | | | - -## DataConnect.(constructor) - -Constructs a new instance of the `DataConnect` class - -Signature: - -```typescript -constructor(app: FirebaseApp, dataConnectOptions: DataConnectOptions, _authProvider: Provider, _appCheckProvider: Provider); -``` - -#### Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| app | [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) | | -| dataConnectOptions | [DataConnectOptions](./data-connect.dataconnectoptions.md#dataconnectoptions_interface) | | -| \_authProvider | Provider<FirebaseAuthInternalName> | | -| \_appCheckProvider | Provider<AppCheckInternalComponentName> | | - -## DataConnect.app - -Signature: - -```typescript -readonly app: FirebaseApp; -``` - -## DataConnect.isEmulator - -Signature: - -```typescript -isEmulator: boolean; -``` - -## DataConnect.enableEmulator() - -Signature: - -```typescript -enableEmulator(transportOptions: TransportOptions): void; -``` - -#### Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| transportOptions | [TransportOptions](./data-connect.transportoptions.md#transportoptions_interface) | | - -Returns: - -void - -## DataConnect.getSettings() - -Signature: - -```typescript -getSettings(): ConnectorConfig; -``` -Returns: - -[ConnectorConfig](./data-connect.connectorconfig.md#connectorconfig_interface) - -## DataConnect.setInitialized() - -Signature: - -```typescript -setInitialized(): void; -``` -Returns: - -void - diff --git a/docs-devsite/data-connect.dataconnectoptions.md b/docs-devsite/data-connect.dataconnectoptions.md deleted file mode 100644 index 8428cb65ca4..00000000000 --- a/docs-devsite/data-connect.dataconnectoptions.md +++ /dev/null @@ -1,34 +0,0 @@ -Project: /docs/reference/js/_project.yaml -Book: /docs/reference/_book.yaml -page_type: reference - -{% comment %} -DO NOT EDIT THIS FILE! -This is generated by the JS SDK team, and any local changes will be -overwritten. Changes should be made in the source code at -https://github.com/firebase/firebase-js-sdk -{% endcomment %} - -# DataConnectOptions interface -DataConnectOptions including project id - -Signature: - -```typescript -export declare interface DataConnectOptions extends ConnectorConfig -``` -Extends: [ConnectorConfig](./data-connect.connectorconfig.md#connectorconfig_interface) - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [projectId](./data-connect.dataconnectoptions.md#dataconnectoptionsprojectid) | string | | - -## DataConnectOptions.projectId - -Signature: - -```typescript -projectId: string; -``` diff --git a/docs-devsite/data-connect.dataconnectresult.md b/docs-devsite/data-connect.dataconnectresult.md deleted file mode 100644 index 788eef76869..00000000000 --- a/docs-devsite/data-connect.dataconnectresult.md +++ /dev/null @@ -1,32 +0,0 @@ -Project: /docs/reference/js/_project.yaml -Book: /docs/reference/_book.yaml -page_type: reference - -{% comment %} -DO NOT EDIT THIS FILE! -This is generated by the JS SDK team, and any local changes will be -overwritten. Changes should be made in the source code at -https://github.com/firebase/firebase-js-sdk -{% endcomment %} - -# DataConnectResult interface -Signature: - -```typescript -export declare interface DataConnectResult extends OpResult -``` -Extends: [OpResult](./data-connect.opresult.md#opresult_interface)<Data> - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [ref](./data-connect.dataconnectresult.md#dataconnectresultref) | [OperationRef](./data-connect.operationref.md#operationref_interface)<Data, Variables> | | - -## DataConnectResult.ref - -Signature: - -```typescript -ref: OperationRef; -``` diff --git a/docs-devsite/data-connect.dataconnectsubscription.md b/docs-devsite/data-connect.dataconnectsubscription.md deleted file mode 100644 index 8eb7d303faa..00000000000 --- a/docs-devsite/data-connect.dataconnectsubscription.md +++ /dev/null @@ -1,51 +0,0 @@ -Project: /docs/reference/js/_project.yaml -Book: /docs/reference/_book.yaml -page_type: reference - -{% comment %} -DO NOT EDIT THIS FILE! -This is generated by the JS SDK team, and any local changes will be -overwritten. Changes should be made in the source code at -https://github.com/firebase/firebase-js-sdk -{% endcomment %} - -# DataConnectSubscription interface -Representation of user provided subscription options. - -Signature: - -```typescript -export declare interface DataConnectSubscription -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [errCallback](./data-connect.dataconnectsubscription.md#dataconnectsubscriptionerrcallback) | (e?: [FirebaseError](./util.firebaseerror.md#firebaseerror_class)) => void | | -| [unsubscribe](./data-connect.dataconnectsubscription.md#dataconnectsubscriptionunsubscribe) | () => void | | -| [userCallback](./data-connect.dataconnectsubscription.md#dataconnectsubscriptionusercallback) | [OnResultSubscription](./data-connect.md#onresultsubscription)<Data, Variables> | | - -## DataConnectSubscription.errCallback - -Signature: - -```typescript -errCallback?: (e?: FirebaseError) => void; -``` - -## DataConnectSubscription.unsubscribe - -Signature: - -```typescript -unsubscribe: () => void; -``` - -## DataConnectSubscription.userCallback - -Signature: - -```typescript -userCallback: OnResultSubscription; -``` diff --git a/docs-devsite/data-connect.md b/docs-devsite/data-connect.md deleted file mode 100644 index a8a8699487a..00000000000 --- a/docs-devsite/data-connect.md +++ /dev/null @@ -1,503 +0,0 @@ -Project: /docs/reference/js/_project.yaml -Book: /docs/reference/_book.yaml -page_type: reference - -{% comment %} -DO NOT EDIT THIS FILE! -This is generated by the JS SDK team, and any local changes will be -overwritten. Changes should be made in the source code at -https://github.com/firebase/firebase-js-sdk -{% endcomment %} - -# data-connect package -Firebase Data Connect - -## Functions - -| Function | Description | -| --- | --- | -| function(app, ...) | -| [getDataConnect(app, options)](./data-connect.md#getdataconnect_4a1329a) | Initialize DataConnect instance | -| function(dataConnect, ...) | -| [terminate(dataConnect)](./data-connect.md#terminate_43d8d88) | Delete DataConnect instance | -| function(dc, ...) | -| [connectDataConnectEmulator(dc, host, port, sslEnabled)](./data-connect.md#connectdataconnectemulator_842a91d) | Connect to the DataConnect Emulator | -| function(dcInstance, ...) | -| [mutationRef(dcInstance, mutationName)](./data-connect.md#mutationref_48ae5fa) | Creates a MutationRef | -| [mutationRef(dcInstance, mutationName, variables)](./data-connect.md#mutationref_1511672) | | -| [queryRef(dcInstance, queryName)](./data-connect.md#queryref_d968e5d) | Execute Query | -| [queryRef(dcInstance, queryName, variables)](./data-connect.md#queryref_6179f74) | Execute Query | -| function(logLevel, ...) | -| [setLogLevel(logLevel)](./data-connect.md#setloglevel_697d53a) | | -| function(mutationRef, ...) | -| [executeMutation(mutationRef)](./data-connect.md#executemutation_e8103b9) | Execute Mutation | -| function(options, ...) | -| [getDataConnect(options)](./data-connect.md#getdataconnect_f991922) | Initialize DataConnect instance | -| function(queryRef, ...) | -| [executeQuery(queryRef)](./data-connect.md#executequery_692a270) | Execute Query | -| function(queryRefOrSerializedResult, ...) | -| [subscribe(queryRefOrSerializedResult, observer)](./data-connect.md#subscribe_b2b4b07) | Subscribe to a QueryRef | -| [subscribe(queryRefOrSerializedResult, onNext, onError, onComplete)](./data-connect.md#subscribe_c02958e) | Subscribe to a QueryRef | -| function(serializedRef, ...) | -| [toQueryRef(serializedRef)](./data-connect.md#toqueryref_2c70e31) | Converts serialized ref to query ref | - -## Classes - -| Class | Description | -| --- | --- | -| [DataConnect](./data-connect.dataconnect.md#dataconnect_class) | Class representing Firebase Data Connect | - -## Interfaces - -| Interface | Description | -| --- | --- | -| [CancellableOperation](./data-connect.cancellableoperation.md#cancellableoperation_interface) | | -| [ConnectorConfig](./data-connect.connectorconfig.md#connectorconfig_interface) | Connector Config for calling Data Connect backend. | -| [DataConnectOptions](./data-connect.dataconnectoptions.md#dataconnectoptions_interface) | DataConnectOptions including project id | -| [DataConnectResult](./data-connect.dataconnectresult.md#dataconnectresult_interface) | | -| [DataConnectSubscription](./data-connect.dataconnectsubscription.md#dataconnectsubscription_interface) | Representation of user provided subscription options. | -| [MutationPromise](./data-connect.mutationpromise.md#mutationpromise_interface) | Mutation return value from executeMutation | -| [MutationRef](./data-connect.mutationref.md#mutationref_interface) | | -| [MutationResult](./data-connect.mutationresult.md#mutationresult_interface) | Mutation Result from executeMutation | -| [OperationRef](./data-connect.operationref.md#operationref_interface) | | -| [OpResult](./data-connect.opresult.md#opresult_interface) | | -| [QueryPromise](./data-connect.querypromise.md#querypromise_interface) | Promise returned from executeQuery | -| [QueryRef](./data-connect.queryref.md#queryref_interface) | QueryRef object | -| [QueryResult](./data-connect.queryresult.md#queryresult_interface) | Result of executeQuery | -| [RefInfo](./data-connect.refinfo.md#refinfo_interface) | Serialized RefInfo as a result of QueryResult.toJSON().refInfo | -| [SerializedRef](./data-connect.serializedref.md#serializedref_interface) | Serialized Ref as a result of QueryResult.toJSON() | -| [SubscriptionOptions](./data-connect.subscriptionoptions.md#subscriptionoptions_interface) | Representation of full observer options in subscribe | -| [TransportOptions](./data-connect.transportoptions.md#transportoptions_interface) | Options to connect to emulator | - -## Variables - -| Variable | Description | -| --- | --- | -| [MUTATION\_STR](./data-connect.md#mutation_str) | | -| [QUERY\_STR](./data-connect.md#query_str) | | -| [SOURCE\_CACHE](./data-connect.md#source_cache) | | -| [SOURCE\_SERVER](./data-connect.md#source_server) | | - -## Type Aliases - -| Type Alias | Description | -| --- | --- | -| [DataSource](./data-connect.md#datasource) | | -| [OnCompleteSubscription](./data-connect.md#oncompletesubscription) | OnCompleteSubscription | -| [OnErrorSubscription](./data-connect.md#onerrorsubscription) | Signature for OnErrorSubscription for subscribe | -| [OnResultSubscription](./data-connect.md#onresultsubscription) | Signature for OnResultSubscription for subscribe | -| [QueryUnsubscribe](./data-connect.md#queryunsubscribe) | Signature for unsubscribe from subscribe | -| [ReferenceType](./data-connect.md#referencetype) | | - -## function(app, ...) - -### getDataConnect(app, options) {:#getdataconnect_4a1329a} - -Initialize DataConnect instance - -Signature: - -```typescript -export declare function getDataConnect(app: FirebaseApp, options: ConnectorConfig): DataConnect; -``` - -#### Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| app | [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) | FirebaseApp to initialize to. | -| options | [ConnectorConfig](./data-connect.connectorconfig.md#connectorconfig_interface) | ConnectorConfig | - -Returns: - -[DataConnect](./data-connect.dataconnect.md#dataconnect_class) - -## function(dataConnect, ...) - -### terminate(dataConnect) {:#terminate_43d8d88} - -Delete DataConnect instance - -Signature: - -```typescript -export declare function terminate(dataConnect: DataConnect): Promise; -``` - -#### Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| dataConnect | [DataConnect](./data-connect.dataconnect.md#dataconnect_class) | DataConnect instance | - -Returns: - -Promise<void> - - -## function(dc, ...) - -### connectDataConnectEmulator(dc, host, port, sslEnabled) {:#connectdataconnectemulator_842a91d} - -Connect to the DataConnect Emulator - -Signature: - -```typescript -export declare function connectDataConnectEmulator(dc: DataConnect, host: string, port?: number, sslEnabled?: boolean): void; -``` - -#### Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| dc | [DataConnect](./data-connect.dataconnect.md#dataconnect_class) | Data Connect instance | -| host | string | host of emulator server | -| port | number | port of emulator server | -| sslEnabled | boolean | use https | - -Returns: - -void - -## function(dcInstance, ...) - -### mutationRef(dcInstance, mutationName) {:#mutationref_48ae5fa} - -Creates a `MutationRef` - -Signature: - -```typescript -export declare function mutationRef(dcInstance: DataConnect, mutationName: string): MutationRef; -``` - -#### Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| dcInstance | [DataConnect](./data-connect.dataconnect.md#dataconnect_class) | Data Connect instance | -| mutationName | string | name of mutation | - -Returns: - -[MutationRef](./data-connect.mutationref.md#mutationref_interface)<Data, undefined> - -### mutationRef(dcInstance, mutationName, variables) {:#mutationref_1511672} - -Signature: - -```typescript -export declare function mutationRef(dcInstance: DataConnect, mutationName: string, variables: Variables): MutationRef; -``` - -#### Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| dcInstance | [DataConnect](./data-connect.dataconnect.md#dataconnect_class) | Data Connect instance | -| mutationName | string | name of mutation | -| variables | Variables | variables to send with mutation | - -Returns: - -[MutationRef](./data-connect.mutationref.md#mutationref_interface)<Data, Variables> - -### queryRef(dcInstance, queryName) {:#queryref_d968e5d} - -Execute Query - -Signature: - -```typescript -export declare function queryRef(dcInstance: DataConnect, queryName: string): QueryRef; -``` - -#### Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| dcInstance | [DataConnect](./data-connect.dataconnect.md#dataconnect_class) | Data Connect instance to use. | -| queryName | string | Query to execute | - -Returns: - -[QueryRef](./data-connect.queryref.md#queryref_interface)<Data, undefined> - -`QueryRef` - -### queryRef(dcInstance, queryName, variables) {:#queryref_6179f74} - -Execute Query - -Signature: - -```typescript -export declare function queryRef(dcInstance: DataConnect, queryName: string, variables: Variables): QueryRef; -``` - -#### Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| dcInstance | [DataConnect](./data-connect.dataconnect.md#dataconnect_class) | Data Connect instance to use. | -| queryName | string | Query to execute | -| variables | Variables | Variables to execute with | - -Returns: - -[QueryRef](./data-connect.queryref.md#queryref_interface)<Data, Variables> - -`QueryRef` - -## function(logLevel, ...) - -### setLogLevel(logLevel) {:#setloglevel_697d53a} - -Signature: - -```typescript -export declare function setLogLevel(logLevel: LogLevelString): void; -``` - -#### Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| logLevel | LogLevelString | | - -Returns: - -void - -## function(mutationRef, ...) - -### executeMutation(mutationRef) {:#executemutation_e8103b9} - -Execute Mutation - -Signature: - -```typescript -export declare function executeMutation(mutationRef: MutationRef): MutationPromise; -``` - -#### Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| mutationRef | [MutationRef](./data-connect.mutationref.md#mutationref_interface)<Data, Variables> | mutation to execute | - -Returns: - -[MutationPromise](./data-connect.mutationpromise.md#mutationpromise_interface)<Data, Variables> - -`MutationRef` - -## function(options, ...) - -### getDataConnect(options) {:#getdataconnect_f991922} - -Initialize DataConnect instance - -Signature: - -```typescript -export declare function getDataConnect(options: ConnectorConfig): DataConnect; -``` - -#### Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| options | [ConnectorConfig](./data-connect.connectorconfig.md#connectorconfig_interface) | ConnectorConfig | - -Returns: - -[DataConnect](./data-connect.dataconnect.md#dataconnect_class) - -## function(queryRef, ...) - -### executeQuery(queryRef) {:#executequery_692a270} - -Execute Query - -Signature: - -```typescript -export declare function executeQuery(queryRef: QueryRef): QueryPromise; -``` - -#### Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| queryRef | [QueryRef](./data-connect.queryref.md#queryref_interface)<Data, Variables> | query to execute. | - -Returns: - -[QueryPromise](./data-connect.querypromise.md#querypromise_interface)<Data, Variables> - -`QueryPromise` - -## function(queryRefOrSerializedResult, ...) - -### subscribe(queryRefOrSerializedResult, observer) {:#subscribe_b2b4b07} - -Subscribe to a `QueryRef` - -Signature: - -```typescript -export declare function subscribe(queryRefOrSerializedResult: QueryRef | SerializedRef, observer: SubscriptionOptions): QueryUnsubscribe; -``` - -#### Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| queryRefOrSerializedResult | [QueryRef](./data-connect.queryref.md#queryref_interface)<Data, Variables> \| [SerializedRef](./data-connect.serializedref.md#serializedref_interface)<Data, Variables> | query ref or serialized result. | -| observer | [SubscriptionOptions](./data-connect.subscriptionoptions.md#subscriptionoptions_interface)<Data, Variables> | observer object to use for subscribing. | - -Returns: - -[QueryUnsubscribe](./data-connect.md#queryunsubscribe) - -`SubscriptionOptions` - -### subscribe(queryRefOrSerializedResult, onNext, onError, onComplete) {:#subscribe_c02958e} - -Subscribe to a `QueryRef` - -Signature: - -```typescript -export declare function subscribe(queryRefOrSerializedResult: QueryRef | SerializedRef, onNext: OnResultSubscription, onError?: OnErrorSubscription, onComplete?: OnCompleteSubscription): QueryUnsubscribe; -``` - -#### Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| queryRefOrSerializedResult | [QueryRef](./data-connect.queryref.md#queryref_interface)<Data, Variables> \| [SerializedRef](./data-connect.serializedref.md#serializedref_interface)<Data, Variables> | query ref or serialized result. | -| onNext | [OnResultSubscription](./data-connect.md#onresultsubscription)<Data, Variables> | Callback to call when result comes back. | -| onError | [OnErrorSubscription](./data-connect.md#onerrorsubscription) | Callback to call when error gets thrown. | -| onComplete | [OnCompleteSubscription](./data-connect.md#oncompletesubscription) | Called when subscription completes. | - -Returns: - -[QueryUnsubscribe](./data-connect.md#queryunsubscribe) - -`SubscriptionOptions` - -## function(serializedRef, ...) - -### toQueryRef(serializedRef) {:#toqueryref_2c70e31} - -Converts serialized ref to query ref - -Signature: - -```typescript -export declare function toQueryRef(serializedRef: SerializedRef): QueryRef; -``` - -#### Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| serializedRef | [SerializedRef](./data-connect.serializedref.md#serializedref_interface)<Data, Variables> | ref to convert to QueryRef | - -Returns: - -[QueryRef](./data-connect.queryref.md#queryref_interface)<Data, Variables> - -`QueryRef` - -## MUTATION\_STR - -Signature: - -```typescript -MUTATION_STR = "mutation" -``` - -## QUERY\_STR - -Signature: - -```typescript -QUERY_STR = "query" -``` - -## SOURCE\_CACHE - -Signature: - -```typescript -SOURCE_CACHE = "CACHE" -``` - -## SOURCE\_SERVER - -Signature: - -```typescript -SOURCE_SERVER = "SERVER" -``` - -## DataSource - -Signature: - -```typescript -export declare type DataSource = typeof SOURCE_CACHE | typeof SOURCE_SERVER; -``` - -## OnCompleteSubscription - -`OnCompleteSubscription` - -Signature: - -```typescript -export declare type OnCompleteSubscription = () => void; -``` - -## OnErrorSubscription - -Signature for `OnErrorSubscription` for `subscribe` - -Signature: - -```typescript -export declare type OnErrorSubscription = (err?: FirebaseError) => void; -``` - -## OnResultSubscription - -Signature for `OnResultSubscription` for `subscribe` - -Signature: - -```typescript -export declare type OnResultSubscription = (res: QueryResult) => void; -``` - -## QueryUnsubscribe - -Signature for unsubscribe from `subscribe` - -Signature: - -```typescript -export declare type QueryUnsubscribe = () => void; -``` - -## ReferenceType - -Signature: - -```typescript -export declare type ReferenceType = typeof QUERY_STR | typeof MUTATION_STR; -``` diff --git a/docs-devsite/data-connect.mutationpromise.md b/docs-devsite/data-connect.mutationpromise.md deleted file mode 100644 index 60624870c1d..00000000000 --- a/docs-devsite/data-connect.mutationpromise.md +++ /dev/null @@ -1,21 +0,0 @@ -Project: /docs/reference/js/_project.yaml -Book: /docs/reference/_book.yaml -page_type: reference - -{% comment %} -DO NOT EDIT THIS FILE! -This is generated by the JS SDK team, and any local changes will be -overwritten. Changes should be made in the source code at -https://github.com/firebase/firebase-js-sdk -{% endcomment %} - -# MutationPromise interface -Mutation return value from `executeMutation` - -Signature: - -```typescript -export declare interface MutationPromise extends PromiseLike> -``` -Extends: PromiseLike<[MutationResult](./data-connect.mutationresult.md#mutationresult_interface)<Data, Variables>> - diff --git a/docs-devsite/data-connect.mutationref.md b/docs-devsite/data-connect.mutationref.md deleted file mode 100644 index 0cf5a7185c4..00000000000 --- a/docs-devsite/data-connect.mutationref.md +++ /dev/null @@ -1,32 +0,0 @@ -Project: /docs/reference/js/_project.yaml -Book: /docs/reference/_book.yaml -page_type: reference - -{% comment %} -DO NOT EDIT THIS FILE! -This is generated by the JS SDK team, and any local changes will be -overwritten. Changes should be made in the source code at -https://github.com/firebase/firebase-js-sdk -{% endcomment %} - -# MutationRef interface -Signature: - -```typescript -export declare interface MutationRef extends OperationRef -``` -Extends: [OperationRef](./data-connect.operationref.md#operationref_interface)<Data, Variables> - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [refType](./data-connect.mutationref.md#mutationrefreftype) | typeof [MUTATION\_STR](./data-connect.md#mutation_str) | | - -## MutationRef.refType - -Signature: - -```typescript -refType: typeof MUTATION_STR; -``` diff --git a/docs-devsite/data-connect.mutationresult.md b/docs-devsite/data-connect.mutationresult.md deleted file mode 100644 index 94067123cff..00000000000 --- a/docs-devsite/data-connect.mutationresult.md +++ /dev/null @@ -1,34 +0,0 @@ -Project: /docs/reference/js/_project.yaml -Book: /docs/reference/_book.yaml -page_type: reference - -{% comment %} -DO NOT EDIT THIS FILE! -This is generated by the JS SDK team, and any local changes will be -overwritten. Changes should be made in the source code at -https://github.com/firebase/firebase-js-sdk -{% endcomment %} - -# MutationResult interface -Mutation Result from `executeMutation` - -Signature: - -```typescript -export declare interface MutationResult extends DataConnectResult -``` -Extends: [DataConnectResult](./data-connect.dataconnectresult.md#dataconnectresult_interface)<Data, Variables> - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [ref](./data-connect.mutationresult.md#mutationresultref) | [MutationRef](./data-connect.mutationref.md#mutationref_interface)<Data, Variables> | | - -## MutationResult.ref - -Signature: - -```typescript -ref: MutationRef; -``` diff --git a/docs-devsite/data-connect.operationref.md b/docs-devsite/data-connect.operationref.md deleted file mode 100644 index e73d249fbda..00000000000 --- a/docs-devsite/data-connect.operationref.md +++ /dev/null @@ -1,58 +0,0 @@ -Project: /docs/reference/js/_project.yaml -Book: /docs/reference/_book.yaml -page_type: reference - -{% comment %} -DO NOT EDIT THIS FILE! -This is generated by the JS SDK team, and any local changes will be -overwritten. Changes should be made in the source code at -https://github.com/firebase/firebase-js-sdk -{% endcomment %} - -# OperationRef interface -Signature: - -```typescript -export declare interface OperationRef<_Data, Variables> -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [dataConnect](./data-connect.operationref.md#operationrefdataconnect) | [DataConnect](./data-connect.dataconnect.md#dataconnect_class) | | -| [name](./data-connect.operationref.md#operationrefname) | string | | -| [refType](./data-connect.operationref.md#operationrefreftype) | [ReferenceType](./data-connect.md#referencetype) | | -| [variables](./data-connect.operationref.md#operationrefvariables) | Variables | | - -## OperationRef.dataConnect - -Signature: - -```typescript -dataConnect: DataConnect; -``` - -## OperationRef.name - -Signature: - -```typescript -name: string; -``` - -## OperationRef.refType - -Signature: - -```typescript -refType: ReferenceType; -``` - -## OperationRef.variables - -Signature: - -```typescript -variables: Variables; -``` diff --git a/docs-devsite/data-connect.opresult.md b/docs-devsite/data-connect.opresult.md deleted file mode 100644 index 1f0a4c2c0f2..00000000000 --- a/docs-devsite/data-connect.opresult.md +++ /dev/null @@ -1,49 +0,0 @@ -Project: /docs/reference/js/_project.yaml -Book: /docs/reference/_book.yaml -page_type: reference - -{% comment %} -DO NOT EDIT THIS FILE! -This is generated by the JS SDK team, and any local changes will be -overwritten. Changes should be made in the source code at -https://github.com/firebase/firebase-js-sdk -{% endcomment %} - -# OpResult interface -Signature: - -```typescript -export declare interface OpResult -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [data](./data-connect.opresult.md#opresultdata) | Data | | -| [fetchTime](./data-connect.opresult.md#opresultfetchtime) | string | | -| [source](./data-connect.opresult.md#opresultsource) | [DataSource](./data-connect.md#datasource) | | - -## OpResult.data - -Signature: - -```typescript -data: Data; -``` - -## OpResult.fetchTime - -Signature: - -```typescript -fetchTime: string; -``` - -## OpResult.source - -Signature: - -```typescript -source: DataSource; -``` diff --git a/docs-devsite/data-connect.querypromise.md b/docs-devsite/data-connect.querypromise.md deleted file mode 100644 index 27dc6204260..00000000000 --- a/docs-devsite/data-connect.querypromise.md +++ /dev/null @@ -1,21 +0,0 @@ -Project: /docs/reference/js/_project.yaml -Book: /docs/reference/_book.yaml -page_type: reference - -{% comment %} -DO NOT EDIT THIS FILE! -This is generated by the JS SDK team, and any local changes will be -overwritten. Changes should be made in the source code at -https://github.com/firebase/firebase-js-sdk -{% endcomment %} - -# QueryPromise interface -Promise returned from `executeQuery` - -Signature: - -```typescript -export declare interface QueryPromise extends PromiseLike> -``` -Extends: PromiseLike<[QueryResult](./data-connect.queryresult.md#queryresult_interface)<Data, Variables>> - diff --git a/docs-devsite/data-connect.queryref.md b/docs-devsite/data-connect.queryref.md deleted file mode 100644 index 2e67c884016..00000000000 --- a/docs-devsite/data-connect.queryref.md +++ /dev/null @@ -1,34 +0,0 @@ -Project: /docs/reference/js/_project.yaml -Book: /docs/reference/_book.yaml -page_type: reference - -{% comment %} -DO NOT EDIT THIS FILE! -This is generated by the JS SDK team, and any local changes will be -overwritten. Changes should be made in the source code at -https://github.com/firebase/firebase-js-sdk -{% endcomment %} - -# QueryRef interface -QueryRef object - -Signature: - -```typescript -export declare interface QueryRef extends OperationRef -``` -Extends: [OperationRef](./data-connect.operationref.md#operationref_interface)<Data, Variables> - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [refType](./data-connect.queryref.md#queryrefreftype) | typeof [QUERY\_STR](./data-connect.md#query_str) | | - -## QueryRef.refType - -Signature: - -```typescript -refType: typeof QUERY_STR; -``` diff --git a/docs-devsite/data-connect.queryresult.md b/docs-devsite/data-connect.queryresult.md deleted file mode 100644 index ac0380e2002..00000000000 --- a/docs-devsite/data-connect.queryresult.md +++ /dev/null @@ -1,43 +0,0 @@ -Project: /docs/reference/js/_project.yaml -Book: /docs/reference/_book.yaml -page_type: reference - -{% comment %} -DO NOT EDIT THIS FILE! -This is generated by the JS SDK team, and any local changes will be -overwritten. Changes should be made in the source code at -https://github.com/firebase/firebase-js-sdk -{% endcomment %} - -# QueryResult interface -Result of `executeQuery` - -Signature: - -```typescript -export declare interface QueryResult extends DataConnectResult -``` -Extends: [DataConnectResult](./data-connect.dataconnectresult.md#dataconnectresult_interface)<Data, Variables> - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [ref](./data-connect.queryresult.md#queryresultref) | [QueryRef](./data-connect.queryref.md#queryref_interface)<Data, Variables> | | -| [toJSON](./data-connect.queryresult.md#queryresulttojson) | () => [SerializedRef](./data-connect.serializedref.md#serializedref_interface)<Data, Variables> | | - -## QueryResult.ref - -Signature: - -```typescript -ref: QueryRef; -``` - -## QueryResult.toJSON - -Signature: - -```typescript -toJSON: () => SerializedRef; -``` diff --git a/docs-devsite/data-connect.refinfo.md b/docs-devsite/data-connect.refinfo.md deleted file mode 100644 index 6114fc0a1fc..00000000000 --- a/docs-devsite/data-connect.refinfo.md +++ /dev/null @@ -1,51 +0,0 @@ -Project: /docs/reference/js/_project.yaml -Book: /docs/reference/_book.yaml -page_type: reference - -{% comment %} -DO NOT EDIT THIS FILE! -This is generated by the JS SDK team, and any local changes will be -overwritten. Changes should be made in the source code at -https://github.com/firebase/firebase-js-sdk -{% endcomment %} - -# RefInfo interface -Serialized RefInfo as a result of `QueryResult.toJSON().refInfo` - -Signature: - -```typescript -export declare interface RefInfo -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [connectorConfig](./data-connect.refinfo.md#refinfoconnectorconfig) | [DataConnectOptions](./data-connect.dataconnectoptions.md#dataconnectoptions_interface) | | -| [name](./data-connect.refinfo.md#refinfoname) | string | | -| [variables](./data-connect.refinfo.md#refinfovariables) | Variables | | - -## RefInfo.connectorConfig - -Signature: - -```typescript -connectorConfig: DataConnectOptions; -``` - -## RefInfo.name - -Signature: - -```typescript -name: string; -``` - -## RefInfo.variables - -Signature: - -```typescript -variables: Variables; -``` diff --git a/docs-devsite/data-connect.serializedref.md b/docs-devsite/data-connect.serializedref.md deleted file mode 100644 index f93c94d722d..00000000000 --- a/docs-devsite/data-connect.serializedref.md +++ /dev/null @@ -1,34 +0,0 @@ -Project: /docs/reference/js/_project.yaml -Book: /docs/reference/_book.yaml -page_type: reference - -{% comment %} -DO NOT EDIT THIS FILE! -This is generated by the JS SDK team, and any local changes will be -overwritten. Changes should be made in the source code at -https://github.com/firebase/firebase-js-sdk -{% endcomment %} - -# SerializedRef interface -Serialized Ref as a result of `QueryResult.toJSON()` - -Signature: - -```typescript -export declare interface SerializedRef extends OpResult -``` -Extends: [OpResult](./data-connect.opresult.md#opresult_interface)<Data> - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [refInfo](./data-connect.serializedref.md#serializedrefrefinfo) | [RefInfo](./data-connect.refinfo.md#refinfo_interface)<Variables> | | - -## SerializedRef.refInfo - -Signature: - -```typescript -refInfo: RefInfo; -``` diff --git a/docs-devsite/data-connect.subscriptionoptions.md b/docs-devsite/data-connect.subscriptionoptions.md deleted file mode 100644 index d666f5bb380..00000000000 --- a/docs-devsite/data-connect.subscriptionoptions.md +++ /dev/null @@ -1,51 +0,0 @@ -Project: /docs/reference/js/_project.yaml -Book: /docs/reference/_book.yaml -page_type: reference - -{% comment %} -DO NOT EDIT THIS FILE! -This is generated by the JS SDK team, and any local changes will be -overwritten. Changes should be made in the source code at -https://github.com/firebase/firebase-js-sdk -{% endcomment %} - -# SubscriptionOptions interface -Representation of full observer options in `subscribe` - -Signature: - -```typescript -export declare interface SubscriptionOptions -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [onComplete](./data-connect.subscriptionoptions.md#subscriptionoptionsoncomplete) | [OnCompleteSubscription](./data-connect.md#oncompletesubscription) | | -| [onErr](./data-connect.subscriptionoptions.md#subscriptionoptionsonerr) | [OnErrorSubscription](./data-connect.md#onerrorsubscription) | | -| [onNext](./data-connect.subscriptionoptions.md#subscriptionoptionsonnext) | [OnResultSubscription](./data-connect.md#onresultsubscription)<Data, Variables> | | - -## SubscriptionOptions.onComplete - -Signature: - -```typescript -onComplete?: OnCompleteSubscription; -``` - -## SubscriptionOptions.onErr - -Signature: - -```typescript -onErr?: OnErrorSubscription; -``` - -## SubscriptionOptions.onNext - -Signature: - -```typescript -onNext?: OnResultSubscription; -``` diff --git a/docs-devsite/data-connect.transportoptions.md b/docs-devsite/data-connect.transportoptions.md deleted file mode 100644 index dbff25dc5d1..00000000000 --- a/docs-devsite/data-connect.transportoptions.md +++ /dev/null @@ -1,51 +0,0 @@ -Project: /docs/reference/js/_project.yaml -Book: /docs/reference/_book.yaml -page_type: reference - -{% comment %} -DO NOT EDIT THIS FILE! -This is generated by the JS SDK team, and any local changes will be -overwritten. Changes should be made in the source code at -https://github.com/firebase/firebase-js-sdk -{% endcomment %} - -# TransportOptions interface -Options to connect to emulator - -Signature: - -```typescript -export declare interface TransportOptions -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [host](./data-connect.transportoptions.md#transportoptionshost) | string | | -| [port](./data-connect.transportoptions.md#transportoptionsport) | number | | -| [sslEnabled](./data-connect.transportoptions.md#transportoptionssslenabled) | boolean | | - -## TransportOptions.host - -Signature: - -```typescript -host: string; -``` - -## TransportOptions.port - -Signature: - -```typescript -port?: number; -``` - -## TransportOptions.sslEnabled - -Signature: - -```typescript -sslEnabled?: boolean; -``` diff --git a/docs-devsite/index.md b/docs-devsite/index.md index c8b5bc4fda9..0064652e792 100644 --- a/docs-devsite/index.md +++ b/docs-devsite/index.md @@ -19,7 +19,6 @@ https://github.com/firebase/firebase-js-sdk | [@firebase/app](./app.md#app_package) | Firebase App | | [@firebase/app-check](./app-check.md#app-check_package) | The Firebase App Check Web SDK. | | [@firebase/auth](./auth.md#auth_package) | Firebase Authentication | -| [@firebase/data-connect](./data-connect.md#data-connect_package) | Firebase Data Connect | | [@firebase/database](./database.md#database_package) | Firebase Realtime Database | | [@firebase/firestore](./firestore.md#firestore_package) | Cloud Firestore | | [@firebase/functions](./functions.md#functions_package) | Cloud Functions for Firebase | From 24c3c491d15f5dd087780f59f3120ec222b05f04 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Fri, 20 Sep 2024 00:23:27 +0100 Subject: [PATCH 69/74] Changed data connect to minor --- .changeset/cold-chairs-fold.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/cold-chairs-fold.md b/.changeset/cold-chairs-fold.md index ee89d84984d..0fb82429a87 100644 --- a/.changeset/cold-chairs-fold.md +++ b/.changeset/cold-chairs-fold.md @@ -1,7 +1,7 @@ --- "firebase": minor "@firebase/app": patch -"@firebase/data-connect": patch +"@firebase/data-connect": minor --- Included Data Connect product. From c7590a11eb6f6c60bebf4f2b15ca36b21a5d302e Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Fri, 20 Sep 2024 00:25:56 +0100 Subject: [PATCH 70/74] Fixed formatting --- scripts/docgen/docgen.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/docgen/docgen.ts b/scripts/docgen/docgen.ts index cbe4b754908..3b3b10c8714 100644 --- a/scripts/docgen/docgen.ts +++ b/scripts/docgen/docgen.ts @@ -37,7 +37,12 @@ https://github.com/firebase/firebase-js-sdk `; const tmpDir = `${projectRoot}/temp`; -const EXCLUDED_PACKAGES = ['app-compat', 'util', 'rules-unit-testing', 'data-connect']; +const EXCLUDED_PACKAGES = [ + 'app-compat', + 'util', + 'rules-unit-testing', + 'data-connect' +]; /** * When ordering functions, will prioritize these first params at From b4a27c8b7a23ee9fdbd2318eec6389ec7b36956e Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Fri, 20 Sep 2024 10:54:37 +0100 Subject: [PATCH 71/74] Fixed tests --- packages/data-connect/test/unit/gmpid.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/data-connect/test/unit/gmpid.test.ts b/packages/data-connect/test/unit/gmpid.test.ts index c6679ca242d..77b9f8bcac4 100644 --- a/packages/data-connect/test/unit/gmpid.test.ts +++ b/packages/data-connect/test/unit/gmpid.test.ts @@ -53,7 +53,7 @@ describe('GMPID Tests', () => { // @ts-ignore await executeQuery(queryRef(dc, '')).catch(() => {}); expect(fakeFetchImpl).to.be.calledWithMatch( - 'https://firebasedataconnect.googleapis.com/v1alpha/projects/p/locations/l/services/s/connectors/c:executeQuery', + 'https://firebasedataconnect.googleapis.com/v1beta/projects/p/locations/l/services/s/connectors/c:executeQuery', { headers: { ['x-firebase-gmpid']: APPID @@ -71,7 +71,7 @@ describe('GMPID Tests', () => { // @ts-ignore await executeQuery(queryRef(dc2, '')).catch(() => {}); expect(fakeFetchImpl).to.be.calledWithMatch( - 'https://firebasedataconnect.googleapis.com/v1alpha/projects/p/locations/l/services/s/connectors/c:executeQuery', + 'https://firebasedataconnect.googleapis.com/v1beta/projects/p/locations/l/services/s/connectors/c:executeQuery', { headers: { ['x-firebase-gmpid']: APPID From 5ae1bccba5102c2a31772ea25c965c7ef5c898ca Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Mon, 23 Sep 2024 21:50:27 +0100 Subject: [PATCH 72/74] Addressed comments --- packages/data-connect/karma.conf.js | 2 +- packages/data-connect/rollup.config.js | 15 +-------------- .../src/core/AppCheckTokenProvider.ts | 8 -------- 3 files changed, 2 insertions(+), 23 deletions(-) diff --git a/packages/data-connect/karma.conf.js b/packages/data-connect/karma.conf.js index d51e08d046e..acb47c2ab3b 100644 --- a/packages/data-connect/karma.conf.js +++ b/packages/data-connect/karma.conf.js @@ -1,6 +1,6 @@ /** * @license - * Copyright 2017 Google LLC + * 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. diff --git a/packages/data-connect/rollup.config.js b/packages/data-connect/rollup.config.js index 2422c89e3cb..cb220911d69 100644 --- a/packages/data-connect/rollup.config.js +++ b/packages/data-connect/rollup.config.js @@ -1,6 +1,6 @@ /** * @license - * Copyright 2018 Google LLC + * 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. @@ -148,19 +148,6 @@ const nodeBuilds = [ external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), onwarn: onWarn } - /** - * Standalone Build for Admin SDK - */ - // { - // input: 'src/index.standalone.ts', - // output: [{ file: pkg.standalone, format: 'cjs', sourcemap: true }], - // plugins: es5BuildPlugins, - // treeshake: { - // moduleSideEffects: false - // }, - // external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)), - // onwarn: onWarn - // } ]; export default [...browserBuilds, ...nodeBuilds]; diff --git a/packages/data-connect/src/core/AppCheckTokenProvider.ts b/packages/data-connect/src/core/AppCheckTokenProvider.ts index d4994efc5cd..d9cdaeb6f39 100644 --- a/packages/data-connect/src/core/AppCheckTokenProvider.ts +++ b/packages/data-connect/src/core/AppCheckTokenProvider.ts @@ -66,12 +66,4 @@ export class AppCheckTokenProvider { ?.get() .then(appCheck => appCheck.addTokenListener(listener)); } - - // Not currently used at the moment. Will update if needed. - // notifyForInvalidToken(): void { - // warn( - // `Provided AppCheck credentials for the app named "${this.appName_}" ` + - // 'are invalid. This usually indicates your app was not initialized correctly.' - // ); - // } } From f563ae8720d367aa86673016c64b03fab6de6a58 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Mon, 23 Sep 2024 21:51:37 +0100 Subject: [PATCH 73/74] Addressed comments --- packages/data-connect/test/emulatorSeeder.ts | 21 -------------------- 1 file changed, 21 deletions(-) diff --git a/packages/data-connect/test/emulatorSeeder.ts b/packages/data-connect/test/emulatorSeeder.ts index 36cdf691169..1517deb90f8 100644 --- a/packages/data-connect/test/emulatorSeeder.ts +++ b/packages/data-connect/test/emulatorSeeder.ts @@ -17,30 +17,9 @@ import fs from 'fs'; import * as path from 'path'; -// curl localhost:3628/setupSchema -X POST -d '{ -// "service_id": "s", -// "schema": { -// "files": [ -// { -// "path": "schema/post.gql", -// "content": "type Post @table {content: String!}" -// } -// ] -// }, -// "connectors": { -// "crud": { -// "files": [ -// { -// "path": "operations/post.gql", -// "content": "query getPost($id: UUID!) @auth(level: PUBLIC) {post(id: $id) {content}} query listPosts @auth(level: PUBLIC) {posts {content}} mutation createPost($id: UUID!, content: String!) @auth(level: PUBLIC) {post_insert(data: {id: $id, content: $content})} mutation deletePost($id: UUID!) @auth(level: PUBLIC) { post_delete(id: $id)}" -// } -// ] -// } -// } import { ReferenceType } from '../src'; -// } import { EMULATOR_PORT } from './util'; export interface SeedInfo { From 9809b39d075b1361b17e0a7b735b9082729a2232 Mon Sep 17 00:00:00 2001 From: Maneesh Tewani Date: Mon, 23 Sep 2024 22:09:35 +0100 Subject: [PATCH 74/74] Removed unnecessary files --- packages/firebase/data-connect/index.ts | 2 +- ...es-generics-through-inheritence.input.d.ts | 25 ------------------- 2 files changed, 1 insertion(+), 26 deletions(-) delete mode 100644 repo-scripts/prune-dts/tests/resolves-generics-through-inheritence.input.d.ts diff --git a/packages/firebase/data-connect/index.ts b/packages/firebase/data-connect/index.ts index b4a08750867..cc3ce65c24a 100644 --- a/packages/firebase/data-connect/index.ts +++ b/packages/firebase/data-connect/index.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2020 Google LLC + * 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. diff --git a/repo-scripts/prune-dts/tests/resolves-generics-through-inheritence.input.d.ts b/repo-scripts/prune-dts/tests/resolves-generics-through-inheritence.input.d.ts deleted file mode 100644 index 7208f2cf528..00000000000 --- a/repo-scripts/prune-dts/tests/resolves-generics-through-inheritence.input.d.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @license - * Copyright 2020 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. - */ - -declare class A { - a: T; -} -export class B extends A { - b: T; -} -export class C extends A {} -export {};
` element. + */ +export class DocTableCell extends DocNode { + public readonly content: DocSection; + + public constructor( + parameters: IDocTableCellParameters, + sectionChildNodes?: ReadonlyArray + ) { + super(parameters); + + this.content = new DocSection( + { configuration: this.configuration }, + sectionChildNodes + ); + } + + /** @override */ + public get kind(): string { + return CustomDocNodeKind.TableCell; + } +} diff --git a/repo-scripts/api-documenter/src/nodes/DocTableRow.ts b/repo-scripts/api-documenter/src/nodes/DocTableRow.ts new file mode 100644 index 00000000000..4d14b7b3d03 --- /dev/null +++ b/repo-scripts/api-documenter/src/nodes/DocTableRow.ts @@ -0,0 +1,86 @@ +/** + * @license + * Copyright 2020 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. + */ + +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { IDocNodeParameters, DocNode, DocPlainText } from '@microsoft/tsdoc'; +import { CustomDocNodeKind } from './CustomDocNodeKind'; +import { DocTableCell } from './DocTableCell'; + +/** + * Constructor parameters for {@link DocTableRow}. + */ +export interface IDocTableRowParameters extends IDocNodeParameters {} + +/** + * Represents table row, similar to an HTML `