@vllnt/convex-suppression 0.1.0-canary.261f634
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/LICENSE +21 -0
- package/README.md +138 -0
- package/dist/client/index.d.ts +135 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +131 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/types.d.ts +70 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +3 -0
- package/dist/client/types.js.map +1 -0
- package/dist/component/_generated/api.d.ts +38 -0
- package/dist/component/_generated/api.d.ts.map +1 -0
- package/dist/component/_generated/api.js +31 -0
- package/dist/component/_generated/api.js.map +1 -0
- package/dist/component/_generated/component.d.ts +67 -0
- package/dist/component/_generated/component.d.ts.map +1 -0
- package/dist/component/_generated/component.js +11 -0
- package/dist/component/_generated/component.js.map +1 -0
- package/dist/component/_generated/dataModel.d.ts +46 -0
- package/dist/component/_generated/dataModel.d.ts.map +1 -0
- package/dist/component/_generated/dataModel.js +11 -0
- package/dist/component/_generated/dataModel.js.map +1 -0
- package/dist/component/_generated/server.d.ts +121 -0
- package/dist/component/_generated/server.d.ts.map +1 -0
- package/dist/component/_generated/server.js +78 -0
- package/dist/component/_generated/server.js.map +1 -0
- package/dist/component/convex.config.d.ts +3 -0
- package/dist/component/convex.config.d.ts.map +1 -0
- package/dist/component/convex.config.js +7 -0
- package/dist/component/convex.config.js.map +1 -0
- package/dist/component/mutations.d.ts +49 -0
- package/dist/component/mutations.d.ts.map +1 -0
- package/dist/component/mutations.js +110 -0
- package/dist/component/mutations.js.map +1 -0
- package/dist/component/queries.d.ts +46 -0
- package/dist/component/queries.d.ts.map +1 -0
- package/dist/component/queries.js +112 -0
- package/dist/component/queries.js.map +1 -0
- package/dist/component/schema.d.ts +51 -0
- package/dist/component/schema.d.ts.map +1 -0
- package/dist/component/schema.js +39 -0
- package/dist/component/schema.js.map +1 -0
- package/dist/component/validators.d.ts +50 -0
- package/dist/component/validators.d.ts.map +1 -0
- package/dist/component/validators.js +40 -0
- package/dist/component/validators.js.map +1 -0
- package/dist/shared.d.ts +22 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +26 -0
- package/dist/shared.js.map +1 -0
- package/package.json +101 -0
- package/src/client/index.ts +271 -0
- package/src/client/types.ts +82 -0
- package/src/component/_generated/api.ts +54 -0
- package/src/component/_generated/component.ts +102 -0
- package/src/component/_generated/dataModel.ts +60 -0
- package/src/component/_generated/server.ts +156 -0
- package/src/component/convex.config.ts +9 -0
- package/src/component/mutations.ts +118 -0
- package/src/component/queries.ts +128 -0
- package/src/component/schema.ts +40 -0
- package/src/component/validators.ts +49 -0
- package/src/shared.ts +31 -0
- package/src/test.ts +15 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 bntvllnt
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
<!-- Badges -->
|
|
2
|
+
[](https://www.convex.dev/components)
|
|
3
|
+
[](https://www.npmjs.com/package/@vllnt/convex-suppression)
|
|
4
|
+
[](https://github.com/vllnt/convex-suppression/actions/workflows/ci.yml)
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
|
|
7
|
+
# @vllnt/convex-suppression
|
|
8
|
+
|
|
9
|
+
The do-not-contact suppression list + opt-in proof, as a Convex component.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
const dnc = new Suppression(components.suppression);
|
|
13
|
+
await dnc.suppress(ctx, contactHash, "complaint", { channel: "email" });
|
|
14
|
+
const canSend = await dnc.isEligible(ctx, contactHash, { channel: "email" });
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The GDPR opt-out / CAN-SPAM primitive: a global do-not-contact list keyed by an
|
|
18
|
+
opaque **`contactHash`** (never raw PII, so it survives erasure) plus an opt-in
|
|
19
|
+
proof ledger. Before every send the host asks `isEligible`; an unsubscribe / bounce
|
|
20
|
+
/ complaint calls `suppress`; a double-opt-in confirmation calls `recordOptIn`.
|
|
21
|
+
Domain-neutral — channels and reasons are the host's.
|
|
22
|
+
|
|
23
|
+
## Features
|
|
24
|
+
|
|
25
|
+
- **Hash-keyed, erasure-surviving** — stores only the opaque `contactHash`, never raw PII, so a tombstone outlives erasure of the subject.
|
|
26
|
+
- **Channel-aware** — scope a suppression to one channel (`"email"`/`"sms"`/`"push"`), or omit `channel` for a global all-channel tombstone.
|
|
27
|
+
- **One send gate** — `isEligible` answers `¬suppressed [∧ confirmed]` in one call; suppression always blocks, opt-in is per call.
|
|
28
|
+
- **Opt-in proof ledger** — `recordOptIn` / `getOptInProof` store legal proof-of-consent per list, kept separate from any authz store.
|
|
29
|
+
- **Idempotent** — re-suppressing / re-recording on its key tuple updates in place, so a replayed webhook never duplicates a row.
|
|
30
|
+
- **Typed, opaque proof** — `Suppression<TProof>` with an optional `proofValidator` narrows the stored evidence at the boundary.
|
|
31
|
+
- **Server-sourced time** — `createdAt`/`confirmedAt` are stamped from the server clock; a caller can never supply a timestamp.
|
|
32
|
+
- **Mount-safe** — correct under multiple named `app.use` mounts (e.g. marketing vs. transactional lists), each an isolated sandbox.
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pnpm add @vllnt/convex-suppression
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Peer dependency: `convex@^1.41.0`.
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
// convex/convex.config.ts
|
|
46
|
+
import { defineApp } from "convex/server";
|
|
47
|
+
import suppression from "@vllnt/convex-suppression/convex.config";
|
|
48
|
+
|
|
49
|
+
const app = defineApp();
|
|
50
|
+
app.use(suppression);
|
|
51
|
+
export default app;
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
// convex/email.ts — host owns auth + hashing; pass an opaque contactHash in.
|
|
56
|
+
import { components } from "./_generated/api";
|
|
57
|
+
import { mutation, query } from "./_generated/server";
|
|
58
|
+
import { v } from "convex/values";
|
|
59
|
+
import { Suppression } from "@vllnt/convex-suppression";
|
|
60
|
+
|
|
61
|
+
const dnc = new Suppression<{ ip: string }>(components.suppression, {
|
|
62
|
+
proofValidator: v.object({ ip: v.string() }).parse,
|
|
63
|
+
});
|
|
64
|
+
const hash = (email: string) => myHash(email.trim().toLowerCase()); // host's hashing + salt policy
|
|
65
|
+
|
|
66
|
+
// The send gate — call before every send.
|
|
67
|
+
export const canEmail = query({
|
|
68
|
+
args: { email: v.string() },
|
|
69
|
+
handler: (ctx, { email }) =>
|
|
70
|
+
dnc.isEligible(ctx, hash(email), { channel: "email", listKey: "newsletter", requireOptIn: true }),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// A provider webhook suppresses on complaint; a confirmed double opt-in records proof.
|
|
74
|
+
export const onComplaint = mutation({
|
|
75
|
+
args: { email: v.string() },
|
|
76
|
+
handler: (ctx, { email }) => dnc.suppress(ctx, hash(email), "complaint", { channel: "email" }),
|
|
77
|
+
});
|
|
78
|
+
export const confirmOptIn = mutation({
|
|
79
|
+
args: { email: v.string(), ip: v.string() },
|
|
80
|
+
handler: (ctx, { email, ip }) =>
|
|
81
|
+
dnc.recordOptIn(ctx, hash(email), { listKey: "newsletter", source: "double-opt-in", proof: { ip } }),
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Client options: `new Suppression(component, { proofValidator? })`. Omitting `channel`/`listKey` targets the global entry.
|
|
86
|
+
|
|
87
|
+
## API Reference
|
|
88
|
+
|
|
89
|
+
| Method | Kind | Result |
|
|
90
|
+
|--------|------|--------|
|
|
91
|
+
| `suppress(ctx, contactHash, reason, opts?)` | mutation | `null` (`reason`: `"unsubscribe" \| "bounce" \| "complaint" \| "manual" \| "global"`; `opts`: `{ channel? }`) |
|
|
92
|
+
| `unsuppress(ctx, contactHash, channel?)` | mutation | `boolean` (`true` if an entry was removed) |
|
|
93
|
+
| `recordOptIn(ctx, contactHash, opts)` | mutation | `null` (`opts`: `{ listKey?; source; proof? }`) |
|
|
94
|
+
| `isSuppressed(ctx, contactHash, channel?)` | query | `SuppressionView \| null` |
|
|
95
|
+
| `getOptInProof(ctx, contactHash, listKey?)` | query | `OptInProofView \| null` |
|
|
96
|
+
| `isEligible(ctx, contactHash, opts?)` | query | `boolean` (`opts`: `{ channel?; listKey?; requireOptIn? }`) |
|
|
97
|
+
|
|
98
|
+
Full reference: [docs/API.md](docs/API.md).
|
|
99
|
+
|
|
100
|
+
## React
|
|
101
|
+
|
|
102
|
+
Backend-only — no `./react` entry. The consumer surface is a server-side
|
|
103
|
+
`isEligible` gate and webhook-driven `suppress` writes; a reactive do-not-contact
|
|
104
|
+
badge, if ever needed, is an ordinary `useQuery` over the host's re-exported
|
|
105
|
+
`isSuppressed` ref.
|
|
106
|
+
|
|
107
|
+
## Security
|
|
108
|
+
|
|
109
|
+
- **Auth-agnostic** — the host authenticates the caller, decides who may suppress/query a hash, and passes an opaque `contactHash`; tables are sandboxed.
|
|
110
|
+
- **Hash-keyed, never raw PII** — the host hashes and normalizes the contact and owns the salt policy; a suppression survives erasure of the subject.
|
|
111
|
+
- **Server-sourced time** — `createdAt`/`confirmedAt` come from `Date.now()` in each handler, never the caller; the opt-in `proof` is opaque, narrowed by the host validator.
|
|
112
|
+
|
|
113
|
+
See [docs/API.md](docs/API.md).
|
|
114
|
+
|
|
115
|
+
## Testing
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
pnpm test # single run
|
|
119
|
+
pnpm test:coverage # enforced 100% on covered files
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Tests run against the real component runtime via `convex-test` (`@edge-runtime/vm`), not mocks.
|
|
123
|
+
|
|
124
|
+
## Contributing
|
|
125
|
+
|
|
126
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
127
|
+
|
|
128
|
+
## Author
|
|
129
|
+
|
|
130
|
+
Built by [bntvllnt](https://github.com/bntvllnt) · [bntvllnt.com](https://bntvllnt.com) · [X @bntvllnt](https://x.com/bntvllnt)
|
|
131
|
+
|
|
132
|
+
Part of the [@vllnt](https://github.com/vllnt) Convex component fleet — [vllnt.com](https://vllnt.com)
|
|
133
|
+
|
|
134
|
+
If this is useful, [sponsor the work](https://github.com/sponsors/bntvllnt).
|
|
135
|
+
|
|
136
|
+
## License
|
|
137
|
+
|
|
138
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { FunctionArgs, FunctionReference, FunctionReturnType } from "convex/server";
|
|
2
|
+
import type { IsEligibleOptions, OptInProofView, Parser, RecordOptInOptions, SuppressionOptions, SuppressionReason, SuppressionView, SuppressOptions } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* The component's raw opt-in proof view, before the client narrows opaque host
|
|
5
|
+
* evidence. `proof` is `unknown` here; the {@link Suppression} client runs the
|
|
6
|
+
* host validator over it at its typed boundary.
|
|
7
|
+
*/
|
|
8
|
+
type RawProofView = {
|
|
9
|
+
contactHash: string;
|
|
10
|
+
listKey: string | null;
|
|
11
|
+
source: string;
|
|
12
|
+
proof?: unknown;
|
|
13
|
+
confirmedAt: number;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* The suppression component's function references, as exposed on the host via
|
|
17
|
+
* `components.suppression`. The host's stored opt-in `proof` is opaque here
|
|
18
|
+
* (`unknown`); the {@link Suppression} client narrows it at its own typed boundary.
|
|
19
|
+
*/
|
|
20
|
+
export interface SuppressionComponent {
|
|
21
|
+
mutations: {
|
|
22
|
+
suppress: FunctionReference<"mutation", "internal", {
|
|
23
|
+
contactHash: string;
|
|
24
|
+
channel: string;
|
|
25
|
+
reason: SuppressionReason;
|
|
26
|
+
}, null>;
|
|
27
|
+
unsuppress: FunctionReference<"mutation", "internal", {
|
|
28
|
+
contactHash: string;
|
|
29
|
+
channel: string;
|
|
30
|
+
}, boolean>;
|
|
31
|
+
recordOptIn: FunctionReference<"mutation", "internal", {
|
|
32
|
+
contactHash: string;
|
|
33
|
+
listKey: string;
|
|
34
|
+
source: string;
|
|
35
|
+
proof?: unknown;
|
|
36
|
+
}, null>;
|
|
37
|
+
};
|
|
38
|
+
queries: {
|
|
39
|
+
isSuppressed: FunctionReference<"query", "internal", {
|
|
40
|
+
contactHash: string;
|
|
41
|
+
channel: string;
|
|
42
|
+
}, SuppressionView | null>;
|
|
43
|
+
getOptInProof: FunctionReference<"query", "internal", {
|
|
44
|
+
contactHash: string;
|
|
45
|
+
listKey: string;
|
|
46
|
+
}, RawProofView | null>;
|
|
47
|
+
isEligible: FunctionReference<"query", "internal", {
|
|
48
|
+
contactHash: string;
|
|
49
|
+
channel: string;
|
|
50
|
+
listKey: string;
|
|
51
|
+
requireOptIn: boolean;
|
|
52
|
+
}, boolean>;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
interface RunQueryCtx {
|
|
56
|
+
runQuery<Q extends FunctionReference<"query", "internal">>(reference: Q, args: FunctionArgs<Q>): Promise<FunctionReturnType<Q>>;
|
|
57
|
+
}
|
|
58
|
+
interface RunMutationCtx {
|
|
59
|
+
runMutation<M extends FunctionReference<"mutation", "internal">>(reference: M, args: FunctionArgs<M>): Promise<FunctionReturnType<M>>;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Consumer-facing client for the do-not-contact suppression gate (GDPR opt-out /
|
|
63
|
+
* CAN-SPAM). The host hashes a contact (`hash(normalize(email|phone))`) and passes
|
|
64
|
+
* the opaque `contactHash`; the component stores a `(contactHash, channel)`
|
|
65
|
+
* anti-membership tombstone that survives erasure of the subject. A sender calls
|
|
66
|
+
* `isEligible` before every send (`¬suppressed [∧ confirmed]`); an unsubscribe /
|
|
67
|
+
* bounce / complaint webhook calls `suppress`; a double-opt-in confirmation calls
|
|
68
|
+
* `recordOptIn`. The host owns meaning and auth — it resolves identity, hashes the
|
|
69
|
+
* contact, and decides the channel/list semantics. Pass `proofValidator` to narrow
|
|
70
|
+
* the opaque opt-in evidence to `TProof` at the boundary — there is no unchecked
|
|
71
|
+
* cast.
|
|
72
|
+
*
|
|
73
|
+
* @typeParam TProof - The host's opt-in proof evidence type (defaults to `unknown`).
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* const dnc = new Suppression(components.suppression, {
|
|
78
|
+
* proofValidator: v.object({ ip: v.string() }).parse,
|
|
79
|
+
* });
|
|
80
|
+
* // a webhook suppresses on complaint:
|
|
81
|
+
* await dnc.suppress(ctx, contactHash, "complaint", { channel: "email" });
|
|
82
|
+
* // a sender gates a marketing send:
|
|
83
|
+
* if (await dnc.isEligible(ctx, contactHash, { channel: "email", listKey: "news", requireOptIn: true })) {
|
|
84
|
+
* // ...send
|
|
85
|
+
* }
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export declare class Suppression<TProof = unknown> {
|
|
89
|
+
private readonly component;
|
|
90
|
+
private readonly proofValidator;
|
|
91
|
+
constructor(component: SuppressionComponent, options?: SuppressionOptions<TProof>);
|
|
92
|
+
/** Narrow an opaque value through a host parser; pass `undefined` and unset parsers through. */
|
|
93
|
+
private parse;
|
|
94
|
+
/**
|
|
95
|
+
* Suppress a `(contactHash, channel)` — add it to the do-not-contact list.
|
|
96
|
+
* `opts.channel` scopes the suppression to one channel; omit it for a global
|
|
97
|
+
* (all-channel) suppression. Idempotent on `(contactHash, channel)`. `reason` is
|
|
98
|
+
* recorded for audit.
|
|
99
|
+
*/
|
|
100
|
+
suppress(ctx: RunMutationCtx, contactHash: string, reason: SuppressionReason, opts?: SuppressOptions): Promise<null>;
|
|
101
|
+
/**
|
|
102
|
+
* Remove a `(contactHash, channel)` from the do-not-contact list (a rare,
|
|
103
|
+
* audited re-subscribe). Omit `channel` to clear the global entry. Returns `true`
|
|
104
|
+
* if an entry was removed, `false` if none matched.
|
|
105
|
+
*/
|
|
106
|
+
unsuppress(ctx: RunMutationCtx, contactHash: string, channel?: string): Promise<boolean>;
|
|
107
|
+
/**
|
|
108
|
+
* The matching suppression for `(contactHash, channel)`, or `null` if the
|
|
109
|
+
* contact is not suppressed on that channel. A global suppression matches every
|
|
110
|
+
* channel and wins. Omit `channel` to check the global entry only.
|
|
111
|
+
*/
|
|
112
|
+
isSuppressed(ctx: RunQueryCtx, contactHash: string, channel?: string): Promise<SuppressionView | null>;
|
|
113
|
+
/**
|
|
114
|
+
* Record an opt-in proof for a `(contactHash, listKey)` — the legal evidence of
|
|
115
|
+
* a confirmed opt-in. `opts.listKey` scopes the proof to one list; omit it for a
|
|
116
|
+
* global opt-in. `opts.proof` is opaque host evidence validated against
|
|
117
|
+
* `proofValidator` before storage. Idempotent on `(contactHash, listKey)`.
|
|
118
|
+
*/
|
|
119
|
+
recordOptIn(ctx: RunMutationCtx, contactHash: string, opts: RecordOptInOptions<TProof>): Promise<null>;
|
|
120
|
+
/**
|
|
121
|
+
* The opt-in proof for `(contactHash, listKey)`, or `null` if none is recorded.
|
|
122
|
+
* Omit `listKey` to fetch a global opt-in. `proof` is narrowed by the host
|
|
123
|
+
* validator on read.
|
|
124
|
+
*/
|
|
125
|
+
getOptInProof(ctx: RunQueryCtx, contactHash: string, listKey?: string): Promise<OptInProofView<TProof> | null>;
|
|
126
|
+
/**
|
|
127
|
+
* The send gate: `true` when the contact may be contacted — NOT suppressed on
|
|
128
|
+
* `opts.channel` (global or channel-specific) and, when `opts.requireOptIn` is
|
|
129
|
+
* set, holding a recorded opt-in proof for `opts.listKey`. Suppression always
|
|
130
|
+
* blocks; the opt-in requirement is per call.
|
|
131
|
+
*/
|
|
132
|
+
isEligible(ctx: RunQueryCtx, contactHash: string, opts?: IsEligibleOptions): Promise<boolean>;
|
|
133
|
+
}
|
|
134
|
+
export type { IsEligibleOptions, OptInProofView, Parser, RecordOptInOptions, SuppressionOptions, SuppressionReason, SuppressionView, SuppressOptions, };
|
|
135
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { GLOBAL_CHANNEL } from "../shared.js";
|
|
2
|
+
/**
|
|
3
|
+
* Consumer-facing client for the do-not-contact suppression gate (GDPR opt-out /
|
|
4
|
+
* CAN-SPAM). The host hashes a contact (`hash(normalize(email|phone))`) and passes
|
|
5
|
+
* the opaque `contactHash`; the component stores a `(contactHash, channel)`
|
|
6
|
+
* anti-membership tombstone that survives erasure of the subject. A sender calls
|
|
7
|
+
* `isEligible` before every send (`¬suppressed [∧ confirmed]`); an unsubscribe /
|
|
8
|
+
* bounce / complaint webhook calls `suppress`; a double-opt-in confirmation calls
|
|
9
|
+
* `recordOptIn`. The host owns meaning and auth — it resolves identity, hashes the
|
|
10
|
+
* contact, and decides the channel/list semantics. Pass `proofValidator` to narrow
|
|
11
|
+
* the opaque opt-in evidence to `TProof` at the boundary — there is no unchecked
|
|
12
|
+
* cast.
|
|
13
|
+
*
|
|
14
|
+
* @typeParam TProof - The host's opt-in proof evidence type (defaults to `unknown`).
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* const dnc = new Suppression(components.suppression, {
|
|
19
|
+
* proofValidator: v.object({ ip: v.string() }).parse,
|
|
20
|
+
* });
|
|
21
|
+
* // a webhook suppresses on complaint:
|
|
22
|
+
* await dnc.suppress(ctx, contactHash, "complaint", { channel: "email" });
|
|
23
|
+
* // a sender gates a marketing send:
|
|
24
|
+
* if (await dnc.isEligible(ctx, contactHash, { channel: "email", listKey: "news", requireOptIn: true })) {
|
|
25
|
+
* // ...send
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export class Suppression {
|
|
30
|
+
component;
|
|
31
|
+
proofValidator;
|
|
32
|
+
constructor(component, options = {}) {
|
|
33
|
+
this.component = component;
|
|
34
|
+
this.proofValidator = options.proofValidator;
|
|
35
|
+
}
|
|
36
|
+
/** Narrow an opaque value through a host parser; pass `undefined` and unset parsers through. */
|
|
37
|
+
parse(value) {
|
|
38
|
+
if (value === undefined) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
if (this.proofValidator === undefined) {
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
return this.proofValidator(value);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Suppress a `(contactHash, channel)` — add it to the do-not-contact list.
|
|
48
|
+
* `opts.channel` scopes the suppression to one channel; omit it for a global
|
|
49
|
+
* (all-channel) suppression. Idempotent on `(contactHash, channel)`. `reason` is
|
|
50
|
+
* recorded for audit.
|
|
51
|
+
*/
|
|
52
|
+
suppress(ctx, contactHash, reason, opts = {}) {
|
|
53
|
+
return ctx.runMutation(this.component.mutations.suppress, {
|
|
54
|
+
contactHash,
|
|
55
|
+
channel: opts.channel ?? GLOBAL_CHANNEL,
|
|
56
|
+
reason,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Remove a `(contactHash, channel)` from the do-not-contact list (a rare,
|
|
61
|
+
* audited re-subscribe). Omit `channel` to clear the global entry. Returns `true`
|
|
62
|
+
* if an entry was removed, `false` if none matched.
|
|
63
|
+
*/
|
|
64
|
+
unsuppress(ctx, contactHash, channel) {
|
|
65
|
+
return ctx.runMutation(this.component.mutations.unsuppress, {
|
|
66
|
+
contactHash,
|
|
67
|
+
channel: channel ?? GLOBAL_CHANNEL,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The matching suppression for `(contactHash, channel)`, or `null` if the
|
|
72
|
+
* contact is not suppressed on that channel. A global suppression matches every
|
|
73
|
+
* channel and wins. Omit `channel` to check the global entry only.
|
|
74
|
+
*/
|
|
75
|
+
isSuppressed(ctx, contactHash, channel) {
|
|
76
|
+
return ctx.runQuery(this.component.queries.isSuppressed, {
|
|
77
|
+
contactHash,
|
|
78
|
+
channel: channel ?? GLOBAL_CHANNEL,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Record an opt-in proof for a `(contactHash, listKey)` — the legal evidence of
|
|
83
|
+
* a confirmed opt-in. `opts.listKey` scopes the proof to one list; omit it for a
|
|
84
|
+
* global opt-in. `opts.proof` is opaque host evidence validated against
|
|
85
|
+
* `proofValidator` before storage. Idempotent on `(contactHash, listKey)`.
|
|
86
|
+
*/
|
|
87
|
+
recordOptIn(ctx, contactHash, opts) {
|
|
88
|
+
return ctx.runMutation(this.component.mutations.recordOptIn, {
|
|
89
|
+
contactHash,
|
|
90
|
+
listKey: opts.listKey ?? GLOBAL_CHANNEL,
|
|
91
|
+
source: opts.source,
|
|
92
|
+
proof: opts.proof === undefined ? undefined : this.parse(opts.proof),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The opt-in proof for `(contactHash, listKey)`, or `null` if none is recorded.
|
|
97
|
+
* Omit `listKey` to fetch a global opt-in. `proof` is narrowed by the host
|
|
98
|
+
* validator on read.
|
|
99
|
+
*/
|
|
100
|
+
async getOptInProof(ctx, contactHash, listKey) {
|
|
101
|
+
const raw = await ctx.runQuery(this.component.queries.getOptInProof, {
|
|
102
|
+
contactHash,
|
|
103
|
+
listKey: listKey ?? GLOBAL_CHANNEL,
|
|
104
|
+
});
|
|
105
|
+
if (raw === null) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
contactHash: raw.contactHash,
|
|
110
|
+
listKey: raw.listKey,
|
|
111
|
+
source: raw.source,
|
|
112
|
+
proof: this.parse(raw.proof),
|
|
113
|
+
confirmedAt: raw.confirmedAt,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The send gate: `true` when the contact may be contacted — NOT suppressed on
|
|
118
|
+
* `opts.channel` (global or channel-specific) and, when `opts.requireOptIn` is
|
|
119
|
+
* set, holding a recorded opt-in proof for `opts.listKey`. Suppression always
|
|
120
|
+
* blocks; the opt-in requirement is per call.
|
|
121
|
+
*/
|
|
122
|
+
isEligible(ctx, contactHash, opts = {}) {
|
|
123
|
+
return ctx.runQuery(this.component.queries.isEligible, {
|
|
124
|
+
contactHash,
|
|
125
|
+
channel: opts.channel ?? GLOBAL_CHANNEL,
|
|
126
|
+
listKey: opts.listKey ?? GLOBAL_CHANNEL,
|
|
127
|
+
requireOptIn: opts.requireOptIn ?? false,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/** Public TypeScript surface for the suppression client. */
|
|
2
|
+
/** The five standard reasons a `(contactHash, channel)` is suppressed. */
|
|
3
|
+
export type SuppressionReason = "unsubscribe" | "bounce" | "complaint" | "manual" | "global";
|
|
4
|
+
/**
|
|
5
|
+
* Validates and narrows opaque host opt-in evidence to a host type `T` at the
|
|
6
|
+
* client boundary. Receives the raw value the component returned (`unknown`) and
|
|
7
|
+
* MUST return a typed `T` or throw. A `convex/values` validator's `.parse` (or a
|
|
8
|
+
* Zod `.parse`) fits directly; omit it to keep the value unvalidated.
|
|
9
|
+
*
|
|
10
|
+
* @typeParam T - The host's stored opt-in `proof` type.
|
|
11
|
+
*/
|
|
12
|
+
export type Parser<T> = (value: unknown) => T;
|
|
13
|
+
/** The public view of a suppression returned by {@link Suppression.isSuppressed}. */
|
|
14
|
+
export interface SuppressionView {
|
|
15
|
+
/** The host's opaque contact hash — never a raw email/phone. */
|
|
16
|
+
contactHash: string;
|
|
17
|
+
/** The channel this entry applies to, or `null` for a global (all-channel) suppression. */
|
|
18
|
+
channel: string | null;
|
|
19
|
+
/** Why the contact was suppressed (audit only — never changes the effect). */
|
|
20
|
+
reason: SuppressionReason;
|
|
21
|
+
/** Absolute ms timestamp the suppression was recorded. */
|
|
22
|
+
createdAt: number;
|
|
23
|
+
}
|
|
24
|
+
/** The public view of an opt-in proof returned by {@link Suppression.getOptInProof}. */
|
|
25
|
+
export interface OptInProofView<TProof = unknown> {
|
|
26
|
+
/** The host's opaque contact hash. */
|
|
27
|
+
contactHash: string;
|
|
28
|
+
/** The list/purpose the opt-in applies to, or `null` for a global opt-in. */
|
|
29
|
+
listKey: string | null;
|
|
30
|
+
/** How the opt-in was captured (e.g. `"double-opt-in"`, `"checkbox"`). */
|
|
31
|
+
source: string;
|
|
32
|
+
/** The opaque host evidence (narrowed if a `proofValidator` is set). */
|
|
33
|
+
proof?: TProof;
|
|
34
|
+
/** Absolute ms timestamp the opt-in was confirmed. */
|
|
35
|
+
confirmedAt: number;
|
|
36
|
+
}
|
|
37
|
+
/** Per-call options for {@link Suppression.suppress}. */
|
|
38
|
+
export interface SuppressOptions {
|
|
39
|
+
/** The channel to suppress (`"email"`/`"sms"`/…). Omit for a global suppression. */
|
|
40
|
+
channel?: string;
|
|
41
|
+
}
|
|
42
|
+
/** Per-call options for {@link Suppression.recordOptIn}. */
|
|
43
|
+
export interface RecordOptInOptions<TProof> {
|
|
44
|
+
/** The list/purpose the opt-in applies to. Omit for a global opt-in. */
|
|
45
|
+
listKey?: string;
|
|
46
|
+
/** How the opt-in was captured. */
|
|
47
|
+
source: string;
|
|
48
|
+
/** Opaque host evidence (validated against `proofValidator` before storage). */
|
|
49
|
+
proof?: TProof;
|
|
50
|
+
}
|
|
51
|
+
/** Per-call options for {@link Suppression.isEligible}. */
|
|
52
|
+
export interface IsEligibleOptions {
|
|
53
|
+
/** The channel to check (`"email"`/`"sms"`/…). Omit to check the global gate only. */
|
|
54
|
+
channel?: string;
|
|
55
|
+
/** The list/purpose to require an opt-in for (used only when `requireOptIn`). */
|
|
56
|
+
listKey?: string;
|
|
57
|
+
/** When `true`, also require a recorded opt-in proof for `listKey`. */
|
|
58
|
+
requireOptIn?: boolean;
|
|
59
|
+
}
|
|
60
|
+
/** Construction options for the {@link Suppression} client. */
|
|
61
|
+
export interface SuppressionOptions<TProof> {
|
|
62
|
+
/**
|
|
63
|
+
* Validates/narrows opaque opt-in `proof` to `TProof` at the boundary — applied
|
|
64
|
+
* to the `proof` passed into `recordOptIn` (before storage) and the `proof`
|
|
65
|
+
* returned by `getOptInProof` (on read). Throws on a mismatch. Omit to leave
|
|
66
|
+
* proof evidence unvalidated.
|
|
67
|
+
*/
|
|
68
|
+
proofValidator?: Parser<TProof>;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/client/types.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAE5D,0EAA0E;AAC1E,MAAM,MAAM,iBAAiB,GACzB,aAAa,GACb,QAAQ,GACR,WAAW,GACX,QAAQ,GACR,QAAQ,CAAC;AAEb;;;;;;;GAOG;AACH,MAAM,MAAM,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC,CAAC;AAE9C,qFAAqF;AACrF,MAAM,WAAW,eAAe;IAC9B,gEAAgE;IAChE,WAAW,EAAE,MAAM,CAAC;IACpB,2FAA2F;IAC3F,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,8EAA8E;IAC9E,MAAM,EAAE,iBAAiB,CAAC;IAC1B,0DAA0D;IAC1D,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wFAAwF;AACxF,MAAM,WAAW,cAAc,CAAC,MAAM,GAAG,OAAO;IAC9C,sCAAsC;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,yDAAyD;AACzD,MAAM,WAAW,eAAe;IAC9B,oFAAoF;IACpF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,4DAA4D;AAC5D,MAAM,WAAW,kBAAkB,CAAC,MAAM;IACxC,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mCAAmC;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,2DAA2D;AAC3D,MAAM,WAAW,iBAAiB;IAChC,sFAAsF;IACtF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,+DAA+D;AAC/D,MAAM,WAAW,kBAAkB,CAAC,MAAM;IACxC;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;CACjC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/client/types.ts"],"names":[],"mappings":"AAAA,4DAA4D"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generated `api` utility.
|
|
3
|
+
*
|
|
4
|
+
* THIS CODE IS AUTOMATICALLY GENERATED.
|
|
5
|
+
*
|
|
6
|
+
* To regenerate, run `npx convex dev`.
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import type * as mutations from "../mutations.js";
|
|
10
|
+
import type * as queries from "../queries.js";
|
|
11
|
+
import type * as validators from "../validators.js";
|
|
12
|
+
import type { ApiFromModules, FilterApi, FunctionReference } from "convex/server";
|
|
13
|
+
declare const fullApi: ApiFromModules<{
|
|
14
|
+
mutations: typeof mutations;
|
|
15
|
+
queries: typeof queries;
|
|
16
|
+
validators: typeof validators;
|
|
17
|
+
}>;
|
|
18
|
+
/**
|
|
19
|
+
* A utility for referencing Convex functions in your app's public API.
|
|
20
|
+
*
|
|
21
|
+
* Usage:
|
|
22
|
+
* ```js
|
|
23
|
+
* const myFunctionReference = api.myModule.myFunction;
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare const api: FilterApi<typeof fullApi, FunctionReference<any, "public">>;
|
|
27
|
+
/**
|
|
28
|
+
* A utility for referencing Convex functions in your app's internal API.
|
|
29
|
+
*
|
|
30
|
+
* Usage:
|
|
31
|
+
* ```js
|
|
32
|
+
* const myFunctionReference = internal.myModule.myFunction;
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare const internal: FilterApi<typeof fullApi, FunctionReference<any, "internal">>;
|
|
36
|
+
export declare const components: {};
|
|
37
|
+
export {};
|
|
38
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../src/component/_generated/api.ts"],"names":[],"mappings":"AACA;;;;;;;GAOG;AAEH,OAAO,KAAK,KAAK,SAAS,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,KAAK,OAAO,MAAM,eAAe,CAAC;AAC9C,OAAO,KAAK,KAAK,UAAU,MAAM,kBAAkB,CAAC;AAEpD,OAAO,KAAK,EACV,cAAc,EACd,SAAS,EACT,iBAAiB,EAClB,MAAM,eAAe,CAAC;AAGvB,QAAA,MAAM,OAAO,EAAE,cAAc,CAAC;IAC5B,SAAS,EAAE,OAAO,SAAS,CAAC;IAC5B,OAAO,EAAE,OAAO,OAAO,CAAC;IACxB,UAAU,EAAE,OAAO,UAAU,CAAC;CAC/B,CAAiB,CAAC;AAEnB;;;;;;;GAOG;AACH,eAAO,MAAM,GAAG,EAAE,SAAS,CACzB,OAAO,OAAO,EACd,iBAAiB,CAAC,GAAG,EAAE,QAAQ,CAAC,CACjB,CAAC;AAElB;;;;;;;GAOG;AACH,eAAO,MAAM,QAAQ,EAAE,SAAS,CAC9B,OAAO,OAAO,EACd,iBAAiB,CAAC,GAAG,EAAE,UAAU,CAAC,CACnB,CAAC;AAElB,eAAO,MAAM,UAAU,EAAqC,EAAE,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
/**
|
|
3
|
+
* Generated `api` utility.
|
|
4
|
+
*
|
|
5
|
+
* THIS CODE IS AUTOMATICALLY GENERATED.
|
|
6
|
+
*
|
|
7
|
+
* To regenerate, run `npx convex dev`.
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import { anyApi, componentsGeneric } from "convex/server";
|
|
11
|
+
const fullApi = anyApi;
|
|
12
|
+
/**
|
|
13
|
+
* A utility for referencing Convex functions in your app's public API.
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* ```js
|
|
17
|
+
* const myFunctionReference = api.myModule.myFunction;
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export const api = anyApi;
|
|
21
|
+
/**
|
|
22
|
+
* A utility for referencing Convex functions in your app's internal API.
|
|
23
|
+
*
|
|
24
|
+
* Usage:
|
|
25
|
+
* ```js
|
|
26
|
+
* const myFunctionReference = internal.myModule.myFunction;
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export const internal = anyApi;
|
|
30
|
+
export const components = componentsGeneric();
|
|
31
|
+
//# sourceMappingURL=api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../../src/component/_generated/api.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;;;;;GAOG;AAWH,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAE1D,MAAM,OAAO,GAIR,MAAa,CAAC;AAEnB;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,GAAG,GAGZ,MAAa,CAAC;AAElB;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,QAAQ,GAGjB,MAAa,CAAC;AAElB,MAAM,CAAC,MAAM,UAAU,GAAG,iBAAiB,EAAmB,CAAC"}
|