Skip to content

Core admin init #2953

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@ test/resources/appid.txt
firebase-admin-*.tgz

docgen/markdown/
service_account.json
dataconnect/
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

109 changes: 96 additions & 13 deletions src/data-connect/data-connect-api-client-internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,24 @@ const API_VERSION = 'v1alpha';

/** The Firebase Data Connect backend base URL format. */
const FIREBASE_DATA_CONNECT_BASE_URL_FORMAT =
'https://firebasedataconnect.googleapis.com/{version}/projects/{projectId}/locations/{locationId}/services/{serviceId}:{endpointId}';
'https://firebasedataconnect.googleapis.com/{version}/projects/{projectId}/locations/{locationId}/services/{serviceId}:{endpointId}';

/** Firebase Data Connect base URl format when using the Data Connect emultor. */
/** The Firebase Data Connect backend base URL format including a connector. */
const FIREBASE_DATA_CONNECT_BASE_URL_FORMAT_WITH_CONNECTOR =
'https://firebasedataconnect.googleapis.com/{version}/projects/{projectId}/locations/{locationId}/services/{serviceId}/connectors/{connector}:{endpointId}';

/** Firebase Data Connect base URl format when using the Data Connect emulator. */
const FIREBASE_DATA_CONNECT_EMULATOR_BASE_URL_FORMAT =
'http://{host}/{version}/projects/{projectId}/locations/{locationId}/services/{serviceId}:{endpointId}';

/** Firebase Data Connect base URl format when using the Data Connect emulator including a connector. */
const FIREBASE_DATA_CONNECT_EMULATOR_BASE_URL_FORMAT_WITH_CONNECTOR =
'http://{host}/{version}/projects/{projectId}/locations/{locationId}/services/{serviceId}/connectors/{connector}:{endpointId}';

const EXECUTE_GRAPH_QL_ENDPOINT = 'executeGraphql';
const EXECUTE_GRAPH_QL_READ_ENDPOINT = 'executeGraphqlRead';
const EXECUTE_QUERY_ENDPOINT = 'executeQuery';
const EXECUTE_MUTATION_ENDPOINT = 'executeMutation';

const DATA_CONNECT_CONFIG_HEADERS = {
'X-Firebase-Client': `fire-admin-node/${utils.getSdkVersion()}`
Expand Down Expand Up @@ -87,6 +97,30 @@ export class DataConnectApiClient {
options?: GraphqlOptions<Variables>,
): Promise<ExecuteGraphqlResponse<GraphqlResponse>> {
return this.executeGraphqlHelper(query, EXECUTE_GRAPH_QL_READ_ENDPOINT, options);
// return this.executeHelper(EXECUTE_GRAPH_QL_READ_ENDPOINT,options, query);
}

/**
* Execute pre-existing read-only queries <QueryResult<Data, Variables>>
* @param options - GraphQL Options
* @returns A promise that fulfills with a `ExecuteGraphqlResponse`.
* @throws FirebaseDataConnectError
*/
public async executeQuery<Data, Variables>(
options: GraphqlOptions<Variables>,
): Promise<ExecuteGraphqlResponse<Data>> {
return this.executeOperationHelper(EXECUTE_QUERY_ENDPOINT, options);
}
/**
* Execute pre-existing read and write queries <MutationResult<Data, Variables>>
* @param options - GraphQL Options
* @returns A promise that fulfills with a `ExecuteGraphqlResponse`.
* @throws FirebaseDataConnectError
*/
public async executeMutation<Data, Variables>(
options: GraphqlOptions<Variables>,
): Promise<ExecuteGraphqlResponse<Data>> {
return this.executeOperationHelper(EXECUTE_MUTATION_ENDPOINT, options);
}

private async executeGraphqlHelper<GraphqlResponse, Variables>(
Expand All @@ -106,13 +140,49 @@ export class DataConnectApiClient {
'GraphqlOptions must be a non-null object');
}
}
return this.executeHelper(endpoint, options, query)
}

private async executeOperationHelper<GraphqlResponse, Variables>(
endpoint: string,
options: GraphqlOptions<Variables>,
): Promise<ExecuteGraphqlResponse<GraphqlResponse>> {
if (typeof options == 'undefined') {
throw new FirebaseDataConnectError(
DATA_CONNECT_ERROR_CODE_MAPPING.INVALID_ARGUMENT,
'GraphqlOptions should be a non-null object');
}
if (typeof options !== 'undefined') {
if (!validator.isNonNullObject(options)) {
throw new FirebaseDataConnectError(
DATA_CONNECT_ERROR_CODE_MAPPING.INVALID_ARGUMENT,
'GraphqlOptions must be a non-null object');
}
}

if (!("operationName" in options)) {
throw new FirebaseDataConnectError(
DATA_CONNECT_ERROR_CODE_MAPPING.INVALID_ARGUMENT,
'GraphqlOptions must contain `operationName`.');
}

return this.executeHelper(endpoint, options)
}


private async executeHelper<GraphqlResponse, Variables>(
endpoint: string,
options?: GraphqlOptions<Variables>,
gql?: string
): Promise<ExecuteGraphqlResponse<GraphqlResponse>> {
const data = {
query,
query: gql,
...(!gql && { name: options?.operationName }),
...(options?.variables && { variables: options?.variables }),
...(options?.operationName && { operationName: options?.operationName }),
...(options?.impersonate && { extensions: { impersonate: options?.impersonate } }),
};
return this.getUrl(API_VERSION, this.connectorConfig.location, this.connectorConfig.serviceId, endpoint)
return this.getUrl(API_VERSION, this.connectorConfig.location, this.connectorConfig.serviceId, endpoint, this.connectorConfig.connector)
.then(async (url) => {
const request: HttpRequestConfig = {
method: 'POST',
Expand All @@ -138,23 +208,36 @@ export class DataConnectApiClient {
});
}

private async getUrl(version: string, locationId: string, serviceId: string, endpointId: string): Promise<string> {
private async getUrl(version: string, locationId: string, serviceId: string, endpointId: string, connector?: string): Promise<string> {
return this.getProjectId()
.then((projectId) => {
const urlParams = {
version,
projectId,
locationId,
serviceId,
endpointId
endpointId,
...(connector && { connector })
};
let urlFormat: string;
if (useEmulator()) {
urlFormat = utils.formatString(FIREBASE_DATA_CONNECT_EMULATOR_BASE_URL_FORMAT, {
host: emulatorHost()
});
if ('connector' in urlParams && (endpointId === EXECUTE_QUERY_ENDPOINT || endpointId === EXECUTE_MUTATION_ENDPOINT)) {
urlFormat = utils.formatString(FIREBASE_DATA_CONNECT_EMULATOR_BASE_URL_FORMAT_WITH_CONNECTOR, {
host: emulatorHost()
});
}
else {
urlFormat = utils.formatString(FIREBASE_DATA_CONNECT_EMULATOR_BASE_URL_FORMAT, {
host: emulatorHost()
});
}
} else {
urlFormat = FIREBASE_DATA_CONNECT_BASE_URL_FORMAT;
if ('connector' in urlParams && (endpointId === EXECUTE_QUERY_ENDPOINT || endpointId === EXECUTE_MUTATION_ENDPOINT)) {
urlFormat = FIREBASE_DATA_CONNECT_BASE_URL_FORMAT_WITH_CONNECTOR
}
else {
urlFormat = FIREBASE_DATA_CONNECT_BASE_URL_FORMAT;
}
}
return utils.formatString(urlFormat, urlParams);
});
Expand Down Expand Up @@ -226,13 +309,13 @@ export class DataConnectApiClient {
// GraphQL object keys are typically unquoted.
return `${key}: ${this.objectToString(val)}`;
});

if (kvPairs.length === 0) {
return '{}'; // Represent an object with no defined properties as {}
}
return `{ ${kvPairs.join(', ')} }`;
}

// If value is undefined (and not an object property, which is handled above,
// e.g., if objectToString(undefined) is called directly or for an array element)
// it should be represented as 'null'.
Expand All @@ -255,7 +338,7 @@ export class DataConnectApiClient {
}

private handleBulkImportErrors(err: FirebaseDataConnectError): never {
if (err.code === `data-connect/${DATA_CONNECT_ERROR_CODE_MAPPING.QUERY_ERROR}`){
if (err.code === `data-connect/${DATA_CONNECT_ERROR_CODE_MAPPING.QUERY_ERROR}`) {
throw new FirebaseDataConnectError(
DATA_CONNECT_ERROR_CODE_MAPPING.QUERY_ERROR,
`${err.message}. Make sure that your table name passed in matches the type name in your GraphQL schema file.`);
Expand Down
5 changes: 5 additions & 0 deletions src/data-connect/data-connect-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ export interface ConnectorConfig {
* Service ID of the Data Connect service.
*/
serviceId: string;

/**
* Connector of the Data Connect service.
*/
connector?: string;
}

/**
Expand Down
114 changes: 113 additions & 1 deletion src/data-connect/data-connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/

import { App } from '../app';
import { DataConnectApiClient } from './data-connect-api-client-internal';
import { DATA_CONNECT_ERROR_CODE_MAPPING, DataConnectApiClient, FirebaseDataConnectError } from './data-connect-api-client-internal';

import {
ConnectorConfig,
Expand Down Expand Up @@ -157,4 +157,116 @@ export class DataConnect {
): Promise<ExecuteGraphqlResponse<GraphQlResponse>> {
return this.client.upsertMany(tableName, variables);
}

/**
* Returns Query Reference
* @param name Name of Query
* @returns QueryRef
*/
public queryRef<Data>(name: string): QueryRef<Data, undefined>;
/**
*
* Returns Query Reference
* @param name Name of Query
* @param variables
* @returns QueryRef
*/
public queryRef<Data, Variables>(name: string, variables: Variables): QueryRef<Data, Variables>;
/**
*
* Returns Query Reference
* @param name Name of Query
* @param variables
* @returns QueryRef
*/
public queryRef<Data, Variables>(name: string, variables?: Variables): QueryRef<Data, Variables> {
if (!("connector" in this.connectorConfig)){
throw new FirebaseDataConnectError(DATA_CONNECT_ERROR_CODE_MAPPING.INVALID_ARGUMENT,'executeQuery requires a connector');
}
return new QueryRef(this, name, variables as Variables, this.client);
}
/**
* Returns Mutation Reference
* @param name Name of Mutation
* @returns MutationRef
*/
public mutationRef<Data>(name: string): MutationRef<Data, undefined>;
/**
*
* Returns Mutation Reference
* @param name Name of Mutation
* @param variables
* @returns MutationRef
*/
public mutationRef<Data, Variables>(name: string, variables: Variables): MutationRef<Data, Variables>;
/**
*
* Returns Query Reference
* @param name Name of Mutation
* @param variables
* @returns MutationRef
*/
public mutationRef<Data, Variables>(name: string, variables?: Variables): MutationRef<Data, Variables> {
if (!("connector" in this.connectorConfig)){
throw new FirebaseDataConnectError(DATA_CONNECT_ERROR_CODE_MAPPING.INVALID_ARGUMENT,'executeMutation requires a connector');
}
return new MutationRef(this, name, variables as Variables, this.client);
}
}

abstract class OperationRef<Data, Variables> {
_data?: Data;
constructor(public readonly dataConnect: DataConnect, public readonly name: string, public readonly variables: Variables, protected readonly client: DataConnectApiClient) {

}
abstract execute(): Promise<OperationResult<Data, Variables>>;
}

interface OperationResult<Data, Variables> {
ref: OperationRef<Data, Variables>;
data: Data;
variables: Variables;
dataConnect: DataConnect;
}
export interface QueryResult<Data, Variables> extends OperationResult<Data, Variables> {
ref: QueryRef<Data, Variables>;
}
export interface MutationResult<Data, Variables> extends OperationResult<Data, Variables> {
ref: MutationRef<Data, Variables>;
}

class QueryRef<Data, Variables> extends OperationRef<Data, Variables> {
option_params:GraphqlOptions<Variables>;
async execute(): Promise<QueryResult<Data, Variables>> {
const option_params = {
variables: this.variables,
operationName: this.name
};
const {data} = await this.client.executeQuery<Data, Variables>(option_params)

return {
ref: this,
data: data,
variables: this.variables,
dataConnect: this.dataConnect
}
}
}

class MutationRef<Data, Variables> extends OperationRef<Data, Variables> {
option_params:GraphqlOptions<Variables>;
async execute(): Promise<MutationResult<Data, Variables>> {
const option_params = {
variables: this.variables,
operationName: this.name
};
const {data} = await this.client.executeMutation<Data, Variables>(option_params)

return {
ref: this,
data: data,
variables: this.variables,
dataConnect: this.dataConnect
}
}
}
5 changes: 5 additions & 0 deletions src/data-connect/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ export {
* @returns The default `DataConnect` service with the provided connector configuration
* if no app is provided, or the `DataConnect` service associated with the provided app.
*/
// export function getDataConnect(connectorConfig: ConnectorConfig, app?: App): DataConnect {
// if (typeof app === 'undefined') {
// app = getApp();
// }

export function getDataConnect(connectorConfig: ConnectorConfig, app?: App): DataConnect {
if (typeof app === 'undefined') {
app = getApp();
Expand Down
Loading
Loading