simple-backups 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kartik
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,158 @@
1
+ # SimpleBackups: A Simple Package To Automatically Create And Prune Backups.
2
+
3
+ <div align="left">
4
+
5
+ [![NPM version](https://img.shields.io/npm/v/simple-backups.svg?style=flat)](https://www.npmjs.com/package/simple-backups)
6
+ [![NPM downloads](https://img.shields.io/npm/dm/simple-backups.svg?style=flat)](https://www.npmjs.com/package/simple-backups)
7
+ [![GitHub issues](https://img.shields.io/github/issues/kartikk221/simple-backups)](https://github.com/kartikk221/simple-backups/issues)
8
+ [![GitHub stars](https://img.shields.io/github/stars/kartikk221/simple-backups)](https://github.com/kartikk221/simple-backups/stargazers)
9
+ [![GitHub license](https://img.shields.io/github/license/kartikk221/simple-backups)](https://github.com/kartikk221/simple-backups/blob/master/LICENSE)
10
+
11
+ </div>
12
+
13
+ ## Motivation
14
+ This package aims to simplify the task of automatically creating and pruning backups from any upstream storage mechanism. This package does not care where your backups are stored and instead uses a small adapter interface to list, create and delete backups while maintaining multiple backup retention windows.
15
+
16
+ ## Features
17
+ - Simple-to-use API
18
+ - JSDoc Type Support
19
+ - Automatic Backup Creation
20
+ - Multi-Window Backup Retention
21
+ - CPU & Memory Efficient
22
+ - No Dependencies
23
+
24
+ ## Installation
25
+ SimpleBackups can be installed using node package manager (`npm`)
26
+ ```
27
+ npm i simple-backups
28
+ ```
29
+
30
+ ## How To Use?
31
+ Below is a small snippet that shows how to use a `SimpleBackups` instance.
32
+
33
+ ```javascript
34
+ import { SimpleBackups } from 'simple-backups';
35
+
36
+ const hour = 1000 * 60 * 60;
37
+ const day = hour * 24;
38
+
39
+ // Create a simple backups instance which keeps 24 hourly backups and 30 daily backups
40
+ const backups = new SimpleBackups({
41
+ windows: [
42
+ { interval_ms: hour, limit: 24 },
43
+ { interval_ms: day, limit: 30 }
44
+ ],
45
+ adapters: {
46
+ async list() {
47
+ // Return every backup currently stored in the upstream storage mechanism
48
+ // Each backup must have a unique id and a timestamp in milliseconds
49
+ return await storage.list_backups();
50
+ },
51
+ async create() {
52
+ // Create a new backup in the upstream storage mechanism
53
+ const backup = await storage.create_backup();
54
+
55
+ // Return the backup record so SimpleBackups can track it
56
+ return {
57
+ id: backup.id,
58
+ timestamp: backup.timestamp
59
+ };
60
+ },
61
+ async delete(backup) {
62
+ // Attempt to delete the backup from the upstream storage mechanism
63
+ // Return true only if the backup was successfully deleted
64
+ return await storage.delete_backup(backup.id);
65
+ }
66
+ }
67
+ });
68
+
69
+ // Listen for newly created backups
70
+ backups.on('create', (backup) => {
71
+ console.log('Created backup:', backup.id);
72
+ });
73
+
74
+ // Listen for successfully deleted backups
75
+ backups.on('delete', (backup) => {
76
+ console.log('Deleted backup:', backup.id);
77
+ });
78
+
79
+ // Listen for adapter or validation errors
80
+ backups.on('error', (error) => {
81
+ console.error(error);
82
+ });
83
+ ```
84
+
85
+ ## SimpleBackups
86
+ Below is a breakdown of the `SimpleBackups` class.
87
+
88
+ #### Constructor Parameters
89
+ * `new SimpleBackups(Object: options)`: Creates a new SimpleBackups instance with the provided `options`.
90
+ * `options` [`Object`]: Constructor options for this instance.
91
+ * `adapters` [`BackupAdapters`]: Adapter methods used to list, create and delete backups from the upstream storage mechanism.
92
+ * `windows` [`BackupWindow[]`]: Retention windows used to determine when backups should be created and which backups should be kept.
93
+ * **Note!** the SimpleBackups instance starts automatically when constructed.
94
+ * **Note!** the smallest `interval_ms` from the provided windows is used as the internal tick interval.
95
+ * **Note!** all window `interval_ms` and `limit` values must be positive finite integers.
96
+
97
+ #### SimpleBackups Properties
98
+ | Property | Type | Description |
99
+ | :-------- | :------- | :------------------------- |
100
+ | `backups` | `Backup[]` | The most recently listed backups from the upstream storage mechanism. |
101
+ | `destroyed` | `Boolean` | Whether this SimpleBackups instance has been destroyed. |
102
+
103
+ #### SimpleBackups Methods
104
+ * `destroy()`: Destroys the SimpleBackups instance and stops all internal intervals.
105
+ * **Note** this method also removes all event listeners from the instance.
106
+ * `on(String: event, Function: listener)`: Adds a listener for a SimpleBackups event.
107
+ * **Returns** the SimpleBackups instance.
108
+ * `once(String: event, Function: listener)`: Adds a one-time listener for a SimpleBackups event.
109
+ * **Returns** the SimpleBackups instance.
110
+ * `off(String: event, Function: listener)`: Removes a listener for a SimpleBackups event.
111
+ * **Returns** the SimpleBackups instance.
112
+
113
+ #### SimpleBackups Events
114
+ * [`create`]: The `create` event is emitted whenever a backup is successfully created.
115
+ * **Example:** `backups.on('create', (backup) => { /* Your Code */ });`
116
+ * [`delete`]: The `delete` event is emitted whenever a backup is successfully deleted.
117
+ * **Example:** `backups.on('delete', (backup) => { /* Your Code */ });`
118
+ * [`error`]: The `error` event is emitted whenever an adapter or validation error occurs.
119
+ * **Example:** `backups.on('error', (error) => { /* Your Code */ });`
120
+ * **Note** this is a standard Node.js `error` event and should be handled by the user.
121
+
122
+ ### BackupAdapters Properties
123
+ | Property | Type | Description |
124
+ | :-------- | :------- | :------------------------- |
125
+ | `list` | `function(): Backup[] \| Promise<Backup[]>` | Lists all backups from the upstream storage mechanism. |
126
+ | `create` | `function(): Backup \| Promise<Backup>` | Creates a new backup in the upstream storage mechanism. |
127
+ | `delete` | `function(Backup): Boolean \| Promise<Boolean>` | Attempts to delete the specified backup from the upstream storage mechanism. |
128
+
129
+ ### BackupWindow Properties
130
+ | Property | Type | Description |
131
+ | :-------- | :------- | :------------------------- |
132
+ | `interval_ms` | `Number` | The interval at which this window's backups should be created in milliseconds. |
133
+ | `limit` | `Number` | The maximum number of backups to keep in this window. |
134
+
135
+ ### Backup Properties
136
+ | Property | Type | Description |
137
+ | :-------- | :------- | :------------------------- |
138
+ | `id` | `String` | The unique identifier of the backup. |
139
+ | `timestamp` | `Number` | The timestamp at which the backup was created in milliseconds. |
140
+
141
+ ## Retention Behavior
142
+ SimpleBackups creates a new backup when no existing backup exists within the smallest configured backup window interval. During each tick, every backup window independently selects the best backup for each time slot and stale backups which are not selected by any window are deleted.
143
+
144
+ For example, the following windows will attempt to keep up to 24 hourly backups and up to 30 daily backups.
145
+
146
+ ```javascript
147
+ windows: [
148
+ { interval_ms: 1000 * 60 * 60, limit: 24 },
149
+ { interval_ms: 1000 * 60 * 60 * 24, limit: 30 }
150
+ ]
151
+ ```
152
+
153
+ * **Note** backups can satisfy multiple windows at the same time.
154
+ * **Note** SimpleBackups can only keep backups that exist and cannot create missed backups for time periods where the process was offline.
155
+ * **Note** backups are selected by timestamp and not by filename or storage order.
156
+
157
+ ## License
158
+ [MIT](./LICENSE)
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "simple-backups",
3
+ "version": "1.0.0",
4
+ "description": "Simple yet unopinionated zero-dependency package to easily setup automatic backups for any data source that persists to any storage mechanism.",
5
+ "type": "module",
6
+ "main": "./src/index.js",
7
+ "exports": {
8
+ ".": "./src/index.js"
9
+ },
10
+ "files": [
11
+ "src",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "scripts": {
16
+ "test": "node --test"
17
+ },
18
+ "keywords": [
19
+ "backup",
20
+ "backups",
21
+ "storage",
22
+ "scheduler",
23
+ "nodejs",
24
+ "javascript",
25
+ "s3-bucket",
26
+ "unopinionated",
27
+ "zero-dependency"
28
+ ],
29
+ "license": "MIT",
30
+ "engines": {
31
+ "node": ">=20"
32
+ }
33
+ }
@@ -0,0 +1,370 @@
1
+ import EventEmitter from 'node:events';
2
+
3
+ /**
4
+ * @typedef {Object} Backup
5
+ * @property {string} id The unique identifier of the backup.
6
+ * @property {number} timestamp The timestamp at which the backup was created (in milliseconds).
7
+ */
8
+
9
+ /**
10
+ * @typedef {Object} BackupWindow
11
+ * @property {number} interval_ms The interval at which this window's backups should be created (in milliseconds).
12
+ * @property {number} limit The maximum number of backups to keep in this window.
13
+ */
14
+
15
+ /**
16
+ * @typedef {Object} BackupAdapters
17
+ * @property {function():Backup[]|Promise<Backup[]>} list This method **must** return an array of all backups from the upstream storage mechanism.
18
+ * @property {function():Backup|Promise<Backup>} create This method **must** create a new backup and persist it to the upstream storage mechanism.
19
+ * @property {function(Backup):boolean|Promise<boolean>} delete This method **must** attempt to delete the specified backups from the upstream storage mechanism and return a boolean indicating whether the operation was successful.
20
+ */
21
+
22
+ /**
23
+ * @typedef {Object} SimpleBackupsOptions
24
+ * @property {BackupAdapters} adapters The backup adapters to use.
25
+ * @property {BackupWindow[]} windows The backup windows to use.
26
+ */
27
+
28
+ /**
29
+ * @typedef {'create'|'delete'} SimpleBackupsBackupEvent
30
+ */
31
+
32
+ /**
33
+ * @typedef {'error'} SimpleBackupsErrorEvent
34
+ */
35
+
36
+ /**
37
+ * @typedef {(backup: Backup) => void} SimpleBackupsBackupListener
38
+ */
39
+
40
+ /**
41
+ * @typedef {(error: Error) => void} SimpleBackupsErrorListener
42
+ */
43
+
44
+ export class SimpleBackups extends EventEmitter {
45
+ #destroyed = false;
46
+ #initialized = false;
47
+
48
+ /**
49
+ * The options for the SimpleBackups instance.
50
+ * @type {SimpleBackupsOptions}
51
+ */
52
+ #options;
53
+
54
+ /**
55
+ * The intervals for the backup windows.
56
+ * @type {Map<string, ReturnType<typeof setInterval>>}
57
+ */
58
+ #intervals = new Map();
59
+
60
+ /**
61
+ * The most recently listed backups from the upstream storage mechanism.
62
+ * @type {Backup[]}
63
+ */
64
+ #backups = [];
65
+
66
+ /**
67
+ * The smallest backup window interval.
68
+ * @type {number}
69
+ */
70
+ #smallest_interval_ms = Infinity;
71
+
72
+ /**
73
+ * Constructs a new SimpleBackups instance.
74
+ * @param {SimpleBackupsOptions} options
75
+ */
76
+ constructor(options) {
77
+ super();
78
+ this.#options = options;
79
+
80
+ // Enforce valid options
81
+ if (!Array.isArray(this.#options?.windows) || !this.#options?.windows?.length)
82
+ throw new Error('You must specify at least one backup window.');
83
+ if (typeof this.#options?.adapters?.list !== 'function')
84
+ throw new Error('You must specify a backup adapters list method.');
85
+ if (typeof this.#options?.adapters?.create !== 'function')
86
+ throw new Error('You must specify a backup adapters create method.');
87
+ if (typeof this.#options?.adapters?.delete !== 'function')
88
+ throw new Error('You must specify a backup adapters delete method.');
89
+
90
+ // Find the most frequent backup interval
91
+ for (const window of this.#options.windows) {
92
+ if (
93
+ Number.isNaN(window?.limit) ||
94
+ !Number.isFinite(window?.limit) ||
95
+ !Number.isInteger(window?.limit) ||
96
+ window?.limit <= 0
97
+ )
98
+ throw new Error('Backup windows must have a positive finite limit.');
99
+
100
+ if (
101
+ Number.isNaN(window?.interval_ms) ||
102
+ !Number.isFinite(window?.interval_ms) ||
103
+ !Number.isInteger(window?.interval_ms) ||
104
+ window?.interval_ms <= 0
105
+ )
106
+ throw new Error('Backup windows must have a positive finite interval_ms.');
107
+
108
+ if (window?.interval_ms <= this.#smallest_interval_ms) this.#smallest_interval_ms = window?.interval_ms;
109
+ }
110
+
111
+ // Create a tick interval with the smallest interval (aka. most precision)
112
+ this.#intervals.set(
113
+ 'tick',
114
+ setInterval(() => this.__tick(), this.#smallest_interval_ms),
115
+ );
116
+ setTimeout(() => this.__tick(), 0); // Schedule an immediate tick
117
+ }
118
+
119
+ #tick_in_flight = false;
120
+ /**
121
+ * Ticks the SimpleBackups instance.
122
+ */
123
+ async __tick() {
124
+ if (this.#destroyed || this.#tick_in_flight) return;
125
+ this.#tick_in_flight = true;
126
+ try {
127
+ // If we have not initialized yet, then list all available backups from the upstream storage mechanism and cache them in memory
128
+ if (!this.#initialized) {
129
+ try {
130
+ this.#backups = await this.#options.adapters.list();
131
+ if (!Array.isArray(this.#backups))
132
+ throw new Error('Backup adapters list method must return an array of backups.');
133
+
134
+ // Validate the backups
135
+ const seen_ids = new Set();
136
+ for (const backup of this.#backups) {
137
+ if (!backup?.id || typeof backup?.id !== 'string' || seen_ids.has(backup?.id))
138
+ throw new Error(
139
+ 'Backup adapters list method must return an array of backups with unique IDs.',
140
+ );
141
+ seen_ids.add(backup.id);
142
+
143
+ if (
144
+ typeof backup?.timestamp !== 'number' ||
145
+ !Number.isFinite(backup?.timestamp) ||
146
+ backup?.timestamp < 0
147
+ )
148
+ throw new Error(
149
+ 'Backup adapters list method must return an array of backups with finite timestamps.',
150
+ );
151
+ }
152
+
153
+ this.#backups.sort((a, b) => a.timestamp - b.timestamp); // Sort by ascending timestamp (oldest to newest)
154
+ this.#initialized = true;
155
+ if (this.#destroyed) return; // If the instance has been destroyed, then we should stop ticking
156
+ } catch (error) {
157
+ this.emit('error', error); // Emit the error which may have occurred when listing the backups
158
+ return;
159
+ }
160
+ }
161
+
162
+ let now = Date.now();
163
+ let should_create_backup = true; // Scan all existing backups to determine if we have a backup within the smallest interval slot to determine whether we need to create a new backup
164
+ for (let i = 0; i < this.#backups.length; i++) {
165
+ const backup = this.#backups[i];
166
+ const backup_age = now - backup.timestamp; // Measure the age of the backup (in milliseconds)
167
+ if (backup_age >= 0 && backup_age < this.#smallest_interval_ms) {
168
+ should_create_backup = false; // We already have a backup within the smallest interval, so we don't need to create another one
169
+ break;
170
+ }
171
+ }
172
+ if (should_create_backup) {
173
+ try {
174
+ const backup = await this.#options.adapters.create(); // Create a new backup
175
+
176
+ let already_exists = false; // Determine if a backup with the same ID already exists
177
+ for (const _backup of this.#backups) {
178
+ if (_backup.id === backup?.id) {
179
+ already_exists = true;
180
+ break;
181
+ }
182
+ }
183
+ if (!backup?.id || typeof backup?.id !== 'string' || already_exists)
184
+ throw new Error('Backup adapters create method must return a backup with a unique ID.');
185
+ if (
186
+ typeof backup.timestamp !== 'number' ||
187
+ !Number.isFinite(backup.timestamp) ||
188
+ backup.timestamp < 0
189
+ )
190
+ throw new Error('Backup adapters create method must return a backup with a finite timestamp.');
191
+
192
+ this.#backups.push(backup); // Add the new backup to the list of backups in cache
193
+ this.#backups.sort((a, b) => a.timestamp - b.timestamp); // Sort by ascending timestamp (oldest to newest)
194
+ now = Date.now(); // Update the current time since we did a potentially asynchronous operation
195
+ this.emit('create', backup); // Emit this backup as being created
196
+ if (this.#destroyed) return; // If the instance has been destroyed, then we should stop ticking
197
+ } catch (error) {
198
+ this.emit('error', error); // Emit the error which may have occurred when creating the backup
199
+ return;
200
+ }
201
+ }
202
+
203
+ const keep_backups_ids = new Set(); // Determine which backup IDs to keep
204
+ for (let i = 0; i < this.#options.windows.length; i++) {
205
+ const best_by_slot = new Map();
206
+ const window = this.#options.windows[i];
207
+ for (let j = 0; j < this.#backups.length; j++) {
208
+ const backup = this.#backups[j];
209
+ const backup_age_ms = now - backup.timestamp;
210
+ if (backup_age_ms < 0) continue; // Ignore future backups (this should not even be possible?)
211
+
212
+ const slot = Math.floor(backup_age_ms / window.interval_ms); // Calculate the window slot for the backup
213
+ if (slot >= window.limit) continue; // Ignore backups outside this window
214
+
215
+ const target_timestamp = now - slot * window.interval_ms; // Calculate the target timestamp for this backup's ideal slot
216
+ const distance_to_target_ms = Math.abs(backup.timestamp - target_timestamp); // Calculate the distance between the backup's timestamp and the target timestamp
217
+
218
+ const candidate = best_by_slot.get(slot); // Measure and track the best or lowest distance to target timestamp for a given slot
219
+ if (!candidate || distance_to_target_ms < candidate.distance_to_target_ms) {
220
+ best_by_slot.set(slot, { backup, distance_to_target_ms });
221
+ }
222
+ }
223
+
224
+ for (const [_, { backup }] of best_by_slot) keep_backups_ids.add(backup.id); // Add the remaining best backup slot candidates to the list of backups to keep
225
+ }
226
+
227
+ const stale_backups = []; // Determine which backups are stale and need to be deleted
228
+ const deleted_backups_ids = new Set(); // Determine which backup IDs have been deleted
229
+ for (let i = 0; i < this.#backups.length; i++) {
230
+ const backup = this.#backups[i];
231
+ if (!keep_backups_ids.has(backup.id)) stale_backups.push(backup);
232
+ }
233
+ for (const backup of stale_backups) {
234
+ try {
235
+ const deleted = await this.#options.adapters.delete(backup); // Attempt to delete the stale backup
236
+ if (deleted) {
237
+ this.emit('delete', backup); // Emit this backup as being deleted
238
+ deleted_backups_ids.add(backup.id); // If the deletion was successful, add the backup ID to the list of deleted backups
239
+ }
240
+ if (this.#destroyed) return; // If the instance has been destroyed, then we should stop ticking
241
+ } catch (error) {
242
+ this.emit('error', error); // Emit the error which may have occurred when deleting the backup
243
+ }
244
+ }
245
+ if (deleted_backups_ids.size > 0) {
246
+ const remaining_backups = [];
247
+ for (let i = 0; i < this.#backups.length; i++) {
248
+ const backup = this.#backups[i];
249
+ if (!deleted_backups_ids.has(backup.id)) remaining_backups.push(backup);
250
+ }
251
+ this.#backups = remaining_backups; // Rebuild and overwrite the backups array with the remaining backups
252
+ }
253
+ } catch (error) {
254
+ this.emit('error', error);
255
+ } finally {
256
+ this.#tick_in_flight = false;
257
+ }
258
+ }
259
+
260
+ /**
261
+ * @overload
262
+ * @param {SimpleBackupsBackupEvent} event
263
+ * @param {SimpleBackupsBackupListener} listener
264
+ * @returns {this}
265
+ */
266
+ /**
267
+ * @overload
268
+ * @param {SimpleBackupsErrorEvent} event
269
+ * @param {SimpleBackupsErrorListener} listener
270
+ * @returns {this}
271
+ */
272
+ /**
273
+ * @param {SimpleBackupsBackupEvent|SimpleBackupsErrorEvent} event
274
+ * @param {SimpleBackupsBackupListener|SimpleBackupsErrorListener} listener
275
+ * @returns {this}
276
+ */
277
+ on(event, listener) {
278
+ return super.on(event, listener);
279
+ }
280
+
281
+ /**
282
+ * @overload
283
+ * @param {SimpleBackupsBackupEvent} event
284
+ * @param {SimpleBackupsBackupListener} listener
285
+ * @returns {this}
286
+ */
287
+ /**
288
+ * @overload
289
+ * @param {SimpleBackupsErrorEvent} event
290
+ * @param {SimpleBackupsErrorListener} listener
291
+ * @returns {this}
292
+ */
293
+ /**
294
+ * @param {SimpleBackupsBackupEvent|SimpleBackupsErrorEvent} event
295
+ * @param {SimpleBackupsBackupListener|SimpleBackupsErrorListener} listener
296
+ * @returns {this}
297
+ */
298
+ once(event, listener) {
299
+ return super.once(event, listener);
300
+ }
301
+
302
+ /**
303
+ * @overload
304
+ * @param {SimpleBackupsBackupEvent} event
305
+ * @param {SimpleBackupsBackupListener} listener
306
+ * @returns {this}
307
+ */
308
+ /**
309
+ * @overload
310
+ * @param {SimpleBackupsErrorEvent} event
311
+ * @param {SimpleBackupsErrorListener} listener
312
+ * @returns {this}
313
+ */
314
+ /**
315
+ * @param {SimpleBackupsBackupEvent|SimpleBackupsErrorEvent} event
316
+ * @param {SimpleBackupsBackupListener|SimpleBackupsErrorListener} listener
317
+ * @returns {this}
318
+ */
319
+ off(event, listener) {
320
+ return super.off(event, listener);
321
+ }
322
+
323
+ /**
324
+ * @overload
325
+ * @param {SimpleBackupsBackupEvent} event
326
+ * @param {Backup} backup
327
+ * @returns {boolean}
328
+ */
329
+ /**
330
+ * @overload
331
+ * @param {SimpleBackupsErrorEvent} event
332
+ * @param {Error} error
333
+ * @returns {boolean}
334
+ */
335
+ /**
336
+ * @param {SimpleBackupsBackupEvent|SimpleBackupsErrorEvent} event
337
+ * @param {Backup|Error} value
338
+ * @returns {boolean}
339
+ */
340
+ emit(event, value) {
341
+ return super.emit(event, value);
342
+ }
343
+
344
+ /**
345
+ * Destroys the SimpleBackups instance and stops all intervals.
346
+ */
347
+ destroy() {
348
+ if (this.#destroyed) return;
349
+ this.#destroyed = true; // Only destroy once
350
+
351
+ for (const [_, interval] of this.#intervals) clearInterval(interval);
352
+ this.#intervals.clear(); // Clear all intervals
353
+
354
+ super.removeAllListeners(); // Remove all event listeners (which effectively destroys the EventEmitter)
355
+ }
356
+
357
+ /**
358
+ * Returns the current list of backups (if in cache).
359
+ */
360
+ get backups() {
361
+ return [...this.#backups]; // Return a copy of the backups array
362
+ }
363
+
364
+ /**
365
+ * Whether the SimpleBackups instance has been destroyed.
366
+ */
367
+ get destroyed() {
368
+ return this.#destroyed;
369
+ }
370
+ }
package/src/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import { SimpleBackups } from './SimpleBackups';
2
+
3
+ export { SimpleBackups };