google-ads-api 24.1.0 → 25.1.0-beta.1
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 +932 -433
- package/build/{src → cjs}/protos/autogen/enums.js +949 -430
- package/build/cjs/protos/autogen/fields.d.ts +1191 -0
- package/build/cjs/protos/autogen/fields.js +681 -0
- package/build/{src → cjs}/protos/autogen/resourceNames.d.ts +74 -15
- package/build/{src → cjs}/protos/autogen/resourceNames.js +90 -19
- package/build/{src → cjs}/protos/autogen/serviceFactory.d.ts +206 -215
- package/build/{src → cjs}/protos/autogen/serviceFactory.js +321 -392
- 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/cjs/service.js +263 -0
- 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 +678 -0
- package/build/esm/protos/autogen/resourceNames.d.ts +1668 -0
- package/build/esm/protos/autogen/resourceNames.js +1859 -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/{src → esm}/service.js +42 -41
- 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 +54 -13
- 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/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,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
|
+
}
|
|
@@ -1,37 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const google_gax_1 = require("google-gax");
|
|
9
|
-
const google_auth_library_1 = require("google-auth-library");
|
|
10
|
-
const protos_1 = require("./protos");
|
|
11
|
-
const utils_1 = require("./utils");
|
|
12
|
-
const version_1 = require("./version");
|
|
13
|
-
const ttlcache_1 = __importDefault(require("@isaacs/ttlcache"));
|
|
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";
|
|
14
8
|
// Make sure to update this version number when upgrading
|
|
15
|
-
|
|
9
|
+
export const FAILURE_KEY = `google.ads.googleads.${googleAdsVersion}.errors.googleadsfailure-bin`;
|
|
16
10
|
// Close connections when services are removed from the cache
|
|
17
|
-
function disposeService(service) {
|
|
11
|
+
export function disposeService(service) {
|
|
18
12
|
Promise.resolve()
|
|
19
13
|
.then(() => service.close())
|
|
20
14
|
.catch(() => null);
|
|
21
15
|
}
|
|
22
16
|
// A global service cache to avoid re-initialising services
|
|
23
|
-
|
|
17
|
+
export const serviceCache = new TTLCache({
|
|
24
18
|
max: 1_000,
|
|
25
19
|
ttl: 10 * 60 * 1000, // 10 minutes
|
|
26
20
|
checkAgeOnGet: true,
|
|
27
21
|
dispose: disposeService,
|
|
28
22
|
});
|
|
29
23
|
// A global access token cache used by REST calls. Issued tokens expire after 1 hour, so we cache them for 50 minutes.
|
|
30
|
-
const accessTokenCache = new
|
|
24
|
+
const accessTokenCache = new TTLCache({
|
|
31
25
|
max: 100_000,
|
|
32
26
|
ttl: 50 * 60 * 1000, // 50 minutes
|
|
33
27
|
});
|
|
34
|
-
class Service {
|
|
28
|
+
export class Service {
|
|
35
29
|
clientOptions;
|
|
36
30
|
customerOptions;
|
|
37
31
|
hooks;
|
|
@@ -63,9 +57,15 @@ class Service {
|
|
|
63
57
|
}
|
|
64
58
|
// Used only by gRPC calls
|
|
65
59
|
getCredentials() {
|
|
66
|
-
const sslCreds =
|
|
67
|
-
const authClient = new
|
|
68
|
-
|
|
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
69
|
return credentials;
|
|
70
70
|
}
|
|
71
71
|
// Used only by REST calls
|
|
@@ -74,7 +74,10 @@ class Service {
|
|
|
74
74
|
if (cachedToken) {
|
|
75
75
|
return cachedToken;
|
|
76
76
|
}
|
|
77
|
-
const oAuth2Client = new
|
|
77
|
+
const oAuth2Client = new OAuth2Client({
|
|
78
|
+
clientId: this.clientOptions.client_id,
|
|
79
|
+
clientSecret: this.clientOptions.client_secret,
|
|
80
|
+
});
|
|
78
81
|
oAuth2Client.setCredentials({
|
|
79
82
|
refresh_token: this.customerOptions.refresh_token,
|
|
80
83
|
});
|
|
@@ -86,38 +89,37 @@ class Service {
|
|
|
86
89
|
return token;
|
|
87
90
|
}
|
|
88
91
|
loadService(service, options) {
|
|
89
|
-
const serviceCacheKey = `${service}_${this.clientOptions.client_id}_${this.customerOptions.refresh_token}`;
|
|
92
|
+
const serviceCacheKey = `${service}_${this.clientOptions.client_id}_${this.customerOptions.refresh_token}_${JSON.stringify(this.clientOptions.grpc_channel_options ?? null)}`;
|
|
90
93
|
if (!options?.skipCache) {
|
|
91
|
-
const cachedService =
|
|
94
|
+
const cachedService = serviceCache.get(serviceCacheKey);
|
|
92
95
|
if (cachedService) {
|
|
93
96
|
return cachedService;
|
|
94
97
|
}
|
|
95
98
|
}
|
|
96
|
-
|
|
97
|
-
const { [service]: protoService } = require("google-ads-node");
|
|
99
|
+
const protoService = googleAdsNode[service];
|
|
98
100
|
if (typeof protoService === "undefined") {
|
|
99
101
|
throw new Error(`Service "${String(service)}" could not be found`);
|
|
100
102
|
}
|
|
101
103
|
// Initialising services can take a few ms, so we cache when possible.
|
|
102
104
|
const client = new protoService({
|
|
105
|
+
...this.clientOptions.grpc_channel_options,
|
|
103
106
|
sslCreds: this.getCredentials(),
|
|
107
|
+
universeDomain: "googleapis.com",
|
|
104
108
|
});
|
|
105
109
|
if (!options?.skipCache) {
|
|
106
|
-
|
|
110
|
+
serviceCache.set(serviceCacheKey, client);
|
|
107
111
|
}
|
|
108
112
|
return client;
|
|
109
113
|
}
|
|
110
114
|
getGoogleAdsError(error) {
|
|
111
|
-
|
|
112
|
-
if (typeof
|
|
115
|
+
const trailer = error?.metadata?.internalRepr?.get?.(FAILURE_KEY);
|
|
116
|
+
if (typeof trailer?.[0] === "undefined") {
|
|
113
117
|
return error;
|
|
114
118
|
}
|
|
115
|
-
|
|
116
|
-
const [buffer] = error.metadata.internalRepr.get(exports.FAILURE_KEY);
|
|
117
|
-
return this.decodeGoogleAdsFailureBuffer(buffer);
|
|
119
|
+
return this.decodeGoogleAdsFailureBuffer(trailer[0]);
|
|
118
120
|
}
|
|
119
121
|
decodeGoogleAdsFailureBuffer(buffer) {
|
|
120
|
-
const googleAdsFailure =
|
|
122
|
+
const googleAdsFailure = errors.GoogleAdsFailure.decode(buffer);
|
|
121
123
|
return googleAdsFailure;
|
|
122
124
|
}
|
|
123
125
|
decodePartialFailureError(response) {
|
|
@@ -138,7 +140,7 @@ class Service {
|
|
|
138
140
|
}
|
|
139
141
|
buildSearchRequestAndService(gaql, options) {
|
|
140
142
|
const service = this.loadService("GoogleAdsServiceClient");
|
|
141
|
-
const request = new
|
|
143
|
+
const request = new services.SearchGoogleAdsRequest({
|
|
142
144
|
customer_id: this.customerOptions.customer_id,
|
|
143
145
|
query: gaql,
|
|
144
146
|
...options,
|
|
@@ -147,7 +149,7 @@ class Service {
|
|
|
147
149
|
}
|
|
148
150
|
buildSearchStreamRequestAndService(gaql, options) {
|
|
149
151
|
const service = this.loadService("GoogleAdsServiceClient");
|
|
150
|
-
const request = new
|
|
152
|
+
const request = new services.SearchGoogleAdsStreamRequest({
|
|
151
153
|
customer_id: this.customerOptions.customer_id,
|
|
152
154
|
query: gaql,
|
|
153
155
|
...options,
|
|
@@ -157,7 +159,7 @@ class Service {
|
|
|
157
159
|
buildMutationRequestAndService(mutations, options) {
|
|
158
160
|
const service = this.loadService("GoogleAdsServiceClient");
|
|
159
161
|
const mutateOperations = mutations.map((mutation) => {
|
|
160
|
-
const opKey =
|
|
162
|
+
const opKey = toSnakeCase(`${mutation.entity}Operation`);
|
|
161
163
|
const operation = {
|
|
162
164
|
[mutation.operation ?? "create"]: mutation.resource,
|
|
163
165
|
};
|
|
@@ -170,14 +172,14 @@ class Service {
|
|
|
170
172
|
}
|
|
171
173
|
else if (mutation.operation === "update") {
|
|
172
174
|
// @ts-expect-error Resource operations should have updateMask defined
|
|
173
|
-
operation.update_mask =
|
|
175
|
+
operation.update_mask = getFieldMask(mutation.resource);
|
|
174
176
|
}
|
|
175
|
-
const mutateOperation = new
|
|
177
|
+
const mutateOperation = new services.MutateOperation({
|
|
176
178
|
[opKey]: operation,
|
|
177
179
|
});
|
|
178
180
|
return mutateOperation;
|
|
179
181
|
});
|
|
180
|
-
const request = new
|
|
182
|
+
const request = new services.MutateGoogleAdsRequest({
|
|
181
183
|
customer_id: this.customerOptions.customer_id,
|
|
182
184
|
mutate_operations: mutateOperations,
|
|
183
185
|
...options,
|
|
@@ -199,7 +201,7 @@ class Service {
|
|
|
199
201
|
}
|
|
200
202
|
else if (type === "update") {
|
|
201
203
|
// @ts-expect-error Field required for updates
|
|
202
|
-
op.update_mask =
|
|
204
|
+
op.update_mask = getFieldMask(
|
|
203
205
|
// @ts-expect-error Message types have a toObject method
|
|
204
206
|
message.toObject(e, {
|
|
205
207
|
defaults: false,
|
|
@@ -218,4 +220,3 @@ class Service {
|
|
|
218
220
|
return request;
|
|
219
221
|
}
|
|
220
222
|
}
|
|
221
|
-
exports.Service = Service;
|