npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@volter/twin-googleads

v0.1.30

Published

Local Google Ads twin — a faithful, stateful local Google Ads v22 REST API (accounts, GAQL reporting, conversion actions, offline conversion upload) and Data Manager v1 ingestion API your integration talks to unmodified. Built on @volter/world-core.

Readme

@volter/twin-googleads

The Google Ads twin — a local, stateful, vendor-faithful replica of the Google Ads API on the shared @volter/world-core kernel.

It answers for two vendor hosts, because they are one product's one credential and one account graph:

| host | what it is | |---|---| | googleads.googleapis.com/v22/… | the Google Ads API — accounts, the GAQL search / searchStream reporting surface, conversionActions:mutate, ConversionUploadService | | datamanager.googleapis.com/v1/… | the Data Manager API — the ingestion path Google now steers new conversion integrations to |

A Data Manager destination names a Google Ads customer and a Google Ads conversion action, so splitting them into two packs would leave each half unable to validate the other's ids.

State lives in the kernel's append-only log; reads fold the tree. No real Google is ever called (the only network path is the connector, over an injected executor, behind a fail-closed rate budget). Auth is faked locally, the way every twin fakes it: any non-empty bearer is accepted and none is ever verified — so the ya29.… token a world's googleoauth twin mints works unchanged, and Google sign-in is not re-modeled here. What this twin does model is everything a client branches on: the absence of a credential (a bare 401), the developer-token requirement, the GoogleAdsFailure envelope, and login-customer-id reachability.

Usage

bun run src/cli.ts serve            # both API surfaces (writable)
bun run src/cli.ts serve --read-only
bun run src/cli.ts mirror           # the React mirror (accounts, conversion actions, conversions)
bun run src/cli.ts conformance      # offline property check over projected state

The twin is born holding the account graph a freshly-connected advertiser already has — a Google Ads account is created by signing up in a browser, not through the API:

| customer | kind | reachable | |---|---|---| | 1234567890 Twin Manager Account | manager (MCC) | directly | | 2345678901 Twin Advertiser | advertiser | directly | | 3456789012 Twin Managed Client | advertiser | only with login-customer-id: 1234567890 |

Everything else is created through the vendor's own write paths (conversionActions:mutate, customers/{id}:createCustomerClient, customerClientLinks:mutate).

Coverage

The capability manifest (src/googleads-capabilities.ts) is the real Google Ads surface as the denominator, compiled top-down from the vendor's own protobuf definitions — every rpc in google/ads/googleads/v22/services and google/ads/datamanager/v1 carries a google.api.http annotation that IS its REST binding, and all 182 of them are in the manifest (158 across the 108 Google Ads service protos, 24 across the six Data Manager ones). Coverage is therefore honest and low. Run bun scripts/manifest-baseline-one.ts googleads for the live count. As of writing: done=62 / total=256 (regressions=0) — 182 compiled REST operations plus the auth, transport, connector, mirror and GAQL-semantics capabilities the bindings do not name.

Modeled (proven done, each with a failable offline verify):

  • Accounts — customers:listAccessibleCustomers, customers/{id}:createCustomerClient, customerClientLinks:mutate (create → PENDING, update → ACTIVE). A client created under a manager is not directly accessible, exactly as at Google.
  • Auth and reachability — a missing bearer is the API front end's bare 401 (no GoogleAdsFailure detail); a missing developer-token is 401 requestError: DEVELOPER_TOKEN_PARAMETER_MISSING; a hyphenated login-customer-id is 400 INVALID_CUSTOMER_ID; and an account you cannot reach is 403 authorizationError: USER_PERMISSION_DENIED — never a 404, because Google does not disclose the existence of accounts you were not invited to.
  • GAQL — a real (small, bounded) query engine over customer, customer_client, customer_client_link and conversion_action: SELECT with a proto3-JSON fieldMask, WHERE (=, !=, </>, IN, LIKE, REGEXP_MATCH, IS NULL), ORDER BY, LIMIT, search paging (pageSize / pageToken / returnTotalResultsCount) and searchStream's array-of-chunks wire. int64 renders as a string, enums as their constant, double as a number.
  • Conversion actions — conversionActions:mutate create / update / remove, with validateOnly (results: [], nothing written), partialFailure (good operations applied, failed slots left as {}, partialFailureError located at operations[i]), responseContentType: MUTABLE_RESOURCE, and all-or-nothing semantics when partialFailure is absent.
  • Offline conversions, both lanes — customers/{id}:uploadClickConversions (which really does require partialFailure, as Google does) and Data Manager events:ingest / requestStatus:retrieve, both validating the destination against the Google Ads account graph.
  • Connector — pull the reachable account hierarchy and each advertiser's conversion actions (idempotent re-pull), push a pending entry as the mutate or ingest it represents. A 200 carrying partialFailureError is read as a refusal, and a searchStream error arriving as a 200-shaped array throws rather than folding an empty account over real state.
  • UI mirror — accounts, conversion actions and offline conversions, data-coupled through the store door (seed through the vendor's own write path → read the projection the screen reads → render the mirror's own component over it → assert the seeded value survives).

Todo (real surface, not yet modeled). The bulk of Google Ads is campaign construction: campaigns, budgets, ad groups, ads, criteria, assets, asset groups, audiences, bidding strategies, labels, experiments, drafts, recommendations, the whole planning family (keyword plans, reach plans, geo/keyword-theme constants, audience and creator insights), billing, batch jobs, account links, Local Services, GoogleAdsService.Mutate, googleAdsFields, and the Data Manager audience-member, ad-event, partner-link and user-list-licence services. Also filed: GAQL metrics.* and segments.*, the resources this twin does not store, the DURING/BETWEEN/CONTAINS operators, the PARAMETERS clause, summary rows, multi-chunk streaming, and the gRPC wire (see below).

Never silently empty, and never a lie about the vendor. A GAQL query is answered against the name universe compiled from the vendor's own v22 resource protos (src/googleads-gaql-universe.gen.ts), so three cases stay apart: a name this twin serves is answered; a name Google Ads has and this twin does not refuses with queryError: QUERY_ERROR and a [twin gap] message naming the gap; only a name Google Ads does not have at all gets the vendor's own UNRECOGNIZED_FIELD / BAD_RESOURCE_TYPE_IN_FROM_CLAUSE. Collapsing the middle case into the last would have the twin asserting that Google has no keyword_plan resource. An empty results page is never an answer — it is indistinguishable from "the account has none". The same rule holds at the routing layer: an unmodeled real endpoint is a 404 that says so, and a malformed pageToken or pageSize is refused rather than quietly defaulted.

The gRPC gap

This twin serves the REST binding. google-ads-api (the Node client of record) uses REST for its reporting path — customer.query() / report() / reportStream() go to googleads.googleapis.com/v<N>/customers/<id>/googleAds:searchStream over axios, which is exactly the surface here — and gRPC via google-ads-node/google-gax for its service and mutate calls; google-ads-python defaults to gRPC throughout. That half is filed as googleads.transport.grpc, a real todo, not an exclusion. pack.adoption claims both npm clients and the Python one anyway: a repo depending on them plainly talks to this vendor, and a claimed vendor with a recorded gap is a better answer than mapping it to no vendor at all.

Why there is a UI mirror

Google Ads' core job is done in a browser — advertisers build campaigns, choose conversion actions and switch between manager and client accounts at ads.google.com — so needsUi is true. The mirror ships the screens this twin can data-couple today (accounts, conversion actions, offline conversions); the campaign, ad-group, reporting and account-switcher screens are recorded debt (googleads.ui.* todos), not faked.

It is an archetype D (store door) mirror rather than API-passthrough, and that is forced rather than chosen: Google Ads has no GET read at all — every read is a POST carrying a GAQL string. The named projections (accounts, conversion_actions, conversions) re-enter the pack's own served API, so even the aggregate is composed out of real API responses, and sabotaging the handler reddens every UI verify.

Files

  • src/googleads-twin.ts — the request handler for both hosts (routes → kernel writes/reads).
  • src/googleads-gaql.ts — the GAQL tokenizer, parser, field registry and evaluator.
  • src/googleads-errors.ts — the GoogleAdsFailure / google.rpc.Status envelopes.
  • src/googleads-server.ts — createGoogleAdsTwinFetch / createGoogleAdsTwinServer + the store doors.
  • src/googleads-mirror-ui.ts + client/googleads-mirror.tsx — the React mirror.
  • src/googleads-connector.ts — pull/push over an injected executor (liveGoogleAdsExecute).
  • src/googleads-budget.ts — the vendor's published quota as a fail-closed declaration.
  • src/googleads-conformance.ts — offline property checks (dev-only; lazy-imported by the CLI).
  • src/googleads-capabilities.ts — the capability manifest (the real vendor surface).

Provenance

Google publishes no OpenAPI document for the Google Ads API, and the rendered REST reference for v22 has already rotated off the docs nav. The first-party machine-readable source is the protobuf definitions in googleapis/googleapis, read 2026-09-13: the denominator is compiled from their google.api.http bindings, and every error constant this twin serves is quoted verbatim from errors/*.proto. v22 is the version the motivating application pins; the handler accepts any /v<N>/ segment and reports the one it was addressed under, because clients pin different majors.