@adcp/sdk API Reference - v7.9.0
    Preparing search index...

    Function createRosterAccountStore

    • Build an AccountStore<TCtxMeta> from an adopter-supplied roster source.

      The adopter brings persistence (DB row, admin-UI-managed JSON column, in-memory Map, file). The helper provides:

      • resolution: 'explicit' declaration
      • account_id-arm dispatch from the wire reference
      • Mapping from roster entry → Account<TCtxMeta> via toAccount
      • Optional list_accounts plumbing with cursor envelope passthrough
      • null return for { brand, operator }-shaped refs and ref-less calls (publisher-curated platforms expect explicit ids)

      Adopters who need upsert (buyer-driven write paths via sync_accounts), refreshToken, reportUsage, or getAccountFinancials compose on top of the returned store with a spread:

      const accounts: AccountStore<MyMeta> = {
      ...createRosterAccountStore({ lookup, toAccount }),
      refreshToken: async (account) => myUpstream.refresh(account),
      };

      Ref-less calls (singleton fallback). list_creative_formats, preview_creative, and provide_performance_feedback call accounts.resolve(undefined, ctx). Use resolveWithoutRef when your platform needs a synthetic publisher-wide account for these tools:

      const accounts = createRosterAccountStore({
      lookup,
      toAccount,
      resolveWithoutRef: () => ({ id: '__publisher__', label: 'Publisher', tenantId: myPlatformId }),
      });

      The returned entry flows through toAccount just like a lookup hit. When omitted the helper returns null for ref-less calls — handlers can narrow on ctx.account === undefined and fall back to platform-level config from their closure.

      Ref-less calls (singleton from options):

      const accounts = createRosterAccountStore({
      lookup,
      toAccount,
      resolveWithoutRef: (_ref, ctx) => ({
      id: '__publisher__',
      name: 'Publisher',
      tenant_id: deriveTenant(ctx?.authInfo),
      }),
      });

      Ref-less calls (auth-derived lookup). When the singleton should be derived from the caller's principal and needs to call back into lookup, use the spread-override pattern instead:

      const base = createRosterAccountStore({ lookup, toAccount });
      const accounts: AccountStore<MyMeta> = {
      ...base,
      resolve: async (ref, ctx) => {
      if (ref === undefined) {
      const id = deriveAccountIdFromAuth(ctx?.authInfo);
      return id ? base.resolve({ account_id: id }, ctx) : null;
      }
      return base.resolve(ref, ctx);
      },
      };

      Hybrid roster + buyer-updatable fields. Some publisher-curated platforms (GAM, FreeWheel, several retail-media networks) let the buyer PATCH a narrow set of fields — billing contact, AP email, agency-of-record cert — on a publisher-provisioned account, while keeping creation and commercial terms (credit limit, rate card) read-only to the buyer. Compose a partial upsert over the roster store and gate it on a field allowlist:

      const BUYER_WRITABLE = new Set(['billing_entity', 'setup']);
      const accounts: AccountStore<MyMeta> = {
      ...createRosterAccountStore({ lookup, toAccount }),
      upsert: async (refs, ctx) => refs.map(r => applyBuyerPatch(r, BUYER_WRITABLE, ctx)),
      };

      Brand-arm refs return null. Buyers who pass { brand, operator } (no account_id) hit the framework's ACCOUNT_NOT_FOUND envelope. The helper cannot synthesize INVALID_REQUEST from inside resolve — if your platform needs to reject brand-arm refs as a wire-shape error rather than a not-found, wrap resolve and throw AdcpError('INVALID_REQUEST', { field: 'account.brand' }) before delegating to the helper.

      Type Parameters

      • TRosterEntry
      • TCtxMeta = Record<string, unknown>

      Returns AccountStore<TCtxMeta>

      const accounts = createRosterAccountStore({
      lookup: (id) => roster.get(id),
      toAccount: (row) => ({
      id: row.id,
      name: row.name,
      status: 'active',
      ctx_metadata: { upstreamId: row.upstream_id },
      }),
      });
      const accounts = createRosterAccountStore({
      lookup: async (id, ctx) => {
      const tenantId = deriveTenant(ctx?.authInfo);
      return await db.oneOrNone(
      'SELECT * FROM storefront_accounts WHERE id = $1 AND tenant = $2',
      [id, tenantId],
      );
      },
      toAccount: (row) => ({
      id: row.id,
      name: row.name,
      status: row.status,
      brand: row.brand,
      operator: row.operator,
      ctx_metadata: { tenant: row.tenant, upstreamRef: row.upstream_ref },
      }),
      list: async (filter, ctx) => {
      const tenantId = deriveTenant(ctx?.authInfo);
      const rows = await db.any(buildListQuery(filter, tenantId));
      return { items: rows, nextCursor: nextCursorFor(rows, filter) };
      },
      });