Skip to content
Open

V25.1 #549

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
8920d5b
Upgrade to Google Ads API v25.1 (google-ads-node 25.1.0-beta.0)
Lewis-Clayton Sep 10, 2026
5396255
Use explicit .js extensions and ESM-safe imports
Lewis-Clayton Sep 10, 2026
b7de3c1
Load services through a static import and export googleAdsVersion
Lewis-Clayton Sep 10, 2026
ef142e2
Ship CommonJS and ESM builds with an export map
Lewis-Clayton Sep 10, 2026
4cd06a4
Pin service clients to googleapis.com to skip the credential probe
Lewis-Clayton Sep 10, 2026
10adc82
Return the original error when no GoogleAdsFailure trailer is present
Lewis-Clayton Sep 10, 2026
d3224b7
Allow gRPC channel options to be set per client
Lewis-Clayton Sep 10, 2026
e7feda2
Document the 25.1.0 release
Lewis-Clayton Sep 10, 2026
0373cf1
Type the proto definitions used by the services generator
Lewis-Clayton Sep 10, 2026
8f891f7
Key cached service clients by channel options and keep library option…
Lewis-Clayton Sep 10, 2026
49e1655
Test that gRPC call credentials carry the auth client's headers
Lewis-Clayton Sep 10, 2026
c271214
Smoke-test ESM and CommonJS consumption through the export map in CI
Lewis-Clayton Sep 10, 2026
0988a54
Format the generated files with prettier on regeneration
Lewis-Clayton Sep 10, 2026
8f44c82
Refresh jws in the lockfile and complete the 25.1.0 changelog
Lewis-Clayton Sep 10, 2026
03c1a28
Regenerate the lockfile to resolve patched dependency versions
Lewis-Clayton Sep 10, 2026
62c7a34
Reformat the generated files with the lockfile's prettier
Lewis-Clayton Sep 10, 2026
537475c
Drop unused imports from the spec files
Lewis-Clayton Sep 11, 2026
80e4d71
Scope lint rules that do not apply to generated code and namespace al…
Lewis-Clayton Sep 11, 2026
7338afc
Type the test spies and fixtures instead of using any
Lewis-Clayton Sep 11, 2026
07398d7
25.1.0-beta.1
Lewis-Clayton Sep 11, 2026
eb0979e
Add an About Opteo section to the README
Lewis-Clayton Sep 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,18 +1,39 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"plugins": [
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"overrides": [
{
"files": ["*.ts", "*.spec.ts"],
"files": [
"*.ts",
"*.spec.ts"
],
"rules": {
"@typescript-eslint/ban-ts-comment": "off"
}
},
{
"files": [
"src/protos/autogen/*.ts"
],
"rules": {
"@typescript-eslint/explicit-module-boundary-types": "off"
}
},
{
"files": [
"src/protos/index.ts"
],
"rules": {
"@typescript-eslint/no-unused-vars": "off"
}
}
]
}
8 changes: 7 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20.x]
node-version: [22.x, 24.x]

steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -39,3 +39,9 @@ jobs:

# tests
- run: yarn test

# packaging: ESM and CommonJS consumers through the export map
- run: yarn test:package

# packaging: export map and type resolution
- run: npx --yes @arethetypeswrong/cli@0.18.5 --profile node16 --pack .
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,37 @@
# Changelog

### 25.1.0

### Version Upgrade

- Upgraded google-ads-api version to v25.1. Refer to Google Ads release notes [here](https://developers.google.com/google-ads/api/docs/release-notes) for changes.
- Upgraded google-ads-node dependency to v25.1.0
- Upgraded google-gax dependency to v6.3.0 and google-auth-library to v11
- Service clients are pinned to the `googleapis.com` universe domain, so the new auth stack does not probe for application default credentials (about three seconds per new service client, plus a `MetadataLookupWarning` outside Google Cloud) when a service is first created

### New Features

- The package ships CommonJS and ES module builds with an `exports` map: `import { GoogleAdsApi } from "google-ads-api"` resolves to native ESM and `require("google-ads-api")` to CommonJS.
- Loading both flavours in one process yields two module instances with separate service caches; proto message and error classes come from google-ads-node and stay shared, so `instanceof` checks keep working across them.
- New subpath entries `google-ads-api/enums` and `google-ads-api/fields` expose the generated enums and field types without loading the gRPC service clients.
- `googleAdsVersion` is exported from the package root.
- New `grpc_channel_options` client option forwards gRPC channel settings such as keepalive to every service client; cached service clients are keyed by client id, refresh token and these options (#547).
- New service: `MultiPartyAuthReviewService`
- New resource names: `LiftMeasurementAgeRange`, `LiftMeasurementCampaign`, `LiftMeasurementConfig`, `LiftMeasurementDevice`, `LiftMeasurementFlight`, `LiftMeasurementGender`, `LiftMeasurementVideo`, `MultiPartyAuthReview`
- New enums include `AdSubFormatType`, `BrandLiftMeasurementType`, `LiftMetricType`, `LoyaltyMembership`, `MultiPartyAuthReviewStatus`, `Sentiment`, `SyntheticContentSource` and `ThirdPartyConversionAttributionIntegrationPartner`

### Breaking Changes

- Node.js 22 or newer is required.
- Deep imports of build files (for example `google-ads-api/build/src/protos/autogen/enums`) no longer resolve. Use the package root, `google-ads-api/enums` or `google-ads-api/fields` instead.
- TypeScript projects on `moduleResolution: node` (node10) cannot type-resolve the `google-ads-api/enums` and `google-ads-api/fields` subpaths; use `node16`, `nodenext` or `bundler`.
- Removed services and resources: `CampaignLifecycleGoalService`, `CustomerLifecycleGoalService`, `CampaignLifecycleGoal`, `CustomerLifecycleGoal` (use `Goal`). Removed enum: `CustomerAcquisitionOptimizationMode`.

### Fixes

- `getGoogleAdsError()` no longer throws a `TypeError` when an error carries no `GoogleAdsFailure` trailer (for example REST-style `google.rpc.ErrorInfo` errors such as `SERVICE_DISABLED`); the original error is returned instead (#548).
- google-ads-api does not pin `jws`; a fresh install of this release resolves `jws` 4.0.1, which addresses the advisory reported in #529. Refresh your lockfile if it still holds 4.0.0.

### 24.1.0

### Version Upgrade
Expand Down
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</p>
<p align="center">
<a href="https://developers.google.com/google-ads/api/docs/release-notes">
<img src="https://img.shields.io/badge/google%20ads-v23-009688.svg?style=flat-square">
<img src="https://img.shields.io/badge/google%20ads-v25.1-009688.svg?style=flat-square">
</a>
<a href="https://www.npmjs.com/package/google-ads-api">
<img src="https://img.shields.io/npm/v/google-ads-api.svg?style=flat-square">
Expand Down Expand Up @@ -36,6 +36,8 @@
npm install google-ads-api
```

Requires Node.js 22 or newer. Both `import { GoogleAdsApi } from "google-ads-api"` (ES modules) and `require("google-ads-api")` (CommonJS) are supported. `google-ads-api/enums` and `google-ads-api/fields` expose the generated enums and field types without loading the gRPC service clients.

# Usage

- Setup
Expand Down Expand Up @@ -70,6 +72,12 @@ const client = new GoogleAdsApi({
});
```

Optional client settings:

- `grpc_channel_options`: [gRPC channel options](https://github.com/grpc/grpc-node/tree/master/packages/grpc-js#supported-channel-options) applied to every service client this instance creates, for example `{ "grpc.keepalive_time_ms": 30000, "grpc.keepalive_timeout_ms": 10000 }`. Cached service clients are keyed by client id, refresh token and these options, so instances with different options never share a client.
- `max_reporting_rows`: throw once a report or query returns more rows than this.
- `disable_parsing`: return raw API responses instead of parsed rows.

---

## Create a customer instance
Expand Down Expand Up @@ -713,3 +721,11 @@ try {
8. Test with `yarn test`.
9. Test by linking to a real project and making some real requests.
10. Once confident, bump the major version & publish to NPM.

# About Opteo

This library is developed and maintained by [Opteo](https://opteo.com), the Advertising Efficiency Manager (AEM) for paid media teams working across Google Ads, Meta Ads, Microsoft Advertising, LinkedIn Ads and TikTok Ads.

Opteo brings together live account data, business context, skills, and specialist marketing tools in one workspace. Connect your marketing accounts, analyse your data with professional-grade tools, build expert marketing agents, and implement changes that increase ROAS.

Learn more about [Opteo](https://opteo.com).
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module.exports = {
//collectCoverage: true,
coveragePathIgnorePatterns: ["/node_modules/", "jest.config.js"],
resetMocks: true,
moduleNameMapper: { "^(\\.{1,2}/.*)\\.js$": "$1" },
testMatch: [
// "build/.*?(?=.spec).*?.js",
// "**/__tests__/**/*.[jt]s?(x)",
Expand Down
67 changes: 54 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,29 +1,35 @@
{
"name": "google-ads-api",
"version": "24.1.0",
"version": "25.1.0-beta.1",
"description": "Google Ads API Client Library for Node.js",
"repository": "https://github.com/Opteo/google-ads-api",
"main": "build/src/index.js",
"repository": {
"type": "git",
"url": "git+https://github.com/Opteo/google-ads-api.git"
},
"main": "./build/cjs/index.js",
"files": [
"build/src"
"build/cjs",
"build/esm"
],
"scripts": {
"test": "jest",
"lint": "eslint . --ext .ts",
"build": "tsc",
"build": "tsc -p tsconfig.cjs.json && tsc -p tsconfig.esm.json && printf '{\"type\":\"module\"}' > build/esm/package.json",
"compile": "esbuild --bundle --platform=node ./scripts/index.ts --outfile=./build/scripts/index.js && node ./build/scripts/index.js",
"prepare": "rm -rf build && npm run build",
"node": "tsx"
"node": "tsx",
"test:package": "npm run build && node tests/package.mjs",
"postcompile": "prettier --write src/protos/autogen"
},
"author": "Opteo",
"license": "MIT",
"dependencies": {
"@isaacs/ttlcache": "^1.4.0",
"axios": "^1.6.7",
"circ-json": "^1.0.4",
"google-ads-node": "24.1.0",
"google-auth-library": "^9.15.1",
"google-gax": "^5.0.7",
"google-ads-node": "25.1.0-beta.1",
"google-auth-library": "^11.0.2",
"google-gax": "^6.3.0",
"long": "^4.0.0",
"stream-json": "^1.8.0"
},
Expand All @@ -44,7 +50,7 @@
"pluralize": "^8.0.0",
"prettier": "^3.6.2",
"protobufjs": "^7.2.6",
"ts-jest": "^29.1.2",
"ts-jest": "29.3.4",
"tsx": "^4.19.3",
"typescript": "^5.5.4"
},
Expand All @@ -60,8 +66,43 @@
"adwords javascript",
"gads"
],
"resolutions": {
"google-auth-library": "^9.15.1"
"packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72",
"type": "commonjs",
"engines": {
"node": ">=22"
},
"packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72"
"types": "./build/cjs/index.d.ts",
"exports": {
".": {
"import": {
"types": "./build/esm/index.d.ts",
"default": "./build/esm/index.js"
},
"require": {
"types": "./build/cjs/index.d.ts",
"default": "./build/cjs/index.js"
}
},
"./enums": {
"import": {
"types": "./build/esm/protos/autogen/enums.d.ts",
"default": "./build/esm/protos/autogen/enums.js"
},
"require": {
"types": "./build/cjs/protos/autogen/enums.d.ts",
"default": "./build/cjs/protos/autogen/enums.js"
}
},
"./fields": {
"import": {
"types": "./build/esm/protos/autogen/fields.d.ts",
"default": "./build/esm/protos/autogen/fields.js"
},
"require": {
"types": "./build/cjs/protos/autogen/fields.d.ts",
"default": "./build/cjs/protos/autogen/fields.js"
}
},
"./package.json": "./package.json"
}
}
28 changes: 17 additions & 11 deletions scripts/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,17 @@ export async function compileServices(): Promise<void> {
stream.write(`
/* Autogenerated File! Do Not Edit */

import { ClientOptions } from "../../client";
import { CustomerOptions } from "../../types";
import { Service } from "../../service";
import { resources, services, protobuf, longrunning } from "../index";
import { ClientOptions } from "../../client.js";
import { CustomerOptions } from "../../types.js";
import { Service } from "../../service.js";
import { resources, services, protobuf, longrunning } from "../index.js";
import {
BaseMutationHookArgs,
BaseServiceHookArgs,
HookedCancellation,
HookedResolution,
Hooks,
} from "../../hooks";
} from "../../hooks.js";

export default class ServiceFactory extends Service {
constructor(
Expand Down Expand Up @@ -249,12 +249,16 @@ function compileMutateMethods(
const mutateMethods: string[] = [];

const { requestType } = methodDef;
const req = (allServices as any)[requestType];
const definitions = allServices as unknown as Record<string, ProtoDefinition>;
const req = definitions[requestType];

const opType = req.fields.operation ?? req.fields.operations;
const op = (allServices as any)[opType.type];
const opType = req.fields?.operation ?? req.fields?.operations;
if (!opType) {
throw new Error(`${requestType} has no operation field`);
}
const op = definitions[opType.type];

const methods = Object.keys(op.fields).filter((o) =>
const methods = Object.keys(op.fields ?? {}).filter((o) =>
["create", "update", "remove"].includes(o)
);

Expand Down Expand Up @@ -364,8 +368,10 @@ function buildMutateMethod(
*/
${mutation}: async (
${argName}: ${
mutation === "remove" ? `${resourceType}[]` : `${policyViolationArgType}`
} ,
mutation === "remove"
? `${resourceType}[]`
: `${policyViolationArgType}`
} ,
options?: MutateOptions
): Promise<${responseType} > => {
const ops = this.buildOperations<
Expand Down
26 changes: 17 additions & 9 deletions src/client.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Client } from "./client";
import { Service } from "./service";
import { Client } from "./client.js";
import { Service } from "./service.js";

describe("listAccessibleCustomers", () => {
afterEach(() => {
Expand All @@ -21,7 +21,10 @@ describe("listAccessibleCustomers", () => {
.mockResolvedValue([{ resource_names: ["customers/123"] }]);

const loadServiceSpy = jest
.spyOn(Service.prototype as any, "loadService")
.spyOn(
Service.prototype as unknown as { loadService: () => unknown },
"loadService"
)
.mockReturnValue({ listAccessibleCustomers, close });

const response = await client.listAccessibleCustomers("MOCK_REFRESH_TOKEN");
Expand All @@ -37,12 +40,17 @@ describe("listAccessibleCustomers", () => {
const client = newClient();
const close = jest.fn().mockResolvedValue(undefined);

jest.spyOn(Service.prototype as any, "loadService").mockReturnValue({
listAccessibleCustomers: jest
.fn()
.mockRejectedValue(new Error("rpc failed")),
close,
});
jest
.spyOn(
Service.prototype as unknown as { loadService: () => unknown },
"loadService"
)
.mockReturnValue({
listAccessibleCustomers: jest
.fn()
.mockRejectedValue(new Error("rpc failed")),
close,
});
jest.spyOn(console, "log").mockImplementation(() => undefined);

await expect(
Expand Down
12 changes: 7 additions & 5 deletions src/client.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { Customer } from "./customer";
import { CustomerOptions } from "./types";
import { Hooks } from "./hooks";
import { services } from "./protos";
import { Service } from "./service";
import type { grpc } from "google-gax";
import { Customer } from "./customer.js";
import { CustomerOptions } from "./types.js";
import { Hooks } from "./hooks.js";
import { services } from "./protos/index.js";
import { Service } from "./service.js";

export interface ClientOptions {
client_id: string;
client_secret: string;
developer_token: string;
disable_parsing?: boolean;
max_reporting_rows?: number;
grpc_channel_options?: grpc.ChannelOptions;
}

export class Client {
Expand Down
Loading
Loading