async confirmEmail(token: string, primary: boolean, reply: Reply) {
        try {
            //find user by token (primary or backup)
            let user: User = null;
            primary ? user = await this.findByPrimaryEmailVerificationToken(token) : await this.findByBackupEmailVerificationToken(token);
            if (user) {
                if (user.emailVerificationTokenExpiration.valueOf() > Date.now()) {
                    if (primary) {
                        user.isPrimaryEmailAddressVerified = true;
                        user.primaryEmailVerificationToken = null;
                    } else {
                        user.isBackupEmailAddressVerified = true;
                        user.backupEmailVerificationToken = null;
                    }
                    user.emailVerificationTokenExpiration = null;

                    await this.userRepository.save(user);

                    reply.view('users/confirm-email-feedback.html', { title: 'SGVI-1 Mini CMS - Confirm Email', notificationClass: "is-success", notificationMessage: "Email confirmed!" });
                } else {//expired token
                    reply.view('users/confirm-email-feedback.html', { title: 'SGVI-1 Mini CMS - Confirm Email', notificationClass: "is-danger", notificationMessage: "Problem confirming email. Token has expired!" });
                }
            } else {//user with the sent token not found
                reply.view('users/confirm-email-feedback.html', { title: 'SGVI-1 Mini CMS - Confirm Email', notificationClass: "is-danger", notificationMessage: "Problem confirming email" });
            }

        } catch (error) {
            reply.view('users/confirm-email-feedback.html', { title: 'SGVI-1 Mini CMS - Confirm Email', notificationClass: "is-danger", notificationMessage: "Problem confirming email!" });
        }
    }


/**
     * Invoked to setRefreshTokenHash after successful login.
     * @param refreshToken
     * @param userId
     */
async setRefreshTokenHash(refreshToken: string, userId: number) {
        const refreshTokenHash = await bcrypt.hash(refreshToken, 10);
        await this.userRepository.update(userId, {
            refreshTokenHash
        });
    }


async addFacebookProfile(userId: number, facebookProfile: FacebookProfileDto): Promise<User> {

        try {
            await this.userRepository.createQueryBuilder()
                .relation(User, "facebookProfile")
                .of(userId)
                .set(facebookProfile)

            //return the user modified
            return await this.userRepository.findOne(userId, { relations: ['facebookProfile', 'roles'] });

        } catch (error) {
            if (error && error.code === PG_UNIQUE_CONSTRAINT_VIOLATION) {
                throw new HttpException({
                    status: HttpStatus.BAD_REQUEST,
                    error: `There was a problem adding facebookProfile to user: ${error.message}`,
                }, HttpStatus.BAD_REQUEST)
            } else {
                throw new HttpException({
                    status: HttpStatus.INTERNAL_SERVER_ERROR,
                    error: `There was a problem with adding facebookProfile to user: ${error.message}`,
                }, HttpStatus.INTERNAL_SERVER_ERROR);
            }
        }

    }


export interface TextFieldOptions extends FieldProps, FlexContainerStyleProps {
  /**
   * Component(s) to show after input
   */
  outerEndComponent?: React.ReactNode;

  /**
   * Component(s) to show before input
   */
  outerStartComponent?: React.ReactNode;

  /**
   * FieldGroupIconButton component to show inside of input at start
   */
  innerStartComponent?: React.ReactNode;

  /**
   * FieldGroupIconButton component to show inside of input at end
   */
  innerEndComponent?: React.ReactNode;

  /**
   * Input field type (only applies when isMultiline = false)
   */
  type?: InputProps['type'];
}


export interface TextInputFieldProps extends TextFieldOptions, InputProps {}


export interface TextAreaFieldProps extends TextFieldOptions, TextAreaProps {}


export type TextFieldProps<Multiline extends boolean> = (Multiline extends true
  ? TextAreaFieldProps
  : TextInputFieldProps) & {
  isMultiline?: Multiline;
};


function SvgComponent(props: React.SVGProps<SVGSVGElement>) {
    return (
        <svg
            viewBox="0 0 24 24"
            fill="none"
            xmlns="http://www.w3.org/2000/svg"
            width="1em"
            height="1em"
            focusable={false}
            aria-hidden={true}
            {...props}
        >
            <path
                fillRule="evenodd"
                clipRule="evenodd"
                d="M5 4.5C5 3.67157 5.67157 3 6.5 3H19.5C20.3284 3 21 3.67157 21 4.5V17.5C21 18.3284 20.3284 19 19.5 19C18.6716 19 18 18.3284 18 17.5V8.12132L4.56066 21.5606C3.97487 22.1464 3.02513 22.1464 2.43934 21.5606C1.85355 20.9749 1.85355 20.0251 2.43934 19.4393L15.8787 6H6.5C5.67157 6 5 5.32843 5 4.5Z"
                fill="currentColor"
            />
        </svg>
    );
}


/**
 * __useGetOfferQuery__
 *
 * To run a query within a React component, call `useGetOfferQuery` and pass it any options that fit your needs.
 * When your component renders, `useGetOfferQuery` returns an object from Apollo Client that contains loading, error, and data properties
 * you can use to render your UI.
 *
 * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
 *
 * @example
 * const { data, loading, error } = useGetOfferQuery({
 *   variables: {
 *      id: // value for 'id'
 *   },
 * });
 */
export function useGetOfferQuery(baseOptions?: ApolloReactHooks.QueryHookOptions<GetOfferQuery, GetOfferQueryVariables>) {
        return ApolloReactHooks.useQuery<GetOfferQuery, GetOfferQueryVariables>(GetOfferDocument, baseOptions);
      }


export function useGetOfferLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<GetOfferQuery, GetOfferQueryVariables>) {
          return ApolloReactHooks.useLazyQuery<GetOfferQuery, GetOfferQueryVariables>(GetOfferDocument, baseOptions);
        }


/**
 * __useGetProductQuery__
 *
 * To run a query within a React component, call `useGetProductQuery` and pass it any options that fit your needs.
 * When your component renders, `useGetProductQuery` returns an object from Apollo Client that contains loading, error, and data properties
 * you can use to render your UI.
 *
 * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
 *
 * @example
 * const { data, loading, error } = useGetProductQuery({
 *   variables: {
 *      id: // value for 'id'
 *   },
 * });
 */
export function useGetProductQuery(baseOptions?: ApolloReactHooks.QueryHookOptions<GetProductQuery, GetProductQueryVariables>) {
        return ApolloReactHooks.useQuery<GetProductQuery, GetProductQueryVariables>(GetProductDocument, baseOptions);
      }


export function useGetProductLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<GetProductQuery, GetProductQueryVariables>) {
          return ApolloReactHooks.useLazyQuery<GetProductQuery, GetProductQueryVariables>(GetProductDocument, baseOptions);
        }


/**
 * __useGetProductsQuery__
 *
 * To run a query within a React component, call `useGetProductsQuery` and pass it any options that fit your needs.
 * When your component renders, `useGetProductsQuery` returns an object from Apollo Client that contains loading, error, and data properties
 * you can use to render your UI.
 *
 * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
 *
 * @example
 * const { data, loading, error } = useGetProductsQuery({
 *   variables: {
 *      id: // value for 'id'
 *   },
 * });
 */
export function useGetProductsQuery(baseOptions?: ApolloReactHooks.QueryHookOptions<GetProductsQuery, GetProductsQueryVariables>) {
        return ApolloReactHooks.useQuery<GetProductsQuery, GetProductsQueryVariables>(GetProductsDocument, baseOptions);
      }


export function useGetProductsLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<GetProductsQuery, GetProductsQueryVariables>) {
          return ApolloReactHooks.useLazyQuery<GetProductsQuery, GetProductsQueryVariables>(GetProductsDocument, baseOptions);
        }


export enum CacheControlScope {
  Public = 'PUBLIC',
  Private = 'PRIVATE'
}


/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
  ID: string,
  String: string,
  Boolean: boolean,
  Int: number,
  Float: number,
};


/** Show Offer for a Product */
export type Offer = {
   __typename?: 'Offer',
  id: Scalars['String'],
  condition: Scalars['String'],
  price: Scalars['Float'],
  availabilityCode: Scalars['String'],
  availabilityDescription: Scalars['String'],
  seller: OfferSeller,
};


export type OfferSeller = {
   __typename?: 'OfferSeller',
  id: Scalars['String'],
  sellerType: Scalars['String'],
  displayName: Scalars['String'],
};


export type Product = {
   __typename?: 'Product',
  offer?: Maybe<Offer>,
  id: Scalars['String'],
  ean?: Maybe<Scalars['String']>,
  title: Scalars['String'],
  specsTag?: Maybe<Scalars['String']>,
  summary?: Maybe<Scalars['String']>,
  rating?: Maybe<Scalars['Int']>,
  shortDescription?: Maybe<Scalars['String']>,
  urls?: Maybe<Array<Maybe<ProductUrls>>>,
  images?: Maybe<Array<Maybe<ProductImage>>>,
  /** TODO: question does 'offerData' belong here? */
  offerData?: Maybe<ProductOfferData>,
  parentCategoryPaths?: Maybe<ProductParentCategoryPaths>,
};


export type ProductImage = {
   __typename?: 'ProductImage',
  type?: Maybe<Scalars['String']>,
  key?: Maybe<Scalars['String']>,
  url?: Maybe<Scalars['String']>,
};


export type ProductOfferData = {
   __typename?: 'ProductOfferData',
  bolCom?: Maybe<Scalars['Int']>,
  nonProfessionalSellers?: Maybe<Scalars['Int']>,
  professionalSellers?: Maybe<Scalars['Int']>,
  offers?: Maybe<Array<Maybe<ProductOfferDataOffer>>>,
};


/** TODO: is this Offer? Schema usage validator should pick this up */
export type ProductOfferDataOffer = {
   __typename?: 'ProductOfferDataOffer',
  id: Scalars['String'],
  condition: Scalars['String'],
  price: Scalars['Float'],
  listPrice?: Maybe<Scalars['Float']>,
  availabilityCode: Scalars['String'],
  availabilityDescription: Scalars['String'],
  comment: Scalars['String'],
  seller: ProductSeller,
  bestOffer?: Maybe<Scalars['Boolean']>,
  releaseDate?: Maybe<Scalars['String']>,
};


export type ProductParentCategory = {
   __typename?: 'ProductParentCategory',
  id?: Maybe<Scalars['String']>,
  name?: Maybe<Scalars['String']>,
};


export type ProductParentCategoryPaths = {
   __typename?: 'ProductParentCategoryPaths',
  parentCategories?: Maybe<ProductParentCategory>,
};


/** Products for a specific category, model is taken as is from bol.com */
export type Products = {
   __typename?: 'Products',
  products?: Maybe<Array<Maybe<Product>>>,
  schemaVersion?: Maybe<Scalars['String']>,
  totalResultSize?: Maybe<Scalars['Int']>,
  originalRequest?: Maybe<ProductsOriginalRequest>,
};


export type ProductSeller = {
   __typename?: 'ProductSeller',
  id: Scalars['String'],
  sellerType: Scalars['String'],
  displayName: Scalars['String'],
  url?: Maybe<Scalars['String']>,
  topSeller: Scalars['Boolean'],
  useWarrantyRepairConditions: Scalars['Boolean'],
};


export type ProductsOriginalRequest = {
   __typename?: 'ProductsOriginalRequest',
  category?: Maybe<Array<Maybe<ProductsOriginalRequestCategory>>>,
};


export type ProductsOriginalRequestCategory = {
   __typename?: 'ProductsOriginalRequestCategory',
  id?: Maybe<Scalars['Int']>,
  name?: Maybe<Scalars['String']>,
  productCount?: Maybe<Scalars['Int']>,
};


export type ProductUrls = {
   __typename?: 'ProductUrls',
  key?: Maybe<Scalars['String']>,
  value?: Maybe<Scalars['String']>,
};


export type Query = {
   __typename?: 'Query',
  /** Get best offer for specific product */
  getOffer?: Maybe<Offer>,
  /** Get all products for a specific list */
  getProducts?: Maybe<Products>,
  /** Get single product */
  getProduct?: Maybe<Product>,
};


export type QueryGetOfferArgs = {
  id: Scalars['String']
};


export type QueryGetProductsArgs = {
  id: Scalars['String']
};


export type QueryGetProductArgs = {
  id: Scalars['String']
};


export type ProductFragment = (
  { __typename?: 'Product' }
  & Pick<Product, 'id' | 'title' | 'rating' | 'shortDescription'>
  & { images: Maybe<Array<Maybe<(
    { __typename?: 'ProductImage' }
    & Pick<ProductImage, 'key' | 'url'>
  )>>>, urls: Maybe<Array<Maybe<(
    { __typename?: 'ProductUrls' }
    & Pick<ProductUrls, 'key' | 'value'>
  )>>> }
);


export type GetOfferQueryVariables = {
  id: Scalars['String']
};


export type GetOfferQuery = (
  { __typename?: 'Query' }
  & { getOffer: Maybe<(
    { __typename?: 'Offer' }
    & Pick<Offer, 'id' | 'price' | 'availabilityDescription'>
    & { seller: (
      { __typename?: 'OfferSeller' }
      & Pick<OfferSeller, 'displayName'>
    ) }
  )> }
);


export type GetProductQueryVariables = {
  id: Scalars['String']
};


export type GetProductQuery = (
  { __typename?: 'Query' }
  & { getProduct: Maybe<(
    { __typename?: 'Product' }
    & ProductFragment
  )> }
);


export type GetProductsQueryVariables = {
  id: Scalars['String']
};


export type GetProductsQuery = (
  { __typename?: 'Query' }
  & { getProducts: Maybe<(
    { __typename?: 'Products' }
    & { products: Maybe<Array<Maybe<(
      { __typename?: 'Product' }
      & ProductFragment
    )>>> }
  )> }
);


export type GetOfferQueryHookResult = ReturnType<typeof useGetOfferQuery>;


export type GetOfferLazyQueryHookResult = ReturnType<typeof useGetOfferLazyQuery>;


export type GetOfferQueryResult = ApolloReactCommon.QueryResult<GetOfferQuery, GetOfferQueryVariables>;


export type GetProductQueryHookResult = ReturnType<typeof useGetProductQuery>;


export type GetProductLazyQueryHookResult = ReturnType<typeof useGetProductLazyQuery>;


export type GetProductQueryResult = ApolloReactCommon.QueryResult<GetProductQuery, GetProductQueryVariables>;


export type GetProductsQueryHookResult = ReturnType<typeof useGetProductsQuery>;


export type GetProductsLazyQueryHookResult = ReturnType<typeof useGetProductsLazyQuery>;


export type GetProductsQueryResult = ApolloReactCommon.QueryResult<GetProductsQuery, GetProductsQueryVariables>;


/**
 *
Removes an IndexField from the search domain. For more information, see Configuring Index Fields in the Amazon CloudSearch Developer Guide.

 * @example
 * Use a bare-bones client and the command you need to make an API call.
 * ```javascript
 * import { CloudSearchClient, DeleteIndexFieldCommand } from "@aws-sdk/client-cloudsearch"; // ES Modules import
 * // const { CloudSearchClient, DeleteIndexFieldCommand } = require("@aws-sdk/client-cloudsearch"); // CommonJS import
 * const client = new CloudSearchClient(config);
 * const command = new DeleteIndexFieldCommand(input);
 * const response = await client.send(command);
 * ```
 *
 * @see {@link DeleteIndexFieldCommandInput} for command's `input` shape.
 * @see {@link DeleteIndexFieldCommandOutput} for command's `response` shape.
 * @see {@link CloudSearchClientResolvedConfig | config} for CloudSearchClient's `config` shape.
 *
 */
export class DeleteIndexFieldCommand extends $Command<
  DeleteIndexFieldCommandInput,
  DeleteIndexFieldCommandOutput,
  CloudSearchClientResolvedConfig
> {
  // Start section: command_properties
  // End section: command_properties

  constructor(readonly input: DeleteIndexFieldCommandInput) {
    // Start section: command_constructor
    super();
    // End section: command_constructor
  }

  /**
   * @internal
   */
  resolveMiddleware(
    clientStack: MiddlewareStack<ServiceInputTypes, ServiceOutputTypes>,
    configuration: CloudSearchClientResolvedConfig,
    options?: __HttpHandlerOptions
  ): Handler<DeleteIndexFieldCommandInput, DeleteIndexFieldCommandOutput> {
    this.middlewareStack.use(getSerdePlugin(configuration, this.serialize, this.deserialize));

    const stack = clientStack.concat(this.middlewareStack);

    const { logger } = configuration;
    const clientName = "CloudSearchClient";
    const commandName = "DeleteIndexFieldCommand";
    const handlerExecutionContext: HandlerExecutionContext = {
      logger,
      clientName,
      commandName,
      inputFilterSensitiveLog: DeleteIndexFieldRequest.filterSensitiveLog,
      outputFilterSensitiveLog: DeleteIndexFieldResponse.filterSensitiveLog,
    };
    const { requestHandler } = configuration;
    return stack.resolve(
      (request: FinalizeHandlerArguments<any>) =>
        requestHandler.handle(request.request as __HttpRequest, options || {}),
      handlerExecutionContext
    );
  }

  private serialize(input: DeleteIndexFieldCommandInput, context: __SerdeContext): Promise<__HttpRequest> {
    return serializeAws_queryDeleteIndexFieldCommand(input, context);
  }

  private deserialize(output: __HttpResponse, context: __SerdeContext): Promise<DeleteIndexFieldCommandOutput> {
    return deserializeAws_queryDeleteIndexFieldCommand(output, context);
  }

  // Start section: command_body_extra
  // End section: command_body_extra
}


export interface DeleteIndexFieldCommandInput extends DeleteIndexFieldRequest {}


export interface DeleteIndexFieldCommandOutput extends DeleteIndexFieldResponse, __MetadataBearer {}


/**
   * @internal
   */
resolveMiddleware(
    clientStack: MiddlewareStack<ServiceInputTypes, ServiceOutputTypes>,
    configuration: CloudSearchClientResolvedConfig,
    options?: __HttpHandlerOptions
  ): Handler<DeleteIndexFieldCommandInput, DeleteIndexFieldCommandOutput> {
    this.middlewareStack.use(getSerdePlugin(configuration, this.serialize, this.deserialize));

    const stack = clientStack.concat(this.middlewareStack);

    const { logger } = configuration;
    const clientName = "CloudSearchClient";
    const commandName = "DeleteIndexFieldCommand";
    const handlerExecutionContext: HandlerExecutionContext = {
      logger,
      clientName,
      commandName,
      inputFilterSensitiveLog: DeleteIndexFieldRequest.filterSensitiveLog,
      outputFilterSensitiveLog: DeleteIndexFieldResponse.filterSensitiveLog,
    };
    const { requestHandler } = configuration;
    return stack.resolve(
      (request: FinalizeHandlerArguments<any>) =>
        requestHandler.handle(request.request as __HttpRequest, options || {}),
      handlerExecutionContext
    );
  }


private serialize(input: DeleteIndexFieldCommandInput, context: __SerdeContext): Promise<__HttpRequest> {
    return serializeAws_queryDeleteIndexFieldCommand(input, context);
  }


private deserialize(output: __HttpResponse, context: __SerdeContext): Promise<DeleteIndexFieldCommandOutput> {
    return deserializeAws_queryDeleteIndexFieldCommand(output, context);
  }


function setProvidersVisibility(providers: ResolvedProvider[], visibility: Visibility,
                                result: Map<number, Visibility>) {
  for (var i = 0; i < providers.length; i++) {
    result.set(providers[i].key.id, visibility);
  }
}


p => p instanceof AttributeMetadata


p => p instanceof QueryMetadata


(meta, fieldName) => {
        var setter = reflector.setter(fieldName);
        queries.push(new QueryMetadataWithSetter(setter, meta));
      }


d => {
      if (isPresent(d.queryDecorator)) {
        queries.push(new QueryMetadataWithSetter(null, d.queryDecorator));
      }
    }


(provider, _) => {
      providers.push(
          new ProviderWithVisibility(provider, providerVisibilityMap.get(provider.key.id)));
    }


p => new ProviderWithVisibility(p, Visibility.Public)


() => this._debugContext()


p => new QueryRef(p, ei)


export class StaticKeys {
  templateRefId: number;
  viewContainerId: number;
  changeDetectorRefId: number;
  elementRefId: number;
  rendererId: number;

  constructor() {
    this.templateRefId = Key.get(TemplateRef).id;
    this.viewContainerId = Key.get(ViewContainerRef).id;
    this.changeDetectorRefId = Key.get(ChangeDetectorRef).id;
    this.elementRefId = Key.get(ElementRef).id;
    this.rendererId = Key.get(Renderer).id;
  }

  static instance(): StaticKeys {
    if (isBlank(_staticKeys)) _staticKeys = new StaticKeys();
    return _staticKeys;
  }
}


export class DirectiveDependency extends Dependency {
  constructor(key: Key, optional: boolean, lowerBoundVisibility: Object,
              upperBoundVisibility: Object, properties: any[], public attributeName: string,
              public queryDecorator: QueryMetadata) {
    super(key, optional, lowerBoundVisibility, upperBoundVisibility, properties);
    this._verify();
  }

  /** @internal */
  _verify(): void {
    var count = 0;
    if (isPresent(this.queryDecorator)) count++;
    if (isPresent(this.attributeName)) count++;
    if (count > 1)
      throw new BaseException(
          'A directive injectable can contain only one of the following @Attribute or @Query.');
  }

  static createFrom(d: Dependency): DirectiveDependency {
    return new DirectiveDependency(
        d.key, d.optional, d.lowerBoundVisibility, d.upperBoundVisibility, d.properties,
        DirectiveDependency._attributeName(d.properties), DirectiveDependency._query(d.properties));
  }

  /** @internal */
  static _attributeName(properties: any[]): string {
    var p = <AttributeMetadata>properties.find(p => p instanceof AttributeMetadata);
    return isPresent(p) ? p.attributeName : null;
  }

  /** @internal */
  static _query(properties: any[]): QueryMetadata {
    return <QueryMetadata>properties.find(p => p instanceof QueryMetadata);
  }
}


export class DirectiveProvider extends ResolvedProvider_ {
  constructor(key: Key, factory: Function, deps: Dependency[], public isComponent: boolean,
              public providers: ResolvedProvider[], public viewProviders: ResolvedProvider[],
              public queries: QueryMetadataWithSetter[]) {
    super(key, [new ResolvedFactory(factory, deps)], false);
  }

  get displayName(): string { return this.key.displayName; }

  static createFromType(type: Type, meta: DirectiveMetadata): DirectiveProvider {
    var provider = new Provider(type, {useClass: type});
    if (isBlank(meta)) {
      meta = new DirectiveMetadata();
    }
    var rb = resolveProvider(provider);
    var rf = rb.resolvedFactories[0];
    var deps: DirectiveDependency[] = rf.dependencies.map(DirectiveDependency.createFrom);
    var isComponent = meta instanceof ComponentMetadata;
    var resolvedProviders = isPresent(meta.providers) ? Injector.resolve(meta.providers) : null;
    var resolvedViewProviders = meta instanceof ComponentMetadata && isPresent(meta.viewProviders) ?
                                    Injector.resolve(meta.viewProviders) :
                                    null;
    var queries = [];
    if (isPresent(meta.queries)) {
      StringMapWrapper.forEach(meta.queries, (meta, fieldName) => {
        var setter = reflector.setter(fieldName);
        queries.push(new QueryMetadataWithSetter(setter, meta));
      });
    }
    // queries passed into the constructor.
    // TODO: remove this after constructor queries are no longer supported
    deps.forEach(d => {
      if (isPresent(d.queryDecorator)) {
        queries.push(new QueryMetadataWithSetter(null, d.queryDecorator));
      }
    });
    return new DirectiveProvider(rb.key, rf.factory, deps, isComponent, resolvedProviders,
                                 resolvedViewProviders, queries);
  }
}


export class QueryMetadataWithSetter {
  constructor(public setter: SetterFn, public metadata: QueryMetadata) {}
}


export class AppProtoElement {
  protoInjector: ProtoInjector;

  static create(metadataCache: ResolvedMetadataCache, index: number,
                attributes: {[key: string]: string}, directiveTypes: Type[],
                directiveVariableBindings: {[key: string]: number}): AppProtoElement {
    var componentDirProvider = null;
    var mergedProvidersMap: Map<number, ResolvedProvider> = new Map<number, ResolvedProvider>();
    var providerVisibilityMap: Map<number, Visibility> = new Map<number, Visibility>();
    var providers = ListWrapper.createGrowableSize(directiveTypes.length);

    var protoQueryRefs = [];
    for (var i = 0; i < directiveTypes.length; i++) {
      var dirProvider = metadataCache.getResolvedDirectiveMetadata(directiveTypes[i]);
      providers[i] = new ProviderWithVisibility(
          dirProvider, dirProvider.isComponent ? Visibility.PublicAndPrivate : Visibility.Public);

      if (dirProvider.isComponent) {
        componentDirProvider = dirProvider;
      } else {
        if (isPresent(dirProvider.providers)) {
          mergeResolvedProviders(dirProvider.providers, mergedProvidersMap);
          setProvidersVisibility(dirProvider.providers, Visibility.Public, providerVisibilityMap);
        }
      }
      if (isPresent(dirProvider.viewProviders)) {
        mergeResolvedProviders(dirProvider.viewProviders, mergedProvidersMap);
        setProvidersVisibility(dirProvider.viewProviders, Visibility.Private,
                               providerVisibilityMap);
      }
      for (var queryIdx = 0; queryIdx < dirProvider.queries.length; queryIdx++) {
        var q = dirProvider.queries[queryIdx];
        protoQueryRefs.push(new ProtoQueryRef(i, q.setter, q.metadata));
      }
    }
    if (isPresent(componentDirProvider) && isPresent(componentDirProvider.providers)) {
      // directive providers need to be prioritized over component providers
      mergeResolvedProviders(componentDirProvider.providers, mergedProvidersMap);
      setProvidersVisibility(componentDirProvider.providers, Visibility.Public,
                             providerVisibilityMap);
    }
    mergedProvidersMap.forEach((provider, _) => {
      providers.push(
          new ProviderWithVisibility(provider, providerVisibilityMap.get(provider.key.id)));
    });

    return new AppProtoElement(isPresent(componentDirProvider), index, attributes, providers,
                               protoQueryRefs, directiveVariableBindings);
  }

  constructor(public firstProviderIsComponent: boolean, public index: number,
              public attributes: {[key: string]: string}, pwvs: ProviderWithVisibility[],
              public protoQueryRefs: ProtoQueryRef[],
              public directiveVariableBindings: {[key: string]: number}) {
    var length = pwvs.length;
    if (length > 0) {
      this.protoInjector = new ProtoInjector(pwvs);
    } else {
      this.protoInjector = null;
      this.protoQueryRefs = [];
    }
  }

  getProviderAtIndex(index: number): any { return this.protoInjector.getProviderAtIndex(index); }
}


class _Context {
  constructor(public element: any, public componentElement: any, public injector: any) {}
}


export class InjectorWithHostBoundary {
  constructor(public injector: Injector, public hostInjectorBoundary: boolean) {}
}


class _EmptyQueryStrategy implements _QueryStrategy {
  setContentQueriesAsDirty(): void {}
  setViewQueriesAsDirty(): void {}
  updateContentQueries(): void {}
  updateViewQueries(): void {}
  findQuery(query: QueryMetadata): QueryRef {
    throw new BaseException(`Cannot find query for directive ${query}.`);
  }
}


class InlineQueryStrategy implements _QueryStrategy {
  static NUMBER_OF_SUPPORTED_QUERIES = 3;

  query0: QueryRef;
  query1: QueryRef;
  query2: QueryRef;

  constructor(ei: AppElement) {
    var protoRefs = ei.proto.protoQueryRefs;
    if (protoRefs.length > 0) this.query0 = new QueryRef(protoRefs[0], ei);
    if (protoRefs.length > 1) this.query1 = new QueryRef(protoRefs[1], ei);
    if (protoRefs.length > 2) this.query2 = new QueryRef(protoRefs[2], ei);
  }

  setContentQueriesAsDirty(): void {
    if (isPresent(this.query0) && !this.query0.isViewQuery) this.query0.dirty = true;
    if (isPresent(this.query1) && !this.query1.isViewQuery) this.query1.dirty = true;
    if (isPresent(this.query2) && !this.query2.isViewQuery) this.query2.dirty = true;
  }

  setViewQueriesAsDirty(): void {
    if (isPresent(this.query0) && this.query0.isViewQuery) this.query0.dirty = true;
    if (isPresent(this.query1) && this.query1.isViewQuery) this.query1.dirty = true;
    if (isPresent(this.query2) && this.query2.isViewQuery) this.query2.dirty = true;
  }

  updateContentQueries() {
    if (isPresent(this.query0) && !this.query0.isViewQuery) {
      this.query0.update();
    }
    if (isPresent(this.query1) && !this.query1.isViewQuery) {
      this.query1.update();
    }
    if (isPresent(this.query2) && !this.query2.isViewQuery) {
      this.query2.update();
    }
  }

  updateViewQueries() {
    if (isPresent(this.query0) && this.query0.isViewQuery) {
      this.query0.update();
    }
    if (isPresent(this.query1) && this.query1.isViewQuery) {
      this.query1.update();
    }
    if (isPresent(this.query2) && this.query2.isViewQuery) {
      this.query2.update();
    }
  }

  findQuery(query: QueryMetadata): QueryRef {
    if (isPresent(this.query0) && this.query0.protoQueryRef.query === query) {
      return this.query0;
    }
    if (isPresent(this.query1) && this.query1.protoQueryRef.query === query) {
      return this.query1;
    }
    if (isPresent(this.query2) && this.query2.protoQueryRef.query === query) {
      return this.query2;
    }
    throw new BaseException(`Cannot find query for directive ${query}.`);
  }
}


class DynamicQueryStrategy implements _QueryStrategy {
  queries: QueryRef[];

  constructor(ei: AppElement) {
    this.queries = ei.proto.protoQueryRefs.map(p => new QueryRef(p, ei));
  }

  setContentQueriesAsDirty(): void {
    for (var i = 0; i < this.queries.length; ++i) {
      var q = this.queries[i];
      if (!q.isViewQuery) q.dirty = true;
    }
  }

  setViewQueriesAsDirty(): void {
    for (var i = 0; i < this.queries.length; ++i) {
      var q = this.queries[i];
      if (q.isViewQuery) q.dirty = true;
    }
  }

  updateContentQueries() {
    for (var i = 0; i < this.queries.length; ++i) {
      var q = this.queries[i];
      if (!q.isViewQuery) {
        q.update();
      }
    }
  }

  updateViewQueries() {
    for (var i = 0; i < this.queries.length; ++i) {
      var q = this.queries[i];
      if (q.isViewQuery) {
        q.update();
      }
    }
  }

  findQuery(query: QueryMetadata): QueryRef {
    for (var i = 0; i < this.queries.length; ++i) {
      var q = this.queries[i];
      if (q.protoQueryRef.query === query) {
        return q;
      }
    }
    throw new BaseException(`Cannot find query for directive ${query}.`);
  }
}


/**
 * Strategy used by the `ElementInjector` when the number of providers is 10 or less.
 * In such a case, inlining fields is beneficial for performances.
 */
class ElementDirectiveInlineStrategy implements _ElementDirectiveStrategy {
  constructor(public injectorStrategy: InjectorInlineStrategy, public _ei: AppElement) {}

  init(): void {
    var i = this.injectorStrategy;
    var p = i.protoStrategy;
    i.resetConstructionCounter();

    if (p.provider0 instanceof DirectiveProvider && isPresent(p.keyId0) && i.obj0 === UNDEFINED)
      i.obj0 = i.instantiateProvider(p.provider0, p.visibility0);
    if (p.provider1 instanceof DirectiveProvider && isPresent(p.keyId1) && i.obj1 === UNDEFINED)
      i.obj1 = i.instantiateProvider(p.provider1, p.visibility1);
    if (p.provider2 instanceof DirectiveProvider && isPresent(p.keyId2) && i.obj2 === UNDEFINED)
      i.obj2 = i.instantiateProvider(p.provider2, p.visibility2);
    if (p.provider3 instanceof DirectiveProvider && isPresent(p.keyId3) && i.obj3 === UNDEFINED)
      i.obj3 = i.instantiateProvider(p.provider3, p.visibility3);
    if (p.provider4 instanceof DirectiveProvider && isPresent(p.keyId4) && i.obj4 === UNDEFINED)
      i.obj4 = i.instantiateProvider(p.provider4, p.visibility4);
    if (p.provider5 instanceof DirectiveProvider && isPresent(p.keyId5) && i.obj5 === UNDEFINED)
      i.obj5 = i.instantiateProvider(p.provider5, p.visibility5);
    if (p.provider6 instanceof DirectiveProvider && isPresent(p.keyId6) && i.obj6 === UNDEFINED)
      i.obj6 = i.instantiateProvider(p.provider6, p.visibility6);
    if (p.provider7 instanceof DirectiveProvider && isPresent(p.keyId7) && i.obj7 === UNDEFINED)
      i.obj7 = i.instantiateProvider(p.provider7, p.visibility7);
    if (p.provider8 instanceof DirectiveProvider && isPresent(p.keyId8) && i.obj8 === UNDEFINED)
      i.obj8 = i.instantiateProvider(p.provider8, p.visibility8);
    if (p.provider9 instanceof DirectiveProvider && isPresent(p.keyId9) && i.obj9 === UNDEFINED)
      i.obj9 = i.instantiateProvider(p.provider9, p.visibility9);
  }

  getComponent(): any { return this.injectorStrategy.obj0; }

  isComponentKey(key: Key): boolean {
    return this._ei.proto.firstProviderIsComponent && isPresent(key) &&
           key.id === this.injectorStrategy.protoStrategy.keyId0;
  }

  addDirectivesMatchingQuery(query: QueryMetadata, list: any[]): void {
    var i = this.injectorStrategy;
    var p = i.protoStrategy;
    if (isPresent(p.provider0) && p.provider0.key.token === query.selector) {
      if (i.obj0 === UNDEFINED) i.obj0 = i.instantiateProvider(p.provider0, p.visibility0);
      list.push(i.obj0);
    }
    if (isPresent(p.provider1) && p.provider1.key.token === query.selector) {
      if (i.obj1 === UNDEFINED) i.obj1 = i.instantiateProvider(p.provider1, p.visibility1);
      list.push(i.obj1);
    }
    if (isPresent(p.provider2) && p.provider2.key.token === query.selector) {
      if (i.obj2 === UNDEFINED) i.obj2 = i.instantiateProvider(p.provider2, p.visibility2);
      list.push(i.obj2);
    }
    if (isPresent(p.provider3) && p.provider3.key.token === query.selector) {
      if (i.obj3 === UNDEFINED) i.obj3 = i.instantiateProvider(p.provider3, p.visibility3);
      list.push(i.obj3);
    }
    if (isPresent(p.provider4) && p.provider4.key.token === query.selector) {
      if (i.obj4 === UNDEFINED) i.obj4 = i.instantiateProvider(p.provider4, p.visibility4);
      list.push(i.obj4);
    }
    if (isPresent(p.provider5) && p.provider5.key.token === query.selector) {
      if (i.obj5 === UNDEFINED) i.obj5 = i.instantiateProvider(p.provider5, p.visibility5);
      list.push(i.obj5);
    }
    if (isPresent(p.provider6) && p.provider6.key.token === query.selector) {
      if (i.obj6 === UNDEFINED) i.obj6 = i.instantiateProvider(p.provider6, p.visibility6);
      list.push(i.obj6);
    }
    if (isPresent(p.provider7) && p.provider7.key.token === query.selector) {
      if (i.obj7 === UNDEFINED) i.obj7 = i.instantiateProvider(p.provider7, p.visibility7);
      list.push(i.obj7);
    }
    if (isPresent(p.provider8) && p.provider8.key.token === query.selector) {
      if (i.obj8 === UNDEFINED) i.obj8 = i.instantiateProvider(p.provider8, p.visibility8);
      list.push(i.obj8);
    }
    if (isPresent(p.provider9) && p.provider9.key.token === query.selector) {
      if (i.obj9 === UNDEFINED) i.obj9 = i.instantiateProvider(p.provider9, p.visibility9);
      list.push(i.obj9);
    }
  }
}


/**
 * Strategy used by the `ElementInjector` when the number of bindings is 11 or more.
 * In such a case, there are too many fields to inline (see ElementInjectorInlineStrategy).
 */
class ElementDirectiveDynamicStrategy implements _ElementDirectiveStrategy {
  constructor(public injectorStrategy: InjectorDynamicStrategy, public _ei: AppElement) {}

  init(): void {
    var inj = this.injectorStrategy;
    var p = inj.protoStrategy;
    inj.resetConstructionCounter();

    for (var i = 0; i < p.keyIds.length; i++) {
      if (p.providers[i] instanceof DirectiveProvider && isPresent(p.keyIds[i]) &&
          inj.objs[i] === UNDEFINED) {
        inj.objs[i] = inj.instantiateProvider(p.providers[i], p.visibilities[i]);
      }
    }
  }

  getComponent(): any { return this.injectorStrategy.objs[0]; }

  isComponentKey(key: Key): boolean {
    var p = this.injectorStrategy.protoStrategy;
    return this._ei.proto.firstProviderIsComponent && isPresent(key) && key.id === p.keyIds[0];
  }

  addDirectivesMatchingQuery(query: QueryMetadata, list: any[]): void {
    var ist = this.injectorStrategy;
    var p = ist.protoStrategy;

    for (var i = 0; i < p.providers.length; i++) {
      if (p.providers[i].key.token === query.selector) {
        if (ist.objs[i] === UNDEFINED) {
          ist.objs[i] = ist.instantiateProvider(p.providers[i], p.visibilities[i]);
        }
        list.push(ist.objs[i]);
      }
    }
  }
}


export class ProtoQueryRef {
  constructor(public dirIndex: number, public setter: SetterFn, public query: QueryMetadata) {}

  get usesPropertySyntax(): boolean { return isPresent(this.setter); }
}


export class QueryRef {
  public list: QueryList<any>;
  public dirty: boolean;

  constructor(public protoQueryRef: ProtoQueryRef, private originator: AppElement) {
    this.list = new QueryList<any>();
    this.dirty = true;
  }

  get isViewQuery(): boolean { return this.protoQueryRef.query.isViewQuery; }

  update(): void {
    if (!this.dirty) return;
    this._update();
    this.dirty = false;

    // TODO delete the check once only field queries are supported
    if (this.protoQueryRef.usesPropertySyntax) {
      var dir = this.originator.getDirectiveAtIndex(this.protoQueryRef.dirIndex);
      if (this.protoQueryRef.query.first) {
        this.protoQueryRef.setter(dir, this.list.length > 0 ? this.list.first : null);
      } else {
        this.protoQueryRef.setter(dir, this.list);
      }
    }

    this.list.notifyOnChanges();
  }

  private _update(): void {
    var aggregator = [];
    if (this.protoQueryRef.query.isViewQuery) {
      // intentionally skipping originator for view queries.
      var nestedView = this.originator.componentView;
      if (isPresent(nestedView)) this._visitView(nestedView, aggregator);
    } else {
      this._visit(this.originator, aggregator);
    }
    this.list.reset(aggregator);
  };

  private _visit(inj: AppElement, aggregator: any[]): void {
    var view = inj.parentView;
    var startIdx = inj.proto.index;
    for (var i = startIdx; i < view.appElements.length; i++) {
      var curInj = view.appElements[i];
      // The first injector after inj, that is outside the subtree rooted at
      // inj has to have a null parent or a parent that is an ancestor of inj.
      if (i > startIdx && (isBlank(curInj.parent) || curInj.parent.proto.index < startIdx)) {
        break;
      }

      if (!this.protoQueryRef.query.descendants &&
          !(curInj.parent == this.originator || curInj == this.originator))
        continue;

      // We visit the view container(VC) views right after the injector that contains
      // the VC. Theoretically, that might not be the right order if there are
      // child injectors of said injector. Not clear whether if such case can
      // even be constructed with the current apis.
      this._visitInjector(curInj, aggregator);
      this._visitViewContainerViews(curInj.nestedViews, aggregator);
    }
  }

  private _visitInjector(inj: AppElement, aggregator: any[]) {
    if (this.protoQueryRef.query.isVarBindingQuery) {
      this._aggregateVariableBinding(inj, aggregator);
    } else {
      this._aggregateDirective(inj, aggregator);
    }
  }

  private _visitViewContainerViews(views: AppView[], aggregator: any[]) {
    if (isPresent(views)) {
      for (var j = 0; j < views.length; j++) {
        this._visitView(views[j], aggregator);
      }
    }
  }

  private _visitView(view: AppView, aggregator: any[]) {
    for (var i = 0; i < view.appElements.length; i++) {
      var inj = view.appElements[i];
      this._visitInjector(inj, aggregator);
      this._visitViewContainerViews(inj.nestedViews, aggregator);
    }
  }

  private _aggregateVariableBinding(inj: AppElement, aggregator: any[]): void {
    var vb = this.protoQueryRef.query.varBindings;
    for (var i = 0; i < vb.length; ++i) {
      if (inj.hasVariableBinding(vb[i])) {
        aggregator.push(inj.getVariableBinding(vb[i]));
      }
    }
  }

  private _aggregateDirective(inj: AppElement, aggregator: any[]): void {
    inj.addDirectivesMatchingQuery(this.protoQueryRef.query, aggregator);
  }
}


class _ComponentViewChangeDetectorRef extends ChangeDetectorRef {
  constructor(private _appElement: AppElement) { super(); }

  markForCheck(): void { this._appElement.componentView.changeDetector.ref.markForCheck(); }
  detach(): void { this._appElement.componentView.changeDetector.ref.detach(); }
  detectChanges(): void { this._appElement.componentView.changeDetector.ref.detectChanges(); }
  checkNoChanges(): void { this._appElement.componentView.changeDetector.ref.checkNoChanges(); }
  reattach(): void { this._appElement.componentView.changeDetector.ref.reattach(); }
}


interface _QueryStrategy {
  setContentQueriesAsDirty(): void;
  setViewQueriesAsDirty(): void;
  updateContentQueries(): void;
  updateViewQueries(): void;
  findQuery(query: QueryMetadata): QueryRef;
}


interface _ElementDirectiveStrategy {
  getComponent(): any;
  isComponentKey(key: Key): boolean;
  addDirectivesMatchingQuery(q: QueryMetadata, res: any[]): void;
  init(): void;
}


static instance(): StaticKeys {
    if (isBlank(_staticKeys)) _staticKeys = new StaticKeys();
    return _staticKeys;
  }


/** @internal */
_verify(): void {
    var count = 0;
    if (isPresent(this.queryDecorator)) count++;
    if (isPresent(this.attributeName)) count++;
    if (count > 1)
      throw new BaseException(
          'A directive injectable can contain only one of the following @Attribute or @Query.');
  }


static createFrom(d: Dependency): DirectiveDependency {
    return new DirectiveDependency(
        d.key, d.optional, d.lowerBoundVisibility, d.upperBoundVisibility, d.properties,
        DirectiveDependency._attributeName(d.properties), DirectiveDependency._query(d.properties));
  }


/** @internal */
static _attributeName(properties: any[]): string {
    var p = <AttributeMetadata>properties.find(p => p instanceof AttributeMetadata);
    return isPresent(p) ? p.attributeName : null;
  }


/** @internal */
static _query(properties: any[]): QueryMetadata {
    return <QueryMetadata>properties.find(p => p instanceof QueryMetadata);
  }


static createFromType(type: Type, meta: DirectiveMetadata): DirectiveProvider {
    var provider = new Provider(type, {useClass: type});
    if (isBlank(meta)) {
      meta = new DirectiveMetadata();
    }
    var rb = resolveProvider(provider);
    var rf = rb.resolvedFactories[0];
    var deps: DirectiveDependency[] = rf.dependencies.map(DirectiveDependency.createFrom);
    var isComponent = meta instanceof ComponentMetadata;
    var resolvedProviders = isPresent(meta.providers) ? Injector.resolve(meta.providers) : null;
    var resolvedViewProviders = meta instanceof ComponentMetadata && isPresent(meta.viewProviders) ?
                                    Injector.resolve(meta.viewProviders) :
                                    null;
    var queries = [];
    if (isPresent(meta.queries)) {
      StringMapWrapper.forEach(meta.queries, (meta, fieldName) => {
        var setter = reflector.setter(fieldName);
        queries.push(new QueryMetadataWithSetter(setter, meta));
      });
    }
    // queries passed into the constructor.
    // TODO: remove this after constructor queries are no longer supported
    deps.forEach(d => {
      if (isPresent(d.queryDecorator)) {
        queries.push(new QueryMetadataWithSetter(null, d.queryDecorator));
      }
    });
    return new DirectiveProvider(rb.key, rf.factory, deps, isComponent, resolvedProviders,
                                 resolvedViewProviders, queries);
  }


static create(metadataCache: ResolvedMetadataCache, index: number,
                attributes: {[key: string]: string}, directiveTypes: Type[],
                directiveVariableBindings: {[key: string]: number}): AppProtoElement {
    var componentDirProvider = null;
    var mergedProvidersMap: Map<number, ResolvedProvider> = new Map<number, ResolvedProvider>();
    var providerVisibilityMap: Map<number, Visibility> = new Map<number, Visibility>();
    var providers = ListWrapper.createGrowableSize(directiveTypes.length);

    var protoQueryRefs = [];
    for (var i = 0; i < directiveTypes.length; i++) {
      var dirProvider = metadataCache.getResolvedDirectiveMetadata(directiveTypes[i]);
      providers[i] = new ProviderWithVisibility(
          dirProvider, dirProvider.isComponent ? Visibility.PublicAndPrivate : Visibility.Public);

      if (dirProvider.isComponent) {
        componentDirProvider = dirProvider;
      } else {
        if (isPresent(dirProvider.providers)) {
          mergeResolvedProviders(dirProvider.providers, mergedProvidersMap);
          setProvidersVisibility(dirProvider.providers, Visibility.Public, providerVisibilityMap);
        }
      }
      if (isPresent(dirProvider.viewProviders)) {
        mergeResolvedProviders(dirProvider.viewProviders, mergedProvidersMap);
        setProvidersVisibility(dirProvider.viewProviders, Visibility.Private,
                               providerVisibilityMap);
      }
      for (var queryIdx = 0; queryIdx < dirProvider.queries.length; queryIdx++) {
        var q = dirProvider.queries[queryIdx];
        protoQueryRefs.push(new ProtoQueryRef(i, q.setter, q.metadata));
      }
    }
    if (isPresent(componentDirProvider) && isPresent(componentDirProvider.providers)) {
      // directive providers need to be prioritized over component providers
      mergeResolvedProviders(componentDirProvider.providers, mergedProvidersMap);
      setProvidersVisibility(componentDirProvider.providers, Visibility.Public,
                             providerVisibilityMap);
    }
    mergedProvidersMap.forEach((provider, _) => {
      providers.push(
          new ProviderWithVisibility(provider, providerVisibilityMap.get(provider.key.id)));
    });

    return new AppProtoElement(isPresent(componentDirProvider), index, attributes, providers,
                               protoQueryRefs, directiveVariableBindings);
  }


getProviderAtIndex(index: number): any { return this.protoInjector.getProviderAtIndex(index); }


static getViewParentInjector(parentViewType: ViewType, containerAppElement: AppElement,
                               imperativelyCreatedProviders: ResolvedProvider[],
                               rootInjector: Injector): InjectorWithHostBoundary {
    var parentInjector;
    var hostInjectorBoundary;
    switch (parentViewType) {
      case ViewType.COMPONENT:
        parentInjector = containerAppElement._injector;
        hostInjectorBoundary = true;
        break;
      case ViewType.EMBEDDED:
        parentInjector = isPresent(containerAppElement.proto.protoInjector) ?
                             containerAppElement._injector.parent :
                             containerAppElement._injector;
        hostInjectorBoundary = containerAppElement._injector.hostBoundary;
        break;
      case ViewType.HOST:
        if (isPresent(containerAppElement)) {
          // host view is attached to a container
          parentInjector = isPresent(containerAppElement.proto.protoInjector) ?
                               containerAppElement._injector.parent :
                               containerAppElement._injector;
          if (isPresent(imperativelyCreatedProviders)) {
            var imperativeProvidersWithVisibility = imperativelyCreatedProviders.map(
                p => new ProviderWithVisibility(p, Visibility.Public));
            // The imperative injector is similar to having an element between
            // the dynamic-loaded component and its parent => no boundary between
            // the component and imperativelyCreatedInjector.
            parentInjector = new Injector(new ProtoInjector(imperativeProvidersWithVisibility),
                                          parentInjector, true, null, null);
            hostInjectorBoundary = false;
          } else {
            hostInjectorBoundary = containerAppElement._injector.hostBoundary;
          }
        } else {
          // bootstrap
          parentInjector = rootInjector;
          hostInjectorBoundary = true;
        }
        break;
    }
    return new InjectorWithHostBoundary(parentInjector, hostInjectorBoundary);
  }


attachComponentView(componentView: AppView) { this.componentView = componentView; }


private _debugContext(): any {
    var c = this.parentView.getDebugContext(this, null, null);
    return isPresent(c) ? new _Context(c.element, c.componentElement, c.injector) : null;
  }


hasVariableBinding(name: string): boolean {
    var vb = this.proto.directiveVariableBindings;
    return isPresent(vb) && StringMapWrapper.contains(vb, name);
  }


getVariableBinding(name: string): any {
    var index = this.proto.directiveVariableBindings[name];
    return isPresent(index) ? this.getDirectiveAtIndex(<number>index) : this.getElementRef();
  }


get(token: any): any { return this._injector.get(token); }


hasDirective(type: Type): boolean { return isPresent(this._injector.getOptional(type)); }


getComponent(): any { return isPresent(this._strategy) ? this._strategy.getComponent() : null; }


getInjector(): Injector { return this._injector; }


getElementRef(): ElementRef { return this.ref; }


getViewContainerRef(): ViewContainerRef { return new ViewContainerRef_(this); }


getTemplateRef(): TemplateRef {
    if (isPresent(this.embeddedViewFactory)) {
      return new TemplateRef_(this.ref);
    }
    return null;
  }


getDependency(injector: Injector, provider: ResolvedProvider, dep: Dependency): any {
    if (provider instanceof DirectiveProvider) {
      var dirDep = <DirectiveDependency>dep;

      if (isPresent(dirDep.attributeName)) return this._buildAttribute(dirDep);

      if (isPresent(dirDep.queryDecorator))
        return this._queryStrategy.findQuery(dirDep.queryDecorator).list;

      if (dirDep.key.id === StaticKeys.instance().changeDetectorRefId) {
        // We provide the component's view change detector to components and
        // the surrounding component's change detector to directives.
        if (this.proto.firstProviderIsComponent) {
          // Note: The component view is not yet created when
          // this method is called!
          return new _ComponentViewChangeDetectorRef(this);
        } else {
          return this.parentView.changeDetector.ref;
        }
      }

      if (dirDep.key.id === StaticKeys.instance().elementRefId) {
        return this.getElementRef();
      }

      if (dirDep.key.id === StaticKeys.instance().viewContainerId) {
        return this.getViewContainerRef();
      }

      if (dirDep.key.id === StaticKeys.instance().templateRefId) {
        var tr = this.getTemplateRef();
        if (isBlank(tr) && !dirDep.optional) {
          throw new NoProviderError(null, dirDep.key);
        }
        return tr;
      }

      if (dirDep.key.id === StaticKeys.instance().rendererId) {
        return this.parentView.renderer;
      }

    } else if (provider instanceof PipeProvider) {
      if (dep.key.id === StaticKeys.instance().changeDetectorRefId) {
        // We provide the component's view change detector to components and
        // the surrounding component's change detector to directives.
        if (this.proto.firstProviderIsComponent) {
          // Note: The component view is not yet created when
          // this method is called!
          return new _ComponentViewChangeDetectorRef(this);
        } else {
          return this.parentView.changeDetector;
        }
      }
    }

    return UNDEFINED;
  }


private _buildAttribute(dep: DirectiveDependency): string {
    var attributes = this.proto.attributes;
    if (isPresent(attributes) && StringMapWrapper.contains(attributes, dep.attributeName)) {
      return attributes[dep.attributeName];
    } else {
      return null;
    }
  }


addDirectivesMatchingQuery(query: QueryMetadata, list: any[]): void {
    var templateRef = this.getTemplateRef();
    if (query.selector === TemplateRef && isPresent(templateRef)) {
      list.push(templateRef);
    }
    if (this._strategy != null) {
      this._strategy.addDirectivesMatchingQuery(query, list);
    }
  }


private _buildQueryStrategy(): _QueryStrategy {
    if (this.proto.protoQueryRefs.length === 0) {
      return _emptyQueryStrategy;
    } else if (this.proto.protoQueryRefs.length <=
               InlineQueryStrategy.NUMBER_OF_SUPPORTED_QUERIES) {
      return new InlineQueryStrategy(this);
    } else {
      return new DynamicQueryStrategy(this);
    }
  }


getDirectiveAtIndex(index: number): any { return this._injector.getAt(index); }


ngAfterViewChecked(): void {
    if (isPresent(this._queryStrategy)) this._queryStrategy.updateViewQueries();
  }


ngAfterContentChecked(): void {
    if (isPresent(this._queryStrategy)) this._queryStrategy.updateContentQueries();
  }


traverseAndSetQueriesAsDirty(): void {
    var inj: AppElement = this;
    while (isPresent(inj)) {
      inj._setQueriesAsDirty();
      inj = inj.parent;
    }
  }


private _setQueriesAsDirty(): void {
    if (isPresent(this._queryStrategy)) {
      this._queryStrategy.setContentQueriesAsDirty();
    }
    if (this.parentView.proto.type === ViewType.COMPONENT) {
      this.parentView.containerAppElement._queryStrategy.setViewQueriesAsDirty();
    }
  }


setContentQueriesAsDirty(): void {}


setViewQueriesAsDirty(): void {}


updateContentQueries(): void {}


updateViewQueries(): void {}


findQuery(query: QueryMetadata): QueryRef {
    throw new BaseException(`Cannot find query for directive ${query}.`);
  }


setContentQueriesAsDirty(): void {
    if (isPresent(this.query0) && !this.query0.isViewQuery) this.query0.dirty = true;
    if (isPresent(this.query1) && !this.query1.isViewQuery) this.query1.dirty = true;
    if (isPresent(this.query2) && !this.query2.isViewQuery) this.query2.dirty = true;
  }


setViewQueriesAsDirty(): void {
    if (isPresent(this.query0) && this.query0.isViewQuery) this.query0.dirty = true;
    if (isPresent(this.query1) && this.query1.isViewQuery) this.query1.dirty = true;
    if (isPresent(this.query2) && this.query2.isViewQuery) this.query2.dirty = true;
  }


updateContentQueries() {
    if (isPresent(this.query0) && !this.query0.isViewQuery) {
      this.query0.update();
    }
    if (isPresent(this.query1) && !this.query1.isViewQuery) {
      this.query1.update();
    }
    if (isPresent(this.query2) && !this.query2.isViewQuery) {
      this.query2.update();
    }
  }


updateViewQueries() {
    if (isPresent(this.query0) && this.query0.isViewQuery) {
      this.query0.update();
    }
    if (isPresent(this.query1) && this.query1.isViewQuery) {
      this.query1.update();
    }
    if (isPresent(this.query2) && this.query2.isViewQuery) {
      this.query2.update();
    }
  }


findQuery(query: QueryMetadata): QueryRef {
    if (isPresent(this.query0) && this.query0.protoQueryRef.query === query) {
      return this.query0;
    }
    if (isPresent(this.query1) && this.query1.protoQueryRef.query === query) {
      return this.query1;
    }
    if (isPresent(this.query2) && this.query2.protoQueryRef.query === query) {
      return this.query2;
    }
    throw new BaseException(`Cannot find query for directive ${query}.`);
  }


setContentQueriesAsDirty(): void {
    for (var i = 0; i < this.queries.length; ++i) {
      var q = this.queries[i];
      if (!q.isViewQuery) q.dirty = true;
    }
  }


setViewQueriesAsDirty(): void {
    for (var i = 0; i < this.queries.length; ++i) {
      var q = this.queries[i];
      if (q.isViewQuery) q.dirty = true;
    }
  }


updateContentQueries() {
    for (var i = 0; i < this.queries.length; ++i) {
      var q = this.queries[i];
      if (!q.isViewQuery) {
        q.update();
      }
    }
  }


updateViewQueries() {
    for (var i = 0; i < this.queries.length; ++i) {
      var q = this.queries[i];
      if (q.isViewQuery) {
        q.update();
      }
    }
  }


findQuery(query: QueryMetadata): QueryRef {
    for (var i = 0; i < this.queries.length; ++i) {
      var q = this.queries[i];
      if (q.protoQueryRef.query === query) {
        return q;
      }
    }
    throw new BaseException(`Cannot find query for directive ${query}.`);
  }


init(): void {
    var i = this.injectorStrategy;
    var p = i.protoStrategy;
    i.resetConstructionCounter();

    if (p.provider0 instanceof DirectiveProvider && isPresent(p.keyId0) && i.obj0 === UNDEFINED)
      i.obj0 = i.instantiateProvider(p.provider0, p.visibility0);
    if (p.provider1 instanceof DirectiveProvider && isPresent(p.keyId1) && i.obj1 === UNDEFINED)
      i.obj1 = i.instantiateProvider(p.provider1, p.visibility1);
    if (p.provider2 instanceof DirectiveProvider && isPresent(p.keyId2) && i.obj2 === UNDEFINED)
      i.obj2 = i.instantiateProvider(p.provider2, p.visibility2);
    if (p.provider3 instanceof DirectiveProvider && isPresent(p.keyId3) && i.obj3 === UNDEFINED)
      i.obj3 = i.instantiateProvider(p.provider3, p.visibility3);
    if (p.provider4 instanceof DirectiveProvider && isPresent(p.keyId4) && i.obj4 === UNDEFINED)
      i.obj4 = i.instantiateProvider(p.provider4, p.visibility4);
    if (p.provider5 instanceof DirectiveProvider && isPresent(p.keyId5) && i.obj5 === UNDEFINED)
      i.obj5 = i.instantiateProvider(p.provider5, p.visibility5);
    if (p.provider6 instanceof DirectiveProvider && isPresent(p.keyId6) && i.obj6 === UNDEFINED)
      i.obj6 = i.instantiateProvider(p.provider6, p.visibility6);
    if (p.provider7 instanceof DirectiveProvider && isPresent(p.keyId7) && i.obj7 === UNDEFINED)
      i.obj7 = i.instantiateProvider(p.provider7, p.visibility7);
    if (p.provider8 instanceof DirectiveProvider && isPresent(p.keyId8) && i.obj8 === UNDEFINED)
      i.obj8 = i.instantiateProvider(p.provider8, p.visibility8);
    if (p.provider9 instanceof DirectiveProvider && isPresent(p.keyId9) && i.obj9 === UNDEFINED)
      i.obj9 = i.instantiateProvider(p.provider9, p.visibility9);
  }


getComponent(): any { return this.injectorStrategy.obj0; }


isComponentKey(key: Key): boolean {
    return this._ei.proto.firstProviderIsComponent && isPresent(key) &&
           key.id === this.injectorStrategy.protoStrategy.keyId0;
  }


addDirectivesMatchingQuery(query: QueryMetadata, list: any[]): void {
    var i = this.injectorStrategy;
    var p = i.protoStrategy;
    if (isPresent(p.provider0) && p.provider0.key.token === query.selector) {
      if (i.obj0 === UNDEFINED) i.obj0 = i.instantiateProvider(p.provider0, p.visibility0);
      list.push(i.obj0);
    }
    if (isPresent(p.provider1) && p.provider1.key.token === query.selector) {
      if (i.obj1 === UNDEFINED) i.obj1 = i.instantiateProvider(p.provider1, p.visibility1);
      list.push(i.obj1);
    }
    if (isPresent(p.provider2) && p.provider2.key.token === query.selector) {
      if (i.obj2 === UNDEFINED) i.obj2 = i.instantiateProvider(p.provider2, p.visibility2);
      list.push(i.obj2);
    }
    if (isPresent(p.provider3) && p.provider3.key.token === query.selector) {
      if (i.obj3 === UNDEFINED) i.obj3 = i.instantiateProvider(p.provider3, p.visibility3);
      list.push(i.obj3);
    }
    if (isPresent(p.provider4) && p.provider4.key.token === query.selector) {
      if (i.obj4 === UNDEFINED) i.obj4 = i.instantiateProvider(p.provider4, p.visibility4);
      list.push(i.obj4);
    }
    if (isPresent(p.provider5) && p.provider5.key.token === query.selector) {
      if (i.obj5 === UNDEFINED) i.obj5 = i.instantiateProvider(p.provider5, p.visibility5);
      list.push(i.obj5);
    }
    if (isPresent(p.provider6) && p.provider6.key.token === query.selector) {
      if (i.obj6 === UNDEFINED) i.obj6 = i.instantiateProvider(p.provider6, p.visibility6);
      list.push(i.obj6);
    }
    if (isPresent(p.provider7) && p.provider7.key.token === query.selector) {
      if (i.obj7 === UNDEFINED) i.obj7 = i.instantiateProvider(p.provider7, p.visibility7);
      list.push(i.obj7);
    }
    if (isPresent(p.provider8) && p.provider8.key.token === query.selector) {
      if (i.obj8 === UNDEFINED) i.obj8 = i.instantiateProvider(p.provider8, p.visibility8);
      list.push(i.obj8);
    }
    if (isPresent(p.provider9) && p.provider9.key.token === query.selector) {
      if (i.obj9 === UNDEFINED) i.obj9 = i.instantiateProvider(p.provider9, p.visibility9);
      list.push(i.obj9);
    }
  }


init(): void {
    var inj = this.injectorStrategy;
    var p = inj.protoStrategy;
    inj.resetConstructionCounter();

    for (var i = 0; i < p.keyIds.length; i++) {
      if (p.providers[i] instanceof DirectiveProvider && isPresent(p.keyIds[i]) &&
          inj.objs[i] === UNDEFINED) {
        inj.objs[i] = inj.instantiateProvider(p.providers[i], p.visibilities[i]);
      }
    }
  }


getComponent(): any { return this.injectorStrategy.objs[0]; }


isComponentKey(key: Key): boolean {
    var p = this.injectorStrategy.protoStrategy;
    return this._ei.proto.firstProviderIsComponent && isPresent(key) && key.id === p.keyIds[0];
  }


addDirectivesMatchingQuery(query: QueryMetadata, list: any[]): void {
    var ist = this.injectorStrategy;
    var p = ist.protoStrategy;

    for (var i = 0; i < p.providers.length; i++) {
      if (p.providers[i].key.token === query.selector) {
        if (ist.objs[i] === UNDEFINED) {
          ist.objs[i] = ist.instantiateProvider(p.providers[i], p.visibilities[i]);
        }
        list.push(ist.objs[i]);
      }
    }
  }


update(): void {
    if (!this.dirty) return;
    this._update();
    this.dirty = false;

    // TODO delete the check once only field queries are supported
    if (this.protoQueryRef.usesPropertySyntax) {
      var dir = this.originator.getDirectiveAtIndex(this.protoQueryRef.dirIndex);
      if (this.protoQueryRef.query.first) {
        this.protoQueryRef.setter(dir, this.list.length > 0 ? this.list.first : null);
      } else {
        this.protoQueryRef.setter(dir, this.list);
      }
    }

    this.list.notifyOnChanges();
  }


private _update(): void {
    var aggregator = [];
    if (this.protoQueryRef.query.isViewQuery) {
      // intentionally skipping originator for view queries.
      var nestedView = this.originator.componentView;
      if (isPresent(nestedView)) this._visitView(nestedView, aggregator);
    } else {
      this._visit(this.originator, aggregator);
    }
    this.list.reset(aggregator);
  }


private _visit(inj: AppElement, aggregator: any[]): void {
    var view = inj.parentView;
    var startIdx = inj.proto.index;
    for (var i = startIdx; i < view.appElements.length; i++) {
      var curInj = view.appElements[i];
      // The first injector after inj, that is outside the subtree rooted at
      // inj has to have a null parent or a parent that is an ancestor of inj.
      if (i > startIdx && (isBlank(curInj.parent) || curInj.parent.proto.index < startIdx)) {
        break;
      }

      if (!this.protoQueryRef.query.descendants &&
          !(curInj.parent == this.originator || curInj == this.originator))
        continue;

      // We visit the view container(VC) views right after the injector that contains
      // the VC. Theoretically, that might not be the right order if there are
      // child injectors of said injector. Not clear whether if such case can
      // even be constructed with the current apis.
      this._visitInjector(curInj, aggregator);
      this._visitViewContainerViews(curInj.nestedViews, aggregator);
    }
  }


private _visitInjector(inj: AppElement, aggregator: any[]) {
    if (this.protoQueryRef.query.isVarBindingQuery) {
      this._aggregateVariableBinding(inj, aggregator);
    } else {
      this._aggregateDirective(inj, aggregator);
    }
  }


private _visitViewContainerViews(views: AppView[], aggregator: any[]) {
    if (isPresent(views)) {
      for (var j = 0; j < views.length; j++) {
        this._visitView(views[j], aggregator);
      }
    }
  }


private _visitView(view: AppView, aggregator: any[]) {
    for (var i = 0; i < view.appElements.length; i++) {
      var inj = view.appElements[i];
      this._visitInjector(inj, aggregator);
      this._visitViewContainerViews(inj.nestedViews, aggregator);
    }
  }


private _aggregateVariableBinding(inj: AppElement, aggregator: any[]): void {
    var vb = this.protoQueryRef.query.varBindings;
    for (var i = 0; i < vb.length; ++i) {
      if (inj.hasVariableBinding(vb[i])) {
        aggregator.push(inj.getVariableBinding(vb[i]));
      }
    }
  }


private _aggregateDirective(inj: AppElement, aggregator: any[]): void {
    inj.addDirectivesMatchingQuery(this.protoQueryRef.query, aggregator);
  }


markForCheck(): void { this._appElement.componentView.changeDetector.ref.markForCheck(); }


detach(): void { this._appElement.componentView.changeDetector.ref.detach(); }


detectChanges(): void { this._appElement.componentView.changeDetector.ref.detectChanges(); }


checkNoChanges(): void { this._appElement.componentView.changeDetector.ref.checkNoChanges(); }


reattach(): void { this._appElement.componentView.changeDetector.ref.reattach(); }


(piece) => {
      return piece.length === 0;
    }


(res) => {
      // Return the 'document' property for each element in the response,
      // where it exists.
      return res.body
        .filter((x) => {
          return x.document;
        })
        .map((x) => {
          return x.document;
        });
    }


(x) => {
          return x.document;
        }


() => {
      // No documents left to delete
      if (queue.length === 0 && numPendingDeletes === 0 && !pagesRemaining) {
        return true;
      }

      // Failure that can't be retried again
      if (failures.length > 0) {
        logger.debug("Found " + failures.length + " failed operations, failing.");
        return true;
      }

      // We have room in the queue for more documents and more exist on the server,
      // so fetch more.
      if (queue.length <= this.maxQueueSize && pagesRemaining && !pageIncoming) {
        pageIncoming = true;

        this.getDescendantBatch(this.allDescendants, this.readBatchSize, lastDocName)
          .then((docs: any[]) => {
            fetchFailures = 0;
            pageIncoming = false;

            if (docs.length === 0) {
              pagesRemaining = false;
              return;
            }

            queue = queue.concat(docs);
            lastDocName = docs[docs.length - 1].name;
          })
          .catch((e) => {
            logger.debug("Failed to fetch page after " + lastDocName, e);
            pageIncoming = false;

            fetchFailures++;
            if (fetchFailures === 3) {
              failures.push(e);
            }
          });
      }

      // We want to see one batch succeed before we scale up, so this case
      // limits parallelism until first success
      if (numDocsDeleted === 0 && numPendingDeletes >= 1) {
        return false;
      }

      // There are too many outstanding deletes alread
      if (numPendingDeletes > this.maxPendingDeletes) {
        return false;
      }

      // There are no documents to delete right now
      if (queue.length === 0) {
        return false;
      }

      // At this point we want to delete another batch
      const toDelete: any[] = [];
      const numToDelete = Math.min(this.deleteBatchSize, queue.length);

      for (let i = 0; i < numToDelete; i++) {
        toDelete.push(queue.shift());
      }

      numPendingDeletes++;
      firestore
        .deleteDocuments(this.project, toDelete)
        .then((numDeleted) => {
          FirestoreDelete.progressBar.tick(numDeleted);
          numDocsDeleted += numDeleted;
          numPendingDeletes--;
        })
        .catch((e) => {
          // If the transaction is too large, reduce the batch size
          if (
            e.status === 400 &&
            e.message.includes("Transaction too big") &&
            this.deleteBatchSize >= 2
          ) {
            logger.debug("Transaction too big error deleting doc batch", e);

            // Cut batch size way down. If one batch is over 10MB then we need to go much
            // lower in order to keep the total I/O appropriately low.
            //
            // Note that we have multiple batches out at once so we need to account for multiple
            // concurrent failures hitting this branch.
            const newBatchSize = Math.floor(toDelete.length / 10);

            if (newBatchSize < this.deleteBatchSize) {
              utils.logLabeledWarning(
                "firestore",
                `delete transaction too large, reducing batch size from ${this.deleteBatchSize} to ${newBatchSize}`
              );
              this.setDeleteBatchSize(newBatchSize);
            }

            // Retry this batch
            queue.unshift(...toDelete);
          } else if (e.status >= 500 && e.status < 600) {
            // For server errors, retry if the document has not yet been retried.
            logger.debug("Server error deleting doc batch", e);

            // Retry each doc up to one time
            toDelete.forEach((doc) => {
              if (retried[doc.name]) {
                logger.debug("Failed to delete doc " + doc.name + " multiple times.");
                failures.push(doc.name);
              } else {
                retried[doc.name] = true;
                queue.push(doc);
              }
            });
          } else {
            logger.debug("Fatal error deleting docs ", e);
            failures = failures.concat(toDelete);
          }

          numPendingDeletes--;
        });

      return false;
    }


(docs: any[]) => {
            fetchFailures = 0;
            pageIncoming = false;

            if (docs.length === 0) {
              pagesRemaining = false;
              return;
            }

            queue = queue.concat(docs);
            lastDocName = docs[docs.length - 1].name;
          }


(e) => {
            logger.debug("Failed to fetch page after " + lastDocName, e);
            pageIncoming = false;

            fetchFailures++;
            if (fetchFailures === 3) {
              failures.push(e);
            }
          }


(numDeleted) => {
          FirestoreDelete.progressBar.tick(numDeleted);
          numDocsDeleted += numDeleted;
          numPendingDeletes--;
        }


(e) => {
          // If the transaction is too large, reduce the batch size
          if (
            e.status === 400 &&
            e.message.includes("Transaction too big") &&
            this.deleteBatchSize >= 2
          ) {
            logger.debug("Transaction too big error deleting doc batch", e);

            // Cut batch size way down. If one batch is over 10MB then we need to go much
            // lower in order to keep the total I/O appropriately low.
            //
            // Note that we have multiple batches out at once so we need to account for multiple
            // concurrent failures hitting this branch.
            const newBatchSize = Math.floor(toDelete.length / 10);

            if (newBatchSize < this.deleteBatchSize) {
              utils.logLabeledWarning(
                "firestore",
                `delete transaction too large, reducing batch size from ${this.deleteBatchSize} to ${newBatchSize}`
              );
              this.setDeleteBatchSize(newBatchSize);
            }

            // Retry this batch
            queue.unshift(...toDelete);
          } else if (e.status >= 500 && e.status < 600) {
            // For server errors, retry if the document has not yet been retried.
            logger.debug("Server error deleting doc batch", e);

            // Retry each doc up to one time
            toDelete.forEach((doc) => {
              if (retried[doc.name]) {
                logger.debug("Failed to delete doc " + doc.name + " multiple times.");
                failures.push(doc.name);
              } else {
                retried[doc.name] = true;
                queue.push(doc);
              }
            });
          } else {
            logger.debug("Fatal error deleting docs ", e);
            failures = failures.concat(toDelete);
          }

          numPendingDeletes--;
        }


(doc) => {
              if (retried[doc.name]) {
                logger.debug("Failed to delete doc " + doc.name + " multiple times.");
                failures.push(doc.name);
              } else {
                retried[doc.name] = true;
                queue.push(doc);
              }
            }


(resolve, reject) => {
      const intervalId = setInterval(() => {
        if (queueLoop()) {
          clearInterval(intervalId);

          if (failures.length === 0) {
            resolve();
          } else {
            const docIds = failures.map((d) => d.name).join(", ");
            reject(new FirebaseError("Failed to delete documents " + docIds, { exit: 1 }));
          }
        }
      }, 0);
    }


() => {
        if (queueLoop()) {
          clearInterval(intervalId);

          if (failures.length === 0) {
            resolve();
          } else {
            const docIds = failures.map((d) => d.name).join(", ");
            reject(new FirebaseError("Failed to delete documents " + docIds, { exit: 1 }));
          }
        }
      }


(err) => {
        logger.debug("deletePath:initialDelete:error", err);
        if (this.allDescendants) {
          // On a recursive delete, we are insensitive to
          // failures of the initial delete
          return Promise.resolve();
        }

        // For a shallow delete, this error is fatal.
        return utils.reject("Unable to delete " + clc.cyan(this.path));
      }


() => {
      return this.recursiveBatchDelete();
    }


(err) => {
        logger.debug("deleteDatabase:listCollectionIds:error", err);
        return utils.reject("Unable to list collection IDs");
      }


(collectionIds) => {
        const promises = [];

        logger.info("Deleting the following collections: " + clc.cyan(collectionIds.join(", ")));

        for (let i = 0; i < collectionIds.length; i++) {
          const collectionId = collectionIds[i];
          const deleteOp = new FirestoreDelete(this.project, collectionId, {
            recursive: true,
          });

          promises.push(deleteOp.execute());
        }

        return Promise.all(promises);
      }


(docs: any[]) => {
      return docs.length > 0;
    }


(multiple: boolean) => {
        if (multiple) {
          return utils.reject("Document has children, must specify -r or --shallow.", { exit: 1 });
        }
      }


() => {
      return this.deletePath();
    }


/**
   * Update the delete batch size and dependent properties.
   */
private setDeleteBatchSize(size: number): void {
    this.deleteBatchSize = size;
    this.maxQueueSize = this.deleteBatchSize * this.maxPendingDeletes * 2;
  }


/**
   * Validate all options, throwing an exception for any fatal errors.
   */
private validateOptions(): void {
    if (this.recursive && this.shallow) {
      throw new FirebaseError("Cannot pass recursive and shallow options together.");
    }

    if (this.isCollectionPath && !this.recursive && !this.shallow) {
      throw new FirebaseError("Must pass recursive or shallow option when deleting a collection.");
    }

    const pieces = this.path.split("/");

    if (pieces.length === 0) {
      throw new FirebaseError("Path length must be greater than zero.");
    }

    const hasEmptySegment = pieces.some((piece) => {
      return piece.length === 0;
    });

    if (hasEmptySegment) {
      throw new FirebaseError("Path must not have any empty segments.");
    }
  }


/**
   * Construct a StructuredQuery to find descendant documents of a collection.
   *
   * See:
   * https://firebase.google.com/docs/firestore/reference/rest/v1/StructuredQuery
   *
   * @param allDescendants true if subcollections should be included.
   * @param batchSize maximum number of documents to target (limit).
   * @param startAfter document name to start after (optional).
   * @return a StructuredQuery.
   */
private collectionDescendantsQuery(
    allDescendants: boolean,
    batchSize: number,
    startAfter?: string
  ) {
    const nullChar = String.fromCharCode(0);

    const startAt = this.root + "/" + this.path + "/" + MIN_ID;
    const endAt = this.root + "/" + this.path + nullChar + "/" + MIN_ID;

    const where = {
      compositeFilter: {
        op: "AND",
        filters: [
          {
            fieldFilter: {
              field: {
                fieldPath: "__name__",
              },
              op: "GREATER_THAN_OR_EQUAL",
              value: {
                referenceValue: startAt,
              },
            },
          },
          {
            fieldFilter: {
              field: {
                fieldPath: "__name__",
              },
              op: "LESS_THAN",
              value: {
                referenceValue: endAt,
              },
            },
          },
        ],
      },
    };

    const query: any = {
      structuredQuery: {
        where: where,
        limit: batchSize,
        from: [
          {
            allDescendants: allDescendants,
          },
        ],
        select: {
          fields: [{ fieldPath: "__name__" }],
        },
        orderBy: [{ field: { fieldPath: "__name__" } }],
      },
    };

    if (startAfter) {
      query.structuredQuery.startAt = {
        values: [{ referenceValue: startAfter }],
        before: false,
      };
    }

    return query;
  }


/**
   * Construct a StructuredQuery to find descendant documents of a document.
   * The document itself will not be included
   * among the results.
   *
   * See:
   * https://firebase.google.com/docs/firestore/reference/rest/v1/StructuredQuery
   *
   * @param allDescendants true if subcollections should be included.
   * @param batchSize maximum number of documents to target (limit).
   * @param startAfter document name to start after (optional).
   * @return a StructuredQuery.
   */
private docDescendantsQuery(allDescendants: boolean, batchSize: number, startAfter?: string) {
    const query: any = {
      structuredQuery: {
        limit: batchSize,
        from: [
          {
            allDescendants: allDescendants,
          },
        ],
        select: {
          fields: [{ fieldPath: "__name__" }],
        },
        orderBy: [{ field: { fieldPath: "__name__" } }],
      },
    };

    if (startAfter) {
      query.structuredQuery.startAt = {
        values: [{ referenceValue: startAfter }],
        before: false,
      };
    }

    return query;
  }


/**
   * Query for a batch of 'descendants' of a given path.
   *
   * For document format see:
   * https://firebase.google.com/docs/firestore/reference/rest/v1/Document
   *
   * @param allDescendants true if subcollections should be included,
   * @param batchSize the maximum size of the batch.
   * @param startAfter the name of the document to start after (optional).
   * @return a promise for an array of documents.
   */
private getDescendantBatch(
    allDescendants: boolean,
    batchSize: number,
    startAfter?: string
  ): Promise<any[]> {
    const url = this.parent + ":runQuery";
    const body = this.isDocumentPath
      ? this.docDescendantsQuery(allDescendants, batchSize, startAfter)
      : this.collectionDescendantsQuery(allDescendants, batchSize, startAfter);

    return this.apiClient.post<any, Array<{ document?: any }>>(url, body).then((res) => {
      // Return the 'document' property for each element in the response,
      // where it exists.
      return res.body
        .filter((x) => {
          return x.document;
        })
        .map((x) => {
          return x.document;
        });
    });
  }


/**
   * Repeatedly query for descendants of a path and delete them in batches
   * until no documents remain.
   *
   * @return a promise for the entire operation.
   */
private recursiveBatchDelete() {
    let queue: any[] = [];
    let numDocsDeleted = 0;
    let numPendingDeletes = 0;
    let pagesRemaining = true;
    let pageIncoming = false;
    let lastDocName: string | undefined = undefined;

    const retried: { [name: string]: boolean } = {};
    let failures: any[] = [];
    let fetchFailures = 0;

    const queueLoop = () => {
      // No documents left to delete
      if (queue.length === 0 && numPendingDeletes === 0 && !pagesRemaining) {
        return true;
      }

      // Failure that can't be retried again
      if (failures.length > 0) {
        logger.debug("Found " + failures.length + " failed operations, failing.");
        return true;
      }

      // We have room in the queue for more documents and more exist on the server,
      // so fetch more.
      if (queue.length <= this.maxQueueSize && pagesRemaining && !pageIncoming) {
        pageIncoming = true;

        this.getDescendantBatch(this.allDescendants, this.readBatchSize, lastDocName)
          .then((docs: any[]) => {
            fetchFailures = 0;
            pageIncoming = false;

            if (docs.length === 0) {
              pagesRemaining = false;
              return;
            }

            queue = queue.concat(docs);
            lastDocName = docs[docs.length - 1].name;
          })
          .catch((e) => {
            logger.debug("Failed to fetch page after " + lastDocName, e);
            pageIncoming = false;

            fetchFailures++;
            if (fetchFailures === 3) {
              failures.push(e);
            }
          });
      }

      // We want to see one batch succeed before we scale up, so this case
      // limits parallelism until first success
      if (numDocsDeleted === 0 && numPendingDeletes >= 1) {
        return false;
      }

      // There are too many outstanding deletes alread
      if (numPendingDeletes > this.maxPendingDeletes) {
        return false;
      }

      // There are no documents to delete right now
      if (queue.length === 0) {
        return false;
      }

      // At this point we want to delete another batch
      const toDelete: any[] = [];
      const numToDelete = Math.min(this.deleteBatchSize, queue.length);

      for (let i = 0; i < numToDelete; i++) {
        toDelete.push(queue.shift());
      }

      numPendingDeletes++;
      firestore
        .deleteDocuments(this.project, toDelete)
        .then((numDeleted) => {
          FirestoreDelete.progressBar.tick(numDeleted);
          numDocsDeleted += numDeleted;
          numPendingDeletes--;
        })
        .catch((e) => {
          // If the transaction is too large, reduce the batch size
          if (
            e.status === 400 &&
            e.message.includes("Transaction too big") &&
            this.deleteBatchSize >= 2
          ) {
            logger.debug("Transaction too big error deleting doc batch", e);

            // Cut batch size way down. If one batch is over 10MB then we need to go much
            // lower in order to keep the total I/O appropriately low.
            //
            // Note that we have multiple batches out at once so we need to account for multiple
            // concurrent failures hitting this branch.
            const newBatchSize = Math.floor(toDelete.length / 10);

            if (newBatchSize < this.deleteBatchSize) {
              utils.logLabeledWarning(
                "firestore",
                `delete transaction too large, reducing batch size from ${this.deleteBatchSize} to ${newBatchSize}`
              );
              this.setDeleteBatchSize(newBatchSize);
            }

            // Retry this batch
            queue.unshift(...toDelete);
          } else if (e.status >= 500 && e.status < 600) {
            // For server errors, retry if the document has not yet been retried.
            logger.debug("Server error deleting doc batch", e);

            // Retry each doc up to one time
            toDelete.forEach((doc) => {
              if (retried[doc.name]) {
                logger.debug("Failed to delete doc " + doc.name + " multiple times.");
                failures.push(doc.name);
              } else {
                retried[doc.name] = true;
                queue.push(doc);
              }
            });
          } else {
            logger.debug("Fatal error deleting docs ", e);
            failures = failures.concat(toDelete);
          }

          numPendingDeletes--;
        });

      return false;
    };

    return new Promise((resolve, reject) => {
      const intervalId = setInterval(() => {
        if (queueLoop()) {
          clearInterval(intervalId);

          if (failures.length === 0) {
            resolve();
          } else {
            const docIds = failures.map((d) => d.name).join(", ");
            reject(new FirebaseError("Failed to delete documents " + docIds, { exit: 1 }));
          }
        }
      }, 0);
    });
  }


/**
   * Delete everything under a given path. If the path represents
   * a document the document is deleted and then all descendants
   * are deleted.
   *
   * @return a promise for the entire operation.
   */
private deletePath(): Promise<any> {
    let initialDelete;
    if (this.isDocumentPath) {
      const doc = { name: this.root + "/" + this.path };
      initialDelete = firestore.deleteDocument(doc).catch((err) => {
        logger.debug("deletePath:initialDelete:error", err);
        if (this.allDescendants) {
          // On a recursive delete, we are insensitive to
          // failures of the initial delete
          return Promise.resolve();
        }

        // For a shallow delete, this error is fatal.
        return utils.reject("Unable to delete " + clc.cyan(this.path));
      });
    } else {
      initialDelete = Promise.resolve();
    }

    return initialDelete.then(() => {
      return this.recursiveBatchDelete();
    });
  }


/**
   * Delete an entire database by finding and deleting each collection.
   *
   * @return a promise for all of the operations combined.
   */
public deleteDatabase(): Promise<any[]> {
    return firestore
      .listCollectionIds(this.project)
      .catch((err) => {
        logger.debug("deleteDatabase:listCollectionIds:error", err);
        return utils.reject("Unable to list collection IDs");
      })
      .then((collectionIds) => {
        const promises = [];

        logger.info("Deleting the following collections: " + clc.cyan(collectionIds.join(", ")));

        for (let i = 0; i < collectionIds.length; i++) {
          const collectionId = collectionIds[i];
          const deleteOp = new FirestoreDelete(this.project, collectionId, {
            recursive: true,
          });

          promises.push(deleteOp.execute());
        }

        return Promise.all(promises);
      });
  }


/**
   * Check if a path has any children. Useful for determining
   * if deleting a path will affect more than one document.
   *
   * @return a promise that retruns true if the path has children and false otherwise.
   */
public checkHasChildren(): Promise<boolean> {
    return this.getDescendantBatch(true, 1).then((docs: any[]) => {
      return docs.length > 0;
    });
  }


/**
   * Run the delete operation.
   */
public execute() {
    let verifyRecurseSafe;
    if (this.isDocumentPath && !this.recursive && !this.shallow) {
      verifyRecurseSafe = this.checkHasChildren().then((multiple: boolean) => {
        if (multiple) {
          return utils.reject("Document has children, must specify -r or --shallow.", { exit: 1 });
        }
      });
    } else {
      verifyRecurseSafe = Promise.resolve();
    }

    return verifyRecurseSafe.then(() => {
      return this.deletePath();
    });
  }


() => {
  it('should render snapshot correctly', () => {
    const { container } = render(
      <>
        <ButtonIcon icon="search" />
        <ButtonIcon color="error" icon="minus" />
        <ButtonIcon title="title test" icon="view" />
        <ButtonIcon title="title test" icon="view" isDisabled />
        <ButtonIcon icon="view" isLoading />
      </>,
    )
    expect(container).toMatchSnapshot()
  })

  it('should add the className in button component', () => {
    const { container } = render(
      <ButtonIcon className="customClass" icon="check" />,
    )
    expect(container.querySelector('.customClass')).toBeInstanceOf(
      HTMLButtonElement,
    )
  })

  it('should receive the isDisabled attribute', () => {
    const handleClick = jest.fn()
    const { container } = render(
      <ButtonIcon icon="check" onClick={handleClick} isDisabled />,
    )
    const btnElement = container.querySelector('button')

    if (!btnElement) throw new Error("Can't find the icon button")

    fireEvent.click(btnElement)
    expect(handleClick).toHaveBeenCalledTimes(0)
  })

  it('should receive the isLoading attribute', () => {
    const { container } = render(<ButtonIcon icon="check" isLoading />)
    expect(
      container.querySelector('[data-icon-name="spinnerSolid"]'),
    ).toBeDefined()
  })

  it('should receive the onChange attribute', () => {
    const handleClick = jest.fn()
    const { container } = render(
      <ButtonIcon icon="check" onClick={handleClick} />,
    )
    const btnElement = container.querySelector('button')

    if (!btnElement) throw new Error("Can't find the icon button")

    fireEvent.click(btnElement)
    expect(handleClick).toHaveBeenCalledTimes(1)
  })
}


() => {
    const { container } = render(
      <>
        <ButtonIcon icon="search" />
        <ButtonIcon color="error" icon="minus" />
        <ButtonIcon title="title test" icon="view" />
        <ButtonIcon title="title test" icon="view" isDisabled />
        <ButtonIcon icon="view" isLoading />
      </>,
    )
    expect(container).toMatchSnapshot()
  }


() => {
    const { container } = render(
      <ButtonIcon className="customClass" icon="check" />,
    )
    expect(container.querySelector('.customClass')).toBeInstanceOf(
      HTMLButtonElement,
    )
  }


() => {
    const handleClick = jest.fn()
    const { container } = render(
      <ButtonIcon icon="check" onClick={handleClick} isDisabled />,
    )
    const btnElement = container.querySelector('button')

    if (!btnElement) throw new Error("Can't find the icon button")

    fireEvent.click(btnElement)
    expect(handleClick).toHaveBeenCalledTimes(0)
  }


() => {
    const { container } = render(<ButtonIcon icon="check" isLoading />)
    expect(
      container.querySelector('[data-icon-name="spinnerSolid"]'),
    ).toBeDefined()
  }


() => {
    const handleClick = jest.fn()
    const { container } = render(
      <ButtonIcon icon="check" onClick={handleClick} />,
    )
    const btnElement = container.querySelector('button')

    if (!btnElement) throw new Error("Can't find the icon button")

    fireEvent.click(btnElement)
    expect(handleClick).toHaveBeenCalledTimes(1)
  }


(option: string) => logger.log(` \u203A ${option}`)


() => {
  logger.log();
  logger.log(chalk.bold('Usage'));
  printOption(
    `${chalk.dim('Press')} ${KEYS.FIX_ALL_ISSUES} ${chalk.dim(
      'to fix all issues.',
    )}`,
  );
  printOption(
    `${chalk.dim('Press')} ${KEYS.FIX_ERRORS} ${chalk.dim('to fix errors.')}`,
  );
  printOption(
    `${chalk.dim('Press')} ${KEYS.FIX_WARNINGS} ${chalk.dim(
      'to fix warnings.',
    )}`,
  );
  printOption(`${chalk.dim('Press')} Enter ${chalk.dim('to exit.')}`);
}


({onKeyPress}: {onKeyPress: (...args: any[]) => void}) => {
  printOptions();

  if (process.stdin.setRawMode) {
    process.stdin.setRawMode(true);
  }
  process.stdin.resume();
  process.stdin.setEncoding('utf8');
  process.stdin.on('data', onKeyPress);
}


@Component({
    selector: 'fd-action-sheet-compact-example',
    templateUrl: './action-sheet-compact-example.component.html',
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class ActionSheetCompactExampleComponent {
    @ViewChild(ActionSheetComponent)
    actionSheetComponent: ActionSheetComponent;

    constructor(private _messageToastService: MessageToastService) {}

    actionPicked(action: string): void {
        this.openMessageToast(action);
        this.actionSheetComponent.close();
    }

    openMessageToast(action: string): void {
        const content = `${action} action performed`;
        this._messageToastService.open(content, {
            duration: 5000
        });
    }
}


actionPicked(action: string): void {
        this.openMessageToast(action);
        this.actionSheetComponent.close();
    }


openMessageToast(action: string): void {
        const content = `${action} action performed`;
        this._messageToastService.open(content, {
            duration: 5000
        });
    }


export async function initSSM (): Promise<void> {
  const parameterStoreService = container.get<IParameterStoreService>(
    IParameterStoreServiceToken,
  )
  const inMemoryCacheService = container.get<IInMemoryCacheService>(
    IInMemoryCacheServiceToken,
  )
  const cache = container.get<NodeCache>(TYPES.NodeCache)
  const fetchParams = async (): Promise<Parameters> => {
    const parameters: string[] = cache.get<string[]>(CACHE.parameterStore) ?? []
    const groups = chunk(parameters, 10)
    let result: Parameters = []
    for (const group of groups) {
      const params = await parameterStoreService.getAll(group)
      result = concat(result, params)
    }
    return result
  }
  let ssm = inMemoryCacheService.get<Parameters>('ssm')
  if (!ssm) {
    ssm = await fetchParams()
    inMemoryCacheService.set('ssm', ssm)
    for (const param of ssm) {
      if (container.isBound(param.name)) {
        container.unbind(param.name)
      }
      container.bind(param.name).toConstantValue(param.value)
    }
  }
}


async (): Promise<Parameters> => {
    const parameters: string[] = cache.get<string[]>(CACHE.parameterStore) ?? []
    const groups = chunk(parameters, 10)
    let result: Parameters = []
    for (const group of groups) {
      const params = await parameterStoreService.getAll(group)
      result = concat(result, params)
    }
    return result
  }


type Parameters = Array<{ name: string, value: string | string[] }>


(預設值: 0 表示不修剪區塊，>


() => {
      // Move dropdown element on attach node
      this.attachNode.shadowRoot.appendChild(this.popoverEl);
    }


/**
 * Custom dropdown component
 */
@Component({
  tag: 'antivirus-card-dropdown',
})
export class Dropdown {
  /** Ref for ui5-popover element */
  popoverEl: PopoverElType;

  /** Element for attaching dropdown component, by default is root component */
  @Prop() attachNode: HTMLElement = document.querySelector('antivirus-card');

  /** Max width for dropdown content */
  @Prop({ reflect: true }) maxWidth = '330px';

  /**
   * Toggle dropdown state
   * @param event - DOM event
   */
  @Method()
  async toggle(event: Event) {
    if (this.popoverEl._isOpen) {
      this.popoverEl.close();
    } else {
      this.popoverEl.openBy(event.currentTarget);
    }
  }

  componentDidLoad() {
    setTimeout(() => {
      // Move dropdown element on attach node
      this.attachNode.shadowRoot.appendChild(this.popoverEl);
    });
  }

  /**
   * Lifecycle, remove dropdown element in attachNode
   */
  componentDidUnload() {
    this.popoverEl.remove();
  }

  render() {
    return (
      <ui5-popover class="popover" ref={(el: PopoverElType) => (this.popoverEl = el)} no


/** Type for popover element */
export type PopoverElType = HTMLElement & {
  _isOpen: boolean;
  openBy: (control: HTMLElement | EventTarget) => void;
  close: () => void;
};


/**
   * Toggle dropdown state
   * @param event - DOM event
   */
@Method()
  async toggle(event: Event) {
    if (this.popoverEl._isOpen) {
      this.popoverEl.close();
    } else {
      this.popoverEl.openBy(event.currentTarget);
    }
  }


componentDidLoad() {
    setTimeout(() => {
      // Move dropdown element on attach node
      this.attachNode.shadowRoot.appendChild(this.popoverEl);
    });
  }


/**
   * Lifecycle, remove dropdown element in attachNode
   */
componentDidUnload() {
    this.popoverEl.remove();
  }


render() {
    return (
      <ui5-popover class="popover" ref={(el: PopoverElType) => (this.popoverEl = el)}


@Injectable()
export class BotLoaderService {
  constructor(private config: ConfigService) {}

  @UsePlugin()
  loadBots() {
    const onebotConfig =
      this.config.get<Adapter.PluginConfig<AdapterConfig, BotConfig>>('onebot');
    if (onebotConfig.selfId) {
      onebotConfig.selfId = onebotConfig.selfId.toString();
    }
    if (onebotConfig.bots) {
      for (const bot of onebotConfig.bots) {
        bot.selfId = bot.selfId.toString();
      }
    }
    return PluginDef(PluginOnebot, onebotConfig);
  }
}


interface EventMap {
    dispatch: (session: Session) => void;
  }


@UsePlugin()
  loadBots() {
    const onebotConfig =
      this.config.get<Adapter.PluginConfig<AdapterConfig, BotConfig>>('onebot');
    if (onebotConfig.selfId) {
      onebotConfig.selfId = onebotConfig.selfId.toString();
    }
    if (onebotConfig.bots) {
      for (const bot of onebotConfig.bots) {
        bot.selfId = bot.selfId.toString();
      }
    }
    return PluginDef(PluginOnebot, onebotConfig);
  }


export interface PreparedFlowie {
  readonly flows: Flows
  readonly name?: string
}


export interface PipeFlow {
  readonly pipe: FlowieItemDeclaration
  readonly name?: string
}


export interface SplitFlow {
  readonly split: ReadonlyArray<FlowieItemDeclaration>
  readonly name?: string
}


export interface PreparedFlowieExecution extends PreparedFlowie {
  readonly isAsync: boolean
  readonly allFunctionsNames: ReadonlySet<string>
}


export type Flows = ReadonlyArray<FlowElement>


export type FlowElement = PipeFlow | SplitFlow | PreparedFlowie


export type FlowieItemDeclaration = string | FlowElement


/**
 * `FirebaseFunctions` represents a Functions app, and is the entry point for
 * all Functions operations.
 */
export class FirebaseFunctions {
  private constructor();

  /**
   * Gets an `HttpsCallable` instance that refers to the function with the given
   * name.
   *
   * @param name The name of the https callable function.
   * @return The `HttpsCallable` instance.
   */
  httpsCallable(name: string, options?: HttpsCallableOptions): HttpsCallable;

  /**
   * Changes this instance to point to a Cloud Functions emulator running
   * locally. See https://firebase.google.com/docs/functions/local-emulator
   *
   * @param origin The origin of the local emulator, such as
   * "http://localhost:5005".
   */
  useFunctionsEmulator(origin: string): void;
}


/**
 * @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.
 */
/**
 * An HttpsCallableResult wraps a single result from a function call.
 */
export interface HttpsCallableResult {
  readonly data: any;
}


/**
 * An HttpsCallable is a reference to a "callable" http trigger in
 * Google Cloud Functions.
 */
export interface HttpsCallable {
  (data?: {} | null): Promise<HttpsCallableResult>;
}