ab-ecosystem-cpass 1.0.8 → 1.0.10
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 +46 -1
- package/dist/resources/campaigns.d.ts +33 -3
- package/dist/resources/campaigns.js +36 -2
- package/dist/resources/campaigns.js.map +1 -1
- package/dist/resources/data-stores.d.ts +92 -19
- package/dist/resources/data-stores.js +91 -18
- package/dist/resources/data-stores.js.map +1 -1
- package/dist/types/campaign.d.ts +28 -2
- package/dist/types/datastore.d.ts +71 -5
- package/dist/types/datastore.js +17 -2
- package/dist/types/datastore.js.map +1 -1
- package/docs/audience/notifications.md +11 -0
- package/docs/campaign/campaigns.md +108 -9
- package/docs/data-store.md +99 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -159,6 +159,44 @@ try {
|
|
|
159
159
|
Common codes: `422` for a body or query that fails validation (`details` lists
|
|
160
160
|
the paths), `404 not_found`, `409 conflict` for a name or slug already taken.
|
|
161
161
|
|
|
162
|
+
### Plan limits
|
|
163
|
+
|
|
164
|
+
Every call needs an active subscription, and the metered resources are capped at
|
|
165
|
+
what the plan includes. Both refusals are `402`, told apart by `code`:
|
|
166
|
+
|
|
167
|
+
| Code | When | `details` |
|
|
168
|
+
| --- | --- | --- |
|
|
169
|
+
| `subscription_required` | The account has no subscription, or it has lapsed. Any call. | — |
|
|
170
|
+
| `not_included` | The plan includes none of that resource. | `{ resource }` |
|
|
171
|
+
| `quota_exceeded` | The plan's allowance is used up. | `{ resource, limit, used }` |
|
|
172
|
+
|
|
173
|
+
What counts, and against which inclusion:
|
|
174
|
+
|
|
175
|
+
| `resource` | Counted as | Inclusion |
|
|
176
|
+
| --- | --- | --- |
|
|
177
|
+
| `contact` | one contact | `contact_18472` — **1 unit = 1000 contacts** |
|
|
178
|
+
| `canned_reply` | one reply | `canned_reply_14172` |
|
|
179
|
+
| `scheduled_series` | one series, whatever its length — a one-off `schedule` is free | `series_91922` |
|
|
180
|
+
| `data_store` | one registered store | `data_store_52360` |
|
|
181
|
+
| `campaign` | one campaign | `campaign_73018` |
|
|
182
|
+
| `super_field` | one custom field | `super_field_29689` |
|
|
183
|
+
| `whatsapp_channel` | one live WhatsApp channel | `whatsapp_channels_28335` |
|
|
184
|
+
|
|
185
|
+
Counts are of what exists now, so deleting a row gives the allowance back.
|
|
186
|
+
`limit` and `used` are returned on `quota_exceeded` so a panel can say how far
|
|
187
|
+
over the account is without a second call.
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
try {
|
|
191
|
+
await crm.campaigns.create(input);
|
|
192
|
+
} catch (err) {
|
|
193
|
+
if (err instanceof CrmApiError && err.status === 402) {
|
|
194
|
+
// err.code: 'subscription_required' | 'not_included' | 'quota_exceeded'
|
|
195
|
+
console.error(err.message, err.details);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
162
200
|
## Modules
|
|
163
201
|
|
|
164
202
|
One page per resource, every action with its payload, query and response. The
|
|
@@ -271,6 +309,13 @@ await crm.campaigns.createGroup({
|
|
|
271
309
|
});
|
|
272
310
|
```
|
|
273
311
|
|
|
312
|
+
**Creating a campaign does not send it.** Every create returns a draft:
|
|
313
|
+
`pending`, with no `scheduledAt`. `crm.campaigns.trigger(id)` sends it now (with
|
|
314
|
+
about a minute in which `cancel` still works), and
|
|
315
|
+
`crm.campaigns.trigger(id, { scheduledAt })` sends it then. A send time is
|
|
316
|
+
refused on `create` and `update`, because arming a campaign is also what records
|
|
317
|
+
who authorised it.
|
|
318
|
+
|
|
274
319
|
Note that `campaign.receivers` is `0` on a campaign you just created: the
|
|
275
320
|
audience is resolved when the campaign sends, not when it is built. The
|
|
276
321
|
`receivers` on the result is the count written into the file, and it is the one
|
|
@@ -278,7 +323,7 @@ to show a user.
|
|
|
278
323
|
|
|
279
324
|
| Resource | Page | Actions |
|
|
280
325
|
| --- | --- | --- |
|
|
281
|
-
| `crm.campaigns` | [campaigns](https://github.com/udghatan-dev/crm-sdk/blob/main/packages/sdk-js/docs/campaign/campaigns.md) | `list` `get` `create` `createBulk` `createGroup` `uploadReceivers` `update` `schedule` `cancel` `delete` |
|
|
326
|
+
| `crm.campaigns` | [campaigns](https://github.com/udghatan-dev/crm-sdk/blob/main/packages/sdk-js/docs/campaign/campaigns.md) | `list` `get` `create` `createBulk` `createGroup` `uploadReceivers` `update` **`trigger`** `schedule` `cancel` `delete` — creating does not send; `trigger` is what does |
|
|
282
327
|
| `crm.campaignMessages` | [campaign reporting](https://github.com/udghatan-dev/crm-sdk/blob/main/packages/sdk-js/docs/campaign/campaign-messages.md) | `stats` `list` `iterate` `replies` — `/v1/campaigns/:id/report`, counted off the per-recipient rows |
|
|
283
328
|
|
|
284
329
|
The audience helpers are exported too, so a UI can show "12,480 recipients, 3
|
|
@@ -2,7 +2,7 @@ import type { HttpClient } from '../http';
|
|
|
2
2
|
import { InboxMedia } from './audience/inbox-media';
|
|
3
3
|
import { type CampaignReceiver, type ReceiverEncoding } from '../internal/receiver-file';
|
|
4
4
|
import { type BulkAudienceInput, type ResolvedAudience } from './campaign-audience';
|
|
5
|
-
import type { Campaign, CampaignNumbersFilter, CreateCampaignInput, ListCampaignsQuery, UpdateCampaignInput } from '../types/campaign';
|
|
5
|
+
import type { Campaign, CampaignNumbersFilter, CreateCampaignInput, ListCampaignsQuery, TriggerCampaignInput, UpdateCampaignInput } from '../types/campaign';
|
|
6
6
|
import type { Page } from '../types/common';
|
|
7
7
|
/** Everything `createBulk` needs: a normal campaign, with an audience instead of `filters`. */
|
|
8
8
|
export interface CreateBulkCampaignInput extends Omit<CreateCampaignInput, 'filters' | 'type'> {
|
|
@@ -102,6 +102,17 @@ export declare class Campaigns {
|
|
|
102
102
|
/**
|
|
103
103
|
* Creates a campaign from a payload you have already assembled.
|
|
104
104
|
*
|
|
105
|
+
* **Creating does not send.** The campaign comes back `pending` with no
|
|
106
|
+
* `scheduledAt`: a draft you can keep editing, invisible to the sender until
|
|
107
|
+
* {@link trigger} arms it.
|
|
108
|
+
*
|
|
109
|
+
* ```ts
|
|
110
|
+
* const campaign = await crm.campaigns.create({ name: 'Diwali offer', messages });
|
|
111
|
+
* // ...show it for review...
|
|
112
|
+
* await crm.campaigns.trigger(campaign.id); // send now
|
|
113
|
+
* await crm.campaigns.trigger(campaign.id, { scheduledAt }); // or then
|
|
114
|
+
* ```
|
|
115
|
+
*
|
|
105
116
|
* For a bulk recipient list, prefer {@link createBulk} -- it does the
|
|
106
117
|
* encrypting and uploading that a file-backed audience requires, which is not
|
|
107
118
|
* something a caller should have to reproduce.
|
|
@@ -129,9 +140,10 @@ export declare class Campaigns {
|
|
|
129
140
|
* name: 'Renewal reminder',
|
|
130
141
|
* audience: { csv: await file.text(), recipientColumn: 'phone' },
|
|
131
142
|
* messages: [{ channel: { id: channelId }, message: template }],
|
|
132
|
-
* trigger: 'schedule',
|
|
133
|
-
* scheduledAt: '2026-11-01T04:30:00.000Z',
|
|
134
143
|
* });
|
|
144
|
+
*
|
|
145
|
+
* // Neither of the above has been sent yet. Arm it when you are ready:
|
|
146
|
+
* await crm.campaigns.trigger(campaign.id, { scheduledAt: '2026-11-01T04:30:00.000Z' });
|
|
135
147
|
* ```
|
|
136
148
|
*
|
|
137
149
|
* ## What it does, in order
|
|
@@ -233,6 +245,24 @@ export declare class Campaigns {
|
|
|
233
245
|
*/
|
|
234
246
|
encoding?: ReceiverEncoding): Promise<CampaignNumbersFilter>;
|
|
235
247
|
update(id: string, input: UpdateCampaignInput): Promise<Campaign>;
|
|
248
|
+
/**
|
|
249
|
+
* Arms a campaign -- the only call that makes one actually go out.
|
|
250
|
+
*
|
|
251
|
+
* ```ts
|
|
252
|
+
* await crm.campaigns.trigger(id); // now
|
|
253
|
+
* await crm.campaigns.trigger(id, { scheduledAt: isoInstant }); // then
|
|
254
|
+
* ```
|
|
255
|
+
*
|
|
256
|
+
* "Now" means a minute from now by default, which is the window `cancel()`
|
|
257
|
+
* works in. The server stamps `triggerInfo` -- the caller, their address, the
|
|
258
|
+
* moment -- as part of this call, so every send has one authorising act behind
|
|
259
|
+
* it; nothing you pass can set it.
|
|
260
|
+
*
|
|
261
|
+
* Only a `pending` campaign can be armed; anything else answers `409`.
|
|
262
|
+
* Triggering one that is armed but not yet picked up simply re-arms it.
|
|
263
|
+
*/
|
|
264
|
+
trigger(id: string, input?: TriggerCampaignInput): Promise<Campaign>;
|
|
265
|
+
/** {@link trigger} with a required time, under the name the panel uses. */
|
|
236
266
|
schedule(id: string, scheduledAt: string): Promise<Campaign>;
|
|
237
267
|
cancel(id: string): Promise<Campaign>;
|
|
238
268
|
delete(id: string): Promise<void>;
|
|
@@ -35,6 +35,17 @@ class Campaigns {
|
|
|
35
35
|
/**
|
|
36
36
|
* Creates a campaign from a payload you have already assembled.
|
|
37
37
|
*
|
|
38
|
+
* **Creating does not send.** The campaign comes back `pending` with no
|
|
39
|
+
* `scheduledAt`: a draft you can keep editing, invisible to the sender until
|
|
40
|
+
* {@link trigger} arms it.
|
|
41
|
+
*
|
|
42
|
+
* ```ts
|
|
43
|
+
* const campaign = await crm.campaigns.create({ name: 'Diwali offer', messages });
|
|
44
|
+
* // ...show it for review...
|
|
45
|
+
* await crm.campaigns.trigger(campaign.id); // send now
|
|
46
|
+
* await crm.campaigns.trigger(campaign.id, { scheduledAt }); // or then
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
38
49
|
* For a bulk recipient list, prefer {@link createBulk} -- it does the
|
|
39
50
|
* encrypting and uploading that a file-backed audience requires, which is not
|
|
40
51
|
* something a caller should have to reproduce.
|
|
@@ -64,9 +75,10 @@ class Campaigns {
|
|
|
64
75
|
* name: 'Renewal reminder',
|
|
65
76
|
* audience: { csv: await file.text(), recipientColumn: 'phone' },
|
|
66
77
|
* messages: [{ channel: { id: channelId }, message: template }],
|
|
67
|
-
* trigger: 'schedule',
|
|
68
|
-
* scheduledAt: '2026-11-01T04:30:00.000Z',
|
|
69
78
|
* });
|
|
79
|
+
*
|
|
80
|
+
* // Neither of the above has been sent yet. Arm it when you are ready:
|
|
81
|
+
* await crm.campaigns.trigger(campaign.id, { scheduledAt: '2026-11-01T04:30:00.000Z' });
|
|
70
82
|
* ```
|
|
71
83
|
*
|
|
72
84
|
* ## What it does, in order
|
|
@@ -260,6 +272,28 @@ class Campaigns {
|
|
|
260
272
|
update(id, input) {
|
|
261
273
|
return this.http.request('PATCH', `/v1/campaigns/${encodeURIComponent(id)}`, { body: input });
|
|
262
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
* Arms a campaign -- the only call that makes one actually go out.
|
|
277
|
+
*
|
|
278
|
+
* ```ts
|
|
279
|
+
* await crm.campaigns.trigger(id); // now
|
|
280
|
+
* await crm.campaigns.trigger(id, { scheduledAt: isoInstant }); // then
|
|
281
|
+
* ```
|
|
282
|
+
*
|
|
283
|
+
* "Now" means a minute from now by default, which is the window `cancel()`
|
|
284
|
+
* works in. The server stamps `triggerInfo` -- the caller, their address, the
|
|
285
|
+
* moment -- as part of this call, so every send has one authorising act behind
|
|
286
|
+
* it; nothing you pass can set it.
|
|
287
|
+
*
|
|
288
|
+
* Only a `pending` campaign can be armed; anything else answers `409`.
|
|
289
|
+
* Triggering one that is armed but not yet picked up simply re-arms it.
|
|
290
|
+
*/
|
|
291
|
+
trigger(id, input = {}) {
|
|
292
|
+
return this.http.request('POST', `/v1/campaigns/${encodeURIComponent(id)}/trigger`, {
|
|
293
|
+
body: input,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
/** {@link trigger} with a required time, under the name the panel uses. */
|
|
263
297
|
schedule(id, scheduledAt) {
|
|
264
298
|
return this.http.request('POST', `/v1/campaigns/${encodeURIComponent(id)}/schedule`, {
|
|
265
299
|
body: { scheduledAt },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"campaigns.js","sourceRoot":"","sources":["../../src/resources/campaigns.ts"],"names":[],"mappings":";;;AACA,sCAAwC;AACxC,wDAA4E;AAC5E,6DAA4G;AAC5G,2DAK6B;
|
|
1
|
+
{"version":3,"file":"campaigns.js","sourceRoot":"","sources":["../../src/resources/campaigns.ts"],"names":[],"mappings":";;;AACA,sCAAwC;AACxC,wDAA4E;AAC5E,6DAA4G;AAC5G,2DAK6B;AAW7B,+DAA+D;AAC/D,MAAM,SAAS,GAAG,eAAe,CAAC;AAElC;;;;;;GAMG;AACH,MAAM,UAAU,GAAG,gBAAgB,CAAC;AAEpC,oFAAoF;AACpF,MAAM,kBAAkB,GAAG,CAAC,CAAC;AAkG7B,MAAa,SAAS;IAID;IAHF,KAAK,CAAmB;IAEzC,YACmB,IAAgB,EACjC,KAAwB;QADP,SAAI,GAAJ,IAAI,CAAY;QAGjC,6EAA6E;QAC7E,kCAAkC;QAClC,IAAI,CAAC,KAAK,GAAG,KAAK,IAAI,IAAI,wBAAU,CAAC,IAAI,CAAC,CAAC;IAC7C,CAAC;IAED,IAAI,CAAC,QAAqC,EAAE;QAC1C,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAiB,KAAK,EAAE,eAAe,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IAC9E,CAAC;IAED,GAAG,CAAC,EAAU;QACZ,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAW,KAAK,EAAE,iBAAiB,kBAAkB,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IACvF,CAAC;IAED;;;;;;;;;;;;;;;;;OAiBG;IACH,MAAM,CAAC,KAA0B;QAC/B,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAW,MAAM,EAAE,eAAe,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC/E,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAwDG;IACH,KAAK,CAAC,UAAU,CAAC,KAA8B;QAC7C,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,QAAQ,EAAE,GAAG,KAAK,CAAC;QAChD,MAAM,QAAQ,GAAG,IAAA,mCAAe,EAAC,QAAQ,CAAC,CAAC;QAE3C,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC;YAC/B,MAAM,IAAI,oBAAW,CACnB,GAAG,EACH,mBAAmB,EACnB,QAAQ,CAAC,OAAO,GAAG,CAAC;gBAClB,CAAC,CAAC,eAAe,QAAQ,CAAC,OAAO,wCAAwC;gBACzE,CAAC,CAAC,wCAAwC,CAC7C,CAAC;QACJ,CAAC;QAED,IAAI,QAAQ,CAAC,SAAS,CAAC,MAAM,GAAG,sCAAkB,EAAE,CAAC;YACnD,MAAM,IAAI,oBAAW,CACnB,GAAG,EACH,mBAAmB,EACnB,uCAAuC,sCAAkB,CAAC,cAAc,CAAC,OAAO,CAAC,eAAe;gBAC9F,qBAAqB,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,cAAc,CAAC,OAAO,CAAC,8BAA8B,CACvG,CAAC;QACJ,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QAEtE,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC;YAChC,GAAG,QAAQ;YACX,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,SAAS;YAC7B,OAAO,EAAE,CAAC,MAAM,CAAC;SAClB,CAAC,CAAC;QAEH,OAAO;YACL,QAAQ,EAAE,OAAO;YACjB,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC,MAAM;YACpC,OAAO,EAAE,QAAQ,CAAC,OAAO;YACzB,UAAU,EAAE,QAAQ,CAAC,UAAU;YAC/B,OAAO,EAAE,MAAM,CAAC,OAAO;SACxB,CAAC;IACJ,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+CG;IACH,KAAK,CAAC,WAAW,CAAC,KAA+B;QAC/C,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,QAAQ,EAAE,GAAG,KAAK,CAAC;QAE7D,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAA,mCAAe,EAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;QACnG,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAE/E,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAC5B,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;YACf,SAAS,EAAE,GAAG,CAAC,SAAS,GAAG,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,MAAM;YAC1D,OAAO,EAAE,GAAG,CAAC,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,OAAO;YAC7C,UAAU,EAAE,GAAG,CAAC,UAAU,GAAG,KAAK,CAAC,QAAQ,CAAC,UAAU;SACvD,CAAC,EACF,EAAE,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,CAC5C,CAAC;QAEF,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YACnB,MAAM,IAAI,oBAAW,CACnB,GAAG,EACH,mBAAmB,EACnB,OAAO,CAAC,MAAM;gBACZ,CAAC,CAAC,eAAe,OAAO,CAAC,MAAM,kCAAkC;gBACjE,CAAC,CAAC,4CAA4C,CACjD,CAAC;QACJ,CAAC;QAED,IAAI,MAAM,CAAC,SAAS,GAAG,sCAAkB,EAAE,CAAC;YAC1C,MAAM,IAAI,oBAAW,CACnB,GAAG,EACH,mBAAmB,EACnB,kCAAkC,sCAAkB,CAAC,cAAc,CAAC,OAAO,CAAC,yBAAyB;gBACnG,yBAAyB,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,OAAO,CAAC,8BAA8B,CAClG,CAAC;QACJ,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,MAAM,EAAE,kBAAkB,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE;YAC7F,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;YAE/E,OAAO;gBACL,MAAM,EAAE;oBACN,GAAG,MAAM,CAAC,KAAK;oBACf,IAAI,EAAE,YAAY;oBAClB,GAAG,CAAC,MAAM,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;oBACpE,GAAG,CAAC,MAAM,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC;oBAC1E,wEAAwE;oBACxE,gEAAgE;oBAChE,KAAK,EAAE,QAAQ,CAAC,SAAS,CAAC,MAAM;oBAChC,GAAG,MAAM;iBACV;gBACD,MAAM,EAAE;oBACN,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC,MAAM;oBACpC,OAAO,EAAE,QAAQ,CAAC,OAAO;oBACzB,UAAU,EAAE,QAAQ,CAAC,UAAU;oBAC/B,OAAO,EAAE,MAAM,CAAC,OAAO;oBACvB,GAAG,CAAC,MAAM,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;oBACpE,GAAG,CAAC,MAAM,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC;iBAC/C;aAC9B,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC;YAChC,GAAG,QAAQ;YACX,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,UAAU;YAC9B,OAAO,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAmC;SACjF,CAAC,CAAC;QAEH,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,GAAG,MAAM,EAAE,CAAC;IAC1F,CAAC;IAED;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,eAAe,CACnB,SAAsC,EACtC,MAAoB;IACpB;;;;OAIG;IACH,WAA6B,MAAM;QAEnC,MAAM,IAAI,GAAG,MAAM,IAAA,iCAAiB,EAAC,CAAC,GAAG,SAAS,CAAC,EAAE,QAAQ,CAAC,CAAC;QAE/D,MAAM,MAAM,GAAsB;YAChC,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,IAAI,EAAE,IAAI,CAAC,IAAI;SAChB,CAAC;QAEF,4EAA4E;QAC5E,2EAA2E;QAC3E,qBAAqB;QACrB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE;YAC9C,IAAI,EAAE,UAAU;YAChB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,MAAM;SACP,CAAC,CAAC;QAEH,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;IACxC,CAAC;IAED,MAAM,CAAC,EAAU,EAAE,KAA0B;QAC3C,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAW,OAAO,EAAE,iBAAiB,kBAAkB,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC1G,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,EAAU,EAAE,QAA8B,EAAE;QAClD,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAW,MAAM,EAAE,iBAAiB,kBAAkB,CAAC,EAAE,CAAC,UAAU,EAAE;YAC5F,IAAI,EAAE,KAAK;SACZ,CAAC,CAAC;IACL,CAAC;IAED,2EAA2E;IAC3E,QAAQ,CAAC,EAAU,EAAE,WAAmB;QACtC,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAW,MAAM,EAAE,iBAAiB,kBAAkB,CAAC,EAAE,CAAC,WAAW,EAAE;YAC7F,IAAI,EAAE,EAAE,WAAW,EAAE;SACtB,CAAC,CAAC;IACL,CAAC;IAED,MAAM,CAAC,EAAU;QACf,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAW,MAAM,EAAE,iBAAiB,kBAAkB,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC;IAC/F,CAAC;IAED,MAAM,CAAC,EAAU;QACf,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAO,QAAQ,EAAE,iBAAiB,kBAAkB,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IACtF,CAAC;CACF;AAnVD,8BAmVC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,YAAY,GAAG,KAAK,EAAQ,KAAmB,EAAE,KAAa,EAAE,GAA4B,EAAgB,EAAE;IAClH,MAAM,OAAO,GAAQ,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7C,IAAI,IAAI,GAAG,CAAC,CAAC;IAEb,MAAM,MAAM,GAAG,KAAK,IAAmB,EAAE;QACvC,OAAO,IAAI,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;YAC3B,MAAM,KAAK,GAAG,IAAI,EAAE,CAAC;YACrB,OAAO,CAAC,KAAK,CAAC,GAAG,MAAM,GAAG,CAAC,KAAK,CAAC,KAAK,CAAE,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;IACjF,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import type { BulkDeleteDataStoreRecordsInput, BulkUpdateDataStoreRecordsInput, CreateDataStoreCollectionInput, CreateDataStoreInput, DataStore, DataStoreCollection, DataStoreDeleteResult, DataStoreRecord, DataStoreTestResult, DataStoreUpdateResult, DataStoreWriteResult, InsertDataStoreRecordsInput, ListDataStoreRecordsQuery, ListQuery, Paginated, SearchDataStoreRecordsInput, UpdateDataStoreCollectionInput, UpdateDataStoreInput, UpdateDataStoreRecordInput } from '../types';
|
|
1
|
+
import type { BulkDeleteDataStoreRecordsInput, CloudDataStoreRegion, BulkUpdateDataStoreRecordsInput, CreateDataStoreCollectionInput, CreateDataStoreInput, DataStore, DataStoreCollection, DataStoreDeleteResult, DataStoreRecord, DataStoreTestResult, DataStoreUpdateResult, DataStoreWriteResult, InsertDataStoreRecordsInput, ListDataStoreRecordsQuery, ListQuery, Paginated, SearchDataStoreRecordsInput, UpdateDataStoreCollectionInput, UpdateDataStoreInput, UpdateDataStoreRecordInput } from '../types';
|
|
2
2
|
import type { HttpClient } from '../http';
|
|
3
3
|
/**
|
|
4
|
-
* Data stores: a MongoDB
|
|
4
|
+
* Data stores: a MongoDB you own, or one the platform runs for you.
|
|
5
5
|
*
|
|
6
6
|
* ```ts
|
|
7
|
+
* // your own cluster, registered with a credential
|
|
7
8
|
* const store = await crm.dataStores.create({
|
|
8
9
|
* title: 'Orders DB',
|
|
9
10
|
* credentials: {
|
|
@@ -15,6 +16,9 @@ import type { HttpClient } from '../http';
|
|
|
15
16
|
* },
|
|
16
17
|
* });
|
|
17
18
|
*
|
|
19
|
+
* // or one of the platform's -- no credential, no cluster to run
|
|
20
|
+
* const hosted = await crm.dataStores.create({ title: 'Leads', type: 'cloud' });
|
|
21
|
+
*
|
|
18
22
|
* const orders = await crm.dataStoreCollections.create(store.id, {
|
|
19
23
|
* title: 'Orders',
|
|
20
24
|
* fields: [
|
|
@@ -29,12 +33,30 @@ import type { HttpClient } from '../http';
|
|
|
29
33
|
* });
|
|
30
34
|
* ```
|
|
31
35
|
*
|
|
36
|
+
* ## Which kind to pick
|
|
37
|
+
*
|
|
38
|
+
* A **cloud** store (`type: 'cloud'`) is a database the platform provisions on
|
|
39
|
+
* one of its clusters, in a location you choose from `regions()` -- Germany for
|
|
40
|
+
* now. There is no credential to manage, nothing to expose to the internet, and
|
|
41
|
+
* no IP access list to maintain. Use it for data that does not already live
|
|
42
|
+
* somewhere.
|
|
43
|
+
*
|
|
44
|
+
* A **self** store is your own cluster, and the default. Use it when the data is
|
|
45
|
+
* already there, or has to stay there.
|
|
46
|
+
*
|
|
47
|
+
* The difference that matters beyond setup is **who the system of record is**,
|
|
48
|
+
* and it shows up on delete -- see below.
|
|
49
|
+
*
|
|
32
50
|
* ## What the platform holds, and what it does not
|
|
33
51
|
*
|
|
34
|
-
*
|
|
35
|
-
* **none of your data**: records are read from and written to
|
|
36
|
-
* every call, so nothing here is a copy that can go stale, and
|
|
37
|
-
* survives you revoking the credential.
|
|
52
|
+
* For a `self` store it holds the credential -- encrypted -- and the schemas you
|
|
53
|
+
* declare. It holds **none of your data**: records are read from and written to
|
|
54
|
+
* your cluster on every call, so nothing here is a copy that can go stale, and
|
|
55
|
+
* nothing here survives you revoking the credential.
|
|
56
|
+
*
|
|
57
|
+
* For a `cloud` store it holds the database itself. Nothing about that cluster
|
|
58
|
+
* is returned -- no host, no user, no connection string -- because there is no
|
|
59
|
+
* second way in: the API is the only door to those records.
|
|
38
60
|
*
|
|
39
61
|
* Which also means every record call costs a round trip to your database. The
|
|
40
62
|
* platform keeps the connection open between calls, so that is one query's
|
|
@@ -42,14 +64,17 @@ import type { HttpClient } from '../http';
|
|
|
42
64
|
* single inserts is a thousand round trips: send them as one `insert` with
|
|
43
65
|
* `records` instead.
|
|
44
66
|
*
|
|
45
|
-
* ## Deleting never deletes your data
|
|
67
|
+
* ## Deleting a `self` store never deletes your data. Deleting a `cloud` store does
|
|
68
|
+
*
|
|
69
|
+
* On a **self** store, `remove()` forgets the credential and the schemas, and
|
|
70
|
+
* `remove()` on a collection forgets the declaration. **Neither touches your
|
|
71
|
+
* database** -- the collections and every document in them are exactly as they
|
|
72
|
+
* were, and re-registering the same credential with the same collection name
|
|
73
|
+
* reads it all straight back.
|
|
46
74
|
*
|
|
47
|
-
* `remove()`
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* re-registering the same credential with the same collection name reads it all
|
|
51
|
-
* straight back. Deleting *records* is the only thing here that deletes
|
|
52
|
-
* anything of yours.
|
|
75
|
+
* On a **cloud** store, `remove()` **destroys the database**. Every record in it
|
|
76
|
+
* is gone, there is no undo, and re-creating the store gets a fresh empty one.
|
|
77
|
+
* Ask before you call it.
|
|
53
78
|
*
|
|
54
79
|
* ## The host must be reachable from the internet
|
|
55
80
|
*
|
|
@@ -62,6 +87,21 @@ import type { HttpClient } from '../http';
|
|
|
62
87
|
export declare class DataStores {
|
|
63
88
|
private readonly http;
|
|
64
89
|
constructor(http: HttpClient);
|
|
90
|
+
/**
|
|
91
|
+
* `GET /v1/datastores/cloud-regions` -- `{ data: [{ code, location }] }`.
|
|
92
|
+
*
|
|
93
|
+
* The clusters a cloud store can be provisioned on. Read it to render the
|
|
94
|
+
* location choice rather than hardcoding one: locations are added over time,
|
|
95
|
+
* and the `code` is what a create takes.
|
|
96
|
+
*
|
|
97
|
+
* An **empty list** means this deployment provisions none -- an on-premise
|
|
98
|
+
* install, typically -- and a `type: 'cloud'` create would be refused.
|
|
99
|
+
*
|
|
100
|
+
* Scope: `datastores:read`.
|
|
101
|
+
*/
|
|
102
|
+
regions(): Promise<{
|
|
103
|
+
data: CloudDataStoreRegion[];
|
|
104
|
+
}>;
|
|
65
105
|
/**
|
|
66
106
|
* `POST /v1/datastores` -- 201 with the store.
|
|
67
107
|
*
|
|
@@ -70,7 +110,25 @@ export declare class DataStores {
|
|
|
70
110
|
* | Field | Type | Required | Notes |
|
|
71
111
|
* | --- | --- | --- | --- |
|
|
72
112
|
* | `title` | string | yes | What you call it. 1-120 characters. |
|
|
73
|
-
* | `
|
|
113
|
+
* | `type` | `'self' \| 'cloud'` | no | Default `self`: your own database. |
|
|
114
|
+
* | `credentials` | object | for `self` | Either form below. Refused for `cloud`. |
|
|
115
|
+
* | `code` | string | no | `cloud` only: a cluster from `regions()`. Defaults to the platform's. |
|
|
116
|
+
*
|
|
117
|
+
* ### A cloud store
|
|
118
|
+
*
|
|
119
|
+
* ```ts
|
|
120
|
+
* await crm.dataStores.create({ title: 'Leads', type: 'cloud' });
|
|
121
|
+
* ```
|
|
122
|
+
*
|
|
123
|
+
* That is the whole payload: the platform provisions a database of its own,
|
|
124
|
+
* in the default location, and the response carries the `code` and `location`
|
|
125
|
+
* it ended up in. Nothing below about credentials, hosts or IP access lists
|
|
126
|
+
* applies -- and neither does the 502 pair, because there is no cluster of
|
|
127
|
+
* yours to be unreachable.
|
|
128
|
+
*
|
|
129
|
+
* | Status | Code | Meaning |
|
|
130
|
+
* | --- | --- | --- |
|
|
131
|
+
* | 422 | `validation_failed` | Credentials sent with `type: 'cloud'`, or a `code` that is not on offer -- including when this deployment offers none. |
|
|
74
132
|
*
|
|
75
133
|
* **`credentials.type: 'field'`**
|
|
76
134
|
*
|
|
@@ -128,7 +186,13 @@ export declare class DataStores {
|
|
|
128
186
|
* | Field | Type | Notes |
|
|
129
187
|
* | --- | --- | --- |
|
|
130
188
|
* | `title` | string | |
|
|
131
|
-
* | `credentials` | object | Replaced whole, and tested before it is stored. |
|
|
189
|
+
* | `credentials` | object | Replaced whole, and tested before it is stored. `self` stores only. |
|
|
190
|
+
*
|
|
191
|
+
* `type` and `code` are not editable: they say where the records are, not how
|
|
192
|
+
* the store is configured. Sending `credentials` for a **cloud** store is a
|
|
193
|
+
* `422` -- there is nothing to replace, and re-pointing it would leave its
|
|
194
|
+
* records in a database nothing addresses. Register your own database as a
|
|
195
|
+
* second store instead.
|
|
132
196
|
*
|
|
133
197
|
* A partial credential is not accepted: send all four fields, or the whole
|
|
134
198
|
* URI. New credentials go through the same checks as `create`, and the old
|
|
@@ -141,10 +205,19 @@ export declare class DataStores {
|
|
|
141
205
|
/**
|
|
142
206
|
* `DELETE /v1/datastores/:store` -- 204.
|
|
143
207
|
*
|
|
144
|
-
*
|
|
145
|
-
* not touched
|
|
146
|
-
* registering the same credential again with the same collection
|
|
147
|
-
* them straight back.
|
|
208
|
+
* **A `self` store:** forgets the credential and every schema declared on it.
|
|
209
|
+
* Your database is not touched -- the collections and all their documents
|
|
210
|
+
* remain, and registering the same credential again with the same collection
|
|
211
|
+
* names reads them straight back.
|
|
212
|
+
*
|
|
213
|
+
* **A `cloud` store:** drops every collection in its database, which destroys
|
|
214
|
+
* the database with them. Every record is gone, there is no undo, and a new
|
|
215
|
+
* store of the same name is a new empty database. Confirm with the user before
|
|
216
|
+
* calling this on one.
|
|
217
|
+
*
|
|
218
|
+
* | Status | Code | Meaning |
|
|
219
|
+
* | --- | --- | --- |
|
|
220
|
+
* | 502 | `datastore_purge_failed` | A collection could not be deleted, named in `details`. Nothing was removed -- the store is intact and the call can be retried. |
|
|
148
221
|
*
|
|
149
222
|
* Scope: `datastores:write`.
|
|
150
223
|
*/
|
|
@@ -8,9 +8,10 @@ const collectionsPath = (store) => `${storePath(store)}/collections`;
|
|
|
8
8
|
const collectionPath = (store, collection) => `${collectionsPath(store)}/${(0, paths_1.seg)(collection)}`;
|
|
9
9
|
const recordsPath = (store, collection) => `${collectionPath(store, collection)}/records`;
|
|
10
10
|
/**
|
|
11
|
-
* Data stores: a MongoDB
|
|
11
|
+
* Data stores: a MongoDB you own, or one the platform runs for you.
|
|
12
12
|
*
|
|
13
13
|
* ```ts
|
|
14
|
+
* // your own cluster, registered with a credential
|
|
14
15
|
* const store = await crm.dataStores.create({
|
|
15
16
|
* title: 'Orders DB',
|
|
16
17
|
* credentials: {
|
|
@@ -22,6 +23,9 @@ const recordsPath = (store, collection) => `${collectionPath(store, collection)}
|
|
|
22
23
|
* },
|
|
23
24
|
* });
|
|
24
25
|
*
|
|
26
|
+
* // or one of the platform's -- no credential, no cluster to run
|
|
27
|
+
* const hosted = await crm.dataStores.create({ title: 'Leads', type: 'cloud' });
|
|
28
|
+
*
|
|
25
29
|
* const orders = await crm.dataStoreCollections.create(store.id, {
|
|
26
30
|
* title: 'Orders',
|
|
27
31
|
* fields: [
|
|
@@ -36,12 +40,30 @@ const recordsPath = (store, collection) => `${collectionPath(store, collection)}
|
|
|
36
40
|
* });
|
|
37
41
|
* ```
|
|
38
42
|
*
|
|
43
|
+
* ## Which kind to pick
|
|
44
|
+
*
|
|
45
|
+
* A **cloud** store (`type: 'cloud'`) is a database the platform provisions on
|
|
46
|
+
* one of its clusters, in a location you choose from `regions()` -- Germany for
|
|
47
|
+
* now. There is no credential to manage, nothing to expose to the internet, and
|
|
48
|
+
* no IP access list to maintain. Use it for data that does not already live
|
|
49
|
+
* somewhere.
|
|
50
|
+
*
|
|
51
|
+
* A **self** store is your own cluster, and the default. Use it when the data is
|
|
52
|
+
* already there, or has to stay there.
|
|
53
|
+
*
|
|
54
|
+
* The difference that matters beyond setup is **who the system of record is**,
|
|
55
|
+
* and it shows up on delete -- see below.
|
|
56
|
+
*
|
|
39
57
|
* ## What the platform holds, and what it does not
|
|
40
58
|
*
|
|
41
|
-
*
|
|
42
|
-
* **none of your data**: records are read from and written to
|
|
43
|
-
* every call, so nothing here is a copy that can go stale, and
|
|
44
|
-
* survives you revoking the credential.
|
|
59
|
+
* For a `self` store it holds the credential -- encrypted -- and the schemas you
|
|
60
|
+
* declare. It holds **none of your data**: records are read from and written to
|
|
61
|
+
* your cluster on every call, so nothing here is a copy that can go stale, and
|
|
62
|
+
* nothing here survives you revoking the credential.
|
|
63
|
+
*
|
|
64
|
+
* For a `cloud` store it holds the database itself. Nothing about that cluster
|
|
65
|
+
* is returned -- no host, no user, no connection string -- because there is no
|
|
66
|
+
* second way in: the API is the only door to those records.
|
|
45
67
|
*
|
|
46
68
|
* Which also means every record call costs a round trip to your database. The
|
|
47
69
|
* platform keeps the connection open between calls, so that is one query's
|
|
@@ -49,14 +71,17 @@ const recordsPath = (store, collection) => `${collectionPath(store, collection)}
|
|
|
49
71
|
* single inserts is a thousand round trips: send them as one `insert` with
|
|
50
72
|
* `records` instead.
|
|
51
73
|
*
|
|
52
|
-
* ## Deleting never deletes your data
|
|
74
|
+
* ## Deleting a `self` store never deletes your data. Deleting a `cloud` store does
|
|
75
|
+
*
|
|
76
|
+
* On a **self** store, `remove()` forgets the credential and the schemas, and
|
|
77
|
+
* `remove()` on a collection forgets the declaration. **Neither touches your
|
|
78
|
+
* database** -- the collections and every document in them are exactly as they
|
|
79
|
+
* were, and re-registering the same credential with the same collection name
|
|
80
|
+
* reads it all straight back.
|
|
53
81
|
*
|
|
54
|
-
* `remove()`
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* re-registering the same credential with the same collection name reads it all
|
|
58
|
-
* straight back. Deleting *records* is the only thing here that deletes
|
|
59
|
-
* anything of yours.
|
|
82
|
+
* On a **cloud** store, `remove()` **destroys the database**. Every record in it
|
|
83
|
+
* is gone, there is no undo, and re-creating the store gets a fresh empty one.
|
|
84
|
+
* Ask before you call it.
|
|
60
85
|
*
|
|
61
86
|
* ## The host must be reachable from the internet
|
|
62
87
|
*
|
|
@@ -71,6 +96,21 @@ class DataStores {
|
|
|
71
96
|
constructor(http) {
|
|
72
97
|
this.http = http;
|
|
73
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* `GET /v1/datastores/cloud-regions` -- `{ data: [{ code, location }] }`.
|
|
101
|
+
*
|
|
102
|
+
* The clusters a cloud store can be provisioned on. Read it to render the
|
|
103
|
+
* location choice rather than hardcoding one: locations are added over time,
|
|
104
|
+
* and the `code` is what a create takes.
|
|
105
|
+
*
|
|
106
|
+
* An **empty list** means this deployment provisions none -- an on-premise
|
|
107
|
+
* install, typically -- and a `type: 'cloud'` create would be refused.
|
|
108
|
+
*
|
|
109
|
+
* Scope: `datastores:read`.
|
|
110
|
+
*/
|
|
111
|
+
regions() {
|
|
112
|
+
return this.http.request('GET', `${BASE}/cloud-regions`);
|
|
113
|
+
}
|
|
74
114
|
/**
|
|
75
115
|
* `POST /v1/datastores` -- 201 with the store.
|
|
76
116
|
*
|
|
@@ -79,7 +119,25 @@ class DataStores {
|
|
|
79
119
|
* | Field | Type | Required | Notes |
|
|
80
120
|
* | --- | --- | --- | --- |
|
|
81
121
|
* | `title` | string | yes | What you call it. 1-120 characters. |
|
|
82
|
-
* | `
|
|
122
|
+
* | `type` | `'self' \| 'cloud'` | no | Default `self`: your own database. |
|
|
123
|
+
* | `credentials` | object | for `self` | Either form below. Refused for `cloud`. |
|
|
124
|
+
* | `code` | string | no | `cloud` only: a cluster from `regions()`. Defaults to the platform's. |
|
|
125
|
+
*
|
|
126
|
+
* ### A cloud store
|
|
127
|
+
*
|
|
128
|
+
* ```ts
|
|
129
|
+
* await crm.dataStores.create({ title: 'Leads', type: 'cloud' });
|
|
130
|
+
* ```
|
|
131
|
+
*
|
|
132
|
+
* That is the whole payload: the platform provisions a database of its own,
|
|
133
|
+
* in the default location, and the response carries the `code` and `location`
|
|
134
|
+
* it ended up in. Nothing below about credentials, hosts or IP access lists
|
|
135
|
+
* applies -- and neither does the 502 pair, because there is no cluster of
|
|
136
|
+
* yours to be unreachable.
|
|
137
|
+
*
|
|
138
|
+
* | Status | Code | Meaning |
|
|
139
|
+
* | --- | --- | --- |
|
|
140
|
+
* | 422 | `validation_failed` | Credentials sent with `type: 'cloud'`, or a `code` that is not on offer -- including when this deployment offers none. |
|
|
83
141
|
*
|
|
84
142
|
* **`credentials.type: 'field'`**
|
|
85
143
|
*
|
|
@@ -143,7 +201,13 @@ class DataStores {
|
|
|
143
201
|
* | Field | Type | Notes |
|
|
144
202
|
* | --- | --- | --- |
|
|
145
203
|
* | `title` | string | |
|
|
146
|
-
* | `credentials` | object | Replaced whole, and tested before it is stored. |
|
|
204
|
+
* | `credentials` | object | Replaced whole, and tested before it is stored. `self` stores only. |
|
|
205
|
+
*
|
|
206
|
+
* `type` and `code` are not editable: they say where the records are, not how
|
|
207
|
+
* the store is configured. Sending `credentials` for a **cloud** store is a
|
|
208
|
+
* `422` -- there is nothing to replace, and re-pointing it would leave its
|
|
209
|
+
* records in a database nothing addresses. Register your own database as a
|
|
210
|
+
* second store instead.
|
|
147
211
|
*
|
|
148
212
|
* A partial credential is not accepted: send all four fields, or the whole
|
|
149
213
|
* URI. New credentials go through the same checks as `create`, and the old
|
|
@@ -158,10 +222,19 @@ class DataStores {
|
|
|
158
222
|
/**
|
|
159
223
|
* `DELETE /v1/datastores/:store` -- 204.
|
|
160
224
|
*
|
|
161
|
-
*
|
|
162
|
-
* not touched
|
|
163
|
-
* registering the same credential again with the same collection
|
|
164
|
-
* them straight back.
|
|
225
|
+
* **A `self` store:** forgets the credential and every schema declared on it.
|
|
226
|
+
* Your database is not touched -- the collections and all their documents
|
|
227
|
+
* remain, and registering the same credential again with the same collection
|
|
228
|
+
* names reads them straight back.
|
|
229
|
+
*
|
|
230
|
+
* **A `cloud` store:** drops every collection in its database, which destroys
|
|
231
|
+
* the database with them. Every record is gone, there is no undo, and a new
|
|
232
|
+
* store of the same name is a new empty database. Confirm with the user before
|
|
233
|
+
* calling this on one.
|
|
234
|
+
*
|
|
235
|
+
* | Status | Code | Meaning |
|
|
236
|
+
* | --- | --- | --- |
|
|
237
|
+
* | 502 | `datastore_purge_failed` | A collection could not be deleted, named in `details`. Nothing was removed -- the store is intact and the call can be retried. |
|
|
165
238
|
*
|
|
166
239
|
* Scope: `datastores:write`.
|
|
167
240
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-stores.js","sourceRoot":"","sources":["../../src/resources/data-stores.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"data-stores.js","sourceRoot":"","sources":["../../src/resources/data-stores.ts"],"names":[],"mappings":"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"}
|
package/dist/types/campaign.d.ts
CHANGED
|
@@ -130,6 +130,13 @@ export interface Campaign {
|
|
|
130
130
|
parentBroadcastId?: string;
|
|
131
131
|
scheduleId?: string;
|
|
132
132
|
hidden: boolean;
|
|
133
|
+
/**
|
|
134
|
+
* When the campaign is armed to go out -- and whether it is armed at all.
|
|
135
|
+
*
|
|
136
|
+
* Absent on a campaign nobody has triggered yet, which is the state every
|
|
137
|
+
* campaign starts in. Set by `trigger()` or `schedule()`, never by `create()`
|
|
138
|
+
* or `update()`.
|
|
139
|
+
*/
|
|
133
140
|
scheduledAt?: IsoDate;
|
|
134
141
|
processedAt?: IsoDate;
|
|
135
142
|
completedAt?: IsoDate;
|
|
@@ -147,11 +154,22 @@ export interface Campaign {
|
|
|
147
154
|
createdAt: IsoDate;
|
|
148
155
|
updatedAt: IsoDate;
|
|
149
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* A campaign to write. Writing one does not send it: see `campaigns.trigger()`.
|
|
159
|
+
*
|
|
160
|
+
* There is no `scheduledAt` here on purpose, and the API rejects one rather than
|
|
161
|
+
* ignoring it -- a send time belongs to the act of arming the campaign, which is
|
|
162
|
+
* also what records who armed it.
|
|
163
|
+
*/
|
|
150
164
|
export interface CreateCampaignInput {
|
|
151
165
|
name: string;
|
|
152
166
|
/** Defaults to `general`. `createBulk()` sets `bulk_campaign` itself. */
|
|
153
167
|
type?: string;
|
|
154
168
|
campaignType?: CampaignType;
|
|
169
|
+
/**
|
|
170
|
+
* How you expect it to go out. Advisory: `trigger()` rewrites it from what it
|
|
171
|
+
* is actually given, so this only matters to your own reading of the record.
|
|
172
|
+
*/
|
|
155
173
|
trigger?: CampaignTrigger;
|
|
156
174
|
channelType?: string;
|
|
157
175
|
messagingPattern?: string;
|
|
@@ -163,10 +181,18 @@ export interface CreateCampaignInput {
|
|
|
163
181
|
repeat?: CampaignRepeat;
|
|
164
182
|
parentBroadcastId?: string;
|
|
165
183
|
hidden?: boolean;
|
|
166
|
-
/** Required when `trigger` is `schedule`. */
|
|
167
|
-
scheduledAt?: IsoDate;
|
|
168
184
|
}
|
|
169
185
|
export type UpdateCampaignInput = Partial<CreateCampaignInput>;
|
|
186
|
+
/**
|
|
187
|
+
* When to send. An empty object -- or no argument at all -- means now.
|
|
188
|
+
*
|
|
189
|
+
* "Now" is not instant: the campaign is armed a short way out (a minute by
|
|
190
|
+
* default) so there is a window in which `cancel()` still stops it.
|
|
191
|
+
*/
|
|
192
|
+
export interface TriggerCampaignInput {
|
|
193
|
+
/** ISO-8601 with an offset, e.g. `2026-11-01T04:30:00.000Z`. */
|
|
194
|
+
scheduledAt?: IsoDate;
|
|
195
|
+
}
|
|
170
196
|
/**
|
|
171
197
|
* Declared as a type alias rather than an interface, and that is load-bearing:
|
|
172
198
|
* only an alias of an object literal gets TypeScript's implicit index signature,
|
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Data Store: a customer's own MongoDB, registered with the platform.
|
|
3
3
|
*
|
|
4
|
+
* A store is one of two things, and `type` says which:
|
|
5
|
+
*
|
|
6
|
+
* - **`self`** -- a database **you** own, registered with a credential. The
|
|
7
|
+
* platform holds that credential and nothing else: no copy, no mirror, and
|
|
8
|
+
* deleting the store never touches your data.
|
|
9
|
+
* - **`cloud`** -- a database the **platform** provisions for you, on one of its
|
|
10
|
+
* clusters, in a location you pick. No credential to manage, nothing to open
|
|
11
|
+
* to the internet -- and the platform is then the system of record, so
|
|
12
|
+
* deleting the store deletes the records with it.
|
|
13
|
+
*
|
|
14
|
+
* Everything else is identical: the same collections, the same records, the same
|
|
15
|
+
* calls. Pick `cloud` to store data you do not already have somewhere, and
|
|
16
|
+
* `self` to work with a database you do.
|
|
17
|
+
*
|
|
4
18
|
* Three nested things:
|
|
5
19
|
*
|
|
6
|
-
* 1. **A data store** --
|
|
7
|
-
* holds the credential and nothing else: no copy of the data, no
|
|
20
|
+
* 1. **A data store** -- the database, yours or ours. For a `self` store the
|
|
21
|
+
* platform holds the credential and nothing else: no copy of the data, no
|
|
22
|
+
* mirror.
|
|
8
23
|
* 2. **A collection** -- a schema you declare, naming a collection inside that
|
|
9
24
|
* database and the fields in it.
|
|
10
25
|
* 3. **Records** -- the documents. They live in your database, and every record
|
|
@@ -16,6 +31,21 @@
|
|
|
16
31
|
* guarantee -- a document may carry fields the schema never mentioned, or lack
|
|
17
32
|
* ones it does.
|
|
18
33
|
*/
|
|
34
|
+
/** Whose database the records are in. */
|
|
35
|
+
export type DataStoreType = 'self' | 'cloud';
|
|
36
|
+
/**
|
|
37
|
+
* One cluster the platform can provision a cloud store on.
|
|
38
|
+
*
|
|
39
|
+
* `code` identifies the cluster and is what you send on a create; `location` is
|
|
40
|
+
* the country it is in, which is the half worth showing a user. New locations
|
|
41
|
+
* appear in this list rather than in a release note -- read it, do not hardcode
|
|
42
|
+
* it -- and an existing store never moves when one is added, which is why its
|
|
43
|
+
* `code` is returned on the store itself.
|
|
44
|
+
*/
|
|
45
|
+
export interface CloudDataStoreRegion {
|
|
46
|
+
code: string;
|
|
47
|
+
location: string;
|
|
48
|
+
}
|
|
19
49
|
/** The five storage types a field can declare. */
|
|
20
50
|
export type DataStoreFieldType = 'String' | 'Number' | 'Boolean' | 'Date' | 'Array';
|
|
21
51
|
/**
|
|
@@ -55,9 +85,32 @@ export type DataStoreCredentials = {
|
|
|
55
85
|
*/
|
|
56
86
|
uri: string;
|
|
57
87
|
};
|
|
88
|
+
/**
|
|
89
|
+
* Register your own database, or ask for one of the platform's.
|
|
90
|
+
*
|
|
91
|
+
* ```ts
|
|
92
|
+
* // yours
|
|
93
|
+
* await crm.dataStores.create({ title: 'Orders DB', credentials: { … } });
|
|
94
|
+
*
|
|
95
|
+
* // theirs, in Germany
|
|
96
|
+
* await crm.dataStores.create({ title: 'Leads', type: 'cloud' });
|
|
97
|
+
* ```
|
|
98
|
+
*
|
|
99
|
+
* `type` defaults to `self`, so existing code is unaffected. The two halves do
|
|
100
|
+
* not mix: a `self` store needs `credentials` and takes no `code`, a `cloud`
|
|
101
|
+
* store takes a `code` and no credentials, and anything else is a `422`.
|
|
102
|
+
*/
|
|
58
103
|
export interface CreateDataStoreInput {
|
|
59
104
|
title: string;
|
|
60
|
-
|
|
105
|
+
/** `self` -- your database. `cloud` -- one of the platform's. Default `self`. */
|
|
106
|
+
type?: DataStoreType;
|
|
107
|
+
/** Required for `self`, and refused for `cloud`. */
|
|
108
|
+
credentials?: DataStoreCredentials;
|
|
109
|
+
/**
|
|
110
|
+
* `cloud` only: which cluster to put it on, from
|
|
111
|
+
* {@link DataStores.regions}. Omitted, the platform picks its default.
|
|
112
|
+
*/
|
|
113
|
+
code?: string;
|
|
61
114
|
}
|
|
62
115
|
/**
|
|
63
116
|
* Rename, re-point, or both.
|
|
@@ -81,12 +134,25 @@ export interface UpdateDataStoreInput {
|
|
|
81
134
|
export interface DataStore {
|
|
82
135
|
id: string;
|
|
83
136
|
title: string;
|
|
84
|
-
|
|
137
|
+
/** Whose database it is. Stores created before cloud stores existed are `self`. */
|
|
138
|
+
type: DataStoreType;
|
|
139
|
+
/** `cloud` only: the cluster it is on. Quote it in a support request. */
|
|
140
|
+
code?: string;
|
|
141
|
+
/** `cloud` only: where that cluster is -- `Germany`. */
|
|
142
|
+
location?: string;
|
|
143
|
+
/** How the credential was given. Absent on a `cloud` store, which has none. */
|
|
144
|
+
credentialType?: 'field' | 'string';
|
|
85
145
|
engine: 'mongodb';
|
|
146
|
+
/** Your host. Empty on a `cloud` store: the platform's own is not disclosed. */
|
|
86
147
|
host: string;
|
|
87
148
|
database: string;
|
|
149
|
+
/** Your database user. Empty on a `cloud` store. */
|
|
88
150
|
username: string;
|
|
89
|
-
/**
|
|
151
|
+
/**
|
|
152
|
+
* For display. The connection string with the password removed for a `self`
|
|
153
|
+
* store, and `cloud://<location>/<database>` for a cloud one -- a label, not
|
|
154
|
+
* something to connect with.
|
|
155
|
+
*/
|
|
90
156
|
summary: string;
|
|
91
157
|
/** When the connection was last proven to work: a create, an update, or a test. */
|
|
92
158
|
verifiedAt?: string;
|
package/dist/types/datastore.js
CHANGED
|
@@ -2,10 +2,25 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Data Store: a customer's own MongoDB, registered with the platform.
|
|
4
4
|
*
|
|
5
|
+
* A store is one of two things, and `type` says which:
|
|
6
|
+
*
|
|
7
|
+
* - **`self`** -- a database **you** own, registered with a credential. The
|
|
8
|
+
* platform holds that credential and nothing else: no copy, no mirror, and
|
|
9
|
+
* deleting the store never touches your data.
|
|
10
|
+
* - **`cloud`** -- a database the **platform** provisions for you, on one of its
|
|
11
|
+
* clusters, in a location you pick. No credential to manage, nothing to open
|
|
12
|
+
* to the internet -- and the platform is then the system of record, so
|
|
13
|
+
* deleting the store deletes the records with it.
|
|
14
|
+
*
|
|
15
|
+
* Everything else is identical: the same collections, the same records, the same
|
|
16
|
+
* calls. Pick `cloud` to store data you do not already have somewhere, and
|
|
17
|
+
* `self` to work with a database you do.
|
|
18
|
+
*
|
|
5
19
|
* Three nested things:
|
|
6
20
|
*
|
|
7
|
-
* 1. **A data store** --
|
|
8
|
-
* holds the credential and nothing else: no copy of the data, no
|
|
21
|
+
* 1. **A data store** -- the database, yours or ours. For a `self` store the
|
|
22
|
+
* platform holds the credential and nothing else: no copy of the data, no
|
|
23
|
+
* mirror.
|
|
9
24
|
* 2. **A collection** -- a schema you declare, naming a collection inside that
|
|
10
25
|
* database and the fields in it.
|
|
11
26
|
* 3. **Records** -- the documents. They live in your database, and every record
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"datastore.js","sourceRoot":"","sources":["../../src/types/datastore.ts"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"datastore.js","sourceRoot":"","sources":["../../src/types/datastore.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG"}
|
|
@@ -331,6 +331,17 @@ There is no presence check: an agent with the conversation open on screen still
|
|
|
331
331
|
gets the push on their phone. That is the behaviour the panel has always had, and
|
|
332
332
|
changing it is a product decision rather than a detail of delivery.
|
|
333
333
|
|
|
334
|
+
A person is matched under **either of their two ids**. A request carries the
|
|
335
|
+
ecosystem's `psuid`, which is what `subscribe()` files a device under; the inbox
|
|
336
|
+
trigger reads its recipient off a chat row the legacy stack wrote, so the id it
|
|
337
|
+
holds is a legacy one. Registration records both, and the lookup matches the one
|
|
338
|
+
id it is given against either field — so it resolves one person under two
|
|
339
|
+
names, and never returns a colleague's devices.
|
|
340
|
+
|
|
341
|
+
The practical consequence: a device registered before this existed is still
|
|
342
|
+
reachable by `psuid` exactly as before, and the next `subscribe()` call — which
|
|
343
|
+
the frontend makes on every app load — fills in the second id.
|
|
344
|
+
|
|
334
345
|
### Rendering it in a browser
|
|
335
346
|
|
|
336
347
|
If the payload carries a `notification` block, FCM displays it and opens
|
|
@@ -13,12 +13,50 @@ One message sent to many people.
|
|
|
13
13
|
| [`createGroup`](#creategroup) | uploads + `POST /v1/campaigns` | **one bucket per channel or template** |
|
|
14
14
|
| [`uploadReceivers`](#uploadreceivers) | upload only | the file half of `createBulk` |
|
|
15
15
|
| [`update`](#update) | `PATCH /v1/campaigns/:id` | before it runs |
|
|
16
|
-
| [`
|
|
16
|
+
| [`trigger`](#trigger) | `POST /v1/campaigns/:id/trigger` | **the only call that sends** |
|
|
17
|
+
| [`schedule`](#schedule) | `POST /v1/campaigns/:id/schedule` | `trigger` with a required time |
|
|
17
18
|
| [`cancel`](#cancel) | `POST /v1/campaigns/:id/cancel` | |
|
|
18
19
|
| [`delete`](#delete) | `DELETE /v1/campaigns/:id` | |
|
|
19
20
|
|
|
20
21
|
---
|
|
21
22
|
|
|
23
|
+
## Creating a campaign does not send it
|
|
24
|
+
|
|
25
|
+
Two steps, always:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
const campaign = await crm.campaigns.create({ name: 'Diwali offer', messages });
|
|
29
|
+
// nothing has gone out; `campaign.scheduledAt` is undefined
|
|
30
|
+
|
|
31
|
+
await crm.campaigns.trigger(campaign.id); // send now
|
|
32
|
+
await crm.campaigns.trigger(campaign.id, { scheduledAt: isoInstant }); // or at a time
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
A created campaign is a **draft**: `pending`, with no `scheduledAt` and an empty
|
|
36
|
+
`triggerInfo`. You can `update` it, show it for review, and nothing will send it.
|
|
37
|
+
The sender cannot even see it — a campaign with no `scheduledAt` is not a
|
|
38
|
+
campaign with a moment.
|
|
39
|
+
|
|
40
|
+
`trigger` is what arms it, and is also what records *who* armed it: the server
|
|
41
|
+
stamps `triggerInfo` (the caller, their ip, the moment) as part of that call.
|
|
42
|
+
That is why a send time is **rejected** on `create` and `update` rather than
|
|
43
|
+
accepted — a campaign that could be armed by a write could go out with nobody
|
|
44
|
+
named against it.
|
|
45
|
+
|
|
46
|
+
| | `create` / `update` | `trigger` / `schedule` |
|
|
47
|
+
| --- | --- | --- |
|
|
48
|
+
| `scheduledAt` in the body | **`422`** | that is the point |
|
|
49
|
+
| writes `triggerInfo` | no | yes |
|
|
50
|
+
| campaign becomes sendable | no | yes |
|
|
51
|
+
|
|
52
|
+
**"Now" is not instant.** `trigger` with no time arms the campaign about a minute
|
|
53
|
+
out (`CAMPAIGN_IMMEDIATE_DELAY_SECONDS`), and [`cancel`](#cancel) still stops it
|
|
54
|
+
in that window. A second `trigger` on a campaign that is armed but not yet picked
|
|
55
|
+
up just re-arms it, so a double-click costs a fresh window rather than a second
|
|
56
|
+
send.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
22
60
|
## The audience is one field with five modes
|
|
23
61
|
|
|
24
62
|
A campaign's recipients live in `filters`, and **the mode is decided by the shape
|
|
@@ -182,16 +220,18 @@ calls, so what you preview is what gets sent.
|
|
|
182
220
|
### Scheduling one
|
|
183
221
|
|
|
184
222
|
```ts
|
|
185
|
-
await crm.campaigns.createBulk({
|
|
223
|
+
const { campaign } = await crm.campaigns.createBulk({
|
|
186
224
|
name: 'Renewal reminder',
|
|
187
225
|
audience: { csv, recipientColumn: 'phone' },
|
|
188
226
|
messages: [{ channel: { id: channelId }, message: template }],
|
|
189
|
-
trigger: 'schedule',
|
|
190
|
-
scheduledAt: '2026-11-01T04:30:00.000Z',
|
|
191
227
|
});
|
|
228
|
+
|
|
229
|
+
await crm.campaigns.trigger(campaign.id, { scheduledAt: '2026-11-01T04:30:00.000Z' });
|
|
192
230
|
```
|
|
193
231
|
|
|
194
|
-
The file is uploaded
|
|
232
|
+
The file is uploaded during `createBulk`; the campaign itself does not go out
|
|
233
|
+
until you trigger it. Passing `scheduledAt` to `createBulk` is refused — see
|
|
234
|
+
[creating does not send](#creating-a-campaign-does-not-send-it).
|
|
195
235
|
|
|
196
236
|
### Two things to be careful about
|
|
197
237
|
|
|
@@ -385,19 +425,33 @@ const { data, nextCursor } = await crm.campaigns.list({ limit: 50, status: 'comp
|
|
|
385
425
|
const campaign = await crm.campaigns.get(id);
|
|
386
426
|
```
|
|
387
427
|
|
|
428
|
+
### What a campaign's `id` is
|
|
429
|
+
|
|
430
|
+
The `id` on a campaign is its own identity and the only one this API accepts —
|
|
431
|
+
the same value the campaign manager is handed as `broadcast.id` and stamps on
|
|
432
|
+
every report row as `cid`. So it is what you pass to `get`, `trigger`, `report`,
|
|
433
|
+
and what a re-target filter's `cid` must name.
|
|
434
|
+
|
|
435
|
+
It is a 24-character ObjectId string, but it is not the document's `_id`: that
|
|
436
|
+
is Mongo's key and never appears in a request or a response. The `nextCursor`
|
|
437
|
+
from [`list`](#list) is a paging position rather than an id — pass it back as a
|
|
438
|
+
cursor, never as a campaign id.
|
|
439
|
+
|
|
388
440
|
## `create`
|
|
389
441
|
|
|
390
442
|
For an audience you have already expressed as `filters` — every contact,
|
|
391
443
|
a segment, or a re-target. For a bulk list use [`createBulk`](#createbulk).
|
|
392
444
|
|
|
445
|
+
Returns a draft. Call [`trigger`](#trigger) to send it.
|
|
446
|
+
|
|
393
447
|
| Field | Type | Default |
|
|
394
448
|
| --- | --- | --- |
|
|
395
449
|
| `name` | `string` | required, 1–160 |
|
|
396
450
|
| `messages` | `CampaignMessage[]` | required, at least one |
|
|
397
|
-
| `type` | `string` | `general` |
|
|
451
|
+
| `type` | `string` | `general` — also decides the dispatch pacing, see [below](#trigger) |
|
|
398
452
|
| `campaignType` | `general` `payment` | `general` |
|
|
399
|
-
| `trigger` | `immediate` `schedule` | `immediate` |
|
|
400
|
-
| `scheduledAt` |
|
|
453
|
+
| `trigger` | `immediate` `schedule` | `immediate` — advisory; `trigger()` rewrites it |
|
|
454
|
+
| `scheduledAt` | — | **not accepted**; `422`. Pass it to [`trigger`](#trigger) |
|
|
401
455
|
| `channelType` | `string` | `whatsapp` |
|
|
402
456
|
| `messagingPattern` | `string` | `A2O` |
|
|
403
457
|
| `filters` | `CampaignFilter[]` | `[]` — every contact |
|
|
@@ -412,7 +466,49 @@ a segment, or a re-target. For a bulk list use [`createBulk`](#createbulk).
|
|
|
412
466
|
await crm.campaigns.update(id, { name: 'Diwali offer (revised)' });
|
|
413
467
|
```
|
|
414
468
|
|
|
415
|
-
Every `create` field, all optional
|
|
469
|
+
Every `create` field, all optional — `scheduledAt` included, which means it is
|
|
470
|
+
refused here too. Only meaningful while the campaign is `pending`.
|
|
471
|
+
|
|
472
|
+
## `trigger`
|
|
473
|
+
|
|
474
|
+
```ts
|
|
475
|
+
await crm.campaigns.trigger(id); // now
|
|
476
|
+
await crm.campaigns.trigger(id, { scheduledAt: '2026-11-01T04:30:00.000Z' });
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
| | |
|
|
480
|
+
| --- | --- |
|
|
481
|
+
| Route | `POST /v1/campaigns/:id/trigger` |
|
|
482
|
+
| Body | `{}` or `{ scheduledAt }` — ISO-8601 with an offset |
|
|
483
|
+
| Scope | `campaigns:write` |
|
|
484
|
+
| `404` | no such campaign, or not this account's |
|
|
485
|
+
| `409` | the campaign is not `pending` — already sending, sent, failed or cancelled |
|
|
486
|
+
| `409` | `channel_webhook_unset`: a channel cannot report delivery back (see below) |
|
|
487
|
+
|
|
488
|
+
Writes `trigger`, `scheduledAt` and `triggerInfo`, and nothing else. A past
|
|
489
|
+
`scheduledAt` is accepted and means "now".
|
|
490
|
+
|
|
491
|
+
**The webhook preflight runs here.** Before the campaign is armed, each v2
|
|
492
|
+
WhatsApp channel's Meta subscription is checked and repaired if it has drifted. A
|
|
493
|
+
channel that still cannot deliver webhooks fails the call with `409
|
|
494
|
+
channel_webhook_unset` and the campaign stays a draft — because that failure is
|
|
495
|
+
otherwise invisible: an unsubscribed channel *sends perfectly* and reports
|
|
496
|
+
nothing, leaving every recipient stuck at `sent` forever.
|
|
497
|
+
|
|
498
|
+
### How the campaign reaches the sender
|
|
499
|
+
|
|
500
|
+
Once armed, a poller picks the campaign up when its moment arrives and hands it
|
|
501
|
+
to the sending service. Which route it takes depends on `type`:
|
|
502
|
+
|
|
503
|
+
| `type` | route |
|
|
504
|
+
| --- | --- |
|
|
505
|
+
| `general` | straight to the sender |
|
|
506
|
+
| anything else (`bulk_campaign`, `group_campaign`, `retarget`, …) | through the account's pacing lane — `<user_id>_user_campaign`, shared with the legacy middleware — which releases one campaign per minute per account |
|
|
507
|
+
|
|
508
|
+
So an account that arms five bulk campaigns at once has them delivered to the
|
|
509
|
+
sender a minute apart, in the order they were armed, while a general campaign
|
|
510
|
+
goes immediately. This is invisible in the API — the only trace is when the
|
|
511
|
+
report starts filling.
|
|
416
512
|
|
|
417
513
|
## `schedule`
|
|
418
514
|
|
|
@@ -420,6 +516,9 @@ Every `create` field, all optional. Only meaningful before the campaign runs.
|
|
|
420
516
|
await crm.campaigns.schedule(id, '2026-11-01T04:30:00.000Z');
|
|
421
517
|
```
|
|
422
518
|
|
|
519
|
+
The same call as `trigger` with a required time. Kept because it is the name the
|
|
520
|
+
panel uses; new code can use either.
|
|
521
|
+
|
|
423
522
|
## `cancel`
|
|
424
523
|
|
|
425
524
|
```ts
|
package/docs/data-store.md
CHANGED
|
@@ -2,9 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
`crm.dataStores`, `crm.dataStoreCollections`, `crm.dataStoreRecords` — `/v1/datastores`
|
|
4
4
|
|
|
5
|
-
A MongoDB **
|
|
6
|
-
|
|
7
|
-
the API
|
|
5
|
+
A MongoDB — **yours**, registered with a credential, or **ours**, provisioned
|
|
6
|
+
for you in a location you pick. Either way you declare the collections and
|
|
7
|
+
fields you want and read and write records through the API: from a chatbot flow,
|
|
8
|
+
an automation, or your own code.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
// your own cluster
|
|
12
|
+
await crm.dataStores.create({ title: 'Orders DB', credentials: { … } });
|
|
13
|
+
|
|
14
|
+
// or one of ours, no credential to manage
|
|
15
|
+
await crm.dataStores.create({ title: 'Leads', type: 'cloud' });
|
|
16
|
+
```
|
|
8
17
|
|
|
9
18
|
This runs as **its own service**, with its own database and its own deployment.
|
|
10
19
|
Nothing else in the platform depends on it, and it depends on nothing else: a
|
|
@@ -22,12 +31,84 @@ connections it holds to customer clusters cannot crowd out anything but itself.
|
|
|
22
31
|
Three things, nested:
|
|
23
32
|
|
|
24
33
|
```
|
|
25
|
-
data store a
|
|
34
|
+
data store a database — yours by credential, or ours by request
|
|
26
35
|
└── collection a schema: one collection name, and its fields
|
|
27
|
-
└── records the documents, in
|
|
36
|
+
└── records the documents, in that database
|
|
28
37
|
```
|
|
29
38
|
|
|
30
|
-
|
|
39
|
+
## Two kinds of store
|
|
40
|
+
|
|
41
|
+
`type` on a store says whose database the records are actually in, and it is the
|
|
42
|
+
one decision here with consequences:
|
|
43
|
+
|
|
44
|
+
| | `self` *(default)* | `cloud` |
|
|
45
|
+
| --- | --- | --- |
|
|
46
|
+
| The database | Yours | Ours, provisioned per store |
|
|
47
|
+
| To create one | A credential, tested before anything is stored | Nothing. Optionally a `code` for the location |
|
|
48
|
+
| Reachability | Your cluster must be reachable from the internet, and usually needs our egress in its IP access list | Nothing to open, nothing to allow |
|
|
49
|
+
| System of record | You. Anything else with the credential writes the same documents | Us. The API is the only door |
|
|
50
|
+
| `remove()` on the store | Forgets the credential. **Your data stays** | **Drops the database.** The records are gone |
|
|
51
|
+
|
|
52
|
+
Pick `cloud` for data that does not already live somewhere — a form's
|
|
53
|
+
submissions, a chatbot's answers, a lookup table someone maintains in the panel.
|
|
54
|
+
Pick `self` when the data is already in your cluster, or has to stay there.
|
|
55
|
+
|
|
56
|
+
Everything past this point — collections, records, filters, paging — is
|
|
57
|
+
identical for both.
|
|
58
|
+
|
|
59
|
+
### Where a cloud store lives
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
const { data: regions } = await crm.dataStores.regions();
|
|
63
|
+
// [{ code: '6869Z798672Z296Z152Z286Z307', location: 'Germany' }]
|
|
64
|
+
|
|
65
|
+
const store = await crm.dataStores.create({
|
|
66
|
+
title: 'Leads',
|
|
67
|
+
type: 'cloud',
|
|
68
|
+
code: regions[0].code, // omit it and the platform picks its default
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
store.location; // 'Germany'
|
|
72
|
+
store.code; // the cluster it is on — quote it in a support request
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Read `regions()` rather than hardcoding a code: locations are added over time,
|
|
76
|
+
and an existing store **never moves** when one is added. That is why the code
|
|
77
|
+
comes back on the store itself, and it is the answer to a data-residency
|
|
78
|
+
question in writing.
|
|
79
|
+
|
|
80
|
+
An empty `regions()` means this deployment provisions none — an on-premise
|
|
81
|
+
install, typically — and a `type: 'cloud'` create is then a `422`.
|
|
82
|
+
|
|
83
|
+
Nothing about our cluster is returned on a cloud store: `host` and `username`
|
|
84
|
+
are empty, there is no `credentialType`, and `summary` is a label
|
|
85
|
+
(`cloud://Germany/ds_…`) rather than a connection string. There is no second way
|
|
86
|
+
into those records, by design.
|
|
87
|
+
|
|
88
|
+
## What `remove()` deletes
|
|
89
|
+
|
|
90
|
+
Worth stating before anything else, because the opposite assumption is the
|
|
91
|
+
expensive one in both directions:
|
|
92
|
+
|
|
93
|
+
| Call | What it removes |
|
|
94
|
+
| --- | --- |
|
|
95
|
+
| `dataStores.remove()` on a **`self`** store | The stored credential, and every schema on it. **Your database is untouched.** |
|
|
96
|
+
| `dataStores.remove()` on a **`cloud`** store | The schemas, and **every collection in its database — which destroys the database, irreversibly.** |
|
|
97
|
+
| `dataStoreCollections.remove()` | The schema. The collection and its documents remain, in either kind of store. |
|
|
98
|
+
| `dataStoreRecords.remove()` / `bulkRemove()` | Documents, irreversibly. |
|
|
99
|
+
|
|
100
|
+
For a `self` store: register the same credential again, declare the same
|
|
101
|
+
collection name, and all of it reads straight back. For a `cloud` store there is
|
|
102
|
+
nothing to re-register — the database was named after that store and a new one
|
|
103
|
+
is empty — so confirm with the user before calling it.
|
|
104
|
+
|
|
105
|
+
If a collection cannot be deleted, the call is a `502 datastore_purge_failed`
|
|
106
|
+
naming it, and **nothing** has been removed: the store and its schemas are
|
|
107
|
+
intact and the call can be retried once the cause is fixed.
|
|
108
|
+
|
|
109
|
+
## What the platform holds
|
|
110
|
+
|
|
111
|
+
For a `self` store, **the credential and the schemas, and nothing else**. There
|
|
31
112
|
is no copy of your data here, no mirror and no sync: a record read is a live
|
|
32
113
|
query against your cluster, and a record write lands in it before the response
|
|
33
114
|
comes back.
|
|
@@ -43,19 +124,9 @@ Two consequences worth holding on to:
|
|
|
43
124
|
TLS handshake each time — but a loop of a thousand single inserts is a
|
|
44
125
|
thousand round trips. Send one `insert` with `records`.
|
|
45
126
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
expensive one:
|
|
50
|
-
|
|
51
|
-
| Call | What it removes |
|
|
52
|
-
| --- | --- |
|
|
53
|
-
| `dataStores.remove()` | The stored credential, and every schema on it. **Your database is untouched.** |
|
|
54
|
-
| `dataStoreCollections.remove()` | The schema. **Your collection and its documents remain.** |
|
|
55
|
-
| `dataStoreRecords.remove()` / `bulkRemove()` | Documents, from your database, irreversibly. |
|
|
56
|
-
|
|
57
|
-
Register the same credential again, declare the same collection name, and all of
|
|
58
|
-
it reads straight back. Only the record calls delete anything of yours.
|
|
127
|
+
For a `cloud` store the platform holds the database too, and the first
|
|
128
|
+
consequence does not apply: nothing else can write those documents. The second
|
|
129
|
+
still does — it is a different cluster, not a local one.
|
|
59
130
|
|
|
60
131
|
---
|
|
61
132
|
|
|
@@ -140,6 +211,15 @@ order to show which database it is looking at. Host, user and database come back
|
|
|
140
211
|
in the clear because an edit form has to render them, and none of them opens a
|
|
141
212
|
connection on its own.
|
|
142
213
|
|
|
214
|
+
A cloud store takes no credentials at all:
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
await crm.dataStores.create({ title: 'Leads', type: 'cloud' });
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Sending both is a `422`, as is a `code` on a `self` store: the two halves of the
|
|
221
|
+
body do not mix.
|
|
222
|
+
|
|
143
223
|
### Re-pointing one
|
|
144
224
|
|
|
145
225
|
```ts
|