@owlmeans/server-marketing-consent 0.1.18-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -0
- package/agent-meta/manifest.json +16 -0
- package/agent-meta/skills/server-marketing-consent/SKILL.md +148 -0
- package/build/consts.d.ts +12 -0
- package/build/consts.d.ts.map +1 -0
- package/build/consts.js +12 -0
- package/build/consts.js.map +1 -0
- package/build/handlers.d.ts +20 -0
- package/build/handlers.d.ts.map +1 -0
- package/build/handlers.js +23 -0
- package/build/handlers.js.map +1 -0
- package/build/helper.d.ts +19 -0
- package/build/helper.d.ts.map +1 -0
- package/build/helper.js +14 -0
- package/build/helper.js.map +1 -0
- package/build/index.d.ts +7 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +7 -0
- package/build/index.js.map +1 -0
- package/build/model.d.ts +64 -0
- package/build/model.d.ts.map +1 -0
- package/build/model.js +83 -0
- package/build/model.js.map +1 -0
- package/build/service.d.ts +56 -0
- package/build/service.d.ts.map +1 -0
- package/build/service.js +146 -0
- package/build/service.js.map +1 -0
- package/build/subject.d.ts +21 -0
- package/build/subject.d.ts.map +1 -0
- package/build/subject.js +21 -0
- package/build/subject.js.map +1 -0
- package/package.json +54 -0
- package/src/consts.ts +11 -0
- package/src/handlers.ts +44 -0
- package/src/helper.ts +18 -0
- package/src/index.ts +6 -0
- package/src/model.ts +147 -0
- package/src/service.ts +226 -0
- package/src/subject.ts +32 -0
- package/tests/context.ts +52 -0
- package/tests/handlers.spec.ts +85 -0
- package/tests/service.spec.ts +164 -0
- package/tests/subject.spec.ts +38 -0
- package/tests/tsconfig.json +12 -0
- package/tsconfig.json +11 -0
package/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!-- owlmeans:agent-guidance:start -->
|
|
2
|
+
## Agent guidance
|
|
3
|
+
|
|
4
|
+
This package ships embedded agent skills under `agent-meta/`. After installing your
|
|
5
|
+
`@owlmeans/*` packages, run the OwlMeans agent-skills installer to place them into
|
|
6
|
+
your project's skill store (`.agents/skills/`):
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.33
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The embedded files are version-matched to this package release. Do not edit them
|
|
13
|
+
directly — they are regenerated on each publish. To contribute guidance edits,
|
|
14
|
+
open a PR against the source monorepo.
|
|
15
|
+
<!-- owlmeans:agent-guidance:end -->
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 2,
|
|
3
|
+
"package": "@owlmeans/server-marketing-consent",
|
|
4
|
+
"version": "0.1.18-rc.0",
|
|
5
|
+
"generatedAt": "2026-09-22T17:37:10.936Z",
|
|
6
|
+
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
|
+
"entries": [
|
|
8
|
+
{
|
|
9
|
+
"kind": "skill",
|
|
10
|
+
"name": "server-marketing-consent",
|
|
11
|
+
"category": "package-specific",
|
|
12
|
+
"file": "skills/server-marketing-consent/SKILL.md",
|
|
13
|
+
"canonicalPath": ".agents/skills/server-marketing-consent/SKILL.md"
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: server-marketing-consent
|
|
3
|
+
description: How to use @owlmeans/server-marketing-consent — the database-agnostic MarketingConsentService (status/save/terms/purge/isGranted/observe over two resources resolved by alias) plus the guarded status/save/terms entrypoint handlers for @owlmeans/marketing-consent's protocol tree. Auto-invoked when registering the marketing-consent service, writing a Mongo/Postgres extension that stores its two records, saving or reading a person's consent decisions server-side, or gating a send/share on a consent.
|
|
4
|
+
user-invocable: false
|
|
5
|
+
---
|
|
6
|
+
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
7
|
+
|
|
8
|
+
# @owlmeans/server-marketing-consent
|
|
9
|
+
|
|
10
|
+
**Layer:** Server
|
|
11
|
+
**Install:** `"@owlmeans/server-marketing-consent": "^0.1.18-rc.0"` in `dependencies`
|
|
12
|
+
**Contracts:** `@owlmeans/marketing-consent` — the catalogue, `consentStatus`, the protocol tree, the error family
|
|
13
|
+
|
|
14
|
+
## Key Exports
|
|
15
|
+
|
|
16
|
+
| Export | Description |
|
|
17
|
+
|--------|-------------|
|
|
18
|
+
| `RES_MARKETING_CONSENT_STATE` (`marketing-consent-state`) · `RES_MARKETING_CONSENT_LOG` (`marketing-consent-log`) | Resource aliases — never rename, a later Mongo/Postgres extension's generated resource file names derive from these exact strings |
|
|
19
|
+
| `MarketingConsentStateRecord` · `MarketingConsentLogRecord` · `MarketingConsentStateSchema` · `MarketingConsentLogSchema` | The two record shapes and their AJV `JSONSchemaType` schemas — shared with the (not yet built) `@owlmeans/marketing-consent-mongo` / `@owlmeans/marketing-consent-postgres` extensions, which import them rather than redeclaring |
|
|
20
|
+
| `subjectOf(req)` · `subjectKey(subject)` · `MarketingConsentSubject` | Who a decision is recorded for, and its one string record id |
|
|
21
|
+
| `makeMarketingConsentService(opts?)` · `appendMarketingConsentService(ctx, opts?)` | Build/register the `MarketingConsentService` |
|
|
22
|
+
| `MarketingConsentService` | `definitions`, `status`, `save`, `recordTerms`, `isGranted`, `purge`, `observe` |
|
|
23
|
+
| `marketingConsentStatus` · `saveMarketingConsent` · `recordTermsAcceptance` | Handler makers for `makeMarketingConsentProtocols().status/save/terms` |
|
|
24
|
+
| `serveMarketingConsentEntrypoints(protocols, opts?)` | Binds `status`/`save`/`terms` — `base` and `screen` carry no server handler of their own |
|
|
25
|
+
|
|
26
|
+
## Wiring
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { appendMarketingConsentService, serveMarketingConsentEntrypoints } from '@owlmeans/server-marketing-consent'
|
|
30
|
+
import { makeMarketingConsentProtocols } from '@owlmeans/marketing-consent'
|
|
31
|
+
|
|
32
|
+
// shared package
|
|
33
|
+
export const marketingConsentProtocols = makeMarketingConsentProtocols({ parent: appProtocols.account.base })
|
|
34
|
+
|
|
35
|
+
// backend context — after the Mongo/Postgres extension has registered the two resources
|
|
36
|
+
appendMarketingConsentService(context, { config: appMarketingConsentConfig })
|
|
37
|
+
|
|
38
|
+
// API process
|
|
39
|
+
context.registerEntrypoints(serveMarketingConsentEntrypoints(marketingConsentProtocols))
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`appendMarketingConsentService` is the same "an app that cares provides its own; this is the
|
|
43
|
+
working default" shape as `AUTH_CACHE`/`appendOAuthServer`: it registers nothing but the
|
|
44
|
+
`MarketingConsentService` itself (LazyService, `createLazyService`) and is a no-op if the alias is
|
|
45
|
+
already registered. The TWO RESOURCES it reads and writes (`RES_MARKETING_CONSENT_STATE`,
|
|
46
|
+
`RES_MARKETING_CONSENT_LOG`) are resolved BY ALIAS from the context lazily, inside each method call
|
|
47
|
+
— this package never imports `mongo-resource`/`postgres-resource` and never constructs them. A
|
|
48
|
+
Mongo or Postgres extension package registers the concrete resources at those two aliases
|
|
49
|
+
separately, and `appendMarketingConsentService` must run after that registration (resource lookup
|
|
50
|
+
is lazy per call, so registration order relative to `init()` does not matter — only "resource
|
|
51
|
+
exists under this alias before the first `status`/`save`/`terms` call" does).
|
|
52
|
+
|
|
53
|
+
## Why `decisions` is an ARRAY, never an object keyed by consent key
|
|
54
|
+
|
|
55
|
+
This is the single most important gotcha in this package. `MarketingConsentStateRecord.decisions`
|
|
56
|
+
holds `MarketingConsentDecision[]`, one entry per key, folded down to the latest per key on read
|
|
57
|
+
(`consentStatus`). It is never stored as `{ [key]: MarketingConsentDecision }`, because every
|
|
58
|
+
standard consent key is DOTTED (`marketing.email`, `trackers.advertising`) and a dotted key inside
|
|
59
|
+
an object field is read as a PATH by both Mongo's dot-notation queries (`{'decisions.marketing.email':
|
|
60
|
+
...}` reaches into a nested `email` field under a nested `marketing` field, not a literal key) and
|
|
61
|
+
Postgres jsonb path operators (`->` chains on segments). An object-keyed shape works in a quick
|
|
62
|
+
manual test with one key and breaks the moment a second dotted key is added — the two fields
|
|
63
|
+
silently merge into one nested tree instead of staying two siblings. Keep it an array in every
|
|
64
|
+
storage backend; fold by key only in memory, in `consentStatus` or in a resource's own read path.
|
|
65
|
+
|
|
66
|
+
## `subjectOf` never uses the slug
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
export const subjectOf = (req: AbstractRequest): MarketingConsentSubject => {
|
|
70
|
+
if (req.auth?.userId == null || req.auth.userId === '') throw new AuthForbidden(...)
|
|
71
|
+
return { userId: req.auth.userId, profileId: req.auth.profileId, entityId: req.entity?.id }
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`entityId` reads `req.entity?.id` ONLY — never `requireEntityKey(req)`, never a slug off the token.
|
|
76
|
+
A deployment with no organization concept at all (a generated target app serving its own end users,
|
|
77
|
+
say) registers no entity resolver, so `req.entity` stays `undefined` and `entityId` is simply
|
|
78
|
+
absent — a valid, expected shape this package must never throw on. `subjectKey(subject)` —
|
|
79
|
+
`` `${entityId ?? ''}|${userId}|${profileId ?? ''}` `` — is the state record's own `id`: one record
|
|
80
|
+
per subject, looked up with a plain `load(id)`, no secondary index needed for the common read path.
|
|
81
|
+
|
|
82
|
+
## `isGranted` is not `consentStatus`'s display `granted`
|
|
83
|
+
|
|
84
|
+
`status(subject)` returns what `consentStatus` computes for a settings screen: an unanswered
|
|
85
|
+
`opt-out` item (e.g. `data.partners`) reads `granted: true` there — the "on until you turn it off"
|
|
86
|
+
copy an unauthenticated-feeling first visit shows. `isGranted(subject, key)` is a DIFFERENT
|
|
87
|
+
question — the SERVER-SIDE gate a send/share actually checks before doing something — and it must
|
|
88
|
+
never honor that display default: an item whose `status` is `'new'` or `'revised'` has never been
|
|
89
|
+
affirmatively answered by this person, so `isGranted` answers `false` for it regardless of what
|
|
90
|
+
`consentStatus.granted` says, and `false` too for a key `definitions()` does not carry at all
|
|
91
|
+
(unknown or disabled). Only a `'current'` (i.e. actually saved, unrevised) item's `granted` is
|
|
92
|
+
trusted. Getting this backwards — wiring a send to `status(...).items.find(...).granted` instead of
|
|
93
|
+
`isGranted(...)` — sends to people who opened the settings screen and changed nothing.
|
|
94
|
+
|
|
95
|
+
## `save` / `recordTerms` — the upsert and the log
|
|
96
|
+
|
|
97
|
+
- `save` rejects the whole request (`UnknownMarketingConsentError`) if ANY submitted key is not in
|
|
98
|
+
`definitions()` — never silently drops one; a dropped decision is a decision the person believes
|
|
99
|
+
they made and did not (`@owlmeans/marketing-consent`'s own skill).
|
|
100
|
+
- Each submitted decision becomes ONE `MarketingConsentLogRecord` (`kind: 'consent'`), appended —
|
|
101
|
+
never updated, never deleted — then the state record's `decisions` array has that key's entry
|
|
102
|
+
REPLACED in place (by key) and the rest left untouched; a second save for the same key never
|
|
103
|
+
duplicates it, it supersedes it. `recordTerms` mirrors this for the state record's single `terms`
|
|
104
|
+
field, without touching `decisions`.
|
|
105
|
+
- The state record's create-vs-update race is handled the same way
|
|
106
|
+
`@owlmeans/server-auth-session`'s Redis manager handles a natural-id create race: try `create`,
|
|
107
|
+
and on `RecordExists` reload and `save` instead.
|
|
108
|
+
|
|
109
|
+
## The append-only log as GDPR Art. 7(1) evidence
|
|
110
|
+
|
|
111
|
+
`MarketingConsentLogRecord` rows are never updated or deleted, including by `purge()` — `purge`
|
|
112
|
+
clears only the current-state record (so future `status`/`isGranted` reads see a fresh subject);
|
|
113
|
+
the log keeps every decision and terms acceptance ever recorded, because that history is what
|
|
114
|
+
"demonstrates consent" under GDPR Art. 7(1) and is grounds for the whole design (`@owlmeans/
|
|
115
|
+
marketing-consent`'s own skill's legal matrix). A retention/erasure policy for the log itself is a
|
|
116
|
+
product decision this package does not make.
|
|
117
|
+
|
|
118
|
+
## `observe`
|
|
119
|
+
|
|
120
|
+
`save`/`recordTerms` call every registered listener, in registration order, each awaited and each
|
|
121
|
+
wrapped in its own try/catch — one listener's failure never blocks the write or stops the next
|
|
122
|
+
listener (the same shape `@owlmeans/server-auth-identity`'s `IdentityEventsService.
|
|
123
|
+
propagateEntityCreated` uses). Use it to sync a `MarketingConsentBridge` (a cookie-consent widget) or
|
|
124
|
+
to fan a decision out to a downstream system; never to do work the write itself depends on.
|
|
125
|
+
|
|
126
|
+
## Testing
|
|
127
|
+
|
|
128
|
+
Category B: `bun test ./tests` with real `@owlmeans/static-resource` stores for both resources, no
|
|
129
|
+
Mongo/Postgres (`tests/context.ts` builds the context and calls `appendMarketingConsentService`
|
|
130
|
+
directly — the app never calls `context.init()`/`configure()` in this style of test, since a
|
|
131
|
+
`Resource` lookup is synchronous and a `LazyService`'s `lazyInit` fires the first time
|
|
132
|
+
`context.service(alias)` is read). `tests/service.spec.ts` covers the upsert-in-place save, the
|
|
133
|
+
`isGranted` vs display-`granted` distinction (the single most important test in the package),
|
|
134
|
+
`purge` leaving the log untouched, and a throwing observer never blocking a write.
|
|
135
|
+
`tests/handlers.spec.ts` binds the three handlers and asserts `save`/`terms` refuse an
|
|
136
|
+
`AuthroizationType.AuthToken` request (`refuseTokenAuth`) while `status` never does.
|
|
137
|
+
`tests/subject.spec.ts` covers the no-`entity`-resolver case explicitly.
|
|
138
|
+
|
|
139
|
+
## Related
|
|
140
|
+
|
|
141
|
+
- `@owlmeans/marketing-consent` — the catalogue, `consentStatus`, the protocol tree (read its skill
|
|
142
|
+
first; this package builds on it and repeats nothing it already documents)
|
|
143
|
+
- `@owlmeans/server-oauth`, `@owlmeans/server-auth-token` — `refuseTokenAuth`, the session-guarded
|
|
144
|
+
handler shape this package's `save`/`terms` copy
|
|
145
|
+
- `@owlmeans/server-auth-identity` — `IdentityEventsService`, the `observe`/listener idiom this
|
|
146
|
+
package's `observe` mirrors
|
|
147
|
+
- `@owlmeans/marketing-consent-mongo`, `@owlmeans/marketing-consent-postgres` — later workstreams,
|
|
148
|
+
not built yet, that register the two resources this service reads by alias
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two resource aliases a Mongo/Postgres extension package (`@owlmeans/marketing-consent-mongo`,
|
|
3
|
+
* `@owlmeans/marketing-consent-postgres` — later workstreams, not built yet) registers its concrete
|
|
4
|
+
* resources under.
|
|
5
|
+
*
|
|
6
|
+
* These string values matter beyond this package: a target project's generated resource FILE names
|
|
7
|
+
* are derived from an alias via a deterministic `resourceAlias()` helper elsewhere in the monorepo
|
|
8
|
+
* (`entity/type` -> `entity-type`). Keep them exactly as declared here.
|
|
9
|
+
*/
|
|
10
|
+
export declare const RES_MARKETING_CONSENT_STATE = "marketing-consent-state";
|
|
11
|
+
export declare const RES_MARKETING_CONSENT_LOG = "marketing-consent-log";
|
|
12
|
+
//# sourceMappingURL=consts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,eAAO,MAAM,2BAA2B,4BAA4B,CAAA;AACpE,eAAO,MAAM,yBAAyB,0BAA0B,CAAA"}
|
package/build/consts.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two resource aliases a Mongo/Postgres extension package (`@owlmeans/marketing-consent-mongo`,
|
|
3
|
+
* `@owlmeans/marketing-consent-postgres` — later workstreams, not built yet) registers its concrete
|
|
4
|
+
* resources under.
|
|
5
|
+
*
|
|
6
|
+
* These string values matter beyond this package: a target project's generated resource FILE names
|
|
7
|
+
* are derived from an alias via a deterministic `resourceAlias()` helper elsewhere in the monorepo
|
|
8
|
+
* (`entity/type` -> `entity-type`). Keep them exactly as declared here.
|
|
9
|
+
*/
|
|
10
|
+
export const RES_MARKETING_CONSENT_STATE = 'marketing-consent-state';
|
|
11
|
+
export const RES_MARKETING_CONSENT_LOG = 'marketing-consent-log';
|
|
12
|
+
//# sourceMappingURL=consts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,yBAAyB,CAAA;AACpE,MAAM,CAAC,MAAM,yBAAyB,GAAG,uBAAuB,CAAA"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { MarketingConsentEntrypoints } from '@owlmeans/marketing-consent';
|
|
2
|
+
export interface MarketingConsentHandlerOptions {
|
|
3
|
+
serviceAlias?: string;
|
|
4
|
+
/** Let an OAuth-minted access token save decisions or record terms too. Defaults to `false` —
|
|
5
|
+
* `status` is never token-refused regardless of this option. */
|
|
6
|
+
allowAccessTokens?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare const marketingConsentStatus: (protocol: MarketingConsentEntrypoints['status'], opts?: MarketingConsentHandlerOptions) => import("@owlmeans/server-entrypoint").BoundEntrypointHandler<import("@owlmeans/entrypoint").EntrypointProtocol<{}, import("@owlmeans/marketing-consent").MarketingConsentStatusView>>;
|
|
9
|
+
export declare const saveMarketingConsent: (protocol: MarketingConsentEntrypoints['save'], opts?: MarketingConsentHandlerOptions) => import("@owlmeans/server-entrypoint").BoundEntrypointHandler<import("@owlmeans/entrypoint").EntrypointProtocol<{
|
|
10
|
+
body: import("@owlmeans/marketing-consent").SaveMarketingConsentRequest;
|
|
11
|
+
}, {
|
|
12
|
+
ok: boolean;
|
|
13
|
+
status: import("@owlmeans/marketing-consent").MarketingConsentStatusView;
|
|
14
|
+
}>>;
|
|
15
|
+
export declare const recordTermsAcceptance: (protocol: MarketingConsentEntrypoints['terms'], opts?: MarketingConsentHandlerOptions) => import("@owlmeans/server-entrypoint").BoundEntrypointHandler<import("@owlmeans/entrypoint").EntrypointProtocol<{
|
|
16
|
+
body: import("@owlmeans/marketing-consent").TermsAcceptance;
|
|
17
|
+
}, {
|
|
18
|
+
ok: boolean;
|
|
19
|
+
}>>;
|
|
20
|
+
//# sourceMappingURL=handlers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../src/handlers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAA;AAM9E,MAAM,WAAW,8BAA8B;IAC7C,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;oEACgE;IAChE,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B;AAYD,eAAO,MAAM,sBAAsB,aACvB,2BAA2B,CAAC,QAAQ,CAAC,SAAQ,8BAA8B,0LAEF,CAAA;AAErF,eAAO,MAAM,oBAAoB,aACrB,2BAA2B,CAAC,MAAM,CAAC,SAAQ,8BAA8B;;;;;GAKnF,CAAA;AAEF,eAAO,MAAM,qBAAqB,aACtB,2BAA2B,CAAC,OAAO,CAAC,SAAQ,8BAA8B;;;;GAKpF,CAAA"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { MARKETING_CONSENT_SERVICE } from '@owlmeans/marketing-consent';
|
|
2
|
+
import { handlers } from '@owlmeans/server-api';
|
|
3
|
+
import { refuseTokenAuth } from '@owlmeans/server-auth-token';
|
|
4
|
+
import { subjectOf } from './subject.js';
|
|
5
|
+
const serviceOf = (context, opts) => context.service(opts?.serviceAlias ?? MARKETING_CONSENT_SERVICE);
|
|
6
|
+
/** GPC ("Sec-GPC: 1") read as a plain header — Fastify normalizes header names to lower case, and
|
|
7
|
+
* a repeated header arrives as an array, so only its first value is read. */
|
|
8
|
+
const gpcOf = (headers) => {
|
|
9
|
+
const raw = headers['sec-gpc'];
|
|
10
|
+
return (Array.isArray(raw) ? raw[0] : raw) === '1';
|
|
11
|
+
};
|
|
12
|
+
export const marketingConsentStatus = (protocol, opts = {}) => handlers().request(protocol, async (req, context) => serviceOf(context, opts).status(subjectOf(req), { gpc: gpcOf(req.headers ?? {}) }));
|
|
13
|
+
export const saveMarketingConsent = (protocol, opts = {}) => handlers().body(protocol, async (payload, context, req) => {
|
|
14
|
+
if (opts.allowAccessTokens !== true)
|
|
15
|
+
refuseTokenAuth(req, 'marketing-consent-save');
|
|
16
|
+
return serviceOf(context, opts).save(subjectOf(req), payload);
|
|
17
|
+
});
|
|
18
|
+
export const recordTermsAcceptance = (protocol, opts = {}) => handlers().body(protocol, async (payload, context, req) => {
|
|
19
|
+
if (opts.allowAccessTokens !== true)
|
|
20
|
+
refuseTokenAuth(req, 'marketing-consent-terms');
|
|
21
|
+
return serviceOf(context, opts).recordTerms(subjectOf(req), payload);
|
|
22
|
+
});
|
|
23
|
+
//# sourceMappingURL=handlers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handlers.js","sourceRoot":"","sources":["../src/handlers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAA;AAEvE,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAA;AAE7D,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AASxC,MAAM,SAAS,GAAG,CAAC,OAAgC,EAAE,IAAqC,EAA2B,EAAE,CACrH,OAAO,CAAC,OAAO,CAA0B,IAAI,EAAE,YAAY,IAAI,yBAAyB,CAAC,CAAA;AAE3F;6EAC6E;AAC7E,MAAM,KAAK,GAAG,CAAC,OAAsD,EAAW,EAAE;IAChF,MAAM,GAAG,GAAG,OAAO,CAAC,SAAS,CAAC,CAAA;IAC9B,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,GAAG,CAAA;AACpD,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,sBAAsB,GAAG,CACpC,QAA+C,EAAE,IAAI,GAAmC,EAAE,EAC1F,EAAE,CAAC,QAAQ,EAA2B,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,CAChF,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;AAErF,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAClC,QAA6C,EAAE,IAAI,GAAmC,EAAE,EACxF,EAAE,CAAC,QAAQ,EAA2B,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE;IACtF,IAAI,IAAI,CAAC,iBAAiB,KAAK,IAAI;QAAE,eAAe,CAAC,GAAG,EAAE,wBAAwB,CAAC,CAAA;IAEnF,OAAO,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAA;AAC/D,CAAC,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,qBAAqB,GAAG,CACnC,QAA8C,EAAE,IAAI,GAAmC,EAAE,EACzF,EAAE,CAAC,QAAQ,EAA2B,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE;IACtF,IAAI,IAAI,CAAC,iBAAiB,KAAK,IAAI;QAAE,eAAe,CAAC,GAAG,EAAE,yBAAyB,CAAC,CAAA;IAEpF,OAAO,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,WAAW,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAA;AACtE,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { MarketingConsentEntrypoints } from '@owlmeans/marketing-consent';
|
|
2
|
+
import type { MarketingConsentHandlerOptions } from './handlers.js';
|
|
3
|
+
/**
|
|
4
|
+
* Bind this package's handlers to a tree declared by `makeMarketingConsentProtocols` — one binding
|
|
5
|
+
* per protocol that needs a server implementation. `base` and `screen` carry no handler of their
|
|
6
|
+
* own (a mounting anchor and a frontend address, the same shape as `@owlmeans/oauth`'s own consent
|
|
7
|
+
* screens) and are left for the application's route tree to materialize.
|
|
8
|
+
*/
|
|
9
|
+
export declare const serveMarketingConsentEntrypoints: (protocols: MarketingConsentEntrypoints, opts?: MarketingConsentHandlerOptions) => (import("@owlmeans/server-entrypoint").ServerProtocolEntrypoint<import("@owlmeans/entrypoint").EntrypointProtocol<{
|
|
10
|
+
body: import("@owlmeans/marketing-consent").SaveMarketingConsentRequest;
|
|
11
|
+
}, {
|
|
12
|
+
ok: boolean;
|
|
13
|
+
status: import("@owlmeans/marketing-consent").MarketingConsentStatusView;
|
|
14
|
+
}>> | import("@owlmeans/server-entrypoint").ServerProtocolEntrypoint<import("@owlmeans/entrypoint").EntrypointProtocol<{
|
|
15
|
+
body: import("@owlmeans/marketing-consent").TermsAcceptance;
|
|
16
|
+
}, {
|
|
17
|
+
ok: boolean;
|
|
18
|
+
}>> | import("@owlmeans/server-entrypoint").ServerProtocolEntrypoint<import("@owlmeans/entrypoint").EntrypointProtocol<{}, import("@owlmeans/marketing-consent").MarketingConsentStatusView>>)[];
|
|
19
|
+
//# sourceMappingURL=helper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAA;AAE9E,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,eAAe,CAAA;AAEnE;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,cAChC,2BAA2B,SAAQ,8BAA8B;;;;;;;;;gMAK7E,CAAA"}
|
package/build/helper.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { bind } from '@owlmeans/server-entrypoint';
|
|
2
|
+
import { marketingConsentStatus, recordTermsAcceptance, saveMarketingConsent } from './handlers.js';
|
|
3
|
+
/**
|
|
4
|
+
* Bind this package's handlers to a tree declared by `makeMarketingConsentProtocols` — one binding
|
|
5
|
+
* per protocol that needs a server implementation. `base` and `screen` carry no handler of their
|
|
6
|
+
* own (a mounting anchor and a frontend address, the same shape as `@owlmeans/oauth`'s own consent
|
|
7
|
+
* screens) and are left for the application's route tree to materialize.
|
|
8
|
+
*/
|
|
9
|
+
export const serveMarketingConsentEntrypoints = (protocols, opts = {}) => [
|
|
10
|
+
bind(protocols.status, marketingConsentStatus(protocols.status, opts)),
|
|
11
|
+
bind(protocols.save, saveMarketingConsent(protocols.save, opts)),
|
|
12
|
+
bind(protocols.terms, recordTermsAcceptance(protocols.terms, opts)),
|
|
13
|
+
];
|
|
14
|
+
//# sourceMappingURL=helper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helper.js","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAA;AAElD,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AAGnG;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gCAAgC,GAAG,CAC9C,SAAsC,EAAE,IAAI,GAAmC,EAAE,EACjF,EAAE,CAAC;IACH,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,sBAAsB,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACtE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,oBAAoB,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAChE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,qBAAqB,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;CACpE,CAAA"}
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA"}
|
package/build/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA"}
|
package/build/model.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { JSONSchemaType } from 'ajv';
|
|
2
|
+
import type { ResourceRecord } from '@owlmeans/resource';
|
|
3
|
+
import type { MarketingConsentDecision, MarketingConsentMode, MarketingConsentSource, TermsDocumentRef } from '@owlmeans/marketing-consent';
|
|
4
|
+
/**
|
|
5
|
+
* One subject's saved decisions, one record per subject (`id` = `subjectKey(subject)`).
|
|
6
|
+
*
|
|
7
|
+
* `decisions` is an ARRAY, never an object keyed by consent key. A dotted key such as
|
|
8
|
+
* `"marketing.email"` is read as a PATH by both Mongo dot-notation queries and Postgres jsonb path
|
|
9
|
+
* operators — an object-keyed shape breaks the moment a second consent key is added. This is the
|
|
10
|
+
* single most important shape decision in this package; the Mongo/Postgres extensions that will
|
|
11
|
+
* store this record must not "flatten" it into an object for convenience.
|
|
12
|
+
*/
|
|
13
|
+
export interface MarketingConsentStateRecord extends ResourceRecord {
|
|
14
|
+
id: string;
|
|
15
|
+
/** `subjectKey(subjectOf(req))` — see `./subject.js`. Also this record's own `id`. */
|
|
16
|
+
subject: string;
|
|
17
|
+
userId: string;
|
|
18
|
+
profileId?: string;
|
|
19
|
+
entityId?: string;
|
|
20
|
+
decisions: MarketingConsentDecision[];
|
|
21
|
+
terms?: {
|
|
22
|
+
documents: TermsDocumentRef[];
|
|
23
|
+
notices?: TermsDocumentRef[];
|
|
24
|
+
version: string;
|
|
25
|
+
locale?: string;
|
|
26
|
+
acceptedAt: string;
|
|
27
|
+
};
|
|
28
|
+
gpc?: boolean;
|
|
29
|
+
createdAt: string;
|
|
30
|
+
updatedAt: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Append-only evidence of every decision and terms acceptance ever recorded — GDPR Art. 7(1)
|
|
34
|
+
* "demonstrate consent" material. Never updated or deleted, including on `purge()`: only the
|
|
35
|
+
* current-state record is cleared there.
|
|
36
|
+
*/
|
|
37
|
+
export interface MarketingConsentLogRecord extends ResourceRecord {
|
|
38
|
+
id: string;
|
|
39
|
+
subject: string;
|
|
40
|
+
userId: string;
|
|
41
|
+
profileId?: string;
|
|
42
|
+
entityId?: string;
|
|
43
|
+
kind: 'consent' | 'terms';
|
|
44
|
+
/** Present when `kind === 'consent'`. */
|
|
45
|
+
key?: string;
|
|
46
|
+
granted?: boolean;
|
|
47
|
+
revisedAt?: string;
|
|
48
|
+
mode?: MarketingConsentMode;
|
|
49
|
+
/** Present when `kind === 'terms'`. */
|
|
50
|
+
documents?: TermsDocumentRef[];
|
|
51
|
+
notices?: TermsDocumentRef[];
|
|
52
|
+
version?: string;
|
|
53
|
+
decidedAt: string;
|
|
54
|
+
source: MarketingConsentSource;
|
|
55
|
+
locale?: string;
|
|
56
|
+
gpc?: boolean;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Shared with the Mongo/Postgres extension packages this schema is built for — imported by them,
|
|
60
|
+
* never duplicated.
|
|
61
|
+
*/
|
|
62
|
+
export declare const MarketingConsentStateSchema: JSONSchemaType<MarketingConsentStateRecord>;
|
|
63
|
+
export declare const MarketingConsentLogSchema: JSONSchemaType<MarketingConsentLogRecord>;
|
|
64
|
+
//# sourceMappingURL=model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,KAAK,CAAA;AACzC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,KAAK,EACV,wBAAwB,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,gBAAgB,EACzF,MAAM,6BAA6B,CAAA;AAEpC;;;;;;;;GAQG;AACH,MAAM,WAAW,2BAA4B,SAAQ,cAAc;IACjE,EAAE,EAAE,MAAM,CAAA;IACV,sFAAsF;IACtF,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,wBAAwB,EAAE,CAAA;IACrC,KAAK,CAAC,EAAE;QACN,SAAS,EAAE,gBAAgB,EAAE,CAAA;QAC7B,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAA;QAC5B,OAAO,EAAE,MAAM,CAAA;QACf,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,UAAU,EAAE,MAAM,CAAA;KACnB,CAAA;IACD,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,yBAA0B,SAAQ,cAAc;IAC/D,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,SAAS,GAAG,OAAO,CAAA;IACzB,yCAAyC;IACzC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,IAAI,CAAC,EAAE,oBAAoB,CAAA;IAC3B,uCAAuC;IACvC,SAAS,CAAC,EAAE,gBAAgB,EAAE,CAAA;IAC9B,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAA;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,sBAAsB,CAAA;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,GAAG,CAAC,EAAE,OAAO,CAAA;CACd;AA6BD;;;GAGG;AACH,eAAO,MAAM,2BAA2B,EAAE,cAAc,CAAC,2BAA2B,CA4BnF,CAAA;AAED,eAAO,MAAM,yBAAyB,EAAE,cAAc,CAAC,yBAAyB,CAuB/E,CAAA"}
|
package/build/model.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// `@owlmeans/marketing-consent`'s own `TermsDocumentRefSchema` (in its `src/schemas.ts`) is not
|
|
2
|
+
// exported, so the shape is re-declared here rather than imported.
|
|
3
|
+
const TermsDocumentRefSchema = {
|
|
4
|
+
type: 'object',
|
|
5
|
+
properties: {
|
|
6
|
+
key: { type: 'string', minLength: 1, maxLength: 128 },
|
|
7
|
+
href: { type: 'string', minLength: 1, maxLength: 2048 },
|
|
8
|
+
revisedAt: { type: 'string', nullable: true },
|
|
9
|
+
},
|
|
10
|
+
required: ['key', 'href'],
|
|
11
|
+
additionalProperties: false,
|
|
12
|
+
};
|
|
13
|
+
const MarketingConsentDecisionSchema = {
|
|
14
|
+
type: 'object',
|
|
15
|
+
properties: {
|
|
16
|
+
key: { type: 'string', minLength: 1, maxLength: 64 },
|
|
17
|
+
granted: { type: 'boolean' },
|
|
18
|
+
revisedAt: { type: 'string', minLength: 1 },
|
|
19
|
+
mode: { type: 'string', enum: ['opt-in', 'opt-out'] },
|
|
20
|
+
decidedAt: { type: 'string', minLength: 1 },
|
|
21
|
+
source: { type: 'string', enum: ['sign-in', 'settings', 'cookie', 'api'] },
|
|
22
|
+
},
|
|
23
|
+
required: ['key', 'granted', 'revisedAt', 'mode', 'decidedAt', 'source'],
|
|
24
|
+
additionalProperties: false,
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Shared with the Mongo/Postgres extension packages this schema is built for — imported by them,
|
|
28
|
+
* never duplicated.
|
|
29
|
+
*/
|
|
30
|
+
export const MarketingConsentStateSchema = {
|
|
31
|
+
type: 'object',
|
|
32
|
+
properties: {
|
|
33
|
+
id: { type: 'string', minLength: 1 },
|
|
34
|
+
subject: { type: 'string', minLength: 1 },
|
|
35
|
+
userId: { type: 'string', minLength: 1 },
|
|
36
|
+
profileId: { type: 'string', nullable: true },
|
|
37
|
+
entityId: { type: 'string', nullable: true },
|
|
38
|
+
decisions: { type: 'array', items: MarketingConsentDecisionSchema },
|
|
39
|
+
terms: {
|
|
40
|
+
type: 'object',
|
|
41
|
+
nullable: true,
|
|
42
|
+
properties: {
|
|
43
|
+
documents: { type: 'array', items: TermsDocumentRefSchema },
|
|
44
|
+
notices: { type: 'array', items: TermsDocumentRefSchema, nullable: true },
|
|
45
|
+
version: { type: 'string', minLength: 1 },
|
|
46
|
+
locale: { type: 'string', nullable: true },
|
|
47
|
+
acceptedAt: { type: 'string', minLength: 1 },
|
|
48
|
+
},
|
|
49
|
+
required: ['documents', 'version', 'acceptedAt'],
|
|
50
|
+
additionalProperties: false,
|
|
51
|
+
},
|
|
52
|
+
gpc: { type: 'boolean', nullable: true },
|
|
53
|
+
createdAt: { type: 'string', minLength: 1 },
|
|
54
|
+
updatedAt: { type: 'string', minLength: 1 },
|
|
55
|
+
},
|
|
56
|
+
required: ['id', 'subject', 'userId', 'decisions', 'createdAt', 'updatedAt'],
|
|
57
|
+
additionalProperties: false,
|
|
58
|
+
};
|
|
59
|
+
export const MarketingConsentLogSchema = {
|
|
60
|
+
type: 'object',
|
|
61
|
+
properties: {
|
|
62
|
+
id: { type: 'string', minLength: 1 },
|
|
63
|
+
subject: { type: 'string', minLength: 1 },
|
|
64
|
+
userId: { type: 'string', minLength: 1 },
|
|
65
|
+
profileId: { type: 'string', nullable: true },
|
|
66
|
+
entityId: { type: 'string', nullable: true },
|
|
67
|
+
kind: { type: 'string', enum: ['consent', 'terms'] },
|
|
68
|
+
key: { type: 'string', nullable: true },
|
|
69
|
+
granted: { type: 'boolean', nullable: true },
|
|
70
|
+
revisedAt: { type: 'string', nullable: true },
|
|
71
|
+
mode: { type: 'string', enum: ['opt-in', 'opt-out'], nullable: true },
|
|
72
|
+
documents: { type: 'array', items: TermsDocumentRefSchema, nullable: true },
|
|
73
|
+
notices: { type: 'array', items: TermsDocumentRefSchema, nullable: true },
|
|
74
|
+
version: { type: 'string', nullable: true },
|
|
75
|
+
decidedAt: { type: 'string', minLength: 1 },
|
|
76
|
+
source: { type: 'string', enum: ['sign-in', 'settings', 'cookie', 'api'] },
|
|
77
|
+
locale: { type: 'string', nullable: true },
|
|
78
|
+
gpc: { type: 'boolean', nullable: true },
|
|
79
|
+
},
|
|
80
|
+
required: ['id', 'subject', 'userId', 'kind', 'decidedAt', 'source'],
|
|
81
|
+
additionalProperties: false,
|
|
82
|
+
};
|
|
83
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { LazyService } from '@owlmeans/context';
|
|
2
|
+
import type { MarketingConsentConfig, MarketingConsentDecision, MarketingConsentDefinition, MarketingConsentSource, MarketingConsentStatusView, SaveMarketingConsentRequest, TermsAcceptance } from '@owlmeans/marketing-consent';
|
|
3
|
+
import type { ServerConfig, ServerContext } from '@owlmeans/server-context';
|
|
4
|
+
import type { MarketingConsentSubject } from './subject.js';
|
|
5
|
+
export type MarketingConsentContext = ServerContext<ServerConfig>;
|
|
6
|
+
export type MarketingConsentObserver = (event: {
|
|
7
|
+
subject: MarketingConsentSubject;
|
|
8
|
+
decisions: MarketingConsentDecision[];
|
|
9
|
+
}) => void | Promise<void>;
|
|
10
|
+
export interface MarketingConsentService extends LazyService {
|
|
11
|
+
/** The effective catalogue (`resolveMarketingConsents`), computed once and memoized. */
|
|
12
|
+
definitions(): MarketingConsentDefinition[];
|
|
13
|
+
status(subject: MarketingConsentSubject, opts?: {
|
|
14
|
+
gpc?: boolean;
|
|
15
|
+
}): Promise<MarketingConsentStatusView>;
|
|
16
|
+
save(subject: MarketingConsentSubject, request: SaveMarketingConsentRequest | (Omit<SaveMarketingConsentRequest, 'source'> & {
|
|
17
|
+
source: 'api';
|
|
18
|
+
})): Promise<{
|
|
19
|
+
ok: true;
|
|
20
|
+
status: MarketingConsentStatusView;
|
|
21
|
+
}>;
|
|
22
|
+
recordTerms(subject: MarketingConsentSubject, acceptance: TermsAcceptance, opts?: {
|
|
23
|
+
source?: MarketingConsentSource;
|
|
24
|
+
}): Promise<{
|
|
25
|
+
ok: true;
|
|
26
|
+
}>;
|
|
27
|
+
/**
|
|
28
|
+
* The SERVER-SIDE gate a send/share checks — the SAVED, CONFIRMED answer only. An item still
|
|
29
|
+
* `'new'` or `'revised'` has never been affirmatively answered by this person, so it reads as
|
|
30
|
+
* NOT granted here even where `consentStatus`'s own `granted` defaults an opt-out item to `true`
|
|
31
|
+
* for DISPLAY purposes.
|
|
32
|
+
*/
|
|
33
|
+
isGranted(subject: MarketingConsentSubject, key: string, opts?: {
|
|
34
|
+
gpc?: boolean;
|
|
35
|
+
}): Promise<boolean>;
|
|
36
|
+
/** Clears the current-state record. Log rows stay — they are the append-only compliance evidence. */
|
|
37
|
+
purge(subject: MarketingConsentSubject): Promise<void>;
|
|
38
|
+
observe(listener: MarketingConsentObserver): void;
|
|
39
|
+
}
|
|
40
|
+
export interface MakeMarketingConsentServiceOptions {
|
|
41
|
+
alias?: string;
|
|
42
|
+
/** Resource alias holding one current-state record per subject. Defaults to `RES_MARKETING_CONSENT_STATE`. */
|
|
43
|
+
state?: string;
|
|
44
|
+
/** Resource alias holding the append-only log. Defaults to `RES_MARKETING_CONSENT_LOG`. */
|
|
45
|
+
log?: string;
|
|
46
|
+
config?: MarketingConsentConfig;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Build the `MarketingConsentService`. Database-agnostic: both resources are resolved BY ALIAS
|
|
50
|
+
* from the context lazily, inside each method call — never imported or constructed here. A
|
|
51
|
+
* Mongo/Postgres extension package registers the actual resources at those aliases separately.
|
|
52
|
+
*/
|
|
53
|
+
export declare const makeMarketingConsentService: (opts?: MakeMarketingConsentServiceOptions) => MarketingConsentService;
|
|
54
|
+
/** Register the service, unless the application already registered its own under this alias. */
|
|
55
|
+
export declare const appendMarketingConsentService: (context: MarketingConsentContext, opts?: MakeMarketingConsentServiceOptions) => void;
|
|
56
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAIpD,OAAO,KAAK,EACV,sBAAsB,EAAE,wBAAwB,EAAE,0BAA0B,EAC5E,sBAAsB,EAAE,0BAA0B,EAAE,2BAA2B,EAAE,eAAe,EACjG,MAAM,6BAA6B,CAAA;AAGpC,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAI3E,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAA;AAE3D,MAAM,MAAM,uBAAuB,GAAG,aAAa,CAAC,YAAY,CAAC,CAAA;AAEjE,MAAM,MAAM,wBAAwB,GAClC,CAAC,KAAK,EAAE;IAAE,OAAO,EAAE,uBAAuB,CAAC;IAAC,SAAS,EAAE,wBAAwB,EAAE,CAAA;CAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;AAE9G,MAAM,WAAW,uBAAwB,SAAQ,WAAW;IAC1D,wFAAwF;IACxF,WAAW,IAAI,0BAA0B,EAAE,CAAA;IAC3C,MAAM,CAAC,OAAO,EAAE,uBAAuB,EAAE,IAAI,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAAA;IACvG,IAAI,CACF,OAAO,EAAE,uBAAuB,EAChC,OAAO,EAAE,2BAA2B,GAAG,CAAC,IAAI,CAAC,2BAA2B,EAAE,QAAQ,CAAC,GAAG;QAAE,MAAM,EAAE,KAAK,CAAA;KAAE,CAAC,GACvG,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,MAAM,EAAE,0BAA0B,CAAA;KAAE,CAAC,CAAA;IAC5D,WAAW,CACT,OAAO,EAAE,uBAAuB,EAAE,UAAU,EAAE,eAAe,EAAE,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,sBAAsB,CAAA;KAAE,GACxG,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,CAAC,CAAA;IACxB;;;;;OAKG;IACH,SAAS,CAAC,OAAO,EAAE,uBAAuB,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IACpG,qGAAqG;IACrG,KAAK,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACtD,OAAO,CAAC,QAAQ,EAAE,wBAAwB,GAAG,IAAI,CAAA;CAClD;AAED,MAAM,WAAW,kCAAkC;IACjD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,8GAA8G;IAC9G,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,2FAA2F;IAC3F,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,sBAAsB,CAAA;CAChC;AAID;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,UAChC,kCAAkC,KACvC,uBAuJF,CAAA;AAED,gGAAgG;AAChG,eAAO,MAAM,6BAA6B,YAC/B,uBAAuB,SAAQ,kCAAkC,KACzE,IAKF,CAAA"}
|