@denizelderenbos/google-ads-api
Version:
Google Ads API Client Library for Node.js
365 lines • 14.9 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Customer = void 0;
const parser_1 = require("./parser");
const serviceFactory_1 = __importDefault(require("./protos/autogen/serviceFactory"));
const query_1 = require("./query");
const utils_1 = require("./utils");
class Customer extends serviceFactory_1.default {
constructor(clientOptions, customerOptions, hooks) {
super(clientOptions, customerOptions, hooks !== null && hooks !== void 0 ? hooks : {});
}
/**
@description Single query using a raw GAQL string.
@hooks onQueryStart, onQueryError, onQueryEnd
*/
async query(gaqlQuery, requestOptions = {}) {
const { response } = await this.querier(gaqlQuery, requestOptions);
return response;
}
/**
@description Stream query using a raw GAQL string. If a generic type is provided, it must be the type of a single row.
If a summary row is requested then this will be the last emitted row of the stream.
@hooks onStreamStart, onStreamError
@example
const stream = queryStream<T>(gaqlQuery)
for await (const row of stream) { ... }
*/
async *queryStream(gaqlQuery, requestOptions = {}) {
const stream = this.streamer(gaqlQuery, requestOptions);
for await (const row of stream) {
yield row;
}
}
/**
@description Single query using ReportOptions.
If a summary row is requested then this will be the first row of the results.
@hooks onQueryStart, onQueryError, onQueryEnd
*/
async report(options) {
const { gaqlQuery, requestOptions } = (0, query_1.buildQuery)(options);
const { response } = await this.querier(gaqlQuery, requestOptions, options);
return response;
}
/**
@description Get the total row count of a report.
@hooks none
*/
async reportCount(options) {
const { gaqlQuery, requestOptions } = (0, query_1.buildQuery)({ ...options, limit: 1 }); // must get at least one row
// @ts-expect-error we do not allow this field in reportOptions, however it is still a valid request option
requestOptions.return_total_results_count = true;
const useHooks = false; // to avoid cacheing conflicts
const { totalResultsCount } = await this.querier(gaqlQuery, requestOptions, options, useHooks);
return totalResultsCount;
}
/**
@description Stream query using ReportOptions. If a generic type is provided, it must be the type of a single row.
If a summary row is requested then this will be the last emitted row of the stream.
@hooks onStreamStart, onStreamError
@example
const stream = reportStream<T>(reportOptions)
for await (const row of stream) { ... }
*/
async *reportStream(reportOptions) {
const { gaqlQuery, requestOptions } = (0, query_1.buildQuery)(reportOptions);
const stream = this.streamer(gaqlQuery, requestOptions, reportOptions);
for await (const row of stream) {
yield row;
}
}
/**
@description Retreive the raw stream using ReportOptions.
@hooks onStreamStart
@example
const stream = reportStreamRaw(reportOptions)
stream.on('data', (chunk) => { ... }) // a chunk contains up to 10,000 un-parsed rows
stream.on('error', (error) => { ... })
stream.on('end', () => { ... })
*/
async reportStreamRaw(reportOptions) {
const { gaqlQuery, requestOptions } = (0, query_1.buildQuery)(reportOptions);
const baseHookArguments = {
credentials: this.credentials,
query: gaqlQuery,
reportOptions,
};
const queryStart = { cancelled: false };
if (this.hooks.onStreamStart) {
await this.hooks.onStreamStart({
...baseHookArguments,
cancel: () => {
queryStart.cancelled = true;
},
editOptions: (options) => {
Object.entries(options).forEach(([key, val]) => {
// @ts-ignore
requestOptions[key] = val;
});
},
});
if (queryStart.cancelled) {
return;
}
}
const { service, request } = this.buildSearchStreamRequestAndService(gaqlQuery, requestOptions);
return service.searchStream(request, {
otherArgs: { headers: this.callHeaders },
});
}
async search(gaqlQuery, requestOptions) {
const { service, request } = this.buildSearchRequestAndService(gaqlQuery, requestOptions);
const searchResponse = await service.search(request, {
otherArgs: { headers: this.callHeaders },
autoPaginate: false, // autoPaginate doesn't work
});
const response = searchResponse[0];
const summaryRow = searchResponse[2].summary_row;
const nextPageToken = searchResponse[2].next_page_token;
const totalResultsCount = searchResponse[2].total_results_count
? +searchResponse[2].total_results_count
: undefined;
if (summaryRow) {
response.unshift(summaryRow);
}
return { response, nextPageToken, totalResultsCount };
}
async paginatedSearch(gaqlQuery, requestOptions, parser) {
const response = [];
let nextPageToken = undefined;
const initialSearch = await this.search(gaqlQuery, requestOptions);
const totalResultsCount = initialSearch.totalResultsCount;
response.push(...parser(initialSearch.response));
nextPageToken = initialSearch.nextPageToken;
while (nextPageToken) {
const nextSearch = await this.search(gaqlQuery, {
...requestOptions,
page_token: nextPageToken,
});
response.push(...parser(nextSearch.response));
nextPageToken = nextSearch.nextPageToken;
}
return { response, totalResultsCount };
}
async querier(gaqlQuery, requestOptions = {}, reportOptions, useHooks = true) {
const baseHookArguments = {
credentials: this.credentials,
query: gaqlQuery,
reportOptions,
};
if (this.hooks.onQueryStart && useHooks) {
const queryCancellation = { cancelled: false };
await this.hooks.onQueryStart({
...baseHookArguments,
cancel: (res) => {
queryCancellation.cancelled = true;
queryCancellation.res = res;
},
editOptions: (options) => {
Object.entries(options).forEach(([key, val]) => {
// @ts-ignore
requestOptions[key] = val;
});
},
});
if (queryCancellation.cancelled) {
return { response: queryCancellation.res };
}
}
try {
const parsingWapper = (rows) => {
return this.clientOptions.disable_parsing
? rows
: reportOptions
? (0, parser_1.parse)({ results: rows, reportOptions })
: (0, parser_1.parse)({ results: rows, gaqlString: gaqlQuery });
};
const { response, totalResultsCount } = await this.paginatedSearch(gaqlQuery, requestOptions, parsingWapper);
if (this.hooks.onQueryEnd && useHooks) {
const queryResolution = { resolved: false };
await this.hooks.onQueryEnd({
...baseHookArguments,
response,
resolve: (res) => {
queryResolution.resolved = true;
queryResolution.res = res;
},
});
if (queryResolution.resolved) {
return { response: queryResolution.res, totalResultsCount };
}
}
return { response: response, totalResultsCount };
}
catch (searchError) {
const googleAdsError = this.getGoogleAdsError(searchError);
if (this.hooks.onQueryError && useHooks) {
await this.hooks.onQueryError({
...baseHookArguments,
error: googleAdsError,
});
}
throw googleAdsError;
}
}
async *streamer(gaqlQuery, requestOptions = {}, reportOptions) {
const baseHookArguments = {
credentials: this.credentials,
query: gaqlQuery,
reportOptions,
};
if (this.hooks.onStreamStart) {
const queryStart = { cancelled: false };
await this.hooks.onStreamStart({
...baseHookArguments,
cancel: () => {
queryStart.cancelled = true;
},
editOptions: (options) => {
Object.entries(options).forEach(([key, val]) => {
// @ts-expect-error
requestOptions[key] = val;
});
},
});
if (queryStart.cancelled) {
return;
}
}
const { service, request } = this.buildSearchStreamRequestAndService(gaqlQuery, requestOptions);
const stream = service.searchStream(request, {
otherArgs: { headers: this.callHeaders },
});
let streamFinished = false;
const accumulator = [];
let nextChunk = (0, utils_1.createNextChunkArrivedPromise)();
stream.on("data", (chunk) => {
const results = chunk.summary_row ? [chunk.summary_row] : chunk.results;
const parsedResponse = this.clientOptions.disable_parsing
? results
: reportOptions
? (0, parser_1.parse)({ results, reportOptions })
: (0, parser_1.parse)({ results, gaqlString: gaqlQuery });
accumulator.push(...parsedResponse);
nextChunk.resolve();
nextChunk = (0, utils_1.createNextChunkArrivedPromise)();
});
stream.on("error", (searchError) => {
nextChunk.reject(searchError);
});
stream.on("end", () => {
streamFinished = true;
nextChunk.resolve();
});
try {
while (!streamFinished || accumulator.length) {
if (accumulator.length > 0) {
const item = accumulator.shift();
if (item === undefined) {
throw new Error("UNDEFINED_STREAM_ERROR");
}
yield item;
}
else {
await nextChunk.newPromise;
}
}
}
catch (searchError) {
const googleAdsError = this.getGoogleAdsError(searchError);
if (this.hooks.onStreamError) {
await this.hooks.onStreamError({
...baseHookArguments,
error: googleAdsError,
});
}
throw googleAdsError;
}
finally {
stream.destroy();
}
}
/**
* @description Creates, updates, or removes resources. This method supports atomic transactions
* with multiple types of resources. For example, you can atomically create a campaign and a
* campaign budget, or perform up to thousands of mutates atomically.
* @hooks onMutationStart, onMutationError, onMutationEnd
*/
async mutateResources(mutations, mutateOptions = {}) {
const baseHookArguments = {
credentials: this.credentials,
method: "GoogleAdsService.mutate",
mutations,
isServiceCall: false,
};
if (this.hooks.onMutationStart) {
const mutationCancellation = { cancelled: false };
await this.hooks.onMutationStart({
...baseHookArguments,
cancel: (res) => {
mutationCancellation.cancelled = true;
mutationCancellation.res = res;
},
editOptions: (options) => {
Object.entries(options).forEach(([key, val]) => {
// @ts-ignore
mutateOptions[key] = val;
});
},
});
if (mutationCancellation.cancelled) {
return mutationCancellation.res;
}
}
const { service, request } = this.buildMutationRequestAndService(mutations, mutateOptions);
try {
const response = (await service.mutate(request, {
otherArgs: { headers: this.callHeaders },
}))[0];
const parsedResponse = request.partial_failure
? this.decodePartialFailureError(response)
: response;
if (this.hooks.onMutationEnd) {
const mutationResolution = { resolved: false };
await this.hooks.onMutationEnd({
...baseHookArguments,
response: parsedResponse,
resolve: (res) => {
mutationResolution.resolved = true;
mutationResolution.res = res;
},
});
if (mutationResolution.resolved) {
return mutationResolution.res;
}
}
return parsedResponse;
}
catch (mutateError) {
const googleAdsError = this.getGoogleAdsError(mutateError);
if (this.hooks.onMutationError) {
await this.hooks.onMutationError({
...baseHookArguments,
error: googleAdsError,
});
}
throw googleAdsError;
}
}
get googleAdsFields() {
return {
searchGoogleAdsFields: async (request) => {
const service = await this.loadService("GoogleAdsFieldServiceClient");
return service.searchGoogleAdsFields(request, {
// @ts-expect-error This method does support call headers
otherArgs: { headers: this.callHeaders },
});
},
};
}
}
exports.Customer = Customer;
//# sourceMappingURL=customer.js.map