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.
Files changed (76) hide show
  1. package/README.md +9 -1
  2. package/build/{src → cjs}/client.d.ts +6 -4
  3. package/build/{src → cjs}/client.js +4 -4
  4. package/build/{src → cjs}/customer.d.ts +5 -5
  5. package/build/{src → cjs}/customer.js +24 -20
  6. package/build/{src → cjs}/hooks.d.ts +2 -2
  7. package/build/cjs/index.d.ts +9 -0
  8. package/build/{src → cjs}/index.js +22 -20
  9. package/build/{src → cjs}/parser.d.ts +2 -2
  10. package/build/{src → cjs}/parser.js +5 -5
  11. package/build/{src → cjs}/parserRest.js +7 -7
  12. package/build/{src → cjs}/protos/autogen/enums.d.ts +1679 -1180
  13. package/build/{src → cjs}/protos/autogen/enums.js +1696 -1177
  14. package/build/cjs/protos/autogen/fields.d.ts +1191 -0
  15. package/build/cjs/protos/autogen/fields.js +205 -0
  16. package/build/cjs/protos/autogen/resourceNames.d.ts +1668 -0
  17. package/build/cjs/protos/autogen/resourceNames.js +1669 -0
  18. package/build/{src → cjs}/protos/autogen/serviceFactory.d.ts +206 -215
  19. package/build/{src → cjs}/protos/autogen/serviceFactory.js +483 -554
  20. package/build/cjs/protos/index.d.ts +14 -0
  21. package/build/{src → cjs}/protos/index.js +12 -12
  22. package/build/{src → cjs}/query.d.ts +2 -2
  23. package/build/{src → cjs}/query.js +5 -5
  24. package/build/{src → cjs}/service.d.ts +5 -5
  25. package/build/{src → cjs}/service.js +64 -22
  26. package/build/{src → cjs}/types.d.ts +1 -1
  27. package/build/{src → cjs}/utils.d.ts +1 -1
  28. package/build/{src → cjs}/utils.js +2 -2
  29. package/build/cjs/version.d.ts +1 -0
  30. package/build/{src → cjs}/version.js +1 -1
  31. package/build/esm/client.d.ts +19 -0
  32. package/build/esm/client.js +40 -0
  33. package/build/esm/customer.d.ts +71 -0
  34. package/build/esm/customer.js +502 -0
  35. package/build/esm/hooks.d.ts +160 -0
  36. package/build/esm/hooks.js +1 -0
  37. package/build/esm/index.d.ts +9 -0
  38. package/build/esm/index.js +10 -0
  39. package/build/esm/package.json +1 -0
  40. package/build/esm/parser.d.ts +21 -0
  41. package/build/esm/parser.js +113 -0
  42. package/build/esm/parserRest.d.ts +1 -0
  43. package/build/esm/parserRest.js +112 -0
  44. package/build/esm/protos/autogen/enums.d.ts +6002 -0
  45. package/build/esm/protos/autogen/enums.js +6401 -0
  46. package/build/esm/protos/autogen/fields.d.ts +1191 -0
  47. package/build/esm/protos/autogen/fields.js +202 -0
  48. package/build/esm/protos/autogen/resourceNames.d.ts +1668 -0
  49. package/build/esm/protos/autogen/resourceNames.js +1475 -0
  50. package/build/esm/protos/autogen/serviceFactory.d.ts +1825 -0
  51. package/build/esm/protos/autogen/serviceFactory.js +18795 -0
  52. package/build/esm/protos/index.d.ts +14 -0
  53. package/build/esm/protos/index.js +19 -0
  54. package/build/esm/query.d.ts +62 -0
  55. package/build/esm/query.js +304 -0
  56. package/build/esm/service.d.ts +47 -0
  57. package/build/esm/service.js +222 -0
  58. package/build/esm/types.d.ts +75 -0
  59. package/build/esm/types.js +14 -0
  60. package/build/esm/utils.d.ts +49 -0
  61. package/build/esm/utils.js +108 -0
  62. package/build/esm/version.d.ts +1 -0
  63. package/build/esm/version.js +1 -0
  64. package/package.json +50 -11
  65. package/build/src/index.d.ts +0 -8
  66. package/build/src/protos/autogen/fields.d.ts +0 -1122
  67. package/build/src/protos/autogen/fields.js +0 -644
  68. package/build/src/protos/autogen/resourceNames.d.ts +0 -1609
  69. package/build/src/protos/autogen/resourceNames.js +0 -1982
  70. package/build/src/protos/index.d.ts +0 -14
  71. package/build/src/testUtils.d.ts +0 -107
  72. package/build/src/testUtils.js +0 -333
  73. package/build/src/version.d.ts +0 -1
  74. /package/build/{src → cjs}/hooks.js +0 -0
  75. /package/build/{src → cjs}/parserRest.d.ts +0 -0
  76. /package/build/{src → cjs}/types.js +0 -0
@@ -0,0 +1,14 @@
1
+ import allProtos, { protos } from "google-ads-node";
2
+ export declare const VERSION: "v25";
3
+ export import common = protos.google.ads.googleads.v25.common;
4
+ export import errors = protos.google.ads.googleads.v25.errors;
5
+ export import resources = protos.google.ads.googleads.v25.resources;
6
+ export import services = protos.google.ads.googleads.v25.services;
7
+ export import internalEnums = protos.google.ads.googleads.v25.enums;
8
+ export { enums } from "./autogen/enums.js";
9
+ export { fields, fieldDataTypes } from "./autogen/fields.js";
10
+ export { GoogleAdsServiceClient } from "google-ads-node";
11
+ export type AllServices = Omit<typeof allProtos, typeof VERSION>;
12
+ export type ServiceName = keyof Omit<typeof allProtos, typeof VERSION>;
13
+ export import protobuf = protos.google.protobuf;
14
+ export import longrunning = protos.google.longrunning;
@@ -0,0 +1,19 @@
1
+ import { protos } from "google-ads-node";
2
+ import { googleAdsVersion } from "../version.js";
3
+ // "as vN" is required to avoid type issues later on
4
+ export const VERSION = googleAdsVersion;
5
+ // -- Expose the googleads generated types --
6
+ // The namespace path is shortened for ease of use
7
+ export var common = protos.google.ads.googleads.v25.common;
8
+ export var errors = protos.google.ads.googleads.v25.errors;
9
+ export var resources = protos.google.ads.googleads.v25.resources;
10
+ export var services = protos.google.ads.googleads.v25.services;
11
+ // We compile enums ourselves, the Google ones should be used internally only
12
+ export var internalEnums = protos.google.ads.googleads.v25.enums;
13
+ export { enums } from "./autogen/enums.js";
14
+ export { fields, fieldDataTypes } from "./autogen/fields.js";
15
+ // Common service used for report/query methods
16
+ export { GoogleAdsServiceClient } from "google-ads-node";
17
+ // -- Expose some google protobuf helpers --
18
+ export var protobuf = protos.google.protobuf;
19
+ export var longrunning = protos.google.longrunning;
@@ -0,0 +1,62 @@
1
+ import { fields } from "./protos/index.js";
2
+ import { ConstraintKey, ConstraintOperation, ConstraintValue, ReportOptions, RequestOptions, RequestOptionsWithTotalResults, SortOrder } from "./types.js";
3
+ declare enum QueryKeywords {
4
+ SELECT = "SELECT",
5
+ FROM = "FROM",
6
+ WHERE = "WHERE",
7
+ ORDER_BY = "ORDER BY",
8
+ LIMIT = "LIMIT",
9
+ PARAMETERS = "PARAMETERS",
10
+ AND = "AND",
11
+ OR = "OR"
12
+ }
13
+ type ParsedConstraintValue = string | number | boolean;
14
+ type ConstraintString = `${string} ${ConstraintOperation} ${ParsedConstraintValue}`;
15
+ type SelectClause = `${QueryKeywords.SELECT} ${string}`;
16
+ type FromClause = ` ${QueryKeywords.FROM} ${fields.Resource}`;
17
+ type WhereClause = ` ${QueryKeywords.WHERE} ${string}` | ``;
18
+ export type OrderClause = ` ${QueryKeywords.ORDER_BY} ${string} ${SortOrder}` | ` ${QueryKeywords.ORDER_BY} ${string}` | ``;
19
+ type LimitClause = ` ${QueryKeywords.LIMIT} ${number}` | ``;
20
+ type ParametersClause = ` ${QueryKeywords.PARAMETERS} ${string}` | ``;
21
+ type Query = `${SelectClause}${FromClause}${WhereClause}${OrderClause}${LimitClause}${ParametersClause}`;
22
+ export declare const QueryError: {
23
+ INVALID_CONSTRAINTS_FORMAT: string;
24
+ INVALID_CONSTRAINT_KEY: string;
25
+ INVALID_CONSTRAINT_VALUE: (key: ConstraintKey, val: ConstraintValue) => string;
26
+ INVALID_CONSTRAINT_OBJECT_FORMAT: string;
27
+ INVALID_DATE_CONSTANT_TYPE: (dateConstant: ReportOptions["date_constant"]) => string;
28
+ INVALID_FROM_DATE_TYPE: (fromDate: ReportOptions["from_date"]) => string;
29
+ INVALID_TO_DATE_TYPE: (toDate: ReportOptions["to_date"]) => string;
30
+ INVALID_LIMIT: string;
31
+ INVALID_PARAMETERS: string;
32
+ INVALID_ORDER: string;
33
+ INVALID_ORDERLY: string;
34
+ INVALID_ORDERBY: string;
35
+ INVALID_SORT_ORDER: string;
36
+ MISSING_FIELDS: string;
37
+ MISSING_FROM_DATE: string;
38
+ UNDEFINED_ENTITY: string;
39
+ };
40
+ export declare function buildSelectClause(attributes: ReportOptions["attributes"], metrics: ReportOptions["metrics"], segments: ReportOptions["segments"]): SelectClause;
41
+ export declare function buildFromClause(entity: ReportOptions["entity"]): FromClause;
42
+ export declare function validateConstraintKeyAndValue(key: ConstraintKey, op: ConstraintOperation, val: ConstraintValue): {
43
+ op: ConstraintOperation;
44
+ val: ParsedConstraintValue;
45
+ };
46
+ export declare function convertNumericEnumToString(key: ConstraintKey, val: ParsedConstraintValue): ParsedConstraintValue;
47
+ export declare function extractConstraintConditions(constraints: Readonly<ReportOptions["constraints"]>): ConstraintString[];
48
+ export declare function extractDateConstantConditions(dateConstant: ReportOptions["date_constant"]): ConstraintString[];
49
+ export declare function extractDateConditions(fromDate: ReportOptions["from_date"], toDate: ReportOptions["to_date"]): ConstraintString[];
50
+ export declare function buildWhereClause(constraints: Readonly<ReportOptions["constraints"]>, dateConstant: Readonly<ReportOptions["date_constant"]>, fromDate: Readonly<ReportOptions["from_date"]>, toDate: Readonly<ReportOptions["to_date"]>): WhereClause;
51
+ export declare function buildLimitClause(limit: ReportOptions["limit"]): LimitClause;
52
+ export declare function buildParametersClause(parameters: Readonly<ReportOptions["parameters"]>): ParametersClause;
53
+ export declare function completeOrderly(orderly: string, entity: ReportOptions["entity"]): string;
54
+ export declare function buildOrderClauseOld(orderBy: Readonly<ReportOptions["order_by"]>, sortOrder: Readonly<ReportOptions["sort_order"]>, entity: Readonly<ReportOptions["entity"]>): OrderClause;
55
+ export declare function buildOrderClauseNew(order: Readonly<Required<ReportOptions["order"]>>, entity: Readonly<ReportOptions["entity"]>): OrderClause;
56
+ export declare function buildOrderClause(order: Readonly<ReportOptions["order"]>, orderBy: Readonly<ReportOptions["order_by"]>, sortOrder: Readonly<ReportOptions["sort_order"]>, entity: Readonly<ReportOptions["entity"]>): OrderClause;
57
+ export declare function buildRequestOptions(reportOptions: Readonly<ReportOptions>): RequestOptions;
58
+ export declare function buildQuery(reportOptions: Readonly<ReportOptions>): {
59
+ gaqlQuery: Query;
60
+ requestOptions: RequestOptionsWithTotalResults;
61
+ };
62
+ export {};
@@ -0,0 +1,304 @@
1
+ import { enums, fields } from "./protos/index.js";
2
+ import { dateConstants, } from "./types.js";
3
+ var QueryKeywords;
4
+ (function (QueryKeywords) {
5
+ QueryKeywords["SELECT"] = "SELECT";
6
+ QueryKeywords["FROM"] = "FROM";
7
+ QueryKeywords["WHERE"] = "WHERE";
8
+ QueryKeywords["ORDER_BY"] = "ORDER BY";
9
+ QueryKeywords["LIMIT"] = "LIMIT";
10
+ QueryKeywords["PARAMETERS"] = "PARAMETERS";
11
+ QueryKeywords["AND"] = "AND";
12
+ QueryKeywords["OR"] = "OR";
13
+ })(QueryKeywords || (QueryKeywords = {}));
14
+ export const QueryError = {
15
+ INVALID_CONSTRAINTS_FORMAT: "Constraints must be an array of objects or a singular object.",
16
+ INVALID_CONSTRAINT_KEY: "A constraint key must have a string value.",
17
+ INVALID_CONSTRAINT_VALUE: (key, val) =>
18
+ // @ts-ignore
19
+ `The value of the constraint ${key} must be a string, number, boolean, or an array of these types. Here, typeof ${key} is ${typeof val}.`,
20
+ INVALID_CONSTRAINT_OBJECT_FORMAT: "Must specify { key, op, val } or { key: value } when using object-style constraints.",
21
+ INVALID_DATE_CONSTANT_TYPE: (dateConstant) => `Date constant must be a string. Here, typeof date constant is ${typeof dateConstant}`,
22
+ INVALID_FROM_DATE_TYPE: (fromDate) => `From date must be a string. Here, typeof from date is ${typeof fromDate}`,
23
+ INVALID_TO_DATE_TYPE: (toDate) => `To date must be a string. Here, typeof to date is ${typeof toDate}`,
24
+ INVALID_LIMIT: "Limit must be a positive integer.",
25
+ INVALID_PARAMETERS: "Parameters must be a string.",
26
+ INVALID_ORDER: "Order must be an array.",
27
+ INVALID_ORDERLY: "OrderBy arrays must only contain strings.",
28
+ INVALID_ORDERBY: "OrderBy must be a string or an array of strings.",
29
+ INVALID_SORT_ORDER: `Sort order must be "ASC" or "DESC".`,
30
+ MISSING_FIELDS: 'Must specify at least one field in ("attributes","metrics","segments").',
31
+ MISSING_FROM_DATE: 'Expected start date range is missing - "from_date".',
32
+ UNDEFINED_ENTITY: "The entity of the query must be defined.",
33
+ };
34
+ export function buildSelectClause(attributes, metrics, segments) {
35
+ if (!attributes?.length && !metrics?.length && !segments?.length) {
36
+ throw new Error(QueryError.MISSING_FIELDS);
37
+ }
38
+ const selections = [
39
+ ...(attributes || []),
40
+ ...(metrics || []),
41
+ ...(segments || []),
42
+ ].join(", ");
43
+ return `${QueryKeywords.SELECT} ${selections}`;
44
+ }
45
+ export function buildFromClause(entity) {
46
+ if (typeof entity === "undefined") {
47
+ throw new Error(QueryError.UNDEFINED_ENTITY);
48
+ }
49
+ return ` ${QueryKeywords.FROM} ${entity}`;
50
+ }
51
+ function formatGaqlString(val) {
52
+ const len = val.length;
53
+ const startsWithSingle = val.startsWith("'");
54
+ const endsWithSingle = val.endsWith("'");
55
+ const startsWithDouble = val.startsWith('"');
56
+ const endsWithDouble = val.endsWith('"');
57
+ // Returns the original string if it seems correctly quoted already.
58
+ if (len >= 2) {
59
+ if (startsWithSingle && endsWithSingle && !val.slice(1, -1).includes("'")) {
60
+ return val;
61
+ }
62
+ if (startsWithDouble && endsWithDouble && !val.slice(1, -1).includes('"')) {
63
+ return val;
64
+ }
65
+ }
66
+ // Always use double quotes and escape internal double quotes and backslashes
67
+ if (val.includes('"') || val.includes("\\")) {
68
+ const escapedVal = val.replace(/\\/g, "\\\\").replace(/"/g, '\\"'); // Escape backslashes first, then double quotes
69
+ return `"${escapedVal}"`;
70
+ }
71
+ else {
72
+ // No escaping needed, just wrap in double quotes
73
+ return `"${val}"`;
74
+ }
75
+ }
76
+ export function validateConstraintKeyAndValue(key, op, val) {
77
+ if (typeof val === "number" || typeof val === "boolean") {
78
+ return { op: "=", val: convertNumericEnumToString(key, val) };
79
+ }
80
+ if (typeof val === "string") {
81
+ if (dateConstants.includes(val)) {
82
+ return { op, val };
83
+ }
84
+ return { op: "=", val: formatGaqlString(val) };
85
+ }
86
+ if (Array.isArray(val)) {
87
+ const stringifiedValue = val
88
+ .map((v) => {
89
+ if (typeof v === "string") {
90
+ return formatGaqlString(v);
91
+ }
92
+ else {
93
+ return convertNumericEnumToString(key, v);
94
+ }
95
+ })
96
+ .join(`, `);
97
+ return { op: "IN", val: `(${stringifiedValue})` };
98
+ }
99
+ throw new Error(QueryError.INVALID_CONSTRAINT_VALUE(key, val));
100
+ }
101
+ export function convertNumericEnumToString(key, val) {
102
+ // @ts-expect-error key does not always match an enum field
103
+ if (fields.enumFields[key] && typeof val === "number") {
104
+ // @ts-expect-error typescript doesn't like accessing items in a namespace with a string
105
+ const enumStringValue = enums[fields.enumFields[key]][val]; // e.g. enums['CampaignStatus'][2] = "ENABLED"
106
+ if (enumStringValue) {
107
+ return `"${enumStringValue}"`;
108
+ }
109
+ }
110
+ return val;
111
+ }
112
+ export function extractConstraintConditions(constraints) {
113
+ if (typeof constraints === "undefined") {
114
+ return [];
115
+ }
116
+ else if (Array.isArray(constraints)) {
117
+ return constraints.map((con) => {
118
+ if (typeof con === "object" && !Array.isArray(con) && con !== null) {
119
+ // @ts-ignore
120
+ if (con.key && con.op && typeof con.val !== "undefined") {
121
+ const { key, op, val } = con;
122
+ if (typeof key !== "string") {
123
+ throw new Error(QueryError.INVALID_CONSTRAINT_KEY);
124
+ }
125
+ const validatedValue = validateConstraintKeyAndValue(key, op, val);
126
+ // @ts-ignore
127
+ return `${key} ${op} ${validatedValue.val}`;
128
+ }
129
+ else if (Object.keys(con).length === 1) {
130
+ const [[key, val]] = Object.entries(con);
131
+ const validatedValue = validateConstraintKeyAndValue(key, "=", val);
132
+ return `${key} ${validatedValue.op} ${validatedValue.val}`;
133
+ }
134
+ else {
135
+ throw new Error(QueryError.INVALID_CONSTRAINT_OBJECT_FORMAT);
136
+ }
137
+ }
138
+ else if (typeof con === "string") {
139
+ return con;
140
+ }
141
+ else {
142
+ throw new Error(QueryError.INVALID_CONSTRAINT_OBJECT_FORMAT);
143
+ }
144
+ });
145
+ }
146
+ else if (typeof constraints === "object" && constraints !== null) {
147
+ return Object.entries(constraints).map(([key, val]) => {
148
+ const validatedValue = validateConstraintKeyAndValue(key, "=", val);
149
+ return `${key} ${validatedValue.op} ${validatedValue.val}`;
150
+ });
151
+ }
152
+ else {
153
+ throw new Error(QueryError.INVALID_CONSTRAINTS_FORMAT);
154
+ }
155
+ }
156
+ export function extractDateConstantConditions(dateConstant) {
157
+ if (typeof dateConstant === "undefined") {
158
+ return [];
159
+ }
160
+ else if (typeof dateConstant !== "string") {
161
+ throw new Error(QueryError.INVALID_DATE_CONSTANT_TYPE(dateConstant));
162
+ }
163
+ return [`segments.date DURING ${dateConstant}`];
164
+ }
165
+ export function extractDateConditions(fromDate, toDate) {
166
+ if (typeof fromDate === "undefined" && typeof toDate === "undefined") {
167
+ return [];
168
+ }
169
+ if (typeof fromDate === "undefined" && typeof toDate !== "undefined") {
170
+ throw new Error(QueryError.MISSING_FROM_DATE);
171
+ }
172
+ if (typeof fromDate !== "undefined" && typeof toDate === "undefined") {
173
+ const d = new Date();
174
+ toDate = `${d.getFullYear()}-${("0" + (d.getMonth() + 1)).slice(-2)}-${("0" + d.getDate()).slice(-2)}`;
175
+ }
176
+ if (typeof fromDate !== "string") {
177
+ throw new Error(QueryError.INVALID_FROM_DATE_TYPE(fromDate));
178
+ }
179
+ if (typeof toDate !== "string") {
180
+ throw new Error(QueryError.INVALID_TO_DATE_TYPE(toDate));
181
+ }
182
+ return [
183
+ `segments.date >= "${fromDate}"`,
184
+ `segments.date <= "${toDate}"`,
185
+ ];
186
+ }
187
+ export function buildWhereClause(constraints, dateConstant, fromDate, toDate) {
188
+ const constraintClauses = extractConstraintConditions(constraints);
189
+ const dateConstantClauses = extractDateConstantConditions(dateConstant);
190
+ const dateClauses = extractDateConditions(fromDate, toDate);
191
+ const whereClauses = [
192
+ ...constraintClauses,
193
+ ...dateConstantClauses,
194
+ ...dateClauses,
195
+ ].join(` ${QueryKeywords.AND} `);
196
+ return whereClauses.length
197
+ ? ` ${QueryKeywords.WHERE} ${whereClauses}`
198
+ : ``;
199
+ }
200
+ export function buildLimitClause(limit) {
201
+ if (typeof limit === "undefined") {
202
+ return ``;
203
+ }
204
+ if (typeof limit !== "number" || limit < 1 || !Number.isInteger(limit)) {
205
+ throw new Error(QueryError.INVALID_LIMIT);
206
+ }
207
+ return ` ${QueryKeywords.LIMIT} ${limit}`;
208
+ }
209
+ export function buildParametersClause(parameters) {
210
+ if (typeof parameters === "undefined") {
211
+ return ``;
212
+ }
213
+ if (typeof parameters !== "string") {
214
+ throw new Error(QueryError.INVALID_PARAMETERS);
215
+ }
216
+ return ` ${QueryKeywords.PARAMETERS} ${parameters}`;
217
+ }
218
+ export function completeOrderly(orderly, entity) {
219
+ if (!orderly.length) {
220
+ throw new Error(QueryError.INVALID_ORDERLY);
221
+ }
222
+ else if (new RegExp(/^[^.\s]+(\.[^.\s]+)+$/g).test(orderly)) {
223
+ // text containing full stops
224
+ return orderly;
225
+ }
226
+ else if (new RegExp(/^[^.\s]+$/g).test(orderly)) {
227
+ // text without a full stop (e.g. resource_name)
228
+ return `${entity}.${orderly}`;
229
+ }
230
+ else {
231
+ throw new Error(QueryError.INVALID_ORDERLY);
232
+ }
233
+ }
234
+ export function buildOrderClauseOld(orderBy, sortOrder, entity) {
235
+ if (typeof orderBy === "undefined") {
236
+ return ``;
237
+ }
238
+ else if (typeof sortOrder === "undefined") {
239
+ sortOrder = "DESC";
240
+ }
241
+ else if (sortOrder !== "ASC" && sortOrder !== "DESC") {
242
+ throw new Error(QueryError.INVALID_SORT_ORDER);
243
+ }
244
+ if (Array.isArray(orderBy)) {
245
+ const orders = orderBy
246
+ .map((orderly) => {
247
+ if (typeof orderly !== "string") {
248
+ throw new Error(QueryError.INVALID_ORDERLY);
249
+ }
250
+ else {
251
+ return completeOrderly(orderly, entity);
252
+ }
253
+ })
254
+ .join(", ");
255
+ return ` ${QueryKeywords.ORDER_BY} ${orders} ${sortOrder}`;
256
+ }
257
+ else if (typeof orderBy === "string") {
258
+ return ` ${QueryKeywords.ORDER_BY} ${completeOrderly(orderBy, entity)} ${sortOrder}`;
259
+ }
260
+ else {
261
+ throw new Error(QueryError.INVALID_ORDERBY);
262
+ }
263
+ }
264
+ export function buildOrderClauseNew(order, entity) {
265
+ if (!order || !Array.isArray(order)) {
266
+ throw new Error(QueryError.INVALID_ORDER);
267
+ }
268
+ if (!order.length) {
269
+ return "";
270
+ }
271
+ const orders = order
272
+ .map((o) => {
273
+ const orderly = completeOrderly(o.field, entity);
274
+ const sortOrder = o.sort_order ? o.sort_order : "DESC";
275
+ return `${orderly} ${sortOrder}`;
276
+ })
277
+ .join(", ");
278
+ return ` ${QueryKeywords.ORDER_BY} ${orders}`;
279
+ }
280
+ export function buildOrderClause(order, orderBy, sortOrder, entity) {
281
+ if (order) {
282
+ return buildOrderClauseNew(order, entity);
283
+ }
284
+ else {
285
+ return buildOrderClauseOld(orderBy, sortOrder, entity);
286
+ }
287
+ }
288
+ export function buildRequestOptions(reportOptions) {
289
+ const { page_size, page_token, validate_only, search_settings } = reportOptions;
290
+ return { page_size, page_token, validate_only, search_settings };
291
+ }
292
+ export function buildQuery(reportOptions) {
293
+ const SELECT = buildSelectClause(reportOptions.attributes, reportOptions.metrics, reportOptions.segments);
294
+ const FROM = buildFromClause(reportOptions.entity);
295
+ const WHERE = buildWhereClause(reportOptions.constraints, reportOptions.date_constant, reportOptions.from_date, reportOptions.to_date);
296
+ const ORDER = buildOrderClause(reportOptions.order, reportOptions.order_by, reportOptions.sort_order, reportOptions.entity);
297
+ const LIMIT = buildLimitClause(reportOptions.limit);
298
+ const PARAMETERS = buildParametersClause(reportOptions.parameters);
299
+ const requestOptions = buildRequestOptions(reportOptions);
300
+ return {
301
+ gaqlQuery: `${SELECT}${FROM}${WHERE}${ORDER}${LIMIT}${PARAMETERS}`,
302
+ requestOptions,
303
+ };
304
+ }
@@ -0,0 +1,47 @@
1
+ import { ClientOptions } from "./client.js";
2
+ import { AllServices, errors, GoogleAdsServiceClient, ServiceName, services } from "./protos/index.js";
3
+ import { CustomerOptions, CustomerCredentials, RequestOptions, MutateOperation, MutateOptions } from "./types.js";
4
+ import { Hooks } from "./hooks.js";
5
+ import TTLCache from "@isaacs/ttlcache";
6
+ export declare const FAILURE_KEY = "google.ads.googleads.v25.errors.googleadsfailure-bin";
7
+ export interface CallHeaders {
8
+ "developer-token": string;
9
+ "login-customer-id"?: string;
10
+ "linked-customer-id"?: string;
11
+ }
12
+ export declare function disposeService(service: {
13
+ close: () => Promise<void>;
14
+ }): void;
15
+ export declare const serviceCache: TTLCache<unknown, {
16
+ close: () => Promise<void>;
17
+ }>;
18
+ export declare class Service {
19
+ protected readonly clientOptions: ClientOptions;
20
+ protected readonly customerOptions: CustomerOptions;
21
+ protected readonly hooks: Hooks;
22
+ constructor(clientOptions: ClientOptions, customerOptions: CustomerOptions, hooks?: Hooks);
23
+ get credentials(): CustomerCredentials;
24
+ protected get callHeaders(): CallHeaders;
25
+ private getCredentials;
26
+ getAccessToken(): Promise<string>;
27
+ protected loadService<T = AllServices>(service: ServiceName, options?: {
28
+ skipCache?: boolean;
29
+ }): T;
30
+ protected getGoogleAdsError(error: Error): errors.GoogleAdsFailure | Error;
31
+ private decodeGoogleAdsFailureBuffer;
32
+ protected decodePartialFailureError<T>(response: T): T;
33
+ protected buildSearchRequestAndService(gaql: string, options?: RequestOptions): {
34
+ service: GoogleAdsServiceClient;
35
+ request: services.SearchGoogleAdsRequest;
36
+ };
37
+ protected buildSearchStreamRequestAndService(gaql: string, options?: RequestOptions): {
38
+ service: GoogleAdsServiceClient;
39
+ request: services.SearchGoogleAdsStreamRequest;
40
+ };
41
+ protected buildMutationRequestAndService<T>(mutations: MutateOperation<T>[], options?: MutateOptions): {
42
+ service: GoogleAdsServiceClient;
43
+ request: services.MutateGoogleAdsRequest;
44
+ };
45
+ protected buildOperations<Op, Ent>(type: "create" | "update" | "remove", entities: Ent[], message?: Ent): Op[];
46
+ protected buildRequest<Op, Req, Options>(operations: Op[], options?: Options): Req;
47
+ }
@@ -0,0 +1,222 @@
1
+ import { grpc } from "google-gax";
2
+ import * as googleAdsNode from "google-ads-node";
3
+ import { UserRefreshClient, OAuth2Client } from "google-auth-library";
4
+ import { errors, services, } from "./protos/index.js";
5
+ import { getFieldMask, toSnakeCase } from "./utils.js";
6
+ import { googleAdsVersion } from "./version.js";
7
+ import TTLCache from "@isaacs/ttlcache";
8
+ // Make sure to update this version number when upgrading
9
+ export const FAILURE_KEY = `google.ads.googleads.${googleAdsVersion}.errors.googleadsfailure-bin`;
10
+ // Close connections when services are removed from the cache
11
+ export function disposeService(service) {
12
+ Promise.resolve()
13
+ .then(() => service.close())
14
+ .catch(() => null);
15
+ }
16
+ // A global service cache to avoid re-initialising services
17
+ export const serviceCache = new TTLCache({
18
+ max: 1_000,
19
+ ttl: 10 * 60 * 1000, // 10 minutes
20
+ checkAgeOnGet: true,
21
+ dispose: disposeService,
22
+ });
23
+ // A global access token cache used by REST calls. Issued tokens expire after 1 hour, so we cache them for 50 minutes.
24
+ const accessTokenCache = new TTLCache({
25
+ max: 100_000,
26
+ ttl: 50 * 60 * 1000, // 50 minutes
27
+ });
28
+ export class Service {
29
+ clientOptions;
30
+ customerOptions;
31
+ hooks;
32
+ constructor(clientOptions, customerOptions, hooks) {
33
+ this.clientOptions = clientOptions;
34
+ this.customerOptions = customerOptions;
35
+ this.hooks = hooks ?? {};
36
+ // @ts-expect-error All fields don't need to be set here
37
+ this.serviceCache = {};
38
+ }
39
+ get credentials() {
40
+ return {
41
+ customer_id: this.customerOptions.customer_id,
42
+ login_customer_id: this.customerOptions.login_customer_id,
43
+ linked_customer_id: this.customerOptions.linked_customer_id,
44
+ };
45
+ }
46
+ get callHeaders() {
47
+ const headers = {
48
+ "developer-token": this.clientOptions.developer_token,
49
+ };
50
+ if (this.customerOptions.login_customer_id) {
51
+ headers["login-customer-id"] = this.customerOptions.login_customer_id;
52
+ }
53
+ if (this.customerOptions.linked_customer_id) {
54
+ headers["linked-customer-id"] = this.customerOptions.linked_customer_id;
55
+ }
56
+ return headers;
57
+ }
58
+ // Used only by gRPC calls
59
+ getCredentials() {
60
+ const sslCreds = grpc.credentials.createSsl();
61
+ const authClient = new UserRefreshClient({
62
+ clientId: this.clientOptions.client_id,
63
+ clientSecret: this.clientOptions.client_secret,
64
+ refreshToken: this.customerOptions.refresh_token,
65
+ });
66
+ const credentials = grpc.credentials.combineChannelCredentials(sslCreds, grpc.credentials.createFromGoogleCredential({
67
+ getRequestHeaders: async (url) => Object.fromEntries(await authClient.getRequestHeaders(url)),
68
+ }));
69
+ return credentials;
70
+ }
71
+ // Used only by REST calls
72
+ async getAccessToken() {
73
+ const cachedToken = accessTokenCache.get(this.customerOptions.refresh_token);
74
+ if (cachedToken) {
75
+ return cachedToken;
76
+ }
77
+ const oAuth2Client = new OAuth2Client({
78
+ clientId: this.clientOptions.client_id,
79
+ clientSecret: this.clientOptions.client_secret,
80
+ });
81
+ oAuth2Client.setCredentials({
82
+ refresh_token: this.customerOptions.refresh_token,
83
+ });
84
+ const { token } = await oAuth2Client.getAccessToken();
85
+ if (typeof token !== "string") {
86
+ throw new Error("Failed to retrieve access token");
87
+ }
88
+ accessTokenCache.set(this.customerOptions.refresh_token, token);
89
+ return token;
90
+ }
91
+ loadService(service, options) {
92
+ const serviceCacheKey = `${service}_${this.clientOptions.client_id}_${this.customerOptions.refresh_token}`;
93
+ if (!options?.skipCache) {
94
+ const cachedService = serviceCache.get(serviceCacheKey);
95
+ if (cachedService) {
96
+ return cachedService;
97
+ }
98
+ }
99
+ const protoService = googleAdsNode[service];
100
+ if (typeof protoService === "undefined") {
101
+ throw new Error(`Service "${String(service)}" could not be found`);
102
+ }
103
+ // Initialising services can take a few ms, so we cache when possible.
104
+ const client = new protoService({
105
+ sslCreds: this.getCredentials(),
106
+ universeDomain: "googleapis.com",
107
+ ...this.clientOptions.grpc_channel_options,
108
+ });
109
+ if (!options?.skipCache) {
110
+ serviceCache.set(serviceCacheKey, client);
111
+ }
112
+ return client;
113
+ }
114
+ getGoogleAdsError(error) {
115
+ const trailer = error?.metadata?.internalRepr?.get?.(FAILURE_KEY);
116
+ if (typeof trailer?.[0] === "undefined") {
117
+ return error;
118
+ }
119
+ return this.decodeGoogleAdsFailureBuffer(trailer[0]);
120
+ }
121
+ decodeGoogleAdsFailureBuffer(buffer) {
122
+ const googleAdsFailure = errors.GoogleAdsFailure.decode(buffer);
123
+ return googleAdsFailure;
124
+ }
125
+ decodePartialFailureError(response) {
126
+ if (typeof response?.partial_failure_error === "undefined" ||
127
+ !response?.partial_failure_error) {
128
+ return response;
129
+ }
130
+ const { details } = response.partial_failure_error;
131
+ const buffer = details?.find((d) => d.type_url.includes("errors.GoogleAdsFailure"))?.value;
132
+ if (typeof buffer === "undefined") {
133
+ return response;
134
+ }
135
+ // Update the partial failure field with the decoded error details
136
+ return {
137
+ ...response,
138
+ partial_failure_error: this.decodeGoogleAdsFailureBuffer(buffer),
139
+ };
140
+ }
141
+ buildSearchRequestAndService(gaql, options) {
142
+ const service = this.loadService("GoogleAdsServiceClient");
143
+ const request = new services.SearchGoogleAdsRequest({
144
+ customer_id: this.customerOptions.customer_id,
145
+ query: gaql,
146
+ ...options,
147
+ });
148
+ return { service, request };
149
+ }
150
+ buildSearchStreamRequestAndService(gaql, options) {
151
+ const service = this.loadService("GoogleAdsServiceClient");
152
+ const request = new services.SearchGoogleAdsStreamRequest({
153
+ customer_id: this.customerOptions.customer_id,
154
+ query: gaql,
155
+ ...options,
156
+ });
157
+ return { service, request };
158
+ }
159
+ buildMutationRequestAndService(mutations, options) {
160
+ const service = this.loadService("GoogleAdsServiceClient");
161
+ const mutateOperations = mutations.map((mutation) => {
162
+ const opKey = toSnakeCase(`${mutation.entity}Operation`);
163
+ const operation = {
164
+ [mutation.operation ?? "create"]: mutation.resource,
165
+ };
166
+ if (mutation.operation === "create" &&
167
+ //@ts-ignore
168
+ mutation?.exempt_policy_violation_keys?.length) {
169
+ //@ts-ignore
170
+ operation.exempt_policy_violation_keys =
171
+ mutation.exempt_policy_violation_keys;
172
+ }
173
+ else if (mutation.operation === "update") {
174
+ // @ts-expect-error Resource operations should have updateMask defined
175
+ operation.update_mask = getFieldMask(mutation.resource);
176
+ }
177
+ const mutateOperation = new services.MutateOperation({
178
+ [opKey]: operation,
179
+ });
180
+ return mutateOperation;
181
+ });
182
+ const request = new services.MutateGoogleAdsRequest({
183
+ customer_id: this.customerOptions.customer_id,
184
+ mutate_operations: mutateOperations,
185
+ ...options,
186
+ });
187
+ return { service, request };
188
+ }
189
+ buildOperations(type, entities, message) {
190
+ const ops = entities.map((e) => {
191
+ const op = {
192
+ [type]: e,
193
+ operation: type,
194
+ };
195
+ //@ts-ignore
196
+ if (type === "create" && e?.exempt_policy_violation_keys?.length) {
197
+ // @ts-expect-error Field required for policy violation exemptions
198
+ op.exempt_policy_violation_keys = e.exempt_policy_violation_keys;
199
+ //@ts-ignore
200
+ delete e.exempt_policy_violation_keys;
201
+ }
202
+ else if (type === "update") {
203
+ // @ts-expect-error Field required for updates
204
+ op.update_mask = getFieldMask(
205
+ // @ts-expect-error Message types have a toObject method
206
+ message.toObject(e, {
207
+ defaults: false,
208
+ }));
209
+ }
210
+ return op;
211
+ });
212
+ return ops;
213
+ }
214
+ buildRequest(operations, options) {
215
+ const request = {
216
+ customer_id: this.customerOptions.customer_id,
217
+ operations,
218
+ ...options,
219
+ };
220
+ return request;
221
+ }
222
+ }