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) };
},
});
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'declarationaccount_id-arm dispatch from the wire referenceAccount<TCtxMeta>viatoAccountlist_accountsplumbing with cursor envelope passthroughnullreturn for{ brand, operator }-shaped refs and ref-less calls (publisher-curated platforms expect explicit ids)Adopters who need
upsert(buyer-driven write paths viasync_accounts),refreshToken,reportUsage, orgetAccountFinancialscompose on top of the returned store with a spread:Ref-less calls (singleton fallback).
list_creative_formats,preview_creative, andprovide_performance_feedbackcallaccounts.resolve(undefined, ctx). UseresolveWithoutRefwhen your platform needs a synthetic publisher-wide account for these tools:The returned entry flows through
toAccountjust like alookuphit. When omitted the helper returnsnullfor ref-less calls — handlers can narrow onctx.account === undefinedand fall back to platform-level config from their closure.Ref-less calls (singleton from options):
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: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
upsertover the roster store and gate it on a field allowlist:Brand-arm refs return
null. Buyers who pass{ brand, operator }(noaccount_id) hit the framework'sACCOUNT_NOT_FOUNDenvelope. The helper cannot synthesizeINVALID_REQUESTfrom insideresolve— if your platform needs to reject brand-arm refs as a wire-shape error rather than a not-found, wrapresolveand throwAdcpError('INVALID_REQUEST', { field: 'account.brand' })before delegating to the helper.