@8ms/helpers 2.0.45 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.yarnrc.yml CHANGED
@@ -1,2 +1,3 @@
1
1
  nodeLinker: node-modules
2
- yarnPath: .yarn/releases/yarn-4.9.2.cjs
2
+
3
+ yarnPath: .yarn/releases/yarn-4.9.4.cjs
@@ -1,4 +1,10 @@
1
1
  import { BaseClass } from "../../_class";
2
+ type AttachmentSource = {
3
+ filename: string;
4
+ contentType?: string;
5
+ data64?: string;
6
+ url?: string;
7
+ };
2
8
  type ConstructorProps = {
3
9
  bcc?: string[];
4
10
  cc?: string[];
@@ -6,6 +12,7 @@ type ConstructorProps = {
6
12
  html?: string;
7
13
  subject?: string;
8
14
  to?: string | string[];
15
+ attachments?: AttachmentSource[];
9
16
  };
10
17
  /**
11
18
  * Class to build and send an AWS SES email.
@@ -17,17 +24,29 @@ export declare class SimpleEmail extends BaseClass {
17
24
  bcc: string[];
18
25
  html?: string;
19
26
  subject?: string;
20
- constructor({ bcc, cc, from, html, subject, to }: ConstructorProps);
27
+ attachments: AttachmentSource[];
28
+ constructor({ bcc, cc, from, html, subject, to, attachments }: ConstructorProps);
21
29
  setFrom: (from: string) => this;
22
30
  setHtml: (html: string) => this;
23
31
  setSubject: (subject: string) => this;
32
+ setAttachments: (attachments: AttachmentSource[]) => this;
33
+ addAttachment: (attachment: AttachmentSource) => this;
34
+ addAttachmentFromData64: (filename: string, data64: string, contentType?: string) => this;
35
+ addAttachmentFromUrl: (filename: string, url: string, contentType?: string) => this;
36
+ clearAttachments: () => this;
24
37
  pushBcc: (recipient: string | string[]) => this;
25
38
  pushCc: (recipient: string | string[]) => this;
26
39
  pushTo: (recipient: string | string[]) => this;
27
40
  setBcc: (recipient: string | string[]) => this;
28
41
  setCc: (recipient: string | string[]) => this;
29
42
  setTo: (recipient: string | string[]) => this;
30
- getSendParam: () => {
43
+ getSendParam: () => Promise<{
44
+ Destinations: string[];
45
+ RawMessage: {
46
+ Data: string;
47
+ };
48
+ Source: string;
49
+ } | {
31
50
  Destination: {
32
51
  BccAddresses: string[];
33
52
  CcAddresses: string[];
@@ -50,7 +69,10 @@ export declare class SimpleEmail extends BaseClass {
50
69
  };
51
70
  };
52
71
  Source: string;
53
- };
72
+ }>;
73
+ private _getRawEmailParams;
74
+ private _getAttachmentData;
75
+ private _getContentTypeFromFilename;
54
76
  private _filterRecipients;
55
77
  }
56
78
  export {};
@@ -1,4 +1,37 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
@@ -10,11 +43,12 @@ const _class_1 = require("../../_class");
10
43
  * Class to build and send an AWS SES email.
11
44
  */
12
45
  class SimpleEmail extends _class_1.BaseClass {
13
- constructor({ bcc, cc, from, html, subject, to }) {
46
+ constructor({ bcc, cc, from, html, subject, to, attachments }) {
14
47
  super();
15
48
  this.to = [];
16
49
  this.cc = [];
17
50
  this.bcc = [];
51
+ this.attachments = [];
18
52
  this.setFrom = (from) => {
19
53
  return this._setValue("from", from);
20
54
  };
@@ -24,6 +58,31 @@ class SimpleEmail extends _class_1.BaseClass {
24
58
  this.setSubject = (subject) => {
25
59
  return this._setValue("subject", subject);
26
60
  };
61
+ this.setAttachments = (attachments) => {
62
+ return this._setValue("attachments", attachments);
63
+ };
64
+ this.addAttachment = (attachment) => {
65
+ this.attachments.push(attachment);
66
+ return this;
67
+ };
68
+ this.addAttachmentFromData64 = (filename, data64, contentType) => {
69
+ return this.addAttachment({
70
+ filename,
71
+ data64,
72
+ contentType: contentType || this._getContentTypeFromFilename(filename)
73
+ });
74
+ };
75
+ this.addAttachmentFromUrl = (filename, url, contentType) => {
76
+ return this.addAttachment({
77
+ filename,
78
+ url,
79
+ contentType: contentType || this._getContentTypeFromFilename(filename)
80
+ });
81
+ };
82
+ this.clearAttachments = () => {
83
+ this.attachments = [];
84
+ return this;
85
+ };
27
86
  this.pushBcc = (recipient) => {
28
87
  return this._push("bcc", recipient);
29
88
  };
@@ -42,7 +101,11 @@ class SimpleEmail extends _class_1.BaseClass {
42
101
  this.setTo = (recipient) => {
43
102
  return this._setArray("to", recipient);
44
103
  };
45
- this.getSendParam = () => {
104
+ this.getSendParam = async () => {
105
+ // For emails with attachments, we need to use SendRawEmail instead of SendEmail
106
+ if (this.attachments.length > 0) {
107
+ return await this._getRawEmailParams();
108
+ }
46
109
  // https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/ses-examples-sending-email.html
47
110
  const params = {
48
111
  Destination: {
@@ -70,6 +133,110 @@ class SimpleEmail extends _class_1.BaseClass {
70
133
  };
71
134
  return params;
72
135
  };
136
+ this._getRawEmailParams = async () => {
137
+ const boundary = `----=_NextPart_${Date.now()}_${Math.random()
138
+ .toString(36)
139
+ .substr(2, 9)}`;
140
+ // Build recipients list
141
+ const allRecipients = [
142
+ ...this._filterRecipients("to"),
143
+ ...this._filterRecipients("cc"),
144
+ ...this._filterRecipients("bcc")
145
+ ];
146
+ // Build email headers
147
+ let rawMessage = "";
148
+ rawMessage += `From: ${this.from}\r\n`;
149
+ if (this._filterRecipients("to").length > 0) {
150
+ rawMessage += `To: ${this._filterRecipients("to")
151
+ .join(", ")}\r\n`;
152
+ }
153
+ if (this._filterRecipients("cc").length > 0) {
154
+ rawMessage += `Cc: ${this._filterRecipients("cc")
155
+ .join(", ")}\r\n`;
156
+ }
157
+ rawMessage += `Subject: ${this.subject}\r\n`;
158
+ rawMessage += `MIME-Version: 1.0\r\n`;
159
+ rawMessage += `Content-Type: multipart/mixed; boundary="${boundary}"\r\n\r\n`;
160
+ // Add HTML body
161
+ rawMessage += `--${boundary}\r\n`;
162
+ rawMessage += `Content-Type: text/html; charset=UTF-8\r\n`;
163
+ rawMessage += `Content-Transfer-Encoding: 7bit\r\n\r\n`;
164
+ rawMessage += `${this.html}\r\n\r\n`;
165
+ // Add attachments
166
+ for (const attachment of this.attachments) {
167
+ const attachmentData = await this._getAttachmentData(attachment);
168
+ rawMessage += `--${boundary}\r\n`;
169
+ rawMessage += `Content-Type: ${attachment.contentType || "application/octet-stream"}\r\n`;
170
+ rawMessage += `Content-Disposition: attachment; filename="${attachment.filename}"\r\n`;
171
+ rawMessage += `Content-Transfer-Encoding: base64\r\n\r\n`;
172
+ rawMessage += `${attachmentData}\r\n\r\n`;
173
+ }
174
+ rawMessage += `--${boundary}--\r\n`;
175
+ return {
176
+ Destinations: allRecipients,
177
+ RawMessage: {
178
+ Data: rawMessage
179
+ },
180
+ Source: this.from
181
+ };
182
+ };
183
+ this._getAttachmentData = async (attachment) => {
184
+ // If data is already provided as base64
185
+ if (attachment.data64) {
186
+ // Remove data URL prefix if present (e.g., "data:image/png;base64,")
187
+ return attachment.data64.replace(/^data:[^;]+;base64,/, "");
188
+ }
189
+ // If URL is provided, fetch the data
190
+ if (attachment.url) {
191
+ const fetch = await Promise.resolve().then(() => __importStar(require("node-fetch"))).then(mod => mod.default);
192
+ const response = await fetch(attachment.url);
193
+ const buffer = await response.buffer();
194
+ return buffer.toString("base64");
195
+ }
196
+ throw new Error(`No valid data source provided for attachment: ${attachment.filename}`);
197
+ };
198
+ this._getContentTypeFromFilename = (filename) => {
199
+ const extension = filename.split(".")
200
+ .pop()
201
+ ?.toLowerCase();
202
+ const mimeTypes = {
203
+ // Images
204
+ "jpg": "image/jpeg",
205
+ "jpeg": "image/jpeg",
206
+ "png": "image/png",
207
+ "gif": "image/gif",
208
+ "svg": "image/svg+xml",
209
+ "webp": "image/webp",
210
+ "bmp": "image/bmp",
211
+ "ico": "image/x-icon",
212
+ // Documents
213
+ "pdf": "application/pdf",
214
+ "doc": "application/msword",
215
+ "docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
216
+ "xls": "application/vnd.ms-excel",
217
+ "xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
218
+ "ppt": "application/vnd.ms-powerpoint",
219
+ "pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
220
+ // Data
221
+ "csv": "text/csv",
222
+ "json": "application/json",
223
+ "xml": "application/xml",
224
+ "txt": "text/plain",
225
+ // Archives
226
+ "zip": "application/zip",
227
+ "rar": "application/x-rar-compressed",
228
+ "7z": "application/x-7z-compressed",
229
+ "tar": "application/x-tar",
230
+ "gz": "application/gzip",
231
+ // Audio/Video
232
+ "mp3": "audio/mpeg",
233
+ "wav": "audio/wav",
234
+ "mp4": "video/mp4",
235
+ "avi": "video/x-msvideo",
236
+ "mov": "video/quicktime",
237
+ };
238
+ return mimeTypes[extension || ""] || "application/octet-stream";
239
+ };
73
240
  this._filterRecipients = (field) => {
74
241
  // Remove all undefined and null values
75
242
  let response = this[field].filter(recipient => recipient && null !== recipient);
@@ -100,6 +267,9 @@ class SimpleEmail extends _class_1.BaseClass {
100
267
  if (to) {
101
268
  this.setTo(to);
102
269
  }
270
+ if (attachments) {
271
+ this.setAttachments(attachments);
272
+ }
103
273
  return this;
104
274
  }
105
275
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@8ms/helpers",
3
3
  "license": "UNLICENSED",
4
- "version": "2.0.45",
4
+ "version": "2.1.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/8millionstories-organisation/8ms-helpers-ts.git"
@@ -183,5 +183,5 @@
183
183
  "tslib": "2.8.1",
184
184
  "typescript": "^5.0.0"
185
185
  },
186
- "packageManager": "yarn@4.9.2"
186
+ "packageManager": "yarn@4.9.4"
187
187
  }
package/prisma/server.js CHANGED
@@ -10,7 +10,6 @@ const prismaClient = async (key = "default", config) => {
10
10
  }
11
11
  if (!config) {
12
12
  if (key === "default") {
13
- console.log('Library process.env.DATABASE_URL:', process.env.DATABASE_URL);
14
13
  config = {
15
14
  url: process.env.DATABASE_URL,
16
15
  isDebug: 'true' === process.env.DATABASE_IS_DEBUG,
@@ -2,4 +2,4 @@
2
2
  * Chunk a queue of promises into controlled chunks to prevent overloading.
3
3
  * https://stackoverflow.com/a/53964407
4
4
  */
5
- export declare const promiseChunks: (promises: Promise<any>[], size: number) => Promise<any[]>;
5
+ export declare const promiseChunks: (promises: Promise<any>[], size: number, sleepSeconds?: number) => Promise<any[]>;
@@ -2,17 +2,19 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.promiseChunks = void 0;
4
4
  const lodash_1 = require("lodash");
5
+ const sleep_1 = require("./sleep");
5
6
  /**
6
7
  * Chunk a queue of promises into controlled chunks to prevent overloading.
7
8
  * https://stackoverflow.com/a/53964407
8
9
  */
9
- const promiseChunks = async (promises, size) => {
10
+ const promiseChunks = async (promises, size, sleepSeconds = 0) => {
10
11
  const batches = (0, lodash_1.chunk)(promises, size);
11
12
  let results = [];
12
13
  while (batches.length) {
13
14
  const batch = batches.shift();
14
15
  const result = await Promise.all(batch);
15
16
  results.push(result);
17
+ await (0, sleep_1.sleep)(sleepSeconds);
16
18
  }
17
19
  return (0, lodash_1.flatten)(results);
18
20
  };