How to use @owlmeans/marketing-consent-postgres — the Postgres extension that registers the two @owlmeans/server-marketing-consent resources (marketing-consent-state, marketing-consent-log) for a GENERATED TARGET PROJECT. Auto-invoked when wiring marketing-consent storage into a target project's Postgres backend, or touching the state/log table shape.
Scanned 10/6/2026
npx -y skills add owlmeans/common --skill marketing-consent-postgres --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Marketing Consent Postgres?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/owlmeans-marketing-consent-postgres)More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.
---
name: marketing-consent-postgres
description: How to use @owlmeans/marketing-consent-postgres — the Postgres extension that registers the two @owlmeans/server-marketing-consent resources (marketing-consent-state, marketing-consent-log) for a GENERATED TARGET PROJECT. Auto-invoked when wiring marketing-consent storage into a target project's Postgres backend, or touching the state/log table shape.
user-invocable: false
---
# @owlmeans/marketing-consent-postgres
**Layer:** Infra extension
**Install:** `"@owlmeans/marketing-consent-postgres": "^0.1.18-rc.16"` in `dependencies` (peers `pg`, `ajv`)
The Postgres counterpart to `@owlmeans/server-marketing-consent`'s two resources. This package is
for a **generated target project**, which runs Postgres — the platform's own equivalent for
projects that stayed on Mongo is `@owlmeans/marketing-consent-mongo` (a separate, not-yet-built
package); Viable itself runs Mongo and never needs this one.
## Key Exports
| Export | Description |
|--------|-------------|
| `makeMarketingConsentStatePostgres(dbAlias?, serviceAlias?)` | `PostgresResource<MarketingConsentStateRecord>` at `RES_MARKETING_CONSENT_STATE` |
| `makeMarketingConsentLogPostgres(dbAlias?, serviceAlias?)` | `PostgresResource<MarketingConsentLogRecord>` at `RES_MARKETING_CONSENT_LOG` |
| `appendMarketingConsentPostgres(ctx, opts?)` | Registers both, each guarded by `ctx.hasResource(...)` — safe to call more than once |
## The schema is imported, never redeclared
Both makers set `resource.schema` to `MarketingConsentStateSchema` / `MarketingConsentLogSchema`
from `@owlmeans/server-marketing-consent` — this package owns no AJV schema of its own. The two
record shapes are shared with the (not yet built) `@owlmeans/marketing-consent-mongo` sibling;
duplicating the schema here would let the two extensions drift apart from what
`MarketingConsentService` actually reads and writes.
## `decisions` / `terms` / `documents` / `notices` are one jsonb column each — never split
`MarketingConsentStateRecord.decisions` is an ARRAY, never an object keyed by consent key —
`server-marketing-consent`'s own skill explains why: a dotted key like `"marketing.email"` is read
as a PATH by Postgres jsonb operators, so an object-keyed shape breaks the moment a second key is
added. This package does nothing to make that safe by itself — it relies on
`@owlmeans/postgres-resource`'s **default** schema-to-table rule: any `array` of objects (or any
nested `object` property) compiles to a single opaque `jsonb` column, never a child table and never
one column per field. No `pg:` override is applied here, and none is needed — `tests/schema.spec.ts`
asserts this directly against `pgSchemaHelper.schemaToTableSpec`, which is the one test that would catch a
regression here (in this package, in `postgres-resource`'s compiler, or in a future schema edit
that accidentally reshapes `decisions` into something path-addressable).
## Indexes
- `idx_mc_state_subject` — `{ columns: ['subject'], unique: true }`: one state row per subject.
The service addresses a person's row by `subject` (the row's `id` is minted by Postgres), so
this index is what makes that address unique, and what a racing first save collides on.
- `idx_mc_state_user` — `{ columns: ['userId'] }`: lookups across a user's profiles/entities.
- `idx_mc_log_subject_key` — `{ columns: ['subject', 'key', 'decidedAt'] }`: a subject's history
for one consent key, newest first.
- `idx_mc_log_user` — `{ columns: ['userId', 'decidedAt'] }`: a user's whole consent/terms history.
## Wiring
```ts
import { appendMarketingConsentPostgres } from '@owlmeans/marketing-consent-postgres'
import { appendMarketingConsentService } from '@owlmeans/server-marketing-consent'
appendMarketingConsentPostgres(context) // register the two Postgres resources first
appendMarketingConsentService(context) // the service resolves them by alias, lazily, per call
```
Registration order relative to the service append only matters for the resources needing to exist
by the first `status`/`save`/`terms` call — `server-marketing-consent`'s own skill covers this.
## Testing
`bun test ./tests` — `tests/schema.spec.ts` needs no live database (the jsonb-shape assertion
above); `tests/resource.spec.ts` is env-gated on `POSTGRES_URL` exactly like
`@owlmeans/postgres-resource`'s own integration suites (`@owlmeans/test-integration`'s
`gateHelper.postgresGate()`), and skips cleanly with no server reachable.
## Related
- `@owlmeans/server-marketing-consent` — the service, the record types, the AJV schemas this
package imports (read its skill first)
- `@owlmeans/postgres-resource` — the resource factory, the schema-to-table compiler, `PgIndexSpec`
- `@owlmeans/marketing-consent-mongo` — the Mongo counterpart for the platform's own database (not
yet built)
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!