@solytude/listmonk 1.0.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 (108) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +542 -0
  3. package/dist/chunk-saezhne8.js +14 -0
  4. package/dist/chunk-saezhne8.js.map +9 -0
  5. package/dist/client.d.ts +430 -0
  6. package/dist/errors/api.d.ts +178 -0
  7. package/dist/errors/base.d.ts +47 -0
  8. package/dist/errors/configuration.d.ts +49 -0
  9. package/dist/errors/index.d.ts +13 -0
  10. package/dist/errors/network.d.ts +69 -0
  11. package/dist/errors/validation.d.ts +57 -0
  12. package/dist/http/auth.d.ts +39 -0
  13. package/dist/http/client.d.ts +149 -0
  14. package/dist/http/hooks.d.ts +90 -0
  15. package/dist/http/index.d.ts +10 -0
  16. package/dist/http/resource.d.ts +100 -0
  17. package/dist/http/sse.d.ts +36 -0
  18. package/dist/http/url.d.ts +51 -0
  19. package/dist/index.d.ts +64 -0
  20. package/dist/index.js +16039 -0
  21. package/dist/index.js.map +120 -0
  22. package/dist/resources/admin/admin.d.ts +92 -0
  23. package/dist/resources/admin/index.d.ts +10 -0
  24. package/dist/resources/admin/schemas.d.ts +23 -0
  25. package/dist/resources/admin/types.d.ts +29 -0
  26. package/dist/resources/bounces/bounces.d.ts +262 -0
  27. package/dist/resources/bounces/index.d.ts +10 -0
  28. package/dist/resources/bounces/schemas.d.ts +86 -0
  29. package/dist/resources/bounces/types.d.ts +37 -0
  30. package/dist/resources/campaigns/campaigns.d.ts +339 -0
  31. package/dist/resources/campaigns/index.d.ts +10 -0
  32. package/dist/resources/campaigns/schemas.d.ts +374 -0
  33. package/dist/resources/campaigns/types.d.ts +111 -0
  34. package/dist/resources/dashboard/dashboard.d.ts +73 -0
  35. package/dist/resources/dashboard/index.d.ts +10 -0
  36. package/dist/resources/dashboard/schemas.d.ts +55 -0
  37. package/dist/resources/dashboard/types.d.ts +22 -0
  38. package/dist/resources/import/import.d.ts +215 -0
  39. package/dist/resources/import/index.d.ts +10 -0
  40. package/dist/resources/import/schemas.d.ts +109 -0
  41. package/dist/resources/import/types.d.ts +72 -0
  42. package/dist/resources/lists/index.d.ts +10 -0
  43. package/dist/resources/lists/lists.d.ts +180 -0
  44. package/dist/resources/lists/schemas.d.ts +176 -0
  45. package/dist/resources/lists/types.d.ts +56 -0
  46. package/dist/resources/maintenance/index.d.ts +10 -0
  47. package/dist/resources/maintenance/maintenance.d.ts +92 -0
  48. package/dist/resources/maintenance/schemas.d.ts +36 -0
  49. package/dist/resources/maintenance/types.d.ts +31 -0
  50. package/dist/resources/media/index.d.ts +10 -0
  51. package/dist/resources/media/media.d.ts +198 -0
  52. package/dist/resources/media/schemas.d.ts +48 -0
  53. package/dist/resources/media/types.d.ts +48 -0
  54. package/dist/resources/public/index.d.ts +10 -0
  55. package/dist/resources/public/public.d.ts +111 -0
  56. package/dist/resources/public/schemas.d.ts +52 -0
  57. package/dist/resources/public/types.d.ts +30 -0
  58. package/dist/resources/settings/index.d.ts +10 -0
  59. package/dist/resources/settings/schemas.d.ts +374 -0
  60. package/dist/resources/settings/settings.d.ts +106 -0
  61. package/dist/resources/settings/types.d.ts +78 -0
  62. package/dist/resources/subscribers/index.d.ts +10 -0
  63. package/dist/resources/subscribers/schemas.d.ts +360 -0
  64. package/dist/resources/subscribers/subscribers.d.ts +308 -0
  65. package/dist/resources/subscribers/types.d.ts +113 -0
  66. package/dist/resources/templates/index.d.ts +10 -0
  67. package/dist/resources/templates/schemas.d.ts +110 -0
  68. package/dist/resources/templates/templates.d.ts +225 -0
  69. package/dist/resources/templates/types.d.ts +45 -0
  70. package/dist/resources/tx/index.d.ts +10 -0
  71. package/dist/resources/tx/schemas.d.ts +67 -0
  72. package/dist/resources/tx/tx.d.ts +167 -0
  73. package/dist/resources/tx/types.d.ts +88 -0
  74. package/dist/schemas/common.d.ts +48 -0
  75. package/dist/schemas/index.d.ts +6 -0
  76. package/dist/testing/errors.d.ts +25 -0
  77. package/dist/testing/factories/bounce.d.ts +43 -0
  78. package/dist/testing/factories/campaign.d.ts +43 -0
  79. package/dist/testing/factories/common.d.ts +54 -0
  80. package/dist/testing/factories/index.d.ts +12 -0
  81. package/dist/testing/factories/list.d.ts +43 -0
  82. package/dist/testing/factories/media.d.ts +40 -0
  83. package/dist/testing/factories/subscriber.d.ts +43 -0
  84. package/dist/testing/factories/template.d.ts +40 -0
  85. package/dist/testing/index.d.ts +39 -0
  86. package/dist/testing/index.js +573 -0
  87. package/dist/testing/index.js.map +32 -0
  88. package/dist/testing/mock-client.d.ts +119 -0
  89. package/dist/testing/mock-function.d.ts +28 -0
  90. package/dist/testing/resources/admin.d.ts +20 -0
  91. package/dist/testing/resources/bounces.d.ts +22 -0
  92. package/dist/testing/resources/campaigns.d.ts +31 -0
  93. package/dist/testing/resources/dashboard.d.ts +17 -0
  94. package/dist/testing/resources/import.d.ts +19 -0
  95. package/dist/testing/resources/index.d.ts +18 -0
  96. package/dist/testing/resources/lists.d.ts +24 -0
  97. package/dist/testing/resources/maintenance.d.ts +18 -0
  98. package/dist/testing/resources/media.d.ts +21 -0
  99. package/dist/testing/resources/public.d.ts +18 -0
  100. package/dist/testing/resources/settings.d.ts +19 -0
  101. package/dist/testing/resources/subscribers.d.ts +33 -0
  102. package/dist/testing/resources/templates.d.ts +24 -0
  103. package/dist/testing/resources/tx.d.ts +16 -0
  104. package/dist/testing/types.d.ts +138 -0
  105. package/dist/types/config.d.ts +59 -0
  106. package/dist/types/index.d.ts +7 -0
  107. package/dist/types/pagination.d.ts +37 -0
  108. package/package.json +97 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 solytude
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,542 @@
1
+ # @solytude/listmonk
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@solytude/listmonk)](https://www.npmjs.com/package/@solytude/listmonk)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
5
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.7+-blue)](https://www.typescriptlang.org/)
6
+
7
+ Production-grade TypeScript SDK for the [listmonk](https://listmonk.app/) email marketing platform API.
8
+
9
+ ## Table of Contents
10
+
11
+ - [Features](#features)
12
+ - [Requirements](#requirements)
13
+ - [Installation](#installation)
14
+ - [Quick Start](#quick-start)
15
+ - [Authentication](#authentication)
16
+ - [Resources](#resources)
17
+ - [Usage Examples](#usage-examples)
18
+ - [Configuration](#configuration)
19
+ - [Error Handling](#error-handling)
20
+ - [Pagination](#pagination)
21
+ - [TypeScript](#typescript)
22
+ - [Testing](#testing)
23
+ - [Contributing](#contributing)
24
+ - [License](#license)
25
+
26
+ ## Features
27
+
28
+ - **Complete API Coverage** — Full listmonk API coverage across 13 resource modules
29
+ - **Type-Safe** — Full TypeScript support with strict typing and Zod runtime validation
30
+ - **Zero Dependencies** — Only [Zod](https://zod.dev/) as a runtime dependency
31
+ - **Modern Patterns** — Async iterators for pagination, AbortController for timeouts
32
+ - **Stripe-Style API** — Intuitive resource-based interface (`client.subscribers.create()`)
33
+ - **Resilient** — Automatic retries with exponential backoff and jitter
34
+ - **Flexible** — Custom fetch support for edge runtimes and testing
35
+ - **Secure** — HTTPS enforcement, credential redaction in debug mode
36
+
37
+ ## Requirements
38
+
39
+ - **Node.js** 22+ or **Bun** 1.3+
40
+ - **TypeScript** 5.7+ (optional, but recommended)
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ # npm
46
+ npm install @solytude/listmonk
47
+
48
+ # yarn
49
+ yarn add @solytude/listmonk
50
+
51
+ # pnpm
52
+ pnpm add @solytude/listmonk
53
+
54
+ # bun
55
+ bun add @solytude/listmonk
56
+ ```
57
+
58
+ ## Quick Start
59
+
60
+ ```typescript
61
+ import { Listmonk } from '@solytude/listmonk';
62
+
63
+ const client = new Listmonk({
64
+ url: 'https://listmonk.example.com',
65
+ auth: {
66
+ username: process.env.LISTMONK_USERNAME!,
67
+ password: process.env.LISTMONK_PASSWORD!,
68
+ },
69
+ });
70
+
71
+ // Create a subscriber
72
+ const subscriber = await client.subscribers.create({
73
+ email: 'user@example.com',
74
+ name: 'John Doe',
75
+ lists: [1, 2],
76
+ });
77
+
78
+ console.log(`Created subscriber: ${subscriber.id}`);
79
+ ```
80
+
81
+ ## Authentication
82
+
83
+ The SDK uses Basic Authentication with username and password credentials. Create API credentials in your listmonk admin panel under **Settings → API**.
84
+
85
+ ### Using Environment Variables (Recommended)
86
+
87
+ ```bash
88
+ export LISTMONK_URL=https://listmonk.example.com
89
+ export LISTMONK_USERNAME=your_username
90
+ export LISTMONK_PASSWORD=your_password
91
+ ```
92
+
93
+ ```typescript
94
+ const client = new Listmonk({
95
+ url: process.env.LISTMONK_URL!,
96
+ auth: {
97
+ username: process.env.LISTMONK_USERNAME!,
98
+ password: process.env.LISTMONK_PASSWORD!,
99
+ },
100
+ });
101
+ ```
102
+
103
+ > **Security Warning**: Never commit credentials to version control. Always use environment variables or a secrets manager.
104
+
105
+ ## Resources
106
+
107
+ The SDK provides access to all listmonk API resources:
108
+
109
+ | Resource | Description | Methods |
110
+ |----------|-------------|---------|
111
+ | `subscribers` | Subscriber management, blocklisting, bounces | 17 |
112
+ | `lists` | Mailing list CRUD operations | 5 |
113
+ | `campaigns` | Campaign lifecycle, analytics, previews | 14 |
114
+ | `templates` | Email template management | 8 |
115
+ | `tx` | Transactional email sending | 1 |
116
+ | `media` | Media file uploads and management | 4 |
117
+ | `import` | Bulk subscriber imports (CSV/ZIP) | 4 |
118
+ | `bounces` | Bounce record management | 4 |
119
+ | `settings` | Server settings and SMTP configuration | 4 |
120
+ | `dashboard` | Dashboard statistics and charts | 2 |
121
+ | `admin` | Server administration and logs | 3 |
122
+ | `maintenance` | Garbage collection and cleanup | 3 |
123
+ | `public` | Public subscription endpoints | 3 |
124
+
125
+ ## Usage Examples
126
+
127
+ ### Subscribers
128
+
129
+ ```typescript
130
+ // List subscribers with pagination
131
+ const subscribers = await client.subscribers.list({
132
+ page: 1,
133
+ per_page: 100,
134
+ query: 'subscribers.status = \'enabled\'',
135
+ });
136
+
137
+ // Get a single subscriber
138
+ const subscriber = await client.subscribers.get(123);
139
+
140
+ // Update a subscriber
141
+ await client.subscribers.update(123, {
142
+ name: 'Jane Doe',
143
+ attribs: { company: 'Acme Inc' },
144
+ });
145
+
146
+ // Delete a subscriber
147
+ await client.subscribers.delete(123);
148
+
149
+ // Bulk operations
150
+ await client.subscribers.addToLists([1, 2, 3], { lists: [1, 2] });
151
+ await client.subscribers.blocklist([4, 5, 6]);
152
+ ```
153
+
154
+ ### Campaigns
155
+
156
+ ```typescript
157
+ // Create a campaign
158
+ const campaign = await client.campaigns.create({
159
+ name: 'Weekly Newsletter',
160
+ subject: 'This Week in Tech',
161
+ lists: [1],
162
+ type: 'regular',
163
+ content_type: 'richtext',
164
+ body: '<h1>Hello {{.Subscriber.Name}}</h1>',
165
+ });
166
+
167
+ // Preview a campaign
168
+ const preview = await client.campaigns.preview(campaign.id);
169
+
170
+ // Send a test email
171
+ await client.campaigns.test(campaign.id, {
172
+ subscribers: ['test@example.com'],
173
+ });
174
+
175
+ // Start the campaign
176
+ await client.campaigns.updateStatus(campaign.id, 'running');
177
+
178
+ // Get campaign analytics
179
+ const analytics = await client.campaigns.analytics('clicks', {
180
+ campaign_id: campaign.id,
181
+ });
182
+ ```
183
+
184
+ ### Transactional Email
185
+
186
+ ```typescript
187
+ await client.tx.send({
188
+ subscriber_email: 'user@example.com',
189
+ template_id: 1,
190
+ data: {
191
+ order_id: '12345',
192
+ items: ['Widget A', 'Widget B'],
193
+ },
194
+ });
195
+ ```
196
+
197
+ ### Lists
198
+
199
+ ```typescript
200
+ // Create a list
201
+ const list = await client.lists.create({
202
+ name: 'Newsletter Subscribers',
203
+ type: 'public',
204
+ optin: 'double',
205
+ });
206
+
207
+ // Get all lists
208
+ const lists = await client.lists.list();
209
+ ```
210
+
211
+ ### Media
212
+
213
+ ```typescript
214
+ // Upload a file
215
+ const media = await client.media.upload({
216
+ file: new File([buffer], 'image.png', { type: 'image/png' }),
217
+ });
218
+
219
+ // List all media
220
+ const files = await client.media.list();
221
+ ```
222
+
223
+ ## Configuration
224
+
225
+ ```typescript
226
+ const client = new Listmonk({
227
+ // Required
228
+ url: 'https://listmonk.example.com',
229
+ auth: {
230
+ username: 'your_username',
231
+ password: 'your_password',
232
+ },
233
+
234
+ // Optional
235
+ timeout: 30000, // Request timeout in ms (default: 30000)
236
+ maxRetries: 2, // Max retry attempts (default: 2)
237
+ debug: false, // Enable debug logging (default: false)
238
+ fetch: customFetch, // Custom fetch implementation
239
+ });
240
+ ```
241
+
242
+ ### Configuration Options
243
+
244
+ | Option | Type | Default | Description |
245
+ |--------|------|---------|-------------|
246
+ | `url` | `string` | — | listmonk server URL (required) |
247
+ | `auth.username` | `string` | — | API username (required) |
248
+ | `auth.password` | `string` | — | API password (required) |
249
+ | `timeout` | `number` | `30000` | Request timeout in milliseconds |
250
+ | `maxRetries` | `number` | `2` | Maximum retry attempts for failed requests |
251
+ | `debug` | `boolean` | `false` | Enable debug logging (credentials redacted) |
252
+ | `fetch` | `typeof fetch` | `globalThis.fetch` | Custom fetch implementation |
253
+
254
+ ## Error Handling
255
+
256
+ The SDK provides a typed error hierarchy for granular error handling:
257
+
258
+ ```typescript
259
+ import {
260
+ Listmonk,
261
+ ListmonkError,
262
+ ListmonkApiError,
263
+ ListmonkAuthenticationError,
264
+ ListmonkNotFoundError,
265
+ ListmonkRateLimitError,
266
+ ListmonkValidationError,
267
+ ListmonkNetworkError,
268
+ } from '@solytude/listmonk';
269
+
270
+ try {
271
+ await client.subscribers.get(999999);
272
+ } catch (error) {
273
+ if (error instanceof ListmonkNotFoundError) {
274
+ console.error('Subscriber not found:', error.message);
275
+ } else if (error instanceof ListmonkAuthenticationError) {
276
+ console.error('Invalid credentials:', error.message);
277
+ } else if (error instanceof ListmonkRateLimitError) {
278
+ console.error('Rate limited. Retry after:', error.retryAfter);
279
+ } else if (error instanceof ListmonkValidationError) {
280
+ console.error('Validation failed:', error.issues);
281
+ } else if (error instanceof ListmonkApiError) {
282
+ console.error('API error:', error.status, error.message);
283
+ } else if (error instanceof ListmonkNetworkError) {
284
+ console.error('Network error:', error.message);
285
+ } else {
286
+ throw error;
287
+ }
288
+ }
289
+ ```
290
+
291
+ ### Error Types
292
+
293
+ | Error Class | HTTP Status | Description |
294
+ |-------------|-------------|-------------|
295
+ | `ListmonkBadRequestError` | 400 | Invalid request parameters |
296
+ | `ListmonkAuthenticationError` | 401 | Invalid or missing credentials |
297
+ | `ListmonkPermissionDeniedError` | 403 | Insufficient permissions |
298
+ | `ListmonkNotFoundError` | 404 | Resource not found |
299
+ | `ListmonkRateLimitError` | 429 | Too many requests |
300
+ | `ListmonkInternalServerError` | 5xx | Server-side error |
301
+ | `ListmonkValidationError` | — | Zod schema validation failed |
302
+ | `ListmonkNetworkError` | — | Connection or timeout error |
303
+ | `ListmonkConfigurationError` | — | Invalid client configuration |
304
+
305
+ ## Pagination
306
+
307
+ ### Async Iteration (Recommended)
308
+
309
+ Iterate through all results automatically:
310
+
311
+ ```typescript
312
+ for await (const subscriber of client.subscribers.listAll()) {
313
+ console.log(subscriber.email);
314
+ }
315
+
316
+ // With filtering
317
+ for await (const subscriber of client.subscribers.listAll({
318
+ query: 'subscribers.status = \'enabled\'',
319
+ })) {
320
+ console.log(subscriber.email);
321
+ }
322
+ ```
323
+
324
+ ### Manual Pagination
325
+
326
+ Control pagination manually when needed:
327
+
328
+ ```typescript
329
+ let page = 1;
330
+ let hasMore = true;
331
+
332
+ while (hasMore) {
333
+ const response = await client.subscribers.list({
334
+ page,
335
+ per_page: 100,
336
+ });
337
+
338
+ for (const subscriber of response.data.results) {
339
+ console.log(subscriber.email);
340
+ }
341
+
342
+ hasMore = page * 100 < response.data.total;
343
+ page++;
344
+ }
345
+ ```
346
+
347
+ ## TypeScript
348
+
349
+ The SDK is written in TypeScript and provides comprehensive type definitions. Types are inferred from Zod schemas, ensuring runtime validation matches compile-time types.
350
+
351
+ ### Importing Types
352
+
353
+ ```typescript
354
+ import type {
355
+ Subscriber,
356
+ List,
357
+ Campaign,
358
+ Template,
359
+ CreateSubscriberParams,
360
+ UpdateCampaignParams,
361
+ } from '@solytude/listmonk';
362
+ ```
363
+
364
+ ### Using Schemas
365
+
366
+ Zod schemas are exported for custom validation or extension:
367
+
368
+ ```typescript
369
+ import { SubscriberSchema, CreateSubscriberSchema } from '@solytude/listmonk';
370
+
371
+ // Validate external data
372
+ const subscriber = SubscriberSchema.parse(externalData);
373
+
374
+ // Extend schemas
375
+ const CustomSubscriberSchema = SubscriberSchema.extend({
376
+ customField: z.string(),
377
+ });
378
+ ```
379
+
380
+ ## Testing
381
+
382
+ The SDK provides test utilities for unit testing applications without real API calls:
383
+
384
+ ```bash
385
+ # The testing module is included in the main package
386
+ import { MockListmonkClient } from '@solytude/listmonk/testing';
387
+ ```
388
+
389
+ ### MockListmonkClient
390
+
391
+ The `MockListmonkClient` provides a test-runner-agnostic mock implementation:
392
+
393
+ ```typescript
394
+ import { MockListmonkClient, createMockSubscriber } from '@solytude/listmonk/testing';
395
+
396
+ describe('MyApp', () => {
397
+ let mockClient: MockListmonkClient;
398
+
399
+ beforeEach(() => {
400
+ mockClient = new MockListmonkClient();
401
+ });
402
+
403
+ afterEach(() => {
404
+ mockClient.reset(); // Clears all mock configurations and call history
405
+ });
406
+
407
+ it('fetches subscribers', async () => {
408
+ // Configure mock response
409
+ mockClient.subscribers.list.mockResolvedValue({
410
+ data: {
411
+ results: [createMockSubscriber({ email: 'test@example.com' })],
412
+ total: 1,
413
+ per_page: 100,
414
+ page: 1,
415
+ },
416
+ });
417
+
418
+ // Use in your application code
419
+ const result = await mockClient.subscribers.list();
420
+
421
+ // Verify behavior
422
+ expect(result.data.results[0].email).toBe('test@example.com');
423
+ expect(mockClient.subscribers.list.callCount).toBe(1);
424
+ });
425
+
426
+ it('handles errors', async () => {
427
+ mockClient.subscribers.get.mockRejectedValue(new Error('Not found'));
428
+
429
+ await expect(mockClient.subscribers.get(999)).rejects.toThrow('Not found');
430
+ });
431
+ });
432
+ ```
433
+
434
+ ### Mock Call Tracking
435
+
436
+ All mock methods track calls for assertions:
437
+
438
+ ```typescript
439
+ await mockClient.subscribers.create({ email: 'user@example.com', lists: [1] });
440
+
441
+ expect(mockClient.subscribers.create.callCount).toBe(1);
442
+ expect(mockClient.subscribers.create.lastCall).toEqual([
443
+ { email: 'user@example.com', lists: [1] }
444
+ ]);
445
+ expect(mockClient.subscribers.create.calls).toHaveLength(1);
446
+ ```
447
+
448
+ ### Async Iterator Mocking
449
+
450
+ Mock `listAll()` methods that return async iterators:
451
+
452
+ ```typescript
453
+ mockClient.subscribers.listAll.mockAsyncIterator([
454
+ createMockSubscriber({ id: 1 }),
455
+ createMockSubscriber({ id: 2 }),
456
+ createMockSubscriber({ id: 3 }),
457
+ ]);
458
+
459
+ const subscribers = [];
460
+ for await (const sub of mockClient.subscribers.listAll()) {
461
+ subscribers.push(sub);
462
+ }
463
+ expect(subscribers).toHaveLength(3);
464
+ ```
465
+
466
+ ### Factory Functions
467
+
468
+ Create realistic mock data with factory functions:
469
+
470
+ ```typescript
471
+ import {
472
+ createMockSubscriber,
473
+ createMockSubscribers,
474
+ createMockList,
475
+ createMockCampaign,
476
+ createMockTemplate,
477
+ createMockMedia,
478
+ createMockBounce,
479
+ resetFactories,
480
+ } from '@solytude/listmonk/testing';
481
+
482
+ // Create single mock entities
483
+ const subscriber = createMockSubscriber({ email: 'custom@example.com' });
484
+ const list = createMockList({ name: 'Newsletter', type: 'private' });
485
+ const campaign = createMockCampaign({ status: 'running' });
486
+
487
+ // Create multiple mock entities
488
+ const subscribers = createMockSubscribers(5); // 5 unique subscribers
489
+ const lists = createMockLists(3, { type: 'private' }); // 3 private lists
490
+
491
+ // Reset factory counters (call in beforeEach for predictable IDs)
492
+ beforeEach(() => {
493
+ resetFactories();
494
+ });
495
+ ```
496
+
497
+ ### Custom Fetch for Testing
498
+
499
+ Inject a custom fetch function for fine-grained control:
500
+
501
+ ```typescript
502
+ const mockFetch = vi.fn().mockResolvedValue(
503
+ new Response(JSON.stringify({ data: { id: 1 } }), {
504
+ status: 200,
505
+ headers: { 'Content-Type': 'application/json' },
506
+ })
507
+ );
508
+
509
+ const client = new Listmonk({
510
+ url: 'https://test.listmonk.app',
511
+ auth: { username: 'test', password: 'test' },
512
+ fetch: mockFetch,
513
+ });
514
+ ```
515
+
516
+ ## Contributing
517
+
518
+ Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests.
519
+
520
+ ### Development
521
+
522
+ ```bash
523
+ # Install dependencies
524
+ bun install
525
+
526
+ # Run tests
527
+ bun test
528
+
529
+ # Type check
530
+ bun run typecheck
531
+
532
+ # Lint
533
+ bun run check
534
+ ```
535
+
536
+ ## License
537
+
538
+ This project is licensed under the MIT License — see the [LICENSE](LICENSE) file for details.
539
+
540
+ ---
541
+
542
+ **[listmonk](https://listmonk.app/)** is a free and open source, high performance, self-hosted newsletter and mailing list manager.
@@ -0,0 +1,14 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, {
5
+ get: all[name],
6
+ enumerable: true,
7
+ configurable: true,
8
+ set: (newValue) => all[name] = () => newValue
9
+ });
10
+ };
11
+
12
+ export { __export };
13
+
14
+ //# debugId=934851CB66A4F95F64756E2164756E21
@@ -0,0 +1,9 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [
5
+ ],
6
+ "mappings": "",
7
+ "debugId": "934851CB66A4F95F64756E2164756E21",
8
+ "names": []
9
+ }