ab-ecosystem-cpass 1.0.7 → 1.0.8
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 +5 -1
- package/dist/client.d.ts +24 -0
- package/dist/client.js +28 -0
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/resources/audience/canned-replies.d.ts +95 -0
- package/dist/resources/audience/canned-replies.js +112 -0
- package/dist/resources/audience/canned-replies.js.map +1 -0
- package/dist/resources/audience/notifications.d.ts +14 -0
- package/dist/resources/audience/notifications.js +14 -0
- package/dist/resources/audience/notifications.js.map +1 -1
- package/dist/resources/campaign-messages.d.ts +25 -22
- package/dist/resources/campaign-messages.js +34 -27
- package/dist/resources/campaign-messages.js.map +1 -1
- package/dist/resources/data-stores.d.ts +469 -0
- package/dist/resources/data-stores.js +530 -0
- package/dist/resources/data-stores.js.map +1 -0
- package/dist/resources/paths.d.ts +6 -0
- package/dist/resources/paths.js +7 -1
- package/dist/resources/paths.js.map +1 -1
- package/dist/types/campaign.d.ts +11 -5
- package/dist/types/canned-reply.d.ts +124 -0
- package/dist/types/canned-reply.js +6 -0
- package/dist/types/canned-reply.js.map +1 -0
- package/dist/types/datastore.d.ts +260 -0
- package/dist/types/datastore.js +21 -0
- package/dist/types/datastore.js.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -1
- package/docs/audience/canned-replies.md +302 -0
- package/docs/audience/notifications.md +99 -0
- package/docs/campaign/campaign-messages.md +31 -22
- package/docs/campaign/campaigns.md +1 -1
- package/docs/data-store.md +495 -0
- package/package.json +1 -1
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CampaignMessages = void 0;
|
|
4
|
-
const
|
|
4
|
+
const paths_1 = require("./paths");
|
|
5
|
+
/** `/v1/campaigns/:id/report` -- the report is the campaign's own sub-resource. */
|
|
6
|
+
const report = (campaign) => `/v1/campaigns/${(0, paths_1.seg)(campaign)}/report`;
|
|
5
7
|
/**
|
|
6
8
|
* Campaign reporting: what actually happened to each message.
|
|
7
9
|
*
|
|
8
10
|
* ```ts
|
|
9
|
-
* const stats = await crm.campaignMessages.stats(
|
|
11
|
+
* const stats = await crm.campaignMessages.stats(campaign);
|
|
10
12
|
* // { total: 12480, sent: 12410, delivered: 11980, read: 7420, replied: 310, failed: 70 }
|
|
11
13
|
*
|
|
12
|
-
* const { data } = await crm.campaignMessages.list(
|
|
14
|
+
* const { data } = await crm.campaignMessages.list(campaign, { status: 'failed' });
|
|
13
15
|
* data[0].error; // why that one did not go
|
|
14
16
|
* ```
|
|
15
17
|
*
|
|
18
|
+
* Every method takes the campaign id first. A report has no meaning without one,
|
|
19
|
+
* so it is addressed as the campaign's own sub-resource --
|
|
20
|
+
* `/v1/campaigns/:id/report` -- rather than as a collection of rows that a
|
|
21
|
+
* campaign filters. The options object holds only what *narrows* a report that
|
|
22
|
+
* already exists: a message index, a status, a page.
|
|
23
|
+
*
|
|
16
24
|
* ## Where these numbers come from
|
|
17
25
|
*
|
|
18
26
|
* One row per attempted send, written by the campaign manager as it hands each
|
|
@@ -47,18 +55,17 @@ class CampaignMessages {
|
|
|
47
55
|
/**
|
|
48
56
|
* The campaign's funnel counts.
|
|
49
57
|
*
|
|
50
|
-
* `GET /v1/
|
|
58
|
+
* `GET /v1/campaigns/:id/report` · `campaigns:read` · **200**
|
|
51
59
|
*
|
|
52
60
|
* ### Query
|
|
53
61
|
*
|
|
54
|
-
* | Field | Type |
|
|
55
|
-
* | --- | --- | --- |
|
|
56
|
-
* | `
|
|
57
|
-
* | `message` | number | no | One message of a multi-message campaign, zero-based. |
|
|
62
|
+
* | Field | Type | Notes |
|
|
63
|
+
* | --- | --- | --- |
|
|
64
|
+
* | `message` | number | One message of a multi-message campaign, zero-based. Omit for the whole campaign. |
|
|
58
65
|
*
|
|
59
66
|
* ```ts
|
|
60
|
-
* const overall = await crm.campaignMessages.stats(
|
|
61
|
-
* const second = await crm.campaignMessages.stats(
|
|
67
|
+
* const overall = await crm.campaignMessages.stats(campaign);
|
|
68
|
+
* const second = await crm.campaignMessages.stats(campaign, { message: 1 });
|
|
62
69
|
* ```
|
|
63
70
|
*
|
|
64
71
|
* No date window: a campaign is one send, so its whole life is the only
|
|
@@ -68,19 +75,18 @@ class CampaignMessages {
|
|
|
68
75
|
* `404 not_found` if the campaign is not the caller's -- the same answer as a
|
|
69
76
|
* campaign that does not exist, so the error cannot be used to probe ids.
|
|
70
77
|
*/
|
|
71
|
-
stats(query) {
|
|
72
|
-
return this.http.request('GET',
|
|
78
|
+
stats(campaign, query = {}) {
|
|
79
|
+
return this.http.request('GET', report(campaign), { query });
|
|
73
80
|
}
|
|
74
81
|
/**
|
|
75
82
|
* The rows behind the funnel. This is the report table.
|
|
76
83
|
*
|
|
77
|
-
* `GET /v1/
|
|
84
|
+
* `GET /v1/campaigns/:id/report/messages` · `campaigns:read` · **200**
|
|
78
85
|
*
|
|
79
86
|
* ### Query
|
|
80
87
|
*
|
|
81
88
|
* | Field | Type | Default | Notes |
|
|
82
89
|
* | --- | --- | --- | --- |
|
|
83
|
-
* | `campaign` | string | — | Required. |
|
|
84
90
|
* | `message` | number | — | Zero-based index of one campaign message. |
|
|
85
91
|
* | `status` | `sent \| delivered \| read \| replied \| failed` | — | Cumulative; see below. Omit for every row. |
|
|
86
92
|
* | `exact` | boolean | `false` | Only rows sitting at `status` right now. |
|
|
@@ -96,22 +102,22 @@ class CampaignMessages {
|
|
|
96
102
|
*
|
|
97
103
|
* ```ts
|
|
98
104
|
* // the two queries a report screen actually makes
|
|
99
|
-
* const failures = await crm.campaignMessages.list(
|
|
100
|
-
* const unread = await crm.campaignMessages.list(
|
|
105
|
+
* const failures = await crm.campaignMessages.list(campaign, { status: 'failed', rows: 100 });
|
|
106
|
+
* const unread = await crm.campaignMessages.list(campaign, { status: 'delivered', exact: true });
|
|
101
107
|
* ```
|
|
102
108
|
*
|
|
103
109
|
* `reply_type` and `replied_within_hours` are **refused** with
|
|
104
|
-
* `
|
|
110
|
+
* `422 validation_failed` unless `status` is `replied`, rather than quietly
|
|
105
111
|
* matching nothing.
|
|
106
112
|
*/
|
|
107
|
-
list(query) {
|
|
108
|
-
return this.http.request('GET',
|
|
113
|
+
list(campaign, query = {}) {
|
|
114
|
+
return this.http.request('GET', `${report(campaign)}/messages`, { query });
|
|
109
115
|
}
|
|
110
116
|
/**
|
|
111
117
|
* Every matching row, paged through for you. What an export is.
|
|
112
118
|
*
|
|
113
119
|
* ```ts
|
|
114
|
-
* for await (const row of crm.campaignMessages.iterate(
|
|
120
|
+
* for await (const row of crm.campaignMessages.iterate(campaign)) {
|
|
115
121
|
* csv.write([row.identifier, row.name, row.status, row.error ?? '']);
|
|
116
122
|
* }
|
|
117
123
|
* ```
|
|
@@ -126,11 +132,11 @@ class CampaignMessages {
|
|
|
126
132
|
* moving data can repeat or skip a row -- for a stable snapshot, export after
|
|
127
133
|
* the campaign completes.
|
|
128
134
|
*/
|
|
129
|
-
async *iterate(query) {
|
|
135
|
+
async *iterate(campaign, query = {}) {
|
|
130
136
|
const rows = query.rows ?? 100;
|
|
131
137
|
let page = query.page ?? 1;
|
|
132
138
|
for (;;) {
|
|
133
|
-
const result = await this.list({ ...query, page, rows });
|
|
139
|
+
const result = await this.list(campaign, { ...query, page, rows });
|
|
134
140
|
for (const record of result.data)
|
|
135
141
|
yield record;
|
|
136
142
|
if (result.data.length === 0 || page >= result.pagination.pages)
|
|
@@ -141,18 +147,17 @@ class CampaignMessages {
|
|
|
141
147
|
/**
|
|
142
148
|
* What people replied, grouped by the reply itself.
|
|
143
149
|
*
|
|
144
|
-
* `GET /v1/
|
|
150
|
+
* `GET /v1/campaigns/:id/report/replies` · `campaigns:read` · **200**
|
|
145
151
|
*
|
|
146
152
|
* ### Query
|
|
147
153
|
*
|
|
148
154
|
* | Field | Type | Default | Notes |
|
|
149
155
|
* | --- | --- | --- | --- |
|
|
150
|
-
* | `campaign` | string | — | Required. |
|
|
151
156
|
* | `message` | number | — | Zero-based index of one campaign message. |
|
|
152
157
|
* | `limit` | number | `50` | Distinct replies to return, 1–200. |
|
|
153
158
|
*
|
|
154
159
|
* ```ts
|
|
155
|
-
* const { groups, replied, distinct } = await crm.campaignMessages.replies(
|
|
160
|
+
* const { groups, replied, distinct } = await crm.campaignMessages.replies(campaign);
|
|
156
161
|
* groups; // [{ reply: 'Yes', replyType: 'button', count: 182 }, …]
|
|
157
162
|
* ```
|
|
158
163
|
*
|
|
@@ -164,8 +169,10 @@ class CampaignMessages {
|
|
|
164
169
|
* complete. A campaign that asked an open question has as many groups as it
|
|
165
170
|
* has repliers, so a truncated answer there is normal rather than a problem.
|
|
166
171
|
*/
|
|
167
|
-
replies(query) {
|
|
168
|
-
return this.http.request('GET', `${
|
|
172
|
+
replies(campaign, query = {}) {
|
|
173
|
+
return this.http.request('GET', `${report(campaign)}/replies`, {
|
|
174
|
+
query,
|
|
175
|
+
});
|
|
169
176
|
}
|
|
170
177
|
}
|
|
171
178
|
exports.CampaignMessages = CampaignMessages;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"campaign-messages.js","sourceRoot":"","sources":["../../src/resources/campaign-messages.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"campaign-messages.js","sourceRoot":"","sources":["../../src/resources/campaign-messages.ts"],"names":[],"mappings":";;;AAUA,mCAA8B;AAE9B,mFAAmF;AACnF,MAAM,MAAM,GAAG,CAAC,QAAgB,EAAU,EAAE,CAAC,iBAAiB,IAAA,WAAG,EAAC,QAAQ,CAAC,SAAS,CAAC;AAErF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,MAAa,gBAAgB;IACE;IAA7B,YAA6B,IAAgB;QAAhB,SAAI,GAAJ,IAAI,CAAY;IAAG,CAAC;IAEjD;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,KAAK,CAAC,QAAgB,EAAE,QAAmC,EAAE;QAC3D,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAuB,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IACrF,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;IACH,IAAI,CACF,QAAgB,EAChB,QAAmC,EAAE;QAErC,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CACtB,KAAK,EACL,GAAG,MAAM,CAAC,QAAQ,CAAC,WAAW,EAC9B,EAAE,KAAK,EAAE,CACV,CAAC;IACJ,CAAC;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACH,KAAK,CAAC,CAAC,OAAO,CACZ,QAAgB,EAChB,QAAmC,EAAE;QAErC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,IAAI,GAAG,CAAC;QAC/B,IAAI,IAAI,GAAG,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC;QAE3B,SAAS,CAAC;YACR,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YACnE,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,IAAI;gBAAE,MAAM,MAAM,CAAC;YAE/C,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,IAAI,MAAM,CAAC,UAAU,CAAC,KAAK;gBAAE,OAAO;YACxE,IAAI,IAAI,CAAC,CAAC;QACZ,CAAC;IACH,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,OAAO,CAAC,QAAgB,EAAE,QAA8B,EAAE;QACxD,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAyB,KAAK,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC,UAAU,EAAE;YACrF,KAAK;SACN,CAAC,CAAC;IACL,CAAC;CACF;AA1ID,4CA0IC"}
|
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
import type { BulkDeleteDataStoreRecordsInput, BulkUpdateDataStoreRecordsInput, CreateDataStoreCollectionInput, CreateDataStoreInput, DataStore, DataStoreCollection, DataStoreDeleteResult, DataStoreRecord, DataStoreTestResult, DataStoreUpdateResult, DataStoreWriteResult, InsertDataStoreRecordsInput, ListDataStoreRecordsQuery, ListQuery, Paginated, SearchDataStoreRecordsInput, UpdateDataStoreCollectionInput, UpdateDataStoreInput, UpdateDataStoreRecordInput } from '../types';
|
|
2
|
+
import type { HttpClient } from '../http';
|
|
3
|
+
/**
|
|
4
|
+
* Data stores: a MongoDB **you** own, registered with the platform.
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* const store = await crm.dataStores.create({
|
|
8
|
+
* title: 'Orders DB',
|
|
9
|
+
* credentials: {
|
|
10
|
+
* type: 'field',
|
|
11
|
+
* host: 'cluster0.ab12c.mongodb.net',
|
|
12
|
+
* username: 'crm_writer',
|
|
13
|
+
* password: process.env.ORDERS_DB_PASSWORD!,
|
|
14
|
+
* database: 'shop',
|
|
15
|
+
* },
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* const orders = await crm.dataStoreCollections.create(store.id, {
|
|
19
|
+
* title: 'Orders',
|
|
20
|
+
* fields: [
|
|
21
|
+
* { name: 'order_no', type: 'String' },
|
|
22
|
+
* { name: 'total', type: 'Number' },
|
|
23
|
+
* { name: 'paid', type: 'Boolean', default: false },
|
|
24
|
+
* ],
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* await crm.dataStoreRecords.insert(store.id, orders.id, {
|
|
28
|
+
* record: { order_no: 'A-1001', total: 49.9 },
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* ## What the platform holds, and what it does not
|
|
33
|
+
*
|
|
34
|
+
* It holds the credential -- encrypted -- and the schemas you declare. It holds
|
|
35
|
+
* **none of your data**: records are read from and written to your cluster on
|
|
36
|
+
* every call, so nothing here is a copy that can go stale, and nothing here
|
|
37
|
+
* survives you revoking the credential.
|
|
38
|
+
*
|
|
39
|
+
* Which also means every record call costs a round trip to your database. The
|
|
40
|
+
* platform keeps the connection open between calls, so that is one query's
|
|
41
|
+
* latency rather than a fresh TLS handshake each time, but a loop of a thousand
|
|
42
|
+
* single inserts is a thousand round trips: send them as one `insert` with
|
|
43
|
+
* `records` instead.
|
|
44
|
+
*
|
|
45
|
+
* ## Deleting never deletes your data
|
|
46
|
+
*
|
|
47
|
+
* `remove()` on a store forgets the credential and the schemas. `remove()` on a
|
|
48
|
+
* collection forgets the declaration. **Neither touches your database** -- the
|
|
49
|
+
* collections and every document in them are exactly as they were, and
|
|
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.
|
|
53
|
+
*
|
|
54
|
+
* ## The host must be reachable from the internet
|
|
55
|
+
*
|
|
56
|
+
* A credential pointing at a private or loopback address is refused with
|
|
57
|
+
* `422 invalid_datastore_host`: the platform would be connecting to its own
|
|
58
|
+
* network rather than to yours. Atlas users will usually also need to allow the
|
|
59
|
+
* platform's egress addresses in their project's IP access list -- the symptom
|
|
60
|
+
* otherwise is `502 datastore_unreachable` with a server-selection timeout.
|
|
61
|
+
*/
|
|
62
|
+
export declare class DataStores {
|
|
63
|
+
private readonly http;
|
|
64
|
+
constructor(http: HttpClient);
|
|
65
|
+
/**
|
|
66
|
+
* `POST /v1/datastores` -- 201 with the store.
|
|
67
|
+
*
|
|
68
|
+
* ### Payload
|
|
69
|
+
*
|
|
70
|
+
* | Field | Type | Required | Notes |
|
|
71
|
+
* | --- | --- | --- | --- |
|
|
72
|
+
* | `title` | string | yes | What you call it. 1-120 characters. |
|
|
73
|
+
* | `credentials` | object | yes | Either form below. |
|
|
74
|
+
*
|
|
75
|
+
* **`credentials.type: 'field'`**
|
|
76
|
+
*
|
|
77
|
+
* | Field | Type | Notes |
|
|
78
|
+
* | --- | --- | --- |
|
|
79
|
+
* | `host` | string | Hostname only: `cluster0.ab12c.mongodb.net`. |
|
|
80
|
+
* | `username` | string | |
|
|
81
|
+
* | `password` | string | Encrypted at rest; never returned. |
|
|
82
|
+
* | `database` | string | |
|
|
83
|
+
*
|
|
84
|
+
* Assembled into `mongodb+srv://…?retryWrites=true&w=majority`, with both
|
|
85
|
+
* halves of the credential percent-encoded -- so a password containing `@`,
|
|
86
|
+
* `/` or `?` works, which it did not in the legacy panel.
|
|
87
|
+
*
|
|
88
|
+
* **`credentials.type: 'string'`**
|
|
89
|
+
*
|
|
90
|
+
* | Field | Type | Notes |
|
|
91
|
+
* | --- | --- | --- |
|
|
92
|
+
* | `uri` | string | `mongodb://` or `mongodb+srv://`, and it must name a database. |
|
|
93
|
+
*
|
|
94
|
+
* ### What happens before the row is written
|
|
95
|
+
*
|
|
96
|
+
* The credential is checked for shape, the host is resolved and refused if it
|
|
97
|
+
* is private, and a connection is opened and pinged. Only then is anything
|
|
98
|
+
* stored -- so a 201 means the database really did accept these credentials,
|
|
99
|
+
* and a store you can see is a store that worked at least once.
|
|
100
|
+
*
|
|
101
|
+
* | Status | Code | Meaning |
|
|
102
|
+
* | --- | --- | --- |
|
|
103
|
+
* | 422 | `validation_failed` | Malformed URI, a host with a scheme in it, or no database named. |
|
|
104
|
+
* | 422 | `invalid_datastore_host` | The host does not resolve, or resolves somewhere private. |
|
|
105
|
+
* | 502 | `datastore_auth_failed` | The database refused the username or password. |
|
|
106
|
+
* | 502 | `datastore_unreachable` | No answer in time -- usually an IP access list. |
|
|
107
|
+
*
|
|
108
|
+
* Scope: `datastores:write`.
|
|
109
|
+
*/
|
|
110
|
+
create(input: CreateDataStoreInput): Promise<DataStore>;
|
|
111
|
+
/**
|
|
112
|
+
* `GET /v1/datastores` -- offset paginated, newest first.
|
|
113
|
+
*
|
|
114
|
+
* | Query | Type | Default | Notes |
|
|
115
|
+
* | --- | --- | --- | --- |
|
|
116
|
+
* | `page` | number | `1` | 1-based. |
|
|
117
|
+
* | `rows` | number | `25` | 1-100. |
|
|
118
|
+
* | `search` | string | — | Case-insensitive match on the title. |
|
|
119
|
+
*
|
|
120
|
+
* Scope: `datastores:read`.
|
|
121
|
+
*/
|
|
122
|
+
list(query?: Partial<ListQuery>): Promise<Paginated<DataStore>>;
|
|
123
|
+
/** `GET /v1/datastores/:store`. Scope: `datastores:read`. */
|
|
124
|
+
get(store: string): Promise<DataStore>;
|
|
125
|
+
/**
|
|
126
|
+
* `PATCH /v1/datastores/:store` -- rename, re-point, or both.
|
|
127
|
+
*
|
|
128
|
+
* | Field | Type | Notes |
|
|
129
|
+
* | --- | --- | --- |
|
|
130
|
+
* | `title` | string | |
|
|
131
|
+
* | `credentials` | object | Replaced whole, and tested before it is stored. |
|
|
132
|
+
*
|
|
133
|
+
* A partial credential is not accepted: send all four fields, or the whole
|
|
134
|
+
* URI. New credentials go through the same checks as `create`, and the old
|
|
135
|
+
* connection is dropped -- so the next record call reads from the new cluster
|
|
136
|
+
* rather than from a connection that happens to still be open.
|
|
137
|
+
*
|
|
138
|
+
* Scope: `datastores:write`.
|
|
139
|
+
*/
|
|
140
|
+
update(store: string, input: UpdateDataStoreInput): Promise<DataStore>;
|
|
141
|
+
/**
|
|
142
|
+
* `DELETE /v1/datastores/:store` -- 204.
|
|
143
|
+
*
|
|
144
|
+
* Forgets the credential and every schema declared on it. **Your database is
|
|
145
|
+
* not touched**: the collections and all their documents remain, and
|
|
146
|
+
* registering the same credential again with the same collection names reads
|
|
147
|
+
* them straight back.
|
|
148
|
+
*
|
|
149
|
+
* Scope: `datastores:write`.
|
|
150
|
+
*/
|
|
151
|
+
remove(store: string): Promise<void>;
|
|
152
|
+
/**
|
|
153
|
+
* `POST /v1/datastores/:store/test` -- connect, and report.
|
|
154
|
+
*
|
|
155
|
+
* ```ts
|
|
156
|
+
* const { ok, database, collections, latencyMs } = await crm.dataStores.test(store.id);
|
|
157
|
+
* ```
|
|
158
|
+
*
|
|
159
|
+
* The answer to "is it us or them". A credential that worked last month may
|
|
160
|
+
* have been rotated, an Atlas project may be paused, an IP access list may
|
|
161
|
+
* have been tightened -- each of which this distinguishes by status code
|
|
162
|
+
* rather than leaving to be inferred from a failing record call.
|
|
163
|
+
*
|
|
164
|
+
* `collections` is also what you need in order to point a new schema at a
|
|
165
|
+
* collection that already exists. It is empty when the database user has no
|
|
166
|
+
* `listCollections` privilege, which is a perfectly good credential -- not a
|
|
167
|
+
* sign the database is empty.
|
|
168
|
+
*
|
|
169
|
+
* A POST rather than a GET because it opens a connection and updates
|
|
170
|
+
* `verifiedAt`. Scope: `datastores:read`.
|
|
171
|
+
*/
|
|
172
|
+
test(store: string): Promise<DataStoreTestResult>;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* The collections you declare over a data store: a name, and the fields in it.
|
|
176
|
+
*
|
|
177
|
+
* ```ts
|
|
178
|
+
* const customers = await crm.dataStoreCollections.create(store.id, {
|
|
179
|
+
* title: 'Customers',
|
|
180
|
+
* fields: [
|
|
181
|
+
* { name: 'Email', type: 'String' }, // stored as `email`
|
|
182
|
+
* { name: 'Signed Up', type: 'Date' }, // stored as `signed_up`
|
|
183
|
+
* { name: 'Credits', type: 'Number', default: 0 },
|
|
184
|
+
* { name: 'Tags', type: 'Array' },
|
|
185
|
+
* ],
|
|
186
|
+
* });
|
|
187
|
+
*
|
|
188
|
+
* customers.fields.map((field) => field.name); // ['email','signed_up','credits','tags']
|
|
189
|
+
* ```
|
|
190
|
+
*
|
|
191
|
+
* ## Field names are slugged, once
|
|
192
|
+
*
|
|
193
|
+
* The server normalises every name -- punctuation dropped, spaces and hyphens to
|
|
194
|
+
* underscores, lowercased -- and the normalised name is both what comes back and
|
|
195
|
+
* what the document actually stores. **Use the returned names as your record
|
|
196
|
+
* keys.** The legacy panel slugged the name for the database but kept the
|
|
197
|
+
* original in the schema, so a field called `First Name` stored under
|
|
198
|
+
* `first_name` while the schema said otherwise, and records written against the
|
|
199
|
+
* schema's spelling silently went nowhere.
|
|
200
|
+
*
|
|
201
|
+
* ## Declaring a collection does not create one
|
|
202
|
+
*
|
|
203
|
+
* Mongo creates a collection on its first write, so a new schema addresses a
|
|
204
|
+
* collection that does not exist yet. Reads against it correctly return an empty
|
|
205
|
+
* page rather than failing.
|
|
206
|
+
*
|
|
207
|
+
* ## Editing fields rewrites nothing
|
|
208
|
+
*
|
|
209
|
+
* `update()` changes validation and coercion from the next call onward. It does
|
|
210
|
+
* not migrate documents: dropping a field hides the values under that key
|
|
211
|
+
* (re-declare it and they are back), and changing a field's type leaves old
|
|
212
|
+
* documents holding the old type. The collection a schema points at cannot be
|
|
213
|
+
* changed at all -- every record ever written through it lives there.
|
|
214
|
+
*/
|
|
215
|
+
export declare class DataStoreCollections {
|
|
216
|
+
private readonly http;
|
|
217
|
+
constructor(http: HttpClient);
|
|
218
|
+
/**
|
|
219
|
+
* `POST /v1/datastores/:store/collections` -- 201.
|
|
220
|
+
*
|
|
221
|
+
* | Field | Type | Required | Notes |
|
|
222
|
+
* | --- | --- | --- | --- |
|
|
223
|
+
* | `title` | string | yes | 1-120 characters. |
|
|
224
|
+
* | `fields` | array | yes | 1-100 fields; see below. |
|
|
225
|
+
* | `collection` | string | no | An existing collection to adopt. Immutable afterwards. |
|
|
226
|
+
* | `show_in_chat` | boolean | no | Default `false`. Whether the chat panel offers it. |
|
|
227
|
+
* | `display.theme` | string | no | Presentation only. |
|
|
228
|
+
* | `display.fields` | string[] | no | Field names to show, in order. Presentation only. |
|
|
229
|
+
*
|
|
230
|
+
* **Each field**
|
|
231
|
+
*
|
|
232
|
+
* | Field | Type | Notes |
|
|
233
|
+
* | --- | --- | --- |
|
|
234
|
+
* | `name` | string | Slugged by the server; the slug is the document key. |
|
|
235
|
+
* | `type` | `String` `Number` `Boolean` `Date` `Array` | |
|
|
236
|
+
* | `label` | string | Display only. |
|
|
237
|
+
* | `default` | string \| number \| boolean | `String`, `Number` and `Boolean` only. |
|
|
238
|
+
*
|
|
239
|
+
* Omit `collection` and the server generates `<id>_<slugged title>`, which
|
|
240
|
+
* cannot collide with anything already in your database. Supply it to point at
|
|
241
|
+
* a collection you already have -- the way to use a database with data in it
|
|
242
|
+
* rather than duplicating it.
|
|
243
|
+
*
|
|
244
|
+
* | Status | Code | Meaning |
|
|
245
|
+
* | --- | --- | --- |
|
|
246
|
+
* | 404 | `not_found` | No such store in this account. |
|
|
247
|
+
* | 409 | `conflict` | This store already has a schema for that collection name. |
|
|
248
|
+
* | 422 | `validation_failed` | A field name that slugs to nothing, a duplicate after slugging, or a default of the wrong type. |
|
|
249
|
+
*
|
|
250
|
+
* Scope: `datastores:write`.
|
|
251
|
+
*/
|
|
252
|
+
create(store: string, input: CreateDataStoreCollectionInput): Promise<DataStoreCollection>;
|
|
253
|
+
/**
|
|
254
|
+
* `GET /v1/datastores/:store/collections` -- offset paginated,
|
|
255
|
+
* newest first. `page`, `rows`, and `search` on the title.
|
|
256
|
+
*
|
|
257
|
+
* Scope: `datastores:read`.
|
|
258
|
+
*/
|
|
259
|
+
list(store: string, query?: Partial<ListQuery>): Promise<Paginated<DataStoreCollection>>;
|
|
260
|
+
/**
|
|
261
|
+
* `GET /v1/datastores/:store/collections/:collection`.
|
|
262
|
+
*
|
|
263
|
+
* Both ids are checked: a collection id belonging to a different store -- even
|
|
264
|
+
* one of your own -- is a 404 rather than a match.
|
|
265
|
+
*
|
|
266
|
+
* Scope: `datastores:read`.
|
|
267
|
+
*/
|
|
268
|
+
get(store: string, collection: string): Promise<DataStoreCollection>;
|
|
269
|
+
/**
|
|
270
|
+
* `PATCH /v1/datastores/:store/collections/:collection`.
|
|
271
|
+
*
|
|
272
|
+
* `title`, `fields`, `show_in_chat` and `display`. Not `collection`: a schema
|
|
273
|
+
* points at one collection for its whole life, and re-pointing it would orphan
|
|
274
|
+
* every record written through it. Point a new schema at the other collection
|
|
275
|
+
* instead.
|
|
276
|
+
*
|
|
277
|
+
* Scope: `datastores:write`.
|
|
278
|
+
*/
|
|
279
|
+
update(store: string, collection: string, input: UpdateDataStoreCollectionInput): Promise<DataStoreCollection>;
|
|
280
|
+
/**
|
|
281
|
+
* `DELETE /v1/datastores/:store/collections/:collection` -- 204.
|
|
282
|
+
*
|
|
283
|
+
* Forgets the declaration. **Your collection and its documents remain** --
|
|
284
|
+
* declaring the same collection name again reads the same records back.
|
|
285
|
+
*
|
|
286
|
+
* Scope: `datastores:write`.
|
|
287
|
+
*/
|
|
288
|
+
remove(store: string, collection: string): Promise<void>;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* The records in a data store collection -- documents in **your** database.
|
|
292
|
+
*
|
|
293
|
+
* ```ts
|
|
294
|
+
* await crm.dataStoreRecords.insert(store.id, orders.id, {
|
|
295
|
+
* records: [{ order_no: 'A-1', total: 10 }, { order_no: 'A-2', total: 20 }],
|
|
296
|
+
* });
|
|
297
|
+
*
|
|
298
|
+
* const { data, pagination } = await crm.dataStoreRecords.search(store.id, orders.id, {
|
|
299
|
+
* filter: { paid: false, total: { $gte: 20 } },
|
|
300
|
+
* sort: 'total',
|
|
301
|
+
* order: 'desc',
|
|
302
|
+
* });
|
|
303
|
+
* ```
|
|
304
|
+
*
|
|
305
|
+
* ## Writes are validated against the declaration
|
|
306
|
+
*
|
|
307
|
+
* Keys must be declared field names, and values are coerced to the declared
|
|
308
|
+
* type -- `"42"` into a `Number` field is stored as `42`, an ISO string into a
|
|
309
|
+
* `Date` field as a date. An **undeclared key is a 422 naming it**, which is the
|
|
310
|
+
* one deliberate break from the legacy endpoint: there, an unknown key was
|
|
311
|
+
* dropped silently, so a typo meant data quietly went nowhere.
|
|
312
|
+
*
|
|
313
|
+
* `null` clears a field. `created_on` and `updated_on` are stamped by the server
|
|
314
|
+
* -- unix seconds, or real dates if the collection declared them as `Date` --
|
|
315
|
+
* and ignored if you send them. `id` cannot be written.
|
|
316
|
+
*
|
|
317
|
+
* ## Reads return everything that is stored
|
|
318
|
+
*
|
|
319
|
+
* Including keys the schema does not declare, because the documents are yours
|
|
320
|
+
* and may have been written by something else. `_id` reads back as `id`.
|
|
321
|
+
*
|
|
322
|
+
* ## There is no aggregation endpoint
|
|
323
|
+
*
|
|
324
|
+
* Deliberately: a pipeline can read other collections with `$lookup` and write
|
|
325
|
+
* them with `$merge`, which would make a schema's boundary meaningless, and
|
|
326
|
+
* `$function` runs JavaScript on your cluster. The same three operators are
|
|
327
|
+
* refused inside a filter. For anything that needs a pipeline, use your own
|
|
328
|
+
* credentials and your own driver -- it is your database.
|
|
329
|
+
*/
|
|
330
|
+
export declare class DataStoreRecords {
|
|
331
|
+
private readonly http;
|
|
332
|
+
constructor(http: HttpClient);
|
|
333
|
+
/**
|
|
334
|
+
* `POST /v1/datastores/:store/collections/:collection/records` -- 201.
|
|
335
|
+
*
|
|
336
|
+
* | Field | Type | Notes |
|
|
337
|
+
* | --- | --- | --- |
|
|
338
|
+
* | `record` | object | One record. Exactly one of this or `records`. |
|
|
339
|
+
* | `records` | object[] | 1-1000 records. |
|
|
340
|
+
*
|
|
341
|
+
* ```ts
|
|
342
|
+
* const { inserted, ids } = await crm.dataStoreRecords.insert(store, collection, {
|
|
343
|
+
* records: rows,
|
|
344
|
+
* });
|
|
345
|
+
* ```
|
|
346
|
+
*
|
|
347
|
+
* A batch is **unordered**: one document rejected by a unique index of yours
|
|
348
|
+
* does not abandon the rest, so `inserted` may be lower than what you sent.
|
|
349
|
+
* `ids` are in the order the documents were sent.
|
|
350
|
+
*
|
|
351
|
+
* Prefer one call with `records` over a loop of single inserts -- each call is
|
|
352
|
+
* a round trip to your cluster.
|
|
353
|
+
*
|
|
354
|
+
* Scope: `datastore-records:write`.
|
|
355
|
+
*/
|
|
356
|
+
insert(store: string, collection: string, input: InsertDataStoreRecordsInput): Promise<DataStoreWriteResult>;
|
|
357
|
+
/**
|
|
358
|
+
* `GET /v1/datastores/:store/collections/:collection/records`
|
|
359
|
+
*
|
|
360
|
+
* | Query | Type | Default | Notes |
|
|
361
|
+
* | --- | --- | --- | --- |
|
|
362
|
+
* | `page` | number | `1` | 1-based. |
|
|
363
|
+
* | `rows` | number | `25` | 1-100. |
|
|
364
|
+
* | `sort` | string | `created_on` | Any field name. |
|
|
365
|
+
* | `order` | `asc` \| `desc` | `desc` | |
|
|
366
|
+
*
|
|
367
|
+
* `_id` is appended to the sort as a tiebreaker, so paging is reproducible
|
|
368
|
+
* even when the sort field repeats -- or does not exist, which it will not on
|
|
369
|
+
* an adopted collection that something else wrote.
|
|
370
|
+
*
|
|
371
|
+
* Scope: `datastore-records:read`.
|
|
372
|
+
*/
|
|
373
|
+
list(store: string, collection: string, query?: ListDataStoreRecordsQuery): Promise<Paginated<DataStoreRecord>>;
|
|
374
|
+
/**
|
|
375
|
+
* `POST /v1/datastores/:store/collections/:collection/records/search`
|
|
376
|
+
*
|
|
377
|
+
* | Field | Type | Notes |
|
|
378
|
+
* | --- | --- | --- |
|
|
379
|
+
* | `filter` | object | A Mongo query. Required; `{}` matches everything. |
|
|
380
|
+
* | `page` `rows` `sort` `order` | | As `list`. |
|
|
381
|
+
*
|
|
382
|
+
* ```ts
|
|
383
|
+
* await crm.dataStoreRecords.search(store, collection, {
|
|
384
|
+
* filter: {
|
|
385
|
+
* $or: [{ status: 'open' }, { total: { $gte: 100 } }],
|
|
386
|
+
* signed_up: { $gte: '2026-01-01' }, // coerced to a Date for a Date field
|
|
387
|
+
* tags: 'vip', // an Array field contains this
|
|
388
|
+
* },
|
|
389
|
+
* });
|
|
390
|
+
* ```
|
|
391
|
+
*
|
|
392
|
+
* A POST because a Mongo filter is a nested object and encoding one into a
|
|
393
|
+
* query string is a different argument in every HTTP client. It reads and
|
|
394
|
+
* changes nothing.
|
|
395
|
+
*
|
|
396
|
+
* Operand values are coerced per declared field, so dates and numbers written
|
|
397
|
+
* as JSON strings match. `$where`, `$function` and `$accumulator` are refused
|
|
398
|
+
* with `422 filter_operator_not_allowed`, and a filter nested deeper than ten
|
|
399
|
+
* levels or holding more than 500 keys with `422 filter_too_complex`.
|
|
400
|
+
*
|
|
401
|
+
* Scope: `datastore-records:read`.
|
|
402
|
+
*/
|
|
403
|
+
search(store: string, collection: string, input: SearchDataStoreRecordsInput): Promise<Paginated<DataStoreRecord>>;
|
|
404
|
+
/**
|
|
405
|
+
* `GET /v1/datastores/:store/collections/:collection/records/:record`
|
|
406
|
+
*
|
|
407
|
+
* 404 when there is no such document. Scope: `datastore-records:read`.
|
|
408
|
+
*/
|
|
409
|
+
get(store: string, collection: string, record: string): Promise<DataStoreRecord>;
|
|
410
|
+
/**
|
|
411
|
+
* `PATCH /v1/datastores/:store/collections/:collection/records/:record`
|
|
412
|
+
*
|
|
413
|
+
* | Field | Type | Notes |
|
|
414
|
+
* | --- | --- | --- |
|
|
415
|
+
* | `record` | object | The fields to change. Validated exactly as an insert. |
|
|
416
|
+
*
|
|
417
|
+
* A `$set` of those fields and nothing else -- keys you leave out are
|
|
418
|
+
* untouched, and `null` clears a value. `updated_on` is stamped; `created_on`
|
|
419
|
+
* cannot be moved. Returns the record as it is after the write.
|
|
420
|
+
*
|
|
421
|
+
* Scope: `datastore-records:write`.
|
|
422
|
+
*/
|
|
423
|
+
update(store: string, collection: string, record: string, input: UpdateDataStoreRecordInput): Promise<DataStoreRecord>;
|
|
424
|
+
/**
|
|
425
|
+
* `POST /v1/datastores/:store/collections/:collection/records/bulk-update`
|
|
426
|
+
*
|
|
427
|
+
* | Field | Type | Notes |
|
|
428
|
+
* | --- | --- | --- |
|
|
429
|
+
* | `filter` | object | Required and **non-empty**. |
|
|
430
|
+
* | `update` | object | The fields to set on every match. |
|
|
431
|
+
*
|
|
432
|
+
* ```ts
|
|
433
|
+
* const { matched, modified } = await crm.dataStoreRecords.bulkUpdate(store, collection, {
|
|
434
|
+
* filter: { status: 'pending', created_on: { $lt: cutoff } },
|
|
435
|
+
* update: { status: 'expired' },
|
|
436
|
+
* });
|
|
437
|
+
* ```
|
|
438
|
+
*
|
|
439
|
+
* `{}` as the filter is refused: it matches the whole collection, and a
|
|
440
|
+
* one-character slip that rewrites every record is not something to make
|
|
441
|
+
* convenient. `modified` is lower than `matched` where a record already held
|
|
442
|
+
* the values.
|
|
443
|
+
*
|
|
444
|
+
* Scope: `datastore-records:write`.
|
|
445
|
+
*/
|
|
446
|
+
bulkUpdate(store: string, collection: string, input: BulkUpdateDataStoreRecordsInput): Promise<DataStoreUpdateResult>;
|
|
447
|
+
/**
|
|
448
|
+
* `DELETE /v1/datastores/:store/collections/:collection/records/:record`
|
|
449
|
+
*
|
|
450
|
+
* 404 when there is no such document. This one really does delete your data.
|
|
451
|
+
*
|
|
452
|
+
* Scope: `datastore-records:write`.
|
|
453
|
+
*/
|
|
454
|
+
remove(store: string, collection: string, record: string): Promise<DataStoreDeleteResult>;
|
|
455
|
+
/**
|
|
456
|
+
* `POST /v1/datastores/:store/collections/:collection/records/bulk-delete`
|
|
457
|
+
*
|
|
458
|
+
* | Field | Type | Notes |
|
|
459
|
+
* | --- | --- | --- |
|
|
460
|
+
* | `filter` | object | Required and **non-empty**. |
|
|
461
|
+
*
|
|
462
|
+
* Deletes every match, irreversibly, from your database. The empty filter is
|
|
463
|
+
* refused for the same reason as in `bulkUpdate` -- emptying a collection
|
|
464
|
+
* deliberately means paging through it.
|
|
465
|
+
*
|
|
466
|
+
* Scope: `datastore-records:write`.
|
|
467
|
+
*/
|
|
468
|
+
bulkRemove(store: string, collection: string, input: BulkDeleteDataStoreRecordsInput): Promise<DataStoreDeleteResult>;
|
|
469
|
+
}
|