simple-backups 1.0.1 → 1.1.1
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 +21 -21
- package/README.md +31 -4
- package/package.json +41 -33
- package/src/SimpleBackups.js +381 -370
- package/src/index.d.ts +79 -0
- package/src/index.js +3 -3
package/LICENSE
CHANGED
|
@@ -1,21 +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.
|
|
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
CHANGED
|
@@ -16,6 +16,7 @@ This package aims to simplify the task of automatically creating and pruning bac
|
|
|
16
16
|
## Features
|
|
17
17
|
- Simple-to-use API
|
|
18
18
|
- JSDoc Type Support
|
|
19
|
+
- TypeScript Type Definitions
|
|
19
20
|
- Automatic Backup Creation
|
|
20
21
|
- Multi-Window Backup Retention
|
|
21
22
|
- CPU & Memory Efficient
|
|
@@ -82,6 +83,19 @@ backups.on('error', (error) => {
|
|
|
82
83
|
});
|
|
83
84
|
```
|
|
84
85
|
|
|
86
|
+
## TypeScript
|
|
87
|
+
SimpleBackups includes first-party TypeScript declarations. No separate `@types` package is required.
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import {
|
|
91
|
+
SimpleBackups,
|
|
92
|
+
type Backup,
|
|
93
|
+
type BackupAdapters,
|
|
94
|
+
type BackupWindow,
|
|
95
|
+
type SimpleBackupsOptions
|
|
96
|
+
} from 'simple-backups';
|
|
97
|
+
```
|
|
98
|
+
|
|
85
99
|
## SimpleBackups
|
|
86
100
|
Below is a breakdown of the `SimpleBackups` class.
|
|
87
101
|
|
|
@@ -89,9 +103,9 @@ Below is a breakdown of the `SimpleBackups` class.
|
|
|
89
103
|
* `new SimpleBackups(Object: options)`: Creates a new SimpleBackups instance with the provided `options`.
|
|
90
104
|
* `options` [`Object`]: Constructor options for this instance.
|
|
91
105
|
* `adapters` [`BackupAdapters`]: Adapter methods used to list, create and delete backups from the upstream storage mechanism.
|
|
92
|
-
* `windows` [`BackupWindow[]`]: Retention windows used to determine
|
|
106
|
+
* `windows` [`BackupWindow[]`]: Retention windows used to determine the backup creation cadence and which backups should be kept.
|
|
93
107
|
* **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.
|
|
108
|
+
* **Note!** the smallest `interval_ms` from the provided windows is used as the backup creation cadence and internal tick interval. Larger windows retain backups created at that cadence instead of creating additional backups.
|
|
95
109
|
* **Note!** all window `interval_ms` and `limit` values must be positive finite integers.
|
|
96
110
|
|
|
97
111
|
#### SimpleBackups Properties
|
|
@@ -129,7 +143,7 @@ Below is a breakdown of the `SimpleBackups` class.
|
|
|
129
143
|
### BackupWindow Properties
|
|
130
144
|
| Property | Type | Description |
|
|
131
145
|
| :-------- | :------- | :------------------------- |
|
|
132
|
-
| `interval_ms` | `Number` | The
|
|
146
|
+
| `interval_ms` | `Number` | The width of each retention slot in this window in milliseconds. |
|
|
133
147
|
| `limit` | `Number` | The maximum number of backups to keep in this window. |
|
|
134
148
|
|
|
135
149
|
### Backup Properties
|
|
@@ -139,7 +153,11 @@ Below is a breakdown of the `SimpleBackups` class.
|
|
|
139
153
|
| `timestamp` | `Number` | The timestamp at which the backup was created in milliseconds. |
|
|
140
154
|
|
|
141
155
|
## 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
|
|
156
|
+
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 one backup for each populated retention slot and stale backups which are not selected by any window are deleted.
|
|
157
|
+
|
|
158
|
+
Retention slots are fixed and derived only from each backup's timestamp using `Math.floor(timestamp / interval_ms)`. The earliest available backup in a slot is retained because it is closest to the start of that slot. A backup therefore remains in the same slot as time passes, allowing an hourly backup selected by a daily window to remain available for later daily slots instead of being replaced and deleted every hour.
|
|
159
|
+
|
|
160
|
+
Slots are anchored to the Unix epoch. As a result, `day` in the example represents consecutive fixed 24-hour periods rather than local calendar days, and is not affected by time zones or daylight saving time.
|
|
143
161
|
|
|
144
162
|
For example, the following windows will attempt to keep up to 24 hourly backups and up to 30 daily backups.
|
|
145
163
|
|
|
@@ -150,9 +168,18 @@ windows: [
|
|
|
150
168
|
]
|
|
151
169
|
```
|
|
152
170
|
|
|
171
|
+
With uninterrupted hourly backups, this configuration converges to 24 populated hourly slots and 30 populated daily slots (the current slot plus the previous 29 slots). Because one backup can satisfy both windows at the same time, this normally represents 53 unique stored backups rather than 54.
|
|
172
|
+
|
|
153
173
|
* **Note** backups can satisfy multiple windows at the same time.
|
|
154
174
|
* **Note** SimpleBackups can only keep backups that exist and cannot create missed backups for time periods where the process was offline.
|
|
155
175
|
* **Note** backups are selected by timestamp and not by filename or storage order.
|
|
156
176
|
|
|
177
|
+
## Testing
|
|
178
|
+
The test suite uses the built-in Node.js test runner and does not require any additional dependencies.
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
npm test
|
|
182
|
+
```
|
|
183
|
+
|
|
157
184
|
## License
|
|
158
185
|
[MIT](./LICENSE)
|
package/package.json
CHANGED
|
@@ -1,33 +1,41 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "simple-backups",
|
|
3
|
-
"version": "1.
|
|
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
|
-
"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "simple-backups",
|
|
3
|
+
"version": "1.1.1",
|
|
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
|
+
"types": "./src/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./src/index.d.ts",
|
|
11
|
+
"import": "./src/index.js",
|
|
12
|
+
"default": "./src/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"src",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"test": "node --test && tsc --project tsconfig.json"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"backup",
|
|
25
|
+
"backups",
|
|
26
|
+
"storage",
|
|
27
|
+
"scheduler",
|
|
28
|
+
"nodejs",
|
|
29
|
+
"javascript",
|
|
30
|
+
"s3-bucket",
|
|
31
|
+
"unopinionated",
|
|
32
|
+
"zero-dependency"
|
|
33
|
+
],
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=20"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"typescript": "7.0.2"
|
|
40
|
+
}
|
|
41
|
+
}
|
package/src/SimpleBackups.js
CHANGED
|
@@ -1,370 +1,381 @@
|
|
|
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
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
* @param {SimpleBackupsBackupEvent
|
|
274
|
-
* @param {SimpleBackupsBackupListener
|
|
275
|
-
* @returns {this}
|
|
276
|
-
*/
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
* @param {
|
|
285
|
-
* @
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
* @param {SimpleBackupsBackupEvent
|
|
295
|
-
* @param {SimpleBackupsBackupListener
|
|
296
|
-
* @returns {this}
|
|
297
|
-
*/
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
* @param {
|
|
306
|
-
* @
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
* @param {SimpleBackupsBackupEvent
|
|
316
|
-
* @param {SimpleBackupsBackupListener
|
|
317
|
-
* @returns {this}
|
|
318
|
-
*/
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
* @param {
|
|
327
|
-
* @
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
* @param {SimpleBackupsBackupEvent
|
|
337
|
-
* @param {Backup
|
|
338
|
-
* @returns {boolean}
|
|
339
|
-
*/
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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 width of each retention slot in this window (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
|
+
/**
|
|
122
|
+
* Determines which backup IDs should be kept by the configured backup windows.
|
|
123
|
+
* @param {number} now The current timestamp (in milliseconds).
|
|
124
|
+
* @returns {Set<string>}
|
|
125
|
+
*/
|
|
126
|
+
_get_keep_backups_ids(now) {
|
|
127
|
+
/** @type {Set<string>} */
|
|
128
|
+
const keep_backups_ids = new Set();
|
|
129
|
+
for (let i = 0; i < this.#options.windows.length; i++) {
|
|
130
|
+
/** @type {Map<number, Backup>} */
|
|
131
|
+
const best_by_slot = new Map();
|
|
132
|
+
const window = this.#options.windows[i];
|
|
133
|
+
const current_slot = Math.floor(now / window.interval_ms); // Anchor slots to timestamps instead of their ever-changing age
|
|
134
|
+
const oldest_slot = current_slot - window.limit + 1;
|
|
135
|
+
|
|
136
|
+
for (let j = 0; j < this.#backups.length; j++) {
|
|
137
|
+
const backup = this.#backups[j];
|
|
138
|
+
if (backup.timestamp > now) continue; // Ignore future backups (this should not even be possible?)
|
|
139
|
+
|
|
140
|
+
const slot = Math.floor(backup.timestamp / window.interval_ms); // A backup always belongs to the same slot as it ages
|
|
141
|
+
if (slot < oldest_slot || slot > current_slot) continue; // Ignore backups outside this window
|
|
142
|
+
|
|
143
|
+
// Backups are sorted from oldest to newest, so the first backup is the closest one to the start of this slot
|
|
144
|
+
if (!best_by_slot.has(slot)) best_by_slot.set(slot, backup);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
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
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return keep_backups_ids;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Ticks the SimpleBackups instance.
|
|
155
|
+
*/
|
|
156
|
+
async __tick() {
|
|
157
|
+
if (this.#destroyed || this.#tick_in_flight) return;
|
|
158
|
+
this.#tick_in_flight = true;
|
|
159
|
+
try {
|
|
160
|
+
// If we have not initialized yet, then list all available backups from the upstream storage mechanism and cache them in memory
|
|
161
|
+
if (!this.#initialized) {
|
|
162
|
+
try {
|
|
163
|
+
this.#backups = await this.#options.adapters.list();
|
|
164
|
+
if (!Array.isArray(this.#backups))
|
|
165
|
+
throw new Error('Backup adapters list method must return an array of backups.');
|
|
166
|
+
|
|
167
|
+
// Validate the backups
|
|
168
|
+
const seen_ids = new Set();
|
|
169
|
+
for (const backup of this.#backups) {
|
|
170
|
+
if (!backup?.id || typeof backup?.id !== 'string' || seen_ids.has(backup?.id))
|
|
171
|
+
throw new Error(
|
|
172
|
+
'Backup adapters list method must return an array of backups with unique IDs.',
|
|
173
|
+
);
|
|
174
|
+
seen_ids.add(backup.id);
|
|
175
|
+
|
|
176
|
+
if (
|
|
177
|
+
typeof backup?.timestamp !== 'number' ||
|
|
178
|
+
!Number.isFinite(backup?.timestamp) ||
|
|
179
|
+
backup?.timestamp < 0
|
|
180
|
+
)
|
|
181
|
+
throw new Error(
|
|
182
|
+
'Backup adapters list method must return an array of backups with finite timestamps.',
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
this.#backups.sort((a, b) => a.timestamp - b.timestamp); // Sort by ascending timestamp (oldest to newest)
|
|
187
|
+
this.#initialized = true;
|
|
188
|
+
if (this.#destroyed) return; // If the instance has been destroyed, then we should stop ticking
|
|
189
|
+
} catch (error) {
|
|
190
|
+
this.emit('error', error); // Emit the error which may have occurred when listing the backups
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let now = Date.now();
|
|
196
|
+
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
|
|
197
|
+
for (let i = 0; i < this.#backups.length; i++) {
|
|
198
|
+
const backup = this.#backups[i];
|
|
199
|
+
const backup_age = now - backup.timestamp; // Measure the age of the backup (in milliseconds)
|
|
200
|
+
if (backup_age >= 0 && backup_age < this.#smallest_interval_ms) {
|
|
201
|
+
should_create_backup = false; // We already have a backup within the smallest interval, so we don't need to create another one
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (should_create_backup) {
|
|
206
|
+
try {
|
|
207
|
+
const backup = await this.#options.adapters.create(); // Create a new backup
|
|
208
|
+
|
|
209
|
+
let already_exists = false; // Determine if a backup with the same ID already exists
|
|
210
|
+
for (const _backup of this.#backups) {
|
|
211
|
+
if (_backup.id === backup?.id) {
|
|
212
|
+
already_exists = true;
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (!backup?.id || typeof backup?.id !== 'string' || already_exists)
|
|
217
|
+
throw new Error('Backup adapters create method must return a backup with a unique ID.');
|
|
218
|
+
if (
|
|
219
|
+
typeof backup.timestamp !== 'number' ||
|
|
220
|
+
!Number.isFinite(backup.timestamp) ||
|
|
221
|
+
backup.timestamp < 0
|
|
222
|
+
)
|
|
223
|
+
throw new Error('Backup adapters create method must return a backup with a finite timestamp.');
|
|
224
|
+
|
|
225
|
+
this.#backups.push(backup); // Add the new backup to the list of backups in cache
|
|
226
|
+
this.#backups.sort((a, b) => a.timestamp - b.timestamp); // Sort by ascending timestamp (oldest to newest)
|
|
227
|
+
now = Date.now(); // Update the current time since we did a potentially asynchronous operation
|
|
228
|
+
this.emit('create', backup); // Emit this backup as being created
|
|
229
|
+
if (this.#destroyed) return; // If the instance has been destroyed, then we should stop ticking
|
|
230
|
+
} catch (error) {
|
|
231
|
+
this.emit('error', error); // Emit the error which may have occurred when creating the backup
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const keep_backups_ids = this._get_keep_backups_ids(now); // Determine which backup IDs to keep
|
|
237
|
+
|
|
238
|
+
const stale_backups = []; // Determine which backups are stale and need to be deleted
|
|
239
|
+
const deleted_backups_ids = new Set(); // Determine which backup IDs have been deleted
|
|
240
|
+
for (let i = 0; i < this.#backups.length; i++) {
|
|
241
|
+
const backup = this.#backups[i];
|
|
242
|
+
if (!keep_backups_ids.has(backup.id)) stale_backups.push(backup);
|
|
243
|
+
}
|
|
244
|
+
for (const backup of stale_backups) {
|
|
245
|
+
try {
|
|
246
|
+
const deleted = await this.#options.adapters.delete(backup); // Attempt to delete the stale backup
|
|
247
|
+
if (deleted) {
|
|
248
|
+
this.emit('delete', backup); // Emit this backup as being deleted
|
|
249
|
+
deleted_backups_ids.add(backup.id); // If the deletion was successful, add the backup ID to the list of deleted backups
|
|
250
|
+
}
|
|
251
|
+
if (this.#destroyed) return; // If the instance has been destroyed, then we should stop ticking
|
|
252
|
+
} catch (error) {
|
|
253
|
+
this.emit('error', error); // Emit the error which may have occurred when deleting the backup
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (deleted_backups_ids.size > 0) {
|
|
257
|
+
const remaining_backups = [];
|
|
258
|
+
for (let i = 0; i < this.#backups.length; i++) {
|
|
259
|
+
const backup = this.#backups[i];
|
|
260
|
+
if (!deleted_backups_ids.has(backup.id)) remaining_backups.push(backup);
|
|
261
|
+
}
|
|
262
|
+
this.#backups = remaining_backups; // Rebuild and overwrite the backups array with the remaining backups
|
|
263
|
+
}
|
|
264
|
+
} catch (error) {
|
|
265
|
+
this.emit('error', error);
|
|
266
|
+
} finally {
|
|
267
|
+
this.#tick_in_flight = false;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* @overload
|
|
273
|
+
* @param {SimpleBackupsBackupEvent} event
|
|
274
|
+
* @param {SimpleBackupsBackupListener} listener
|
|
275
|
+
* @returns {this}
|
|
276
|
+
*/
|
|
277
|
+
/**
|
|
278
|
+
* @overload
|
|
279
|
+
* @param {SimpleBackupsErrorEvent} event
|
|
280
|
+
* @param {SimpleBackupsErrorListener} listener
|
|
281
|
+
* @returns {this}
|
|
282
|
+
*/
|
|
283
|
+
/**
|
|
284
|
+
* @param {SimpleBackupsBackupEvent|SimpleBackupsErrorEvent} event
|
|
285
|
+
* @param {SimpleBackupsBackupListener|SimpleBackupsErrorListener} listener
|
|
286
|
+
* @returns {this}
|
|
287
|
+
*/
|
|
288
|
+
on(event, listener) {
|
|
289
|
+
return super.on(event, listener);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* @overload
|
|
294
|
+
* @param {SimpleBackupsBackupEvent} event
|
|
295
|
+
* @param {SimpleBackupsBackupListener} listener
|
|
296
|
+
* @returns {this}
|
|
297
|
+
*/
|
|
298
|
+
/**
|
|
299
|
+
* @overload
|
|
300
|
+
* @param {SimpleBackupsErrorEvent} event
|
|
301
|
+
* @param {SimpleBackupsErrorListener} listener
|
|
302
|
+
* @returns {this}
|
|
303
|
+
*/
|
|
304
|
+
/**
|
|
305
|
+
* @param {SimpleBackupsBackupEvent|SimpleBackupsErrorEvent} event
|
|
306
|
+
* @param {SimpleBackupsBackupListener|SimpleBackupsErrorListener} listener
|
|
307
|
+
* @returns {this}
|
|
308
|
+
*/
|
|
309
|
+
once(event, listener) {
|
|
310
|
+
return super.once(event, listener);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* @overload
|
|
315
|
+
* @param {SimpleBackupsBackupEvent} event
|
|
316
|
+
* @param {SimpleBackupsBackupListener} listener
|
|
317
|
+
* @returns {this}
|
|
318
|
+
*/
|
|
319
|
+
/**
|
|
320
|
+
* @overload
|
|
321
|
+
* @param {SimpleBackupsErrorEvent} event
|
|
322
|
+
* @param {SimpleBackupsErrorListener} listener
|
|
323
|
+
* @returns {this}
|
|
324
|
+
*/
|
|
325
|
+
/**
|
|
326
|
+
* @param {SimpleBackupsBackupEvent|SimpleBackupsErrorEvent} event
|
|
327
|
+
* @param {SimpleBackupsBackupListener|SimpleBackupsErrorListener} listener
|
|
328
|
+
* @returns {this}
|
|
329
|
+
*/
|
|
330
|
+
off(event, listener) {
|
|
331
|
+
return super.off(event, listener);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* @overload
|
|
336
|
+
* @param {SimpleBackupsBackupEvent} event
|
|
337
|
+
* @param {Backup} backup
|
|
338
|
+
* @returns {boolean}
|
|
339
|
+
*/
|
|
340
|
+
/**
|
|
341
|
+
* @overload
|
|
342
|
+
* @param {SimpleBackupsErrorEvent} event
|
|
343
|
+
* @param {Error} error
|
|
344
|
+
* @returns {boolean}
|
|
345
|
+
*/
|
|
346
|
+
/**
|
|
347
|
+
* @param {SimpleBackupsBackupEvent|SimpleBackupsErrorEvent} event
|
|
348
|
+
* @param {Backup|Error} value
|
|
349
|
+
* @returns {boolean}
|
|
350
|
+
*/
|
|
351
|
+
emit(event, value) {
|
|
352
|
+
return super.emit(event, value);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Destroys the SimpleBackups instance and stops all intervals.
|
|
357
|
+
*/
|
|
358
|
+
destroy() {
|
|
359
|
+
if (this.#destroyed) return;
|
|
360
|
+
this.#destroyed = true; // Only destroy once
|
|
361
|
+
|
|
362
|
+
for (const [_, interval] of this.#intervals) clearInterval(interval);
|
|
363
|
+
this.#intervals.clear(); // Clear all intervals
|
|
364
|
+
|
|
365
|
+
super.removeAllListeners(); // Remove all event listeners (which effectively destroys the EventEmitter)
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Returns the current list of backups (if in cache).
|
|
370
|
+
*/
|
|
371
|
+
get backups() {
|
|
372
|
+
return [...this.#backups]; // Return a copy of the backups array
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Whether the SimpleBackups instance has been destroyed.
|
|
377
|
+
*/
|
|
378
|
+
get destroyed() {
|
|
379
|
+
return this.#destroyed;
|
|
380
|
+
}
|
|
381
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A backup managed by SimpleBackups.
|
|
3
|
+
*/
|
|
4
|
+
export interface Backup {
|
|
5
|
+
/** The unique identifier of the backup. */
|
|
6
|
+
id: string;
|
|
7
|
+
|
|
8
|
+
/** The timestamp at which the backup was created (in milliseconds). */
|
|
9
|
+
timestamp: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A retention window used by SimpleBackups.
|
|
14
|
+
*/
|
|
15
|
+
export interface BackupWindow {
|
|
16
|
+
/** The width of each retention slot in this window (in milliseconds). */
|
|
17
|
+
interval_ms: number;
|
|
18
|
+
|
|
19
|
+
/** The maximum number of backups to keep in this window. */
|
|
20
|
+
limit: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Adapter methods used to persist backups in an upstream storage mechanism.
|
|
25
|
+
*/
|
|
26
|
+
export interface BackupAdapters {
|
|
27
|
+
/** Lists all backups from the upstream storage mechanism. */
|
|
28
|
+
list(): Backup[] | Promise<Backup[]>;
|
|
29
|
+
|
|
30
|
+
/** Creates and persists a new backup in the upstream storage mechanism. */
|
|
31
|
+
create(): Backup | Promise<Backup>;
|
|
32
|
+
|
|
33
|
+
/** Attempts to delete a backup and reports whether the deletion succeeded. */
|
|
34
|
+
delete(backup: Backup): boolean | Promise<boolean>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Options used to construct a SimpleBackups instance.
|
|
39
|
+
*/
|
|
40
|
+
export interface SimpleBackupsOptions {
|
|
41
|
+
/** The backup adapters to use. */
|
|
42
|
+
adapters: BackupAdapters;
|
|
43
|
+
|
|
44
|
+
/** The backup retention windows to maintain. */
|
|
45
|
+
windows: BackupWindow[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type SimpleBackupsBackupEvent = 'create' | 'delete';
|
|
49
|
+
export type SimpleBackupsErrorEvent = 'error';
|
|
50
|
+
export type SimpleBackupsBackupListener = (backup: Backup) => void;
|
|
51
|
+
export type SimpleBackupsErrorListener = (error: Error) => void;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Automatically creates and prunes backups using the configured adapters and retention windows.
|
|
55
|
+
*/
|
|
56
|
+
export class SimpleBackups {
|
|
57
|
+
constructor(options: SimpleBackupsOptions);
|
|
58
|
+
|
|
59
|
+
on(event: SimpleBackupsBackupEvent, listener: SimpleBackupsBackupListener): this;
|
|
60
|
+
on(event: SimpleBackupsErrorEvent, listener: SimpleBackupsErrorListener): this;
|
|
61
|
+
|
|
62
|
+
once(event: SimpleBackupsBackupEvent, listener: SimpleBackupsBackupListener): this;
|
|
63
|
+
once(event: SimpleBackupsErrorEvent, listener: SimpleBackupsErrorListener): this;
|
|
64
|
+
|
|
65
|
+
off(event: SimpleBackupsBackupEvent, listener: SimpleBackupsBackupListener): this;
|
|
66
|
+
off(event: SimpleBackupsErrorEvent, listener: SimpleBackupsErrorListener): this;
|
|
67
|
+
|
|
68
|
+
emit(event: SimpleBackupsBackupEvent, backup: Backup): boolean;
|
|
69
|
+
emit(event: SimpleBackupsErrorEvent, error: Error): boolean;
|
|
70
|
+
|
|
71
|
+
/** Destroys this instance, stops its internal interval and removes its event listeners. */
|
|
72
|
+
destroy(): void;
|
|
73
|
+
|
|
74
|
+
/** The current cached backups, returned as a new array. */
|
|
75
|
+
readonly backups: Backup[];
|
|
76
|
+
|
|
77
|
+
/** Whether this instance has been destroyed. */
|
|
78
|
+
readonly destroyed: boolean;
|
|
79
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { SimpleBackups } from './SimpleBackups.js';
|
|
2
|
-
|
|
3
|
-
export { SimpleBackups };
|
|
1
|
+
import { SimpleBackups } from './SimpleBackups.js';
|
|
2
|
+
|
|
3
|
+
export { SimpleBackups };
|