@postalsys/email-text-tools 2.4.26 → 2.6.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.
Files changed (88) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +12 -0
  3. package/dist/cjs/index.d.ts +20 -0
  4. package/dist/cjs/index.js +31 -0
  5. package/dist/cjs/lib/commonjs.cjs +17 -0
  6. package/dist/cjs/lib/commonjs.d.cts +4 -0
  7. package/dist/cjs/lib/format-date.d.ts +15 -0
  8. package/dist/cjs/lib/format-date.js +60 -0
  9. package/dist/cjs/lib/html-guard.d.ts +3 -0
  10. package/dist/cjs/lib/html-guard.js +119 -0
  11. package/dist/cjs/lib/html-to-text.d.ts +1 -0
  12. package/dist/cjs/lib/html-to-text.js +49 -0
  13. package/dist/cjs/lib/inline-html.d.ts +19 -0
  14. package/dist/cjs/lib/inline-html.js +150 -0
  15. package/dist/cjs/lib/inline-style.d.ts +2 -0
  16. package/dist/cjs/lib/inline-style.js +42 -0
  17. package/dist/cjs/lib/inline-text.d.ts +3 -0
  18. package/dist/cjs/lib/inline-text.js +111 -0
  19. package/dist/cjs/lib/juice-worker.d.ts +1 -0
  20. package/dist/cjs/lib/juice-worker.js +19 -0
  21. package/dist/cjs/lib/mime-html.d.ts +34 -0
  22. package/dist/cjs/lib/mime-html.js +496 -0
  23. package/dist/cjs/lib/text-to-html.d.ts +1 -0
  24. package/dist/cjs/lib/text-to-html.js +182 -0
  25. package/dist/cjs/lib/worker-pool.d.ts +61 -0
  26. package/dist/cjs/lib/worker-pool.js +229 -0
  27. package/dist/cjs/package.json +3 -0
  28. package/dist/esm/index.d.ts +20 -0
  29. package/dist/esm/index.js +13 -0
  30. package/dist/esm/lib/commonjs.cjs +17 -0
  31. package/dist/esm/lib/commonjs.d.cts +4 -0
  32. package/dist/esm/lib/format-date.d.ts +15 -0
  33. package/{lib → dist/esm/lib}/format-date.js +8 -16
  34. package/dist/esm/lib/html-guard.d.ts +3 -0
  35. package/{lib → dist/esm/lib}/html-guard.js +8 -24
  36. package/dist/esm/lib/html-to-text.d.ts +1 -0
  37. package/{lib → dist/esm/lib}/html-to-text.js +2 -14
  38. package/dist/esm/lib/inline-html.d.ts +19 -0
  39. package/{lib → dist/esm/lib}/inline-html.js +17 -55
  40. package/dist/esm/lib/inline-style.d.ts +2 -0
  41. package/dist/esm/lib/inline-style.js +39 -0
  42. package/dist/esm/lib/inline-text.d.ts +3 -0
  43. package/{lib → dist/esm/lib}/inline-text.js +8 -31
  44. package/dist/esm/lib/juice-worker.d.ts +1 -0
  45. package/{lib → dist/esm/lib}/juice-worker.js +7 -8
  46. package/dist/esm/lib/mime-html.d.ts +34 -0
  47. package/{lib → dist/esm/lib}/mime-html.js +80 -161
  48. package/dist/esm/lib/text-to-html.d.ts +1 -0
  49. package/{lib → dist/esm/lib}/text-to-html.js +35 -63
  50. package/dist/esm/lib/worker-pool.d.ts +61 -0
  51. package/{lib → dist/esm/lib}/worker-pool.js +18 -64
  52. package/dist/esm/package.json +3 -0
  53. package/package.json +67 -10
  54. package/.github/VULNERABILITY_REPORT.yml +0 -75
  55. package/.github/workflows/release.yaml +0 -38
  56. package/.github/workflows/test.yaml +0 -35
  57. package/.ncurc.js +0 -28
  58. package/.prettierignore +0 -14
  59. package/.prettierrc.js +0 -10
  60. package/.release-please-manifest.json +0 -3
  61. package/CLAUDE.md +0 -105
  62. package/benchmark/benchmark.js +0 -348
  63. package/benchmark/concurrency-benchmark.js +0 -176
  64. package/benchmark/quick-benchmark.js +0 -200
  65. package/benchmark/quick-concurrency.js +0 -64
  66. package/benchmark/real-world-benchmark.js +0 -257
  67. package/docs/PERFORMANCE.md +0 -262
  68. package/eslint.config.js +0 -71
  69. package/examples/failing.html +0 -617
  70. package/examples/fixtures/message-1.json +0 -74
  71. package/examples/inline-html.js +0 -24
  72. package/examples/inline-text.js +0 -24
  73. package/examples/mime-html.js +0 -284
  74. package/index.js +0 -22
  75. package/release-please-config.json +0 -10
  76. package/test/comprehensive.test.js +0 -358
  77. package/test/containment.test.js +0 -128
  78. package/test/edge-cases.test.js +0 -62
  79. package/test/hardening.test.js +0 -504
  80. package/test/html-to-text.test.js +0 -253
  81. package/test/inline-html.test.js +0 -230
  82. package/test/inline-text.test.js +0 -204
  83. package/test/mime-html-extra.test.js +0 -39
  84. package/test/mime-html.test.js +0 -302
  85. package/test/text-to-html-extra.test.js +0 -42
  86. package/test/worker-pool-extra.test.js +0 -132
  87. package/test/worker-pool-regression.test.js +0 -128
  88. package/test/worker-pool.test.js +0 -282
@@ -1,28 +1,21 @@
1
- 'use strict';
2
-
3
- const linkifyIt = require('linkify-it');
4
- const tlds = require('tlds');
5
- const he = require('he');
6
-
7
- const linkify = linkifyIt()
8
- .tlds(tlds) // Reload with full tlds list
1
+ import LinkifyIt from 'linkify-it';
2
+ import he from 'he';
3
+ import { TLDS } from './commonjs.cjs';
4
+ const linkify = new LinkifyIt()
5
+ .tlds(TLDS) // Reload with full tlds list
9
6
  .tlds('onion', true) // Add unofficial `.onion` domain
10
7
  .add('git:', 'http:') // Add `git:` protocol as "alias"
11
8
  .set({ fuzzyIP: true });
12
-
13
9
  // Quote levels deeper than this are kept as literal "> " text. Every level costs a few stack
14
10
  // frames here and a nested <blockquote> later, so hostile input with thousands of leading ">"
15
11
  // used to overflow the stack.
16
12
  const MAX_QUOTE_DEPTH = 100;
17
-
18
13
  function generateQuotedTree(text) {
19
14
  let lines = text.split(/\r?\n/);
20
-
21
15
  let tree = {
22
16
  type: 'text',
23
17
  children: []
24
18
  };
25
-
26
19
  let createNode = (parent, type) => {
27
20
  let node = {
28
21
  parent,
@@ -33,7 +26,6 @@ function generateQuotedTree(text) {
33
26
  parent.children.push(node);
34
27
  return node;
35
28
  };
36
-
37
29
  let walkNode = (curNode, lines, depth) => {
38
30
  for (let line of lines) {
39
31
  if (depth < MAX_QUOTE_DEPTH && /^>/.test(line)) {
@@ -41,41 +33,34 @@ function generateQuotedTree(text) {
41
33
  curNode = createNode(curNode.parent, 'quote');
42
34
  }
43
35
  curNode.lines.push(line.replace(/^> ?/, ''));
44
- } else if (curNode.type === 'quote') {
36
+ }
37
+ else if (curNode.type === 'quote') {
45
38
  // process child
46
-
47
39
  let quoteNode = curNode;
48
-
49
40
  let childNode = createNode(curNode, 'text');
50
41
  walkNode(childNode, quoteNode.lines, depth + 1);
51
-
52
42
  curNode = createNode(curNode.parent, 'text');
53
43
  curNode.lines.push(line);
54
- } else {
44
+ }
45
+ else {
55
46
  curNode.lines.push(line);
56
47
  }
57
48
  }
58
-
59
49
  if (curNode.type === 'quote') {
60
50
  // process child
61
-
62
51
  let quoteNode = curNode;
63
-
64
52
  let childNode = createNode(curNode, 'text');
65
53
  walkNode(childNode, quoteNode.lines, depth + 1);
66
54
  }
67
55
  };
68
-
69
56
  let rootNode = createNode(tree, 'text');
70
57
  walkNode(rootNode, lines, 0);
71
-
72
58
  return tree;
73
59
  }
74
-
75
60
  /**
76
61
  * Return HTML escaped text with clickable links
77
- * @param {String} text Text content to HTML escape
78
- * @returns {String} HTML escaped content
62
+ * @param text Text content to HTML escape
63
+ * @returns HTML escaped content
79
64
  */
80
65
  function linkifyAndHtmlEscape(text) {
81
66
  try {
@@ -94,56 +79,55 @@ function linkifyAndHtmlEscape(text) {
94
79
  parts.push(Object.assign({ type: 'link' }, link));
95
80
  cursor = link.lastIndex;
96
81
  }
97
-
98
82
  if (cursor < text.length) {
99
83
  parts.push({
100
84
  type: 'text',
101
- content: text.substr(cursor)
85
+ content: text.slice(cursor)
102
86
  });
103
87
  }
104
-
105
88
  return parts
106
89
  .map(part => {
107
- switch (part.type) {
108
- case 'text': {
109
- // normal text, escape HTML
110
- return he.encode(part.content, { useNamedReferences: true });
111
- }
112
- case 'link':
113
- // URL with html escaped text content and URL
114
- return `<a href="${he.encode(part.url, { useNamedReferences: true })}">${he.encode(part.text, {
115
- useNamedReferences: true
116
- })}</a>`;
90
+ switch (part.type) {
91
+ case 'text': {
92
+ // normal text, escape HTML
93
+ return he.encode(part.content, { useNamedReferences: true });
117
94
  }
118
- return '';
119
- })
95
+ case 'link':
96
+ // URL with html escaped text content and URL
97
+ return `<a href="${he.encode(part.url, { useNamedReferences: true })}">${he.encode(part.text, {
98
+ useNamedReferences: true
99
+ })}</a>`;
100
+ }
101
+ return '';
102
+ })
120
103
  .join('');
121
104
  }
122
- } catch (_err) {
105
+ }
106
+ catch (_err) {
123
107
  // ignore?
124
108
  }
125
-
126
109
  // No links or exception, so HTML escape everything
127
110
  return he.encode(text, { useNamedReferences: true });
128
111
  }
129
-
130
112
  function textLinesToHtml(lines) {
131
113
  let textparts = ['<p>'];
132
114
  for (let i = 0; i < lines.length; i++) {
133
115
  let line = lines[i];
134
-
135
116
  if (!line.trim()) {
136
117
  if (i && lines[i - 1].trim()) {
137
118
  textparts.push('</p><p>');
138
119
  }
139
- } else if (i < lines.length - 1 && lines[i + 1].trim()) {
120
+ }
121
+ else if (i < lines.length - 1 && lines[i + 1].trim()) {
140
122
  let escaped = linkifyAndHtmlEscape(line);
141
123
  if (/^[ \t]*[-_]{2,}[ \t]*$/.test(escaped)) {
142
124
  textparts.push(`<hr />`);
143
- } else {
125
+ }
126
+ else {
144
127
  textparts.push(`${escaped}<br />`);
145
128
  }
146
- } else {
129
+ }
130
+ else {
147
131
  let escaped = linkifyAndHtmlEscape(line);
148
132
  if (/^[ \t]*[-_]{2,}[ \t]*$/.test(escaped)) {
149
133
  escaped = `<hr />`;
@@ -151,19 +135,15 @@ function textLinesToHtml(lines) {
151
135
  textparts.push(escaped);
152
136
  }
153
137
  }
154
-
155
138
  return textparts.join('\n') + '</p>';
156
139
  }
157
-
158
- function textToHtml(text) {
140
+ export default function textToHtml(text) {
159
141
  text = (text || '').toString();
160
142
  let tree = generateQuotedTree(text);
161
-
162
143
  let blockQuoteCounter = 0;
163
144
  function processQuotedNode(curNode, level) {
164
145
  let entries = [];
165
146
  level = (level || 0) + 1;
166
-
167
147
  if (curNode.type === 'text') {
168
148
  for (let child of curNode.children) {
169
149
  let content = processQuotedNode(child);
@@ -171,32 +151,24 @@ function textToHtml(text) {
171
151
  entries.push(content);
172
152
  }
173
153
  }
174
-
175
154
  if (curNode.lines && curNode.lines.length) {
176
155
  entries.push(textLinesToHtml(curNode.lines));
177
156
  }
178
157
  }
179
-
180
158
  if (curNode.type === 'quote') {
181
159
  for (let child of curNode.children) {
182
160
  let content = processQuotedNode(child, level);
183
161
  if (typeof content === 'string') {
184
162
  let id = ++blockQuoteCounter;
185
- entries.push(
186
- `<blockquote type="cite" class="ee-block-${level}"><!-- blocquote ${id} start-->${content}</blockquote><!-- blocquote ${id} end-->`
187
- );
163
+ entries.push(`<blockquote type="cite" class="ee-block-${level}"><!-- blocquote ${id} start-->${content}</blockquote><!-- blocquote ${id} end-->`);
188
164
  }
189
165
  }
190
166
  }
191
-
192
167
  if (entries.length) {
193
168
  return entries.join('\n');
194
169
  }
170
+ return undefined;
195
171
  }
196
-
197
172
  let generatedHtml = processQuotedNode(tree);
198
-
199
173
  return generatedHtml;
200
174
  }
201
-
202
- module.exports = textToHtml;
@@ -0,0 +1,61 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import { Worker } from 'node:worker_threads';
3
+ export interface WorkerPoolOptions {
4
+ /** Worker script, defaults to the juice worker */
5
+ workerPath?: string | URL | undefined;
6
+ minWorkers?: number | undefined;
7
+ maxWorkers?: number | undefined;
8
+ maxQueueSize?: number | undefined;
9
+ /** Default task timeout in milliseconds */
10
+ workerTimeout?: number | undefined;
11
+ /** How long a worker above the minimum may stay idle, in milliseconds */
12
+ idleTimeout?: number | undefined;
13
+ }
14
+ export interface WorkerPoolStats {
15
+ totalWorkers: number;
16
+ busyWorkers: number;
17
+ freeWorkers: number;
18
+ queuedTasks: number;
19
+ minWorkers: number;
20
+ maxWorkers: number;
21
+ }
22
+ interface Task {
23
+ data: unknown;
24
+ resolve: (result: any) => void;
25
+ reject: (err: Error) => void;
26
+ timeout: number;
27
+ }
28
+ interface WorkerInfo {
29
+ worker: Worker;
30
+ busy: boolean;
31
+ lastUsed: number;
32
+ timeoutHandle: NodeJS.Timeout | null;
33
+ currentTask: (Task & {
34
+ timeoutHandle: NodeJS.Timeout;
35
+ }) | null;
36
+ }
37
+ export default class WorkerPool extends EventEmitter {
38
+ workerPath: string | URL;
39
+ minWorkers: number;
40
+ maxWorkers: number;
41
+ maxQueueSize: number;
42
+ workerTimeout: number;
43
+ idleTimeout: number;
44
+ workers: WorkerInfo[];
45
+ freeWorkers: WorkerInfo[];
46
+ queue: Task[];
47
+ closing: boolean;
48
+ constructor(options?: WorkerPoolOptions);
49
+ _initWorkers(): void;
50
+ _createWorker(): WorkerInfo | null;
51
+ _setupIdleTimeout(workerInfo: WorkerInfo): void;
52
+ _removeWorker(workerInfo: WorkerInfo): void;
53
+ process(data: unknown, options?: {
54
+ timeout?: number | undefined;
55
+ }): Promise<any>;
56
+ _executeTask(workerInfo: WorkerInfo, task: Task): void;
57
+ _processQueue(): void;
58
+ close(): Promise<void>;
59
+ getStats(): WorkerPoolStats;
60
+ }
61
+ export {};
@@ -1,38 +1,30 @@
1
- 'use strict';
2
-
3
- const { Worker } = require('worker_threads');
4
- const path = require('path');
5
- const { EventEmitter } = require('events');
6
-
7
- class WorkerPool extends EventEmitter {
1
+ import { EventEmitter } from 'node:events';
2
+ import { Worker } from 'node:worker_threads';
3
+ import { JUICE_WORKER_PATH } from './commonjs.cjs';
4
+ export default class WorkerPool extends EventEmitter {
8
5
  constructor(options = {}) {
9
6
  super();
10
-
11
- this.workerPath = options.workerPath || path.join(__dirname, 'juice-worker.js');
7
+ this.workerPath = options.workerPath || JUICE_WORKER_PATH;
12
8
  this.minWorkers = options.minWorkers || 2;
13
9
  this.maxWorkers = options.maxWorkers || 4;
14
10
  this.maxQueueSize = options.maxQueueSize || 100;
15
11
  this.workerTimeout = options.workerTimeout || 10000;
16
12
  this.idleTimeout = options.idleTimeout || 30000;
17
-
18
13
  this.workers = [];
19
14
  this.freeWorkers = [];
20
15
  this.queue = [];
21
16
  this.closing = false;
22
-
23
17
  // Initialize minimum number of workers
24
18
  this._initWorkers();
25
19
  }
26
-
27
20
  _initWorkers() {
28
21
  for (let i = 0; i < this.minWorkers; i++) {
29
22
  this._createWorker();
30
23
  }
31
24
  }
32
-
33
25
  _createWorker() {
34
- if (this.closing) return null;
35
-
26
+ if (this.closing)
27
+ return null;
36
28
  const worker = new Worker(this.workerPath);
37
29
  const workerInfo = {
38
30
  worker,
@@ -41,174 +33,145 @@ class WorkerPool extends EventEmitter {
41
33
  timeoutHandle: null,
42
34
  currentTask: null
43
35
  };
44
-
45
- worker.on('message', msg => {
36
+ worker.on('message', (msg) => {
46
37
  if (workerInfo.currentTask) {
47
38
  const { resolve, reject, timeoutHandle } = workerInfo.currentTask;
48
39
  clearTimeout(timeoutHandle);
49
-
50
40
  if (msg.success) {
51
41
  resolve(msg.result);
52
- } else {
42
+ }
43
+ else {
53
44
  reject(new Error(msg.error));
54
45
  }
55
-
56
46
  workerInfo.currentTask = null;
57
47
  workerInfo.busy = false;
58
48
  workerInfo.lastUsed = Date.now();
59
-
60
49
  // Add worker back to free list if not already there
61
50
  if (!this.freeWorkers.includes(workerInfo)) {
62
51
  this.freeWorkers.push(workerInfo);
63
52
  }
64
-
65
53
  // Process next item in queue
66
54
  this._processQueue();
67
55
  }
68
56
  });
69
-
70
- worker.on('error', err => {
57
+ worker.on('error', (err) => {
71
58
  if (workerInfo.currentTask) {
72
59
  const { reject, timeoutHandle } = workerInfo.currentTask;
73
60
  clearTimeout(timeoutHandle);
74
61
  reject(err);
75
62
  workerInfo.currentTask = null;
76
63
  }
77
-
78
64
  // Remove failed worker
79
65
  const index = this.workers.indexOf(workerInfo);
80
66
  if (index > -1) {
81
67
  this.workers.splice(index, 1);
82
68
  }
83
-
84
69
  const freeIndex = this.freeWorkers.indexOf(workerInfo);
85
70
  if (freeIndex > -1) {
86
71
  this.freeWorkers.splice(freeIndex, 1);
87
72
  }
88
-
89
73
  // Create replacement if below minimum
90
74
  if (this.workers.length < this.minWorkers && !this.closing) {
91
75
  this._createWorker();
92
76
  }
93
-
94
77
  // Process queue with remaining workers
95
78
  this._processQueue();
96
79
  });
97
-
98
80
  worker.on('exit', () => {
99
81
  // Clean up worker references
100
82
  const index = this.workers.indexOf(workerInfo);
101
83
  if (index > -1) {
102
84
  this.workers.splice(index, 1);
103
85
  }
104
-
105
86
  const freeIndex = this.freeWorkers.indexOf(workerInfo);
106
87
  if (freeIndex > -1) {
107
88
  this.freeWorkers.splice(freeIndex, 1);
108
89
  }
109
90
  });
110
-
111
91
  this.workers.push(workerInfo);
112
92
  this.freeWorkers.push(workerInfo);
113
-
114
93
  // Set up idle timeout for workers above minimum
115
94
  if (this.workers.length > this.minWorkers) {
116
95
  this._setupIdleTimeout(workerInfo);
117
96
  }
118
-
119
97
  return workerInfo;
120
98
  }
121
-
122
99
  _setupIdleTimeout(workerInfo) {
123
100
  if (workerInfo.timeoutHandle) {
124
101
  clearTimeout(workerInfo.timeoutHandle);
125
102
  }
126
-
127
103
  workerInfo.timeoutHandle = setTimeout(() => {
128
104
  if (!workerInfo.busy && this.workers.length > this.minWorkers) {
129
105
  this._removeWorker(workerInfo);
130
106
  }
131
107
  }, this.idleTimeout);
132
108
  }
133
-
134
109
  _removeWorker(workerInfo) {
135
110
  const index = this.workers.indexOf(workerInfo);
136
111
  if (index > -1) {
137
112
  this.workers.splice(index, 1);
138
113
  }
139
-
140
114
  const freeIndex = this.freeWorkers.indexOf(workerInfo);
141
115
  if (freeIndex > -1) {
142
116
  this.freeWorkers.splice(freeIndex, 1);
143
117
  }
144
-
145
118
  if (workerInfo.timeoutHandle) {
146
119
  clearTimeout(workerInfo.timeoutHandle);
147
120
  }
148
-
149
- workerInfo.worker.terminate();
121
+ void workerInfo.worker.terminate();
150
122
  }
151
-
152
123
  async process(data, options = {}) {
153
124
  if (this.closing) {
154
125
  throw new Error('Worker pool is closing');
155
126
  }
156
-
157
127
  const timeout = options.timeout || this.workerTimeout;
158
-
159
128
  return new Promise((resolve, reject) => {
160
129
  const task = { data, resolve, reject, timeout };
161
-
162
130
  // Try to get a free worker
163
131
  const workerInfo = this.freeWorkers.shift();
164
-
165
132
  if (workerInfo) {
166
133
  this._executeTask(workerInfo, task);
167
- } else if (this.workers.length < this.maxWorkers) {
134
+ }
135
+ else if (this.workers.length < this.maxWorkers) {
168
136
  // Create new worker if below max
169
137
  const newWorker = this._createWorker();
170
138
  if (newWorker) {
171
139
  this.freeWorkers.shift(); // Remove from free list
172
140
  this._executeTask(newWorker, task);
173
141
  }
174
- } else if (this.queue.length < this.maxQueueSize) {
142
+ }
143
+ else if (this.queue.length < this.maxQueueSize) {
175
144
  // Queue the task
176
145
  this.queue.push(task);
177
- } else {
146
+ }
147
+ else {
178
148
  reject(new Error('Worker pool queue is full'));
179
149
  }
180
150
  });
181
151
  }
182
-
183
152
  _executeTask(workerInfo, task) {
184
153
  workerInfo.busy = true;
185
154
  workerInfo.lastUsed = Date.now();
186
-
187
155
  // Clear idle timeout while working
188
156
  if (workerInfo.timeoutHandle) {
189
157
  clearTimeout(workerInfo.timeoutHandle);
190
158
  workerInfo.timeoutHandle = null;
191
159
  }
192
-
193
160
  // Set up task timeout
194
161
  const timeoutHandle = setTimeout(() => {
195
162
  task.reject(new Error(`Worker task timed out after ${task.timeout}ms`));
196
163
  workerInfo.currentTask = null;
197
164
  workerInfo.busy = false;
198
-
199
165
  // Terminate and replace the worker
200
166
  this._removeWorker(workerInfo);
201
167
  if (this.workers.length < this.minWorkers && !this.closing) {
202
168
  this._createWorker();
203
169
  }
204
-
205
170
  this._processQueue();
206
171
  }, task.timeout);
207
-
208
172
  workerInfo.currentTask = { ...task, timeoutHandle };
209
173
  workerInfo.worker.postMessage(task.data);
210
174
  }
211
-
212
175
  _processQueue() {
213
176
  if (this.queue.length === 0) {
214
177
  // Mark free workers and set up idle timeouts
@@ -222,7 +185,6 @@ class WorkerPool extends EventEmitter {
222
185
  });
223
186
  return;
224
187
  }
225
-
226
188
  const workerInfo = this.freeWorkers.shift();
227
189
  if (workerInfo) {
228
190
  const task = this.queue.shift();
@@ -231,16 +193,13 @@ class WorkerPool extends EventEmitter {
231
193
  }
232
194
  }
233
195
  }
234
-
235
196
  async close() {
236
197
  this.closing = true;
237
-
238
198
  // Clear the queue
239
199
  while (this.queue.length > 0) {
240
200
  const task = this.queue.shift();
241
201
  task.reject(new Error('Worker pool is closing'));
242
202
  }
243
-
244
203
  // Terminate all workers
245
204
  const terminatePromises = this.workers.map(workerInfo => {
246
205
  if (workerInfo.timeoutHandle) {
@@ -248,13 +207,10 @@ class WorkerPool extends EventEmitter {
248
207
  }
249
208
  return workerInfo.worker.terminate();
250
209
  });
251
-
252
210
  await Promise.all(terminatePromises);
253
-
254
211
  this.workers = [];
255
212
  this.freeWorkers = [];
256
213
  }
257
-
258
214
  getStats() {
259
215
  return {
260
216
  totalWorkers: this.workers.length,
@@ -266,5 +222,3 @@ class WorkerPool extends EventEmitter {
266
222
  };
267
223
  }
268
224
  }
269
-
270
- module.exports = WorkerPool;
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
package/package.json CHANGED
@@ -1,18 +1,69 @@
1
1
  {
2
2
  "name": "@postalsys/email-text-tools",
3
- "version": "2.4.26",
3
+ "version": "2.6.0",
4
4
  "description": "Email text tools",
5
- "main": "index.js",
5
+ "type": "module",
6
+ "main": "./dist/cjs/index.js",
7
+ "types": "./dist/cjs/index.d.ts",
8
+ "typesVersions": {
9
+ "*": {
10
+ "lib/*": [
11
+ "dist/cjs/lib/*"
12
+ ],
13
+ "index": [
14
+ "dist/cjs/index.d.ts"
15
+ ],
16
+ "index.js": [
17
+ "dist/cjs/index.d.ts"
18
+ ]
19
+ }
20
+ },
21
+ "exports": {
22
+ ".": {
23
+ "import": "./dist/esm/index.js",
24
+ "require": "./dist/cjs/index.js"
25
+ },
26
+ "./index": {
27
+ "import": "./dist/esm/index.js",
28
+ "require": "./dist/cjs/index.js"
29
+ },
30
+ "./index.js": {
31
+ "import": "./dist/esm/index.js",
32
+ "require": "./dist/cjs/index.js"
33
+ },
34
+ "./lib/*.js": {
35
+ "import": "./dist/esm/lib/*.js",
36
+ "require": "./dist/cjs/lib/*.js"
37
+ },
38
+ "./lib/*": {
39
+ "import": "./dist/esm/lib/*.js",
40
+ "require": "./dist/cjs/lib/*.js"
41
+ },
42
+ "./package.json": "./package.json"
43
+ },
44
+ "files": [
45
+ "dist",
46
+ "README.md",
47
+ "LICENSE",
48
+ "CHANGELOG.md",
49
+ "SECURITY.md"
50
+ ],
6
51
  "scripts": {
7
- "test": "node --test test/*.test.js",
8
- "test:coverage": "node --test --experimental-test-coverage test/*.test.js",
9
- "test:watch": "node --test --watch test/*.test.js",
10
- "lint": "eslint .",
52
+ "build": "node scripts/build.js",
53
+ "typecheck": "tsc -p tsconfig.json",
54
+ "pretest": "npm run build",
55
+ "test": "node --test test/*.test.cjs test/package/*.test.cjs test/package/*.test.mjs",
56
+ "pretest:coverage": "npm run build",
57
+ "test:coverage": "node --test --experimental-test-coverage test/*.test.cjs",
58
+ "test:watch": "node --test --watch test/*.test.cjs",
59
+ "pretest:cjs": "npm run build",
60
+ "test:cjs": "NODE_OPTIONS=--no-experimental-require-module node --test test/*.test.cjs test/package/*.test.cjs",
61
+ "lint": "npm run typecheck && eslint .",
11
62
  "lint:fix": "eslint . --fix",
12
63
  "format": "prettier --write .",
13
64
  "format:check": "prettier --check .",
14
- "update": "rm -rf node_modules package-lock.json && ncu -u && npm install",
15
- "test:cjs": "NODE_OPTIONS=--no-experimental-require-module npm test"
65
+ "prepare": "npm run build",
66
+ "update": "rm -rf node_modules package-lock.json && ncu -u && npm install"
16
67
  },
17
68
  "repository": {
18
69
  "type": "git",
@@ -29,11 +80,17 @@
29
80
  },
30
81
  "homepage": "https://github.com/postalsys/email-text-tools#readme",
31
82
  "devDependencies": {
32
- "@eslint/js": "10.0.1",
83
+ "@types/he": "1.2.3",
84
+ "@types/html-to-text": "9.0.4",
85
+ "@types/jsdom": "27.0.0",
86
+ "@types/linkify-it": "5.0.0",
87
+ "@types/node": "20.19.43",
33
88
  "eslint": "10.13.0",
34
89
  "eslint-config-prettier": "10.1.8",
35
90
  "globals": "17.13.0",
36
- "prettier": "3.9.10"
91
+ "prettier": "3.9.10",
92
+ "typescript": "6.0.3",
93
+ "typescript-eslint": "8.71.2"
37
94
  },
38
95
  "dependencies": {
39
96
  "@postalsys/email-content": "0.1.17",