google-ads-api 24.1.0 → 25.1.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -1
- package/build/{src → cjs}/client.d.ts +6 -4
- package/build/{src → cjs}/client.js +4 -4
- package/build/{src → cjs}/customer.d.ts +5 -5
- package/build/{src → cjs}/customer.js +24 -20
- package/build/{src → cjs}/hooks.d.ts +2 -2
- package/build/cjs/index.d.ts +9 -0
- package/build/{src → cjs}/index.js +22 -20
- package/build/{src → cjs}/parser.d.ts +2 -2
- package/build/{src → cjs}/parser.js +5 -5
- package/build/{src → cjs}/parserRest.js +7 -7
- package/build/{src → cjs}/protos/autogen/enums.d.ts +1679 -1180
- package/build/{src → cjs}/protos/autogen/enums.js +1696 -1177
- package/build/cjs/protos/autogen/fields.d.ts +1191 -0
- package/build/cjs/protos/autogen/fields.js +205 -0
- package/build/cjs/protos/autogen/resourceNames.d.ts +1668 -0
- package/build/cjs/protos/autogen/resourceNames.js +1669 -0
- package/build/{src → cjs}/protos/autogen/serviceFactory.d.ts +206 -215
- package/build/{src → cjs}/protos/autogen/serviceFactory.js +483 -554
- package/build/cjs/protos/index.d.ts +14 -0
- package/build/{src → cjs}/protos/index.js +12 -12
- package/build/{src → cjs}/query.d.ts +2 -2
- package/build/{src → cjs}/query.js +5 -5
- package/build/{src → cjs}/service.d.ts +5 -5
- package/build/{src → cjs}/service.js +64 -22
- package/build/{src → cjs}/types.d.ts +1 -1
- package/build/{src → cjs}/utils.d.ts +1 -1
- package/build/{src → cjs}/utils.js +2 -2
- package/build/cjs/version.d.ts +1 -0
- package/build/{src → cjs}/version.js +1 -1
- package/build/esm/client.d.ts +19 -0
- package/build/esm/client.js +40 -0
- package/build/esm/customer.d.ts +71 -0
- package/build/esm/customer.js +502 -0
- package/build/esm/hooks.d.ts +160 -0
- package/build/esm/hooks.js +1 -0
- package/build/esm/index.d.ts +9 -0
- package/build/esm/index.js +10 -0
- package/build/esm/package.json +1 -0
- package/build/esm/parser.d.ts +21 -0
- package/build/esm/parser.js +113 -0
- package/build/esm/parserRest.d.ts +1 -0
- package/build/esm/parserRest.js +112 -0
- package/build/esm/protos/autogen/enums.d.ts +6002 -0
- package/build/esm/protos/autogen/enums.js +6401 -0
- package/build/esm/protos/autogen/fields.d.ts +1191 -0
- package/build/esm/protos/autogen/fields.js +202 -0
- package/build/esm/protos/autogen/resourceNames.d.ts +1668 -0
- package/build/esm/protos/autogen/resourceNames.js +1475 -0
- package/build/esm/protos/autogen/serviceFactory.d.ts +1825 -0
- package/build/esm/protos/autogen/serviceFactory.js +18795 -0
- package/build/esm/protos/index.d.ts +14 -0
- package/build/esm/protos/index.js +19 -0
- package/build/esm/query.d.ts +62 -0
- package/build/esm/query.js +304 -0
- package/build/esm/service.d.ts +47 -0
- package/build/esm/service.js +222 -0
- package/build/esm/types.d.ts +75 -0
- package/build/esm/types.js +14 -0
- package/build/esm/utils.d.ts +49 -0
- package/build/esm/utils.js +108 -0
- package/build/esm/version.d.ts +1 -0
- package/build/esm/version.js +1 -0
- package/package.json +50 -11
- package/build/src/index.d.ts +0 -8
- package/build/src/protos/autogen/fields.d.ts +0 -1122
- package/build/src/protos/autogen/fields.js +0 -644
- package/build/src/protos/autogen/resourceNames.d.ts +0 -1609
- package/build/src/protos/autogen/resourceNames.js +0 -1982
- package/build/src/protos/index.d.ts +0 -14
- package/build/src/testUtils.d.ts +0 -107
- package/build/src/testUtils.js +0 -333
- package/build/src/version.d.ts +0 -1
- /package/build/{src → cjs}/hooks.js +0 -0
- /package/build/{src → cjs}/parserRest.d.ts +0 -0
- /package/build/{src → cjs}/types.js +0 -0
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import Chain from "stream-chain";
|
|
3
|
+
import streamJson from "stream-json";
|
|
4
|
+
import StreamArray from "stream-json/streamers/StreamArray.js";
|
|
5
|
+
import { decamelizeKeys } from "./parserRest.js";
|
|
6
|
+
import { errors } from "./protos/index.js";
|
|
7
|
+
import ServiceFactory from "./protos/autogen/serviceFactory.js";
|
|
8
|
+
import { buildQuery } from "./query.js";
|
|
9
|
+
import { googleAdsVersion } from "./version.js";
|
|
10
|
+
const ROWS_PER_STREAMED_CHUNK = 10_000; // From experience, this is what can be expected from the API.
|
|
11
|
+
export class Customer extends ServiceFactory {
|
|
12
|
+
constructor(clientOptions, customerOptions, hooks) {
|
|
13
|
+
super(clientOptions, customerOptions, hooks ?? {});
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
@description Single query using a raw GAQL string.
|
|
17
|
+
@hooks onQueryStart, onQueryError, onQueryEnd
|
|
18
|
+
*/
|
|
19
|
+
async query(gaqlQuery, requestOptions = {}) {
|
|
20
|
+
const { response } = await this.querier(gaqlQuery, requestOptions);
|
|
21
|
+
return response;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
@description Stream query using a raw GAQL string. If a generic type is provided, it must be the type of a single row.
|
|
25
|
+
If a summary row is requested then this will be the last emitted row of the stream.
|
|
26
|
+
@hooks onStreamStart, onStreamError
|
|
27
|
+
@example
|
|
28
|
+
const stream = queryStream<T>(gaqlQuery)
|
|
29
|
+
for await (const row of stream) { ... }
|
|
30
|
+
*/
|
|
31
|
+
async *queryStream(gaqlQuery, requestOptions = {}) {
|
|
32
|
+
const stream = this.streamer(gaqlQuery, requestOptions);
|
|
33
|
+
for await (const row of stream) {
|
|
34
|
+
yield row;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
@description Single query using ReportOptions.
|
|
39
|
+
If a summary row is requested then this will be the first row of the results.
|
|
40
|
+
@hooks onQueryStart, onQueryError, onQueryEnd
|
|
41
|
+
*/
|
|
42
|
+
async report(options) {
|
|
43
|
+
const { gaqlQuery, requestOptions } = buildQuery(options);
|
|
44
|
+
const { response } = await this.querier(gaqlQuery, requestOptions, options);
|
|
45
|
+
return response;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
@description Get the total row count of a report.
|
|
49
|
+
@hooks none
|
|
50
|
+
*/
|
|
51
|
+
async reportCount(options) {
|
|
52
|
+
// must get at least one row
|
|
53
|
+
const { gaqlQuery, requestOptions } = buildQuery({ ...options, limit: 1 });
|
|
54
|
+
// We do not allow this field in reportOptions, however it is still a valid request option
|
|
55
|
+
requestOptions.search_settings = {
|
|
56
|
+
return_total_results_count: true,
|
|
57
|
+
return_summary_row: false,
|
|
58
|
+
};
|
|
59
|
+
const useHooks = false; // to avoid cacheing conflicts
|
|
60
|
+
const { totalResultsCount } = await this.querier(gaqlQuery, requestOptions, options, useHooks);
|
|
61
|
+
return totalResultsCount;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
@description Stream query using ReportOptions. If a generic type is provided, it must be the type of a single row.
|
|
65
|
+
If a summary row is requested then this will be the last emitted row of the stream.
|
|
66
|
+
@hooks onStreamStart, onStreamError
|
|
67
|
+
@example
|
|
68
|
+
const stream = reportStream<T>(reportOptions)
|
|
69
|
+
for await (const row of stream) { ... }
|
|
70
|
+
*/
|
|
71
|
+
async *reportStream(reportOptions) {
|
|
72
|
+
const { gaqlQuery, requestOptions } = buildQuery(reportOptions);
|
|
73
|
+
const stream = this.streamer(gaqlQuery, requestOptions, reportOptions);
|
|
74
|
+
for await (const row of stream) {
|
|
75
|
+
yield row;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
@description Retreive the raw stream using ReportOptions.
|
|
80
|
+
@hooks onStreamStart
|
|
81
|
+
@example
|
|
82
|
+
const stream = reportStreamRaw(reportOptions)
|
|
83
|
+
stream.on('data', (chunk) => { ... }) // a chunk contains up to 10,000 un-parsed rows
|
|
84
|
+
stream.on('error', (error) => { ... })
|
|
85
|
+
stream.on('end', () => { ... })
|
|
86
|
+
*/
|
|
87
|
+
async reportStreamRaw(reportOptions) {
|
|
88
|
+
const { gaqlQuery, requestOptions } = buildQuery(reportOptions);
|
|
89
|
+
const baseHookArguments = {
|
|
90
|
+
credentials: this.credentials,
|
|
91
|
+
query: gaqlQuery,
|
|
92
|
+
reportOptions,
|
|
93
|
+
};
|
|
94
|
+
const queryStart = { cancelled: false };
|
|
95
|
+
if (this.hooks.onStreamStart) {
|
|
96
|
+
await this.hooks.onStreamStart({
|
|
97
|
+
...baseHookArguments,
|
|
98
|
+
cancel: () => {
|
|
99
|
+
queryStart.cancelled = true;
|
|
100
|
+
},
|
|
101
|
+
editOptions: (options) => {
|
|
102
|
+
Object.entries(options).forEach(([key, val]) => {
|
|
103
|
+
// @ts-ignore
|
|
104
|
+
requestOptions[key] = val;
|
|
105
|
+
});
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
if (queryStart.cancelled) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const { service, request } = this.buildSearchStreamRequestAndService(gaqlQuery, requestOptions);
|
|
113
|
+
return service.searchStream(request, {
|
|
114
|
+
otherArgs: { headers: this.callHeaders },
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
async search(gaqlQuery, requestOptions) {
|
|
118
|
+
const accessToken = await this.getAccessToken();
|
|
119
|
+
try {
|
|
120
|
+
const rawResponse = await axios(this.prepareGoogleAdsServicePostRequestArgs("search", accessToken, {
|
|
121
|
+
data: {
|
|
122
|
+
query: gaqlQuery,
|
|
123
|
+
...requestOptions,
|
|
124
|
+
},
|
|
125
|
+
}));
|
|
126
|
+
const searchResponse = rawResponse.data;
|
|
127
|
+
const results = searchResponse.results ?? [];
|
|
128
|
+
const response = results.map((row) => this.decamelizeKeysIfNeeded(row));
|
|
129
|
+
const summaryRow = this.decamelizeKeysIfNeeded(searchResponse.summaryRow);
|
|
130
|
+
const nextPageToken = searchResponse.nextPageToken;
|
|
131
|
+
const totalResultsCount = searchResponse.totalResultsCount
|
|
132
|
+
? +searchResponse.totalResultsCount
|
|
133
|
+
: undefined;
|
|
134
|
+
return { response, nextPageToken, totalResultsCount, summaryRow };
|
|
135
|
+
}
|
|
136
|
+
catch (e) {
|
|
137
|
+
if (e.response?.data.error.details[0]) {
|
|
138
|
+
throw new errors.GoogleAdsFailure(this.decamelizeKeysIfNeeded(e.response.data.error.details[0]));
|
|
139
|
+
}
|
|
140
|
+
throw e;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async paginatedSearch(gaqlQuery, requestOptions) {
|
|
144
|
+
/*
|
|
145
|
+
When possible, use the searchStream method to avoid the overhead of pagination.
|
|
146
|
+
*/
|
|
147
|
+
if (requestOptions.page_size === undefined &&
|
|
148
|
+
requestOptions.search_settings === undefined // If search_settings is set, we can't use searchStream.
|
|
149
|
+
) {
|
|
150
|
+
// If no pagination or summary options are set, we can use the non-paginated search method.
|
|
151
|
+
const { response } = await this.useStreamToImitateRegularSearch(gaqlQuery, requestOptions);
|
|
152
|
+
return { response };
|
|
153
|
+
}
|
|
154
|
+
const response = [];
|
|
155
|
+
let nextPageToken = undefined;
|
|
156
|
+
const initialSearch = await this.search(gaqlQuery, requestOptions);
|
|
157
|
+
let totalResultsCount = initialSearch.totalResultsCount;
|
|
158
|
+
// Sometimes (when no results?) the totalResultsCount field is not included in the response.
|
|
159
|
+
// In this case, we set it to 0.
|
|
160
|
+
if (requestOptions.search_settings?.return_total_results_count &&
|
|
161
|
+
initialSearch.totalResultsCount === undefined) {
|
|
162
|
+
totalResultsCount = 0;
|
|
163
|
+
}
|
|
164
|
+
let summaryRow = initialSearch.summaryRow;
|
|
165
|
+
response.push(...initialSearch.response);
|
|
166
|
+
nextPageToken = initialSearch.nextPageToken;
|
|
167
|
+
while (nextPageToken) {
|
|
168
|
+
const nextSearch = await this.search(gaqlQuery, {
|
|
169
|
+
...requestOptions,
|
|
170
|
+
page_token: nextPageToken,
|
|
171
|
+
});
|
|
172
|
+
response.push(...nextSearch.response);
|
|
173
|
+
nextPageToken = nextSearch.nextPageToken;
|
|
174
|
+
if (nextSearch.summaryRow) {
|
|
175
|
+
summaryRow = nextSearch.summaryRow;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (summaryRow) {
|
|
179
|
+
response.unshift(summaryRow);
|
|
180
|
+
}
|
|
181
|
+
return { response, totalResultsCount };
|
|
182
|
+
}
|
|
183
|
+
// Google's searchStream method is faster than search, but it does not support all features.
|
|
184
|
+
// When report() is called, we use searchStream if possible, otherwise we use paginatedSearch.
|
|
185
|
+
// Note that just like `paginatedSearch`, this method accumulates results in memory. Use
|
|
186
|
+
// `reportStream` for a more memory-efficient alternative (at the cost of more CPU usage).
|
|
187
|
+
async useStreamToImitateRegularSearch(gaqlQuery, requestOptions) {
|
|
188
|
+
const accessToken = await this.getAccessToken();
|
|
189
|
+
try {
|
|
190
|
+
const args = this.prepareGoogleAdsServicePostRequestArgs("searchStream", accessToken, {
|
|
191
|
+
responseType: "stream",
|
|
192
|
+
data: {
|
|
193
|
+
query: gaqlQuery,
|
|
194
|
+
...requestOptions,
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
const response = await axios(args);
|
|
198
|
+
const stream = response.data;
|
|
199
|
+
const buffers = [];
|
|
200
|
+
let rowCount = -ROWS_PER_STREAMED_CHUNK;
|
|
201
|
+
for await (const data of stream) {
|
|
202
|
+
if (this.clientOptions.max_reporting_rows &&
|
|
203
|
+
!this.gaqlQueryStringIncludesLimit(gaqlQuery)) {
|
|
204
|
+
// This is a quick-and-dirty way to count rows, but it's good enough for our purposes.
|
|
205
|
+
// We want to avoid using a proper JSON streamer here for performance reasons.
|
|
206
|
+
if (data.toString("utf-8").includes(`results":`)) {
|
|
207
|
+
rowCount += ROWS_PER_STREAMED_CHUNK;
|
|
208
|
+
}
|
|
209
|
+
if (rowCount > this.clientOptions.max_reporting_rows) {
|
|
210
|
+
throw this.generateTooManyRowsError();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
buffers.push(data);
|
|
214
|
+
}
|
|
215
|
+
const asString = Buffer.concat(buffers).toString("utf-8");
|
|
216
|
+
const accumulator = [];
|
|
217
|
+
let foundSummaryRow;
|
|
218
|
+
for (const { results, summaryRow } of JSON.parse(asString)) {
|
|
219
|
+
if (summaryRow) {
|
|
220
|
+
foundSummaryRow = this.decamelizeKeysIfNeeded(summaryRow);
|
|
221
|
+
}
|
|
222
|
+
accumulator.push(...(results ?? []).map((row) => {
|
|
223
|
+
return this.decamelizeKeysIfNeeded(row);
|
|
224
|
+
}));
|
|
225
|
+
if (foundSummaryRow) {
|
|
226
|
+
accumulator.unshift(foundSummaryRow);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return { response: accumulator };
|
|
230
|
+
}
|
|
231
|
+
catch (e) {
|
|
232
|
+
await this.handleStreamError(e);
|
|
233
|
+
throw e; // The line above should always throw.
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
async querier(gaqlQuery, requestOptions = {}, reportOptions, useHooks = true) {
|
|
237
|
+
const baseHookArguments = {
|
|
238
|
+
credentials: this.credentials,
|
|
239
|
+
query: gaqlQuery,
|
|
240
|
+
reportOptions,
|
|
241
|
+
};
|
|
242
|
+
if (this.hooks.onQueryStart && useHooks) {
|
|
243
|
+
const queryCancellation = { cancelled: false };
|
|
244
|
+
await this.hooks.onQueryStart({
|
|
245
|
+
...baseHookArguments,
|
|
246
|
+
cancel: (res) => {
|
|
247
|
+
queryCancellation.cancelled = true;
|
|
248
|
+
queryCancellation.res = res;
|
|
249
|
+
},
|
|
250
|
+
editOptions: (options) => {
|
|
251
|
+
Object.entries(options).forEach(([key, val]) => {
|
|
252
|
+
// @ts-ignore
|
|
253
|
+
requestOptions[key] = val;
|
|
254
|
+
});
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
if (queryCancellation.cancelled) {
|
|
258
|
+
return { response: queryCancellation.res };
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
try {
|
|
262
|
+
const { response, totalResultsCount } = await this.paginatedSearch(gaqlQuery, requestOptions);
|
|
263
|
+
if (this.hooks.onQueryEnd && useHooks) {
|
|
264
|
+
const queryResolution = { resolved: false };
|
|
265
|
+
await this.hooks.onQueryEnd({
|
|
266
|
+
...baseHookArguments,
|
|
267
|
+
response,
|
|
268
|
+
resolve: (res) => {
|
|
269
|
+
queryResolution.resolved = true;
|
|
270
|
+
queryResolution.res = res;
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
if (queryResolution.resolved) {
|
|
274
|
+
return { response: queryResolution.res, totalResultsCount };
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return { response: response, totalResultsCount };
|
|
278
|
+
}
|
|
279
|
+
catch (searchError) {
|
|
280
|
+
const googleAdsError = this.getGoogleAdsError(searchError);
|
|
281
|
+
if (this.hooks.onQueryError && useHooks) {
|
|
282
|
+
await this.hooks.onQueryError({
|
|
283
|
+
...baseHookArguments,
|
|
284
|
+
error: googleAdsError,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
throw googleAdsError;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
async *streamer(gaqlQuery, requestOptions = {}, reportOptions) {
|
|
291
|
+
const baseHookArguments = {
|
|
292
|
+
credentials: this.credentials,
|
|
293
|
+
query: gaqlQuery,
|
|
294
|
+
reportOptions,
|
|
295
|
+
};
|
|
296
|
+
if (this.hooks.onStreamStart) {
|
|
297
|
+
const queryStart = { cancelled: false };
|
|
298
|
+
await this.hooks.onStreamStart({
|
|
299
|
+
...baseHookArguments,
|
|
300
|
+
cancel: () => {
|
|
301
|
+
queryStart.cancelled = true;
|
|
302
|
+
},
|
|
303
|
+
editOptions: (options) => {
|
|
304
|
+
Object.entries(options).forEach(([key, val]) => {
|
|
305
|
+
// @ts-expect-error
|
|
306
|
+
requestOptions[key] = val;
|
|
307
|
+
});
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
if (queryStart.cancelled) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
try {
|
|
315
|
+
const accessToken = await this.getAccessToken();
|
|
316
|
+
const args = this.prepareGoogleAdsServicePostRequestArgs("searchStream", accessToken, {
|
|
317
|
+
responseType: "stream",
|
|
318
|
+
data: {
|
|
319
|
+
query: gaqlQuery,
|
|
320
|
+
...requestOptions,
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
const response = await axios(args);
|
|
324
|
+
const stream = response.data;
|
|
325
|
+
// The options below help to make the stream less CPU intensive.
|
|
326
|
+
const parser = new streamJson.Parser({
|
|
327
|
+
streamValues: false,
|
|
328
|
+
streamKeys: false,
|
|
329
|
+
packValues: true,
|
|
330
|
+
packKeys: true,
|
|
331
|
+
});
|
|
332
|
+
const pipeline = Chain.chain([stream, parser, StreamArray.streamArray()]);
|
|
333
|
+
let count = 0;
|
|
334
|
+
try {
|
|
335
|
+
for await (const data of pipeline) {
|
|
336
|
+
const results = data.value.results ??
|
|
337
|
+
(data.value.summaryRow ? [data.value.summaryRow] : undefined) ??
|
|
338
|
+
[];
|
|
339
|
+
count += results.length;
|
|
340
|
+
if (this.clientOptions.max_reporting_rows &&
|
|
341
|
+
count > this.clientOptions.max_reporting_rows &&
|
|
342
|
+
!this.gaqlQueryStringIncludesLimit(gaqlQuery)) {
|
|
343
|
+
throw this.generateTooManyRowsError();
|
|
344
|
+
}
|
|
345
|
+
for (const row of results) {
|
|
346
|
+
const parsed = this.decamelizeKeysIfNeeded(row);
|
|
347
|
+
yield parsed;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
finally {
|
|
352
|
+
pipeline.destroy();
|
|
353
|
+
parser.destroy();
|
|
354
|
+
stream.destroy();
|
|
355
|
+
}
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
catch (e) {
|
|
359
|
+
try {
|
|
360
|
+
await this.handleStreamError(e);
|
|
361
|
+
}
|
|
362
|
+
catch (_e) {
|
|
363
|
+
if (this.hooks.onStreamError) {
|
|
364
|
+
await this.hooks.onStreamError({
|
|
365
|
+
...baseHookArguments,
|
|
366
|
+
error: _e,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
throw _e;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
async handleStreamError(e) {
|
|
374
|
+
if (!e?.response?.data) {
|
|
375
|
+
throw e;
|
|
376
|
+
}
|
|
377
|
+
// The error is a stream, so some effort is required to parse it.
|
|
378
|
+
const stream = e.response.data;
|
|
379
|
+
const pipeline = Chain.chain([
|
|
380
|
+
stream,
|
|
381
|
+
streamJson.parser(),
|
|
382
|
+
StreamArray.streamArray(),
|
|
383
|
+
]);
|
|
384
|
+
const defaultErrorMessage = "Unknown GoogleAdsFailure";
|
|
385
|
+
let googleAdsFailure = new Error(defaultErrorMessage);
|
|
386
|
+
// Only throw the first error.
|
|
387
|
+
pipeline.once("data", (data) => {
|
|
388
|
+
if (data?.value?.error?.details?.[0]) {
|
|
389
|
+
googleAdsFailure = new errors.GoogleAdsFailure(this.decamelizeKeysIfNeeded(data.value.error.details[0]));
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
googleAdsFailure = new Error(data?.value?.error?.message ?? defaultErrorMessage, { cause: data?.value?.error ?? data?.value });
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
// Must always reject.
|
|
396
|
+
await new Promise((_, reject) => {
|
|
397
|
+
pipeline.on("end", () => reject(googleAdsFailure));
|
|
398
|
+
pipeline.on("error", (err) => reject(err));
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* @description Creates, updates, or removes resources. This method supports atomic transactions
|
|
403
|
+
* with multiple types of resources. For example, you can atomically create a campaign and a
|
|
404
|
+
* campaign budget, or perform up to thousands of mutates atomically.
|
|
405
|
+
* @hooks onMutationStart, onMutationError, onMutationEnd
|
|
406
|
+
*/
|
|
407
|
+
async mutateResources(mutations, mutateOptions = {}) {
|
|
408
|
+
const baseHookArguments = {
|
|
409
|
+
credentials: this.credentials,
|
|
410
|
+
method: "GoogleAdsService.mutate",
|
|
411
|
+
mutations,
|
|
412
|
+
isServiceCall: false,
|
|
413
|
+
};
|
|
414
|
+
if (this.hooks.onMutationStart) {
|
|
415
|
+
const mutationCancellation = { cancelled: false };
|
|
416
|
+
await this.hooks.onMutationStart({
|
|
417
|
+
...baseHookArguments,
|
|
418
|
+
cancel: (res) => {
|
|
419
|
+
mutationCancellation.cancelled = true;
|
|
420
|
+
mutationCancellation.res = res;
|
|
421
|
+
},
|
|
422
|
+
editOptions: (options) => {
|
|
423
|
+
Object.entries(options).forEach(([key, val]) => {
|
|
424
|
+
// @ts-ignore
|
|
425
|
+
mutateOptions[key] = val;
|
|
426
|
+
});
|
|
427
|
+
},
|
|
428
|
+
});
|
|
429
|
+
if (mutationCancellation.cancelled) {
|
|
430
|
+
return mutationCancellation.res;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
const { service, request } = this.buildMutationRequestAndService(mutations, mutateOptions);
|
|
434
|
+
try {
|
|
435
|
+
const response = (await service.mutate(request, {
|
|
436
|
+
otherArgs: { headers: this.callHeaders },
|
|
437
|
+
}))[0];
|
|
438
|
+
const parsedResponse = request.partial_failure
|
|
439
|
+
? this.decodePartialFailureError(response)
|
|
440
|
+
: response;
|
|
441
|
+
if (this.hooks.onMutationEnd) {
|
|
442
|
+
const mutationResolution = { resolved: false };
|
|
443
|
+
await this.hooks.onMutationEnd({
|
|
444
|
+
...baseHookArguments,
|
|
445
|
+
response: parsedResponse,
|
|
446
|
+
resolve: (res) => {
|
|
447
|
+
mutationResolution.resolved = true;
|
|
448
|
+
mutationResolution.res = res;
|
|
449
|
+
},
|
|
450
|
+
});
|
|
451
|
+
if (mutationResolution.resolved) {
|
|
452
|
+
return mutationResolution.res;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return parsedResponse;
|
|
456
|
+
}
|
|
457
|
+
catch (mutateError) {
|
|
458
|
+
const googleAdsError = this.getGoogleAdsError(mutateError);
|
|
459
|
+
if (this.hooks.onMutationError) {
|
|
460
|
+
await this.hooks.onMutationError({
|
|
461
|
+
...baseHookArguments,
|
|
462
|
+
error: googleAdsError,
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
throw googleAdsError;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
get googleAdsFields() {
|
|
469
|
+
return {
|
|
470
|
+
searchGoogleAdsFields: async (request) => {
|
|
471
|
+
const service = await this.loadService("GoogleAdsFieldServiceClient");
|
|
472
|
+
return service.searchGoogleAdsFields(request, {
|
|
473
|
+
// @ts-expect-error This method does support call headers
|
|
474
|
+
otherArgs: { headers: this.callHeaders },
|
|
475
|
+
});
|
|
476
|
+
},
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
prepareGoogleAdsServicePostRequestArgs(functionName, accessToken, extra) {
|
|
480
|
+
return {
|
|
481
|
+
method: "POST",
|
|
482
|
+
url: `https://googleads.googleapis.com/${googleAdsVersion}/customers/${this.customerOptions.customer_id}/googleAds:${functionName}`,
|
|
483
|
+
headers: {
|
|
484
|
+
Authorization: `Bearer ${accessToken}`,
|
|
485
|
+
...this.callHeaders,
|
|
486
|
+
},
|
|
487
|
+
...extra,
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
decamelizeKeysIfNeeded(input) {
|
|
491
|
+
if (this.clientOptions.disable_parsing) {
|
|
492
|
+
return input;
|
|
493
|
+
}
|
|
494
|
+
return decamelizeKeys(input);
|
|
495
|
+
}
|
|
496
|
+
gaqlQueryStringIncludesLimit(gaqlQuery) {
|
|
497
|
+
return gaqlQuery.toLowerCase().includes("limit ");
|
|
498
|
+
}
|
|
499
|
+
generateTooManyRowsError() {
|
|
500
|
+
return new Error(`Exceeded the maximum number of rows set by "max_reporting_rows" (${this.clientOptions.max_reporting_rows}).`);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { errors, services } from "./protos/index.js";
|
|
2
|
+
import { CustomerCredentials, MutateOperation, ReportOptions, RequestOptions, MutateOptions } from "./types.js";
|
|
3
|
+
export type BaseRequestHookArgs = {
|
|
4
|
+
credentials: CustomerCredentials;
|
|
5
|
+
query: string;
|
|
6
|
+
reportOptions?: ReportOptions;
|
|
7
|
+
};
|
|
8
|
+
export type BaseMutationHookArgs = {
|
|
9
|
+
credentials: CustomerCredentials;
|
|
10
|
+
method: `${keyof typeof services}.${string}`;
|
|
11
|
+
} & (// Mutation was executed with customer.mutateResources
|
|
12
|
+
{
|
|
13
|
+
mutations: MutateOperation<any>[];
|
|
14
|
+
isServiceCall: false;
|
|
15
|
+
} | {
|
|
16
|
+
mutation: any;
|
|
17
|
+
isServiceCall: true;
|
|
18
|
+
});
|
|
19
|
+
export type BaseServiceHookArgs = {
|
|
20
|
+
credentials: CustomerCredentials;
|
|
21
|
+
method: `${keyof typeof services}.${string}`;
|
|
22
|
+
requestOptions: any;
|
|
23
|
+
};
|
|
24
|
+
type StartHookArgs<T = RequestOptions | MutateOptions | any, A = void> = {
|
|
25
|
+
cancel: A extends void ? () => void : (args?: A) => void;
|
|
26
|
+
editOptions: (options: Partial<T>) => void;
|
|
27
|
+
};
|
|
28
|
+
type ErrorHookArgs = {
|
|
29
|
+
error: errors.GoogleAdsFailure | Error;
|
|
30
|
+
};
|
|
31
|
+
type EndHookArgs<T = services.IGoogleAdsRow[] | services.MutateGoogleAdsResponse> = {
|
|
32
|
+
response?: T;
|
|
33
|
+
resolve: (args: any) => void;
|
|
34
|
+
};
|
|
35
|
+
type HookArgs = StartHookArgs | ErrorHookArgs | EndHookArgs;
|
|
36
|
+
type RequestHook<H extends HookArgs> = (a: BaseRequestHookArgs & H) => void;
|
|
37
|
+
type MutationHook<H extends HookArgs> = (a: BaseMutationHookArgs & H) => void;
|
|
38
|
+
type ServiceHook<H extends HookArgs> = (a: BaseServiceHookArgs & H) => void;
|
|
39
|
+
export type OnQueryStart = RequestHook<StartHookArgs<RequestOptions, any>>;
|
|
40
|
+
export type OnQueryError = RequestHook<ErrorHookArgs>;
|
|
41
|
+
export type OnQueryEnd = RequestHook<EndHookArgs<services.IGoogleAdsRow[]>>;
|
|
42
|
+
export type OnStreamStart = RequestHook<StartHookArgs<RequestOptions, void>>;
|
|
43
|
+
export type OnStreamError = RequestHook<ErrorHookArgs>;
|
|
44
|
+
export type OnMutationStart = MutationHook<StartHookArgs<MutateOptions, any>>;
|
|
45
|
+
export type OnMutationError = MutationHook<ErrorHookArgs>;
|
|
46
|
+
export type OnMutationEnd = MutationHook<EndHookArgs<services.MutateGoogleAdsResponse>>;
|
|
47
|
+
export type OnServiceStart = ServiceHook<StartHookArgs<any, any>>;
|
|
48
|
+
export type OnServiceError = ServiceHook<ErrorHookArgs>;
|
|
49
|
+
export type OnServiceEnd = ServiceHook<EndHookArgs<any>>;
|
|
50
|
+
export interface Hooks {
|
|
51
|
+
/**
|
|
52
|
+
* @description Hook called before execution of a query in the `query` and `report` methods
|
|
53
|
+
* @params `{ credentials, query, reportOptions, cancel, editOptions }`
|
|
54
|
+
* @param credentials customer id, login customer id, linked customer id
|
|
55
|
+
* @param query gaql
|
|
56
|
+
* @param reportOptions
|
|
57
|
+
* @param cancel utility function for cancelling the query. if an argument is provided then the query will return this argument
|
|
58
|
+
* @param editOptions utility function for editing the request options. any request option keys that are passed will be changed
|
|
59
|
+
*/
|
|
60
|
+
onQueryStart?: OnQueryStart;
|
|
61
|
+
/**
|
|
62
|
+
* @description Hook called upon a query throwing an error in the `query` and `report` methods
|
|
63
|
+
* @params `{ credentials, query, reportOptions, error }`
|
|
64
|
+
* @param credentials customer id, login customer id, linked customer id
|
|
65
|
+
* @param query gaql
|
|
66
|
+
* @param reportOptions
|
|
67
|
+
* @param error google ads error
|
|
68
|
+
*/
|
|
69
|
+
onQueryError?: OnQueryError;
|
|
70
|
+
/**
|
|
71
|
+
* @description Hook called after successful execution of a query in the `query` and `report` methods
|
|
72
|
+
* @params `{ credentials, query, reportOptions, response, resolve }`
|
|
73
|
+
* @param credentials customer id, login customer id, linked customer id
|
|
74
|
+
* @param query gaql
|
|
75
|
+
* @param reportOptions
|
|
76
|
+
* @param response results of the query, not available on reportStream
|
|
77
|
+
* @param resolve utility function for returning an alternative value from the query
|
|
78
|
+
*/
|
|
79
|
+
onQueryEnd?: OnQueryEnd;
|
|
80
|
+
/**
|
|
81
|
+
* @description Hook called before execution of a stream in the `reportStream` and `reportStreamRaw` methods
|
|
82
|
+
* @params `{ credentials, query, reportOptions, cancel, editOptions }`
|
|
83
|
+
* @param credentials customer id, login customer id, linked customer id
|
|
84
|
+
* @param query gaql
|
|
85
|
+
* @param reportOptions
|
|
86
|
+
* @param cancel utility function for cancelling the stream
|
|
87
|
+
* @param editOptions utility function for editing the request options. any request option keys that are passed will be changed
|
|
88
|
+
*/
|
|
89
|
+
onStreamStart?: OnStreamStart;
|
|
90
|
+
/**
|
|
91
|
+
* @description Hook called upon a stream throwing an error in the `reportStream` method. Will not be called for an error in `reportStreamRaw`
|
|
92
|
+
* @params `{ credentials, query, reportOptions, error }`
|
|
93
|
+
* @param credentials customer id, login customer id, linked customer id
|
|
94
|
+
* @param query gaql
|
|
95
|
+
* @param reportOptions
|
|
96
|
+
* @param error google ads error
|
|
97
|
+
*/
|
|
98
|
+
onStreamError?: OnStreamError;
|
|
99
|
+
/**
|
|
100
|
+
* @description Hook called before execution of a mutation
|
|
101
|
+
* @params `{ credentials, mutations, cancel, editOptions }`
|
|
102
|
+
* @param credentials customer id, login customer id, linked customer id
|
|
103
|
+
* @param mutations
|
|
104
|
+
* @param cancel utility function for cancelling the mutation. if an argument is provided then the query/report will return this argument
|
|
105
|
+
* @param editOptions utility function for editing the mutate options. any mutate option keys that are passed will be changed
|
|
106
|
+
*/
|
|
107
|
+
onMutationStart?: OnMutationStart;
|
|
108
|
+
/**
|
|
109
|
+
* @description Hook called upon a mutation throwing an error
|
|
110
|
+
* @params `{ credentials, mutations, error }`
|
|
111
|
+
* @param credentials customer id, login customer id, linked customer id
|
|
112
|
+
* @param mutations
|
|
113
|
+
* @param error google ads error
|
|
114
|
+
*/
|
|
115
|
+
onMutationError?: OnMutationError;
|
|
116
|
+
/**
|
|
117
|
+
* @description Hook called after successful execution of a mutation
|
|
118
|
+
* @params `{ credentials, mutations, response, resolve }`
|
|
119
|
+
* @param credentials customer id, login customer id, linked customer id
|
|
120
|
+
* @param mutations
|
|
121
|
+
* @param response results of the mutation
|
|
122
|
+
* @param resolve utility function for returning an alternative value from the mutation
|
|
123
|
+
*/
|
|
124
|
+
onMutationEnd?: OnMutationEnd;
|
|
125
|
+
/**
|
|
126
|
+
* @description Hook called before execution of a service
|
|
127
|
+
* @params `{ credentials, method, requestOptions }`
|
|
128
|
+
* @param credentials customer id, login customer id, linked customer id
|
|
129
|
+
* @param method
|
|
130
|
+
* @param cancel utility function for cancelling the service. if an argument is provided then the service will return this argument
|
|
131
|
+
* @param editOptions utility function for editing the service options. any service option keys that are passed will be changed
|
|
132
|
+
*/
|
|
133
|
+
onServiceStart?: OnServiceStart;
|
|
134
|
+
/**
|
|
135
|
+
* @description Hook called upon a service throwing an error
|
|
136
|
+
* @params `{ credentials, method, error }`
|
|
137
|
+
* @param credentials customer id, login customer id, linked customer id
|
|
138
|
+
* @param method
|
|
139
|
+
* @param error google ads error
|
|
140
|
+
*/
|
|
141
|
+
onServiceError?: OnServiceError;
|
|
142
|
+
/**
|
|
143
|
+
* @description Hook called after successful execution of a service
|
|
144
|
+
* @params `{ credentials, method, response, resolve }`
|
|
145
|
+
* @param credentials customer id, login customer id, linked customer id
|
|
146
|
+
* @param method
|
|
147
|
+
* @param response results of the service
|
|
148
|
+
* @param resolve utility function for returning an alternative value from the service
|
|
149
|
+
*/
|
|
150
|
+
onServiceEnd?: OnServiceEnd;
|
|
151
|
+
}
|
|
152
|
+
export type HookedCancellation = {
|
|
153
|
+
cancelled: boolean;
|
|
154
|
+
res?: any;
|
|
155
|
+
};
|
|
156
|
+
export type HookedResolution = {
|
|
157
|
+
resolved: boolean;
|
|
158
|
+
res?: any;
|
|
159
|
+
};
|
|
160
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { Client as GoogleAdsApi, ClientOptions } from "./client.js";
|
|
2
|
+
export { common, enums, fields, errors, resources, services, longrunning, protobuf, } from "./protos/index.js";
|
|
3
|
+
export { fromMicros, toMicros } from "./utils.js";
|
|
4
|
+
export { googleAdsVersion } from "./version.js";
|
|
5
|
+
export { parse } from "./parser.js";
|
|
6
|
+
export * as ResourceNames from "./protos/autogen/resourceNames.js";
|
|
7
|
+
export { CustomerOptions, ReportOptions, MutateOperation, Constraint, Constraints, } from "./types.js";
|
|
8
|
+
export { Hooks, OnQueryStart, OnQueryError, OnQueryEnd, OnStreamStart, OnStreamError, OnMutationStart, OnMutationError, OnMutationEnd, OnServiceStart, OnServiceError, OnServiceEnd, } from "./hooks.js";
|
|
9
|
+
export { Customer } from "./customer.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Core library client
|
|
2
|
+
export { Client as GoogleAdsApi } from "./client.js";
|
|
3
|
+
// Compiled proto types
|
|
4
|
+
export { common, enums, fields, errors, resources, services, longrunning, protobuf, } from "./protos/index.js";
|
|
5
|
+
// Util functions
|
|
6
|
+
export { fromMicros, toMicros } from "./utils.js";
|
|
7
|
+
export { googleAdsVersion } from "./version.js";
|
|
8
|
+
export { parse } from "./parser.js";
|
|
9
|
+
export * as ResourceNames from "./protos/autogen/resourceNames.js";
|
|
10
|
+
export { Customer } from "./customer.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"module"}
|