@cowliss/sdk 0.18.1 → 0.19.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/CHANGELOG.md CHANGED
@@ -4,6 +4,13 @@
4
4
 
5
5
  What changed in each version of `@cowliss/sdk` after 0.16.0, newest first. Earlier versions are listed on [npm](https://www.npmjs.com/package/@cowliss/sdk?activeTab=versions).
6
6
 
7
+ ## 0.19.0 (2026-10-02)
8
+
9
+ ### Features
10
+
11
+ - emails.send takes an idempotencyKey and creates the person it emails
12
+ - cow.preferences.get and set, the hosted preferences page as data
13
+
7
14
  ## 0.18.1 (2026-10-01)
8
15
 
9
16
  ### Fixes
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { BatchBody, BatchResultDto, ErrorCode, EventDto, IdentifyInput, PreferencesLink, PreferencesLinkInput, ProfileDto, SendEmailInput, SendEmailResult, TrackInput } from "./types.js";
1
+ import type { BatchBody, BatchResultDto, ErrorCode, EventDto, GetPreferencesInput, IdentifyInput, Preferences, PreferencesLink, PreferencesLinkInput, ProfileDto, SendEmailInput, SendEmailResult, SetPreferencesInput, TrackInput } from "./types.js";
2
2
  export type * from "./types.js";
3
3
  /**
4
4
  * The Cowliss TS client SDK. Small typed surface over the ingestion API:
@@ -98,20 +98,32 @@ export declare class Cow {
98
98
  *
99
99
  * Synchronous like the rest of the client: the promise resolves once the
100
100
  * message has been handed over, and `deliveryId` is the row that records
101
- * what happened to it. Each call carries its own Idempotency-Key on every
101
+ * what happened to it. Each call carries an Idempotency-Key on every
102
102
  * attempt, so a retry after a lost reply answers with the first send's
103
- * `deliveryId` instead of sending again.
103
+ * `deliveryId` instead of sending again. Pass your own `idempotencyKey`
104
+ * (an order id, a job id) and a second run of your own code dedupes too;
105
+ * without one, each call is a send of its own.
106
+ *
107
+ * Addressed to `{ email }` that no profile in the app holds yet, the send
108
+ * creates that profile first, as `identify` would.
104
109
  */
105
110
  emails: {
106
- send: (input: WithDefaultSource<SendEmailInput>) => Promise<SendEmailResult>;
111
+ send: ({ idempotencyKey, ...input }: WithDefaultSource<SendEmailInput> & {
112
+ idempotencyKey?: string;
113
+ }) => Promise<SendEmailResult>;
107
114
  };
108
115
  /**
109
- * A link to one person's hosted email preferences page, for your app's own
110
- * "Manage email preferences" button: `to` is one identifier naming them,
111
- * as in `emails.send`. The page lists every purpose their app declares as
112
- * a switch, and the link works for an hour, so ask for it when they click.
116
+ * One person's email preferences, `to` naming them as in `emails.send`.
117
+ *
118
+ * `link` is the hosted page, for a "Manage email preferences" button: it
119
+ * lists every purpose their app declares as a switch, and the link works
120
+ * for an hour, so ask for it when they click. `get` and `set` are the same
121
+ * page as data, for an app that draws its own: `set` saves what the person
122
+ * chose, where `identify({ consent })` records what the app decided.
113
123
  */
114
124
  preferences: {
125
+ get: (input: WithDefaultSource<GetPreferencesInput>) => Promise<Preferences>;
126
+ set: (input: WithDefaultSource<SetPreferencesInput>) => Promise<Preferences>;
115
127
  link: (input: WithDefaultSource<PreferencesLinkInput>) => Promise<PreferencesLink>;
116
128
  };
117
129
  /**
package/dist/index.js CHANGED
@@ -120,20 +120,34 @@ export class Cow {
120
120
  *
121
121
  * Synchronous like the rest of the client: the promise resolves once the
122
122
  * message has been handed over, and `deliveryId` is the row that records
123
- * what happened to it. Each call carries its own Idempotency-Key on every
123
+ * what happened to it. Each call carries an Idempotency-Key on every
124
124
  * attempt, so a retry after a lost reply answers with the first send's
125
- * `deliveryId` instead of sending again.
125
+ * `deliveryId` instead of sending again. Pass your own `idempotencyKey`
126
+ * (an order id, a job id) and a second run of your own code dedupes too;
127
+ * without one, each call is a send of its own.
128
+ *
129
+ * Addressed to `{ email }` that no profile in the app holds yet, the send
130
+ * creates that profile first, as `identify` would.
126
131
  */
127
132
  emails = {
128
- send: (input) => this.#watch(async () => this.#post("/v1/emails", { data: { ...input, sourceId: this.#sourceFor(input) } }, crypto.randomUUID())),
133
+ send: ({ idempotencyKey, ...input }) => this.#watch(async () => this.#post("/v1/emails", { data: { ...input, sourceId: this.#sourceFor(input) } }, idempotencyKey ?? crypto.randomUUID())),
129
134
  };
130
135
  /**
131
- * A link to one person's hosted email preferences page, for your app's own
132
- * "Manage email preferences" button: `to` is one identifier naming them,
133
- * as in `emails.send`. The page lists every purpose their app declares as
134
- * a switch, and the link works for an hour, so ask for it when they click.
136
+ * One person's email preferences, `to` naming them as in `emails.send`.
137
+ *
138
+ * `link` is the hosted page, for a "Manage email preferences" button: it
139
+ * lists every purpose their app declares as a switch, and the link works
140
+ * for an hour, so ask for it when they click. `get` and `set` are the same
141
+ * page as data, for an app that draws its own: `set` saves what the person
142
+ * chose, where `identify({ consent })` records what the app decided.
135
143
  */
136
144
  preferences = {
145
+ get: (input) => this.#watch(async () => this.#post("/v1/profile-preferences/find", {
146
+ data: { ...input, sourceId: this.#sourceFor(input) },
147
+ })),
148
+ set: (input) => this.#watch(async () => this.#post("/v1/profile-preferences", {
149
+ data: { ...input, sourceId: this.#sourceFor(input) },
150
+ })),
137
151
  link: (input) => this.#watch(async () => this.#post("/v1/preferences-links", {
138
152
  data: { ...input, sourceId: this.#sourceFor(input) },
139
153
  })),
package/dist/types.d.ts CHANGED
@@ -167,6 +167,29 @@ export type PreferencesLinkInput = {
167
167
  /** Exactly one identifier naming a profile in the source's app. */
168
168
  to: Identifiers;
169
169
  };
170
+ /** One profile's switches, read or saved from your app's own page. */
171
+ export type GetPreferencesInput = PreferencesLinkInput;
172
+ export type SetPreferencesInput = PreferencesLinkInput & {
173
+ /**
174
+ * The switches to change: `marketing` or a purpose your app declares.
175
+ * Keys left out keep their answer.
176
+ */
177
+ consent: ConsentPatch;
178
+ };
179
+ /** One switch: `marketing`, or a purpose your app declares. */
180
+ export type Preference = {
181
+ key: string;
182
+ label: string;
183
+ description: string | null;
184
+ /** Whether a send under this purpose would reach them now. */
185
+ enabled: boolean;
186
+ /** Whether they hold their own answer, rather than the default. */
187
+ answered: boolean;
188
+ };
189
+ export type Preferences = {
190
+ /** `marketing` first, then your app's purposes by key. */
191
+ purposes: Preference[];
192
+ };
170
193
  export type PreferencesLink = {
171
194
  /** The hosted preferences page for that profile. */
172
195
  url: string;
@@ -1 +1 @@
1
- export declare const SDK_VERSION = "0.18.1";
1
+ export declare const SDK_VERSION = "0.19.0";
@@ -1,2 +1,2 @@
1
1
  // Generated by scripts/version.ts from package.json. Do not edit.
2
- export const SDK_VERSION = "0.18.1";
2
+ export const SDK_VERSION = "0.19.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cowliss/sdk",
3
- "version": "0.18.1",
3
+ "version": "0.19.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {