@suprsend/node-sdk 1.9.1 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,218 @@
1
+ import axios from "axios";
2
+ import { cloneDeep } from "lodash";
3
+ import get_request_signature from "./signature";
4
+ import BulkResponse from "./bulk_response";
5
+ import { invalid_record_json, InputValueError } from "./utils";
6
+ import WorkflowTriggerRequest from "./workflow_request";
7
+ import {
8
+ SINGLE_EVENT_MAX_APPARENT_SIZE_IN_BYTES,
9
+ SINGLE_EVENT_MAX_APPARENT_SIZE_IN_BYTES_READABLE,
10
+ BODY_MAX_APPARENT_SIZE_IN_BYTES,
11
+ BODY_MAX_APPARENT_SIZE_IN_BYTES_READABLE,
12
+ MAX_WORKFLOWS_IN_BULK_API,
13
+ ALLOW_ATTACHMENTS_IN_BULK_API,
14
+ } from "./constants";
15
+
16
+ export class _BulkWorkflowTriggerChunk {
17
+ static _chunk_apparent_size_in_bytes = BODY_MAX_APPARENT_SIZE_IN_BYTES;
18
+ static _chunk_apparent_size_in_bytes_readable =
19
+ BODY_MAX_APPARENT_SIZE_IN_BYTES_READABLE;
20
+ static _max_records_in_chunk = MAX_WORKFLOWS_IN_BULK_API;
21
+
22
+ constructor(config) {
23
+ this.config = config;
24
+ this.url = this.__url = `${this.config.base_url}trigger/`;
25
+ this.__chunk = [];
26
+ this.__running_size = 0;
27
+ this.__running_length = 0;
28
+ this.response = null;
29
+ }
30
+
31
+ _get_headers() {
32
+ return {
33
+ "Content-Type": "application/json; charset=utf-8",
34
+ "User-Agent": this.config.user_agent,
35
+ Date: new Date().toUTCString(),
36
+ };
37
+ }
38
+
39
+ __add_body_to_chunk(body, body_size) {
40
+ this.__running_size += body_size;
41
+ this.__chunk.push(body);
42
+ this.__running_length++;
43
+ }
44
+
45
+ _check_limit_reached() {
46
+ return (
47
+ this.__running_length >=
48
+ _BulkWorkflowTriggerChunk._max_records_in_chunk ||
49
+ this.__running_size >=
50
+ _BulkWorkflowTriggerChunk._chunk_apparent_size_in_bytes
51
+ );
52
+ }
53
+
54
+ try_to_add_into_chunk(body, body_size) {
55
+ if (!body) {
56
+ return true;
57
+ }
58
+ if (this._check_limit_reached()) {
59
+ return false;
60
+ }
61
+ if (body_size > SINGLE_EVENT_MAX_APPARENT_SIZE_IN_BYTES) {
62
+ throw new InputValueError(
63
+ `workflow body too big - ${body_size} Bytes, must not exceed ${SINGLE_EVENT_MAX_APPARENT_SIZE_IN_BYTES_READABLE}`
64
+ );
65
+ }
66
+ if (
67
+ this.__running_size + body_size >
68
+ _BulkWorkflowTriggerChunk._chunk_apparent_size_in_bytes
69
+ ) {
70
+ return false;
71
+ }
72
+
73
+ if (!ALLOW_ATTACHMENTS_IN_BULK_API) {
74
+ delete body.data["$attachments"];
75
+ }
76
+
77
+ this.__add_body_to_chunk(body, body_size);
78
+ return true;
79
+ }
80
+
81
+ async trigger() {
82
+ const headers = this._get_headers();
83
+ const content_text = JSON.stringify(this.__chunk);
84
+
85
+ const signature = get_request_signature(
86
+ this.__url,
87
+ "POST",
88
+ content_text,
89
+ headers,
90
+ this.config.workspace_secret
91
+ );
92
+ headers["Authorization"] = `${this.config.workspace_key}:${signature}`;
93
+ try {
94
+ const resp = await axios.post(this.__url, content_text, {
95
+ headers,
96
+ transformResponse: [(data) => data], // dont assume type of response
97
+ });
98
+ this.response = {
99
+ status: "success",
100
+ status_code: resp.status,
101
+ total: this.__chunk.length,
102
+ success: this.__chunk.length,
103
+ failure: 0,
104
+ failed_records: [],
105
+ };
106
+ } catch (err) {
107
+ if (err?.response) {
108
+ const resp_data = err.response;
109
+ this.response = {
110
+ status: "fail",
111
+ status_code: resp_data.status,
112
+ total: this.__chunk.length,
113
+ success: 0,
114
+ failure: this.__chunk.length,
115
+ failed_records: this.__chunk.map((record) => ({
116
+ record: record,
117
+ error: resp_data.data,
118
+ code: resp_data.status,
119
+ })),
120
+ };
121
+ } else {
122
+ this.response = {
123
+ status: "fail",
124
+ status_code: 500,
125
+ total: this.__chunk.length,
126
+ success: 0,
127
+ failure: this.__chunk.length,
128
+ failed_records: this.__chunk.map((record) => ({
129
+ record: record,
130
+ error: err.message,
131
+ code: 500,
132
+ })),
133
+ };
134
+ }
135
+ }
136
+ }
137
+ }
138
+
139
+ export class BulkWorkflowTrigger {
140
+ constructor(config) {
141
+ this.config = config;
142
+ this.__workflows = [];
143
+ this.__pending_records = [];
144
+ this.chunks = [];
145
+ this.response = new BulkResponse();
146
+ // invalid_record json: {"record": workflow-json, "error": error_str, "code": 500}
147
+ this.__invalid_records = [];
148
+ }
149
+
150
+ __validate_workflows() {
151
+ for (const wf of this.__workflows) {
152
+ try {
153
+ const is_part_of_bulk = true;
154
+ const [wf_body, body_size] = wf.get_final_json(
155
+ this.config,
156
+ is_part_of_bulk
157
+ );
158
+ this.__pending_records.push([wf_body, body_size]);
159
+ } catch (ex) {
160
+ const inv_rec = invalid_record_json(wf.as_json(), ex);
161
+ this.__invalid_records.push(inv_rec);
162
+ }
163
+ }
164
+ }
165
+
166
+ __chunkify(start_idx = 0) {
167
+ const curr_chunk = new _BulkWorkflowTriggerChunk(this.config);
168
+ this.chunks.push(curr_chunk);
169
+ const entries = this.__pending_records.slice(start_idx).entries();
170
+ for (const [rel_idx, rec] of entries) {
171
+ const is_added = curr_chunk.try_to_add_into_chunk(rec[0], rec[1]);
172
+ if (!is_added) {
173
+ // create chunks from remaining records
174
+ this.__chunkify(start_idx + rel_idx);
175
+ // Don't forget to break. As current loop must not continue further
176
+ break;
177
+ }
178
+ }
179
+ }
180
+
181
+ append(...workflows) {
182
+ if (!workflows.length) return;
183
+ for (const wf of workflows) {
184
+ if (wf instanceof WorkflowTriggerRequest) {
185
+ const wf_copy = cloneDeep(wf);
186
+ this.__workflows.push(wf_copy);
187
+ }
188
+ }
189
+ }
190
+
191
+ async trigger() {
192
+ this.__validate_workflows();
193
+ if (this.__invalid_records.length > 0) {
194
+ const ch_response = BulkResponse.invalid_records_chunk_response(
195
+ this.__invalid_records
196
+ );
197
+ this.response.merge_chunk_response(ch_response);
198
+ }
199
+ if (this.__pending_records.length) {
200
+ this.__chunkify();
201
+ for (const [c_idx, ch] of this.chunks.entries()) {
202
+ if (this.config.req_log_level > 0) {
203
+ console.log(`DEBUG: triggering api call for chunk: ${cIdx}`);
204
+ }
205
+ // do api call
206
+ await ch.trigger();
207
+ // merge response
208
+ this.response.merge_chunk_response(ch.response);
209
+ }
210
+ } else {
211
+ if (this.__invalid_records.length === 0)
212
+ this.response.merge_chunk_response(
213
+ BulkResponse.empty_chunk_success_response()
214
+ );
215
+ }
216
+ return this.response;
217
+ }
218
+ }
package/types/index.d.ts CHANGED
@@ -86,6 +86,7 @@ declare namespace suprsend {
86
86
  unset(keys: string | string[]): void;
87
87
 
88
88
  set_preferred_language(lang_code: string): void;
89
+ set_timezone(timezone: string): void;
89
90
 
90
91
  add_email(email: string): void;
91
92
  remove_email(email: string): void;
@@ -207,11 +208,39 @@ declare namespace suprsend {
207
208
  delete_version(list_id: string, version_id: string): Promise<Dictionary>;
208
209
  }
209
210
 
211
+ interface WorkflowTriggerRequest {
212
+ new (
213
+ body: Dictionary,
214
+ kwargs?: {
215
+ idempotency_key?: string;
216
+ tenant_id?: string;
217
+ cancellation_id?: string;
218
+ }
219
+ ): WorkflowTriggerRequest;
220
+
221
+ add_attachment(
222
+ file_path: string,
223
+ kwargs?: { file_name?: string; ignore_if_error?: boolean }
224
+ ): void;
225
+ }
226
+
227
+ interface WorkflowsApi {
228
+ trigger(workflow: WorkflowTriggerRequest): Promise<SBulkResponse>;
229
+
230
+ bulk_trigger_instance(): BulkWorkflowTrigger;
231
+ }
232
+
233
+ interface BulkWorkflowTrigger {
234
+ append(...workflows: WorkflowTriggerRequest[]): void;
235
+
236
+ trigger(): Promise<SBulkResponse>;
237
+ }
238
+
210
239
  interface Suprsend {
211
240
  new (
212
241
  workspace_env: string,
213
242
  workspace_secret: string,
214
- config?: { is_staging?: boolean }
243
+ config?: { base_url?: string }
215
244
  ): Suprsend;
216
245
 
217
246
  get bulk_workflows(): BulkWorkflowsFactory;
@@ -228,6 +257,8 @@ declare namespace suprsend {
228
257
 
229
258
  subscriber_lists: SubscriberListsApi;
230
259
 
260
+ workflows: WorkflowsApi;
261
+
231
262
  add_attachment(
232
263
  body: Dictionary,
233
264
  file_path: string,
@@ -255,3 +286,4 @@ export const Suprsend: suprsend.Suprsend;
255
286
  export const Event: suprsend.Event;
256
287
  export const Workflow: suprsend.Workflow;
257
288
  export const SubscriberListBroadcast: suprsend.SubscriberListBroadcast;
289
+ export const WorkflowTriggerRequest: suprsend.WorkflowTriggerRequest;