@jsonkit/db 2.0.1 → 3.0.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/README.md CHANGED
@@ -19,61 +19,22 @@ npm install @jsonkit/db
19
19
 
20
20
  ---
21
21
 
22
- ## Core Types
23
-
24
- ### `Identifiable`
25
-
26
- ```ts
27
- type Identifiable = { id: string }
28
- ```
29
-
30
- All multi-entry databases require entries to have a string `id`.
31
-
32
- ### `Promisable<T>`
33
-
34
- A value or a promise of a value.
35
-
36
- ```ts
37
- type Promisable<T> = T | Promise<T>
38
- ```
39
-
40
- ### `PredicateFn<T>`
41
-
42
- Used for filtering entries.
43
-
44
- ```ts
45
- type PredicateFn<T extends Identifiable> = (entry: T) => boolean
46
- ```
47
-
48
- ### `DeleteManyOutput`
49
-
50
- Returned by bulk delete operations.
51
-
52
- ```ts
53
- type DeleteManyOutput = {
54
- deletedIds: string[]
55
- ignoredIds: string[]
56
- }
57
- ```
58
-
59
- ---
60
-
61
22
  ## Multi-entry Databases
62
23
 
63
24
  Multi-entry databases manage collections of entries keyed by `id`.
64
25
 
65
26
  ### Common API (`MultiEntryDb<T>`)
66
27
 
67
- All multi-entry implementations expose the same async API:
28
+ All multi-entry implementations expose the same methods:
68
29
 
69
30
  * `create(entry)`
70
31
  * `getById(id)`
71
32
  * `getByIdOrThrow(id)`
72
- * `getWhere(predicate, max?)`
33
+ * `getWhere(predicate, pagination?)`
73
34
  * `getAll(ids?)`
74
35
  * `getAllIds()`
75
36
  * `update(id, updater)`
76
- * `delete(id)`
37
+ * `deleteById(id)`
77
38
  * `deleteByIds(ids)`
78
39
  * `deleteWhere(predicate)`
79
40
  * `exists(id)`
@@ -225,6 +186,53 @@ const allUsers = await users.getAll()
225
186
 
226
187
  ---
227
188
 
189
+ ## Core Types
190
+
191
+ ### `Identifiable`
192
+
193
+ ```ts
194
+ type Identifiable = { id: string }
195
+ ```
196
+
197
+ All multi-entry databases require entries to have a string `id`.
198
+
199
+ ### `Promisable<T>`
200
+
201
+ A value or a promise of a value.
202
+
203
+ ```ts
204
+ type Promisable<T> = T | Promise<T>
205
+ ```
206
+
207
+ ### `PredicateFn<T>`
208
+
209
+ Used for filtering entries.
210
+
211
+ ```ts
212
+ type PredicateFn<T extends Identifiable> = (entry: T) => boolean
213
+ ```
214
+
215
+ ### `PaginationInput`
216
+
217
+ Used for filtering entries.
218
+
219
+ ```ts
220
+ type PredicateFn<T extends Identifiable> = (entry: T) => boolean
221
+ ```
222
+
223
+ ### `DeleteManyOutput`
224
+
225
+ Returned by bulk delete operations.
226
+
227
+ ```ts
228
+ type DeleteManyOutput = {
229
+ deletedIds: string[]
230
+ ignoredIds: string[]
231
+ }
232
+ ```
233
+
234
+ ---
235
+
228
236
  ## Non-goals
229
237
 
230
238
  * Concurrency control
@@ -27,16 +27,112 @@ var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
27
27
  var fsSync__namespace = /*#__PURE__*/_interopNamespaceDefault(fsSync);
28
28
  var readline__namespace = /*#__PURE__*/_interopNamespaceDefault(readline);
29
29
 
30
+ class MultiEntryDb {
31
+ async getByIdOrThrow(id) {
32
+ const entry = await this.getById(id);
33
+ if (!entry)
34
+ throw new Error(`Entry with id '${id}' does not exist`);
35
+ return entry;
36
+ }
37
+ async getWhere(predicate, pagination) {
38
+ let totalMatched = 0;
39
+ const entries = [];
40
+ if (!pagination) {
41
+ for await (const entry of this.iterEntries()) {
42
+ const isMatch = predicate(entry);
43
+ if (isMatch)
44
+ entries.push(entry);
45
+ }
46
+ return entries;
47
+ }
48
+ const { take, page } = pagination;
49
+ const skip = pagination.skip ?? 0;
50
+ const startIndex = (page - 1) * take + skip;
51
+ const endIndex = startIndex + take;
52
+ for await (const entry of this.iterEntries()) {
53
+ const isMatch = predicate(entry);
54
+ if (isMatch) {
55
+ if (totalMatched >= startIndex && totalMatched < endIndex) {
56
+ entries.push(entry);
57
+ }
58
+ totalMatched++;
59
+ if (totalMatched >= endIndex)
60
+ break;
61
+ }
62
+ }
63
+ return entries;
64
+ }
65
+ getAll() {
66
+ return this.getWhere(() => true);
67
+ }
68
+ async getAllIds() {
69
+ const ids = [];
70
+ for await (const id of this.iterIds()) {
71
+ ids.push(id);
72
+ }
73
+ return ids;
74
+ }
75
+ deleteByIds(ids) {
76
+ return this.deleteWhere((entry) => ids.includes(entry.id));
77
+ }
78
+ async deleteWhere(predicate) {
79
+ const deletedIds = [];
80
+ const ignoredIds = [];
81
+ for await (const entry of this.iterEntries()) {
82
+ if (!predicate(entry))
83
+ continue;
84
+ const didDelete = await this.deleteById(entry.id);
85
+ if (didDelete) {
86
+ deletedIds.push(entry.id);
87
+ }
88
+ else {
89
+ ignoredIds.push(entry.id);
90
+ }
91
+ }
92
+ return { deletedIds, ignoredIds };
93
+ }
94
+ async exists(id) {
95
+ const entry = await this.getById(id);
96
+ return entry !== null;
97
+ }
98
+ countAll() {
99
+ return this.countWhere(() => true);
100
+ }
101
+ async countWhere(predicate, pagination) {
102
+ let totalMatched = 0;
103
+ let count = 0;
104
+ if (!pagination) {
105
+ for await (const entry of this.iterEntries()) {
106
+ const isMatch = predicate(entry);
107
+ if (isMatch)
108
+ count++;
109
+ }
110
+ return count;
111
+ }
112
+ const { take, page } = pagination;
113
+ const skip = pagination.skip ?? 0;
114
+ const startIndex = (page - 1) * take + skip;
115
+ const endIndex = startIndex + take;
116
+ for await (const entry of this.iterEntries()) {
117
+ const isMatch = predicate(entry);
118
+ if (isMatch) {
119
+ if (totalMatched >= startIndex && totalMatched < endIndex)
120
+ count++;
121
+ totalMatched++;
122
+ if (totalMatched >= endIndex)
123
+ break;
124
+ }
125
+ }
126
+ return count;
127
+ }
128
+ }
129
+
30
130
  exports.FileType = void 0;
31
131
  (function (FileType) {
32
132
  FileType["File"] = "file";
33
133
  FileType["Directory"] = "directory";
34
134
  //Symlink: 'symlink'
35
135
  })(exports.FileType || (exports.FileType = {}));
36
- class SingleEntryDb {
37
- }
38
- class MultiEntryDb {
39
- }
40
136
 
41
137
  const DEFUALT_ENCODING = 'utf-8';
42
138
  class Files {
@@ -260,53 +356,31 @@ class MultiEntryFileDb extends MultiEntryDb {
260
356
  return entry;
261
357
  }
262
358
  async getById(id) {
263
- return await this.readEntry(id);
264
- }
265
- async getByIdOrThrow(id) {
266
- const entry = await this.readEntry(id);
267
- if (!entry) {
268
- throw new Error('Entry with id ' + id + ' does not exist');
269
- }
270
- return entry;
271
- }
272
- async getWhere(predicate, max) {
273
- const entries = await this.getAll();
274
- return entries.filter(predicate).slice(0, max);
275
- }
276
- async getAll(whereIds) {
277
- const ids = whereIds === undefined ? await this.getAllIds() : whereIds;
278
- const entries = [];
279
- for (const id of ids) {
280
- const entry = await this.readEntry(id);
281
- if (entry)
282
- entries.push(entry);
283
- }
284
- return entries;
285
- }
286
- async getAllIds() {
359
+ if (!this.isIdValid(id))
360
+ throw new Error(`Invalid id: ${id}`);
287
361
  try {
288
- const entries = await this.files.list(this.dirpath);
289
- return entries.filter((name) => name.endsWith('.json')).map((name) => name.slice(0, -5)); // Remove .json extension
362
+ const filepath = this.getFilePath(id);
363
+ const text = await this.files.read(filepath);
364
+ const entry = this.parser.parse(text);
365
+ return entry;
290
366
  }
291
- catch {
292
- // Directory might not exist
293
- return [];
367
+ catch (error) {
368
+ console.error('Failed to read entry', error);
369
+ // File doesn't exist or invalid JSON
370
+ return null;
294
371
  }
295
372
  }
296
373
  async update(id, updater) {
297
- const entry = await this.readEntry(id);
298
- if (!entry) {
299
- throw new Error('Entry with id ' + id + ' does not exist');
300
- }
374
+ const entry = await this.getByIdOrThrow(id);
301
375
  const updatedEntryFields = await updater(entry);
302
376
  const updatedEntry = { ...entry, ...updatedEntryFields };
303
377
  await this.writeEntry(updatedEntry);
304
378
  if (updatedEntry.id !== id) {
305
- await this.delete(id);
379
+ await this.deleteById(id);
306
380
  }
307
381
  return updatedEntry;
308
382
  }
309
- async delete(id) {
383
+ async deleteById(id) {
310
384
  try {
311
385
  const filepath = this.getFilePath(id);
312
386
  await this.files.delete(filepath, { force: false });
@@ -320,54 +394,12 @@ class MultiEntryFileDb extends MultiEntryDb {
320
394
  async deleteByIds(ids) {
321
395
  return this.deleteWhere((entry) => ids.includes(entry.id));
322
396
  }
323
- async deleteWhere(predicate) {
324
- const deletedIds = [];
325
- const ignoredIds = [];
326
- for await (const entry of this.iterEntries()) {
327
- if (!predicate(entry))
328
- continue;
329
- const didDelete = await this.delete(entry.id);
330
- if (didDelete) {
331
- deletedIds.push(entry.id);
332
- }
333
- else {
334
- ignoredIds.push(entry.id);
335
- }
336
- }
337
- return { deletedIds, ignoredIds };
338
- }
339
397
  async destroy() {
340
398
  await this.files.delete(this.dirpath);
341
399
  }
342
- async exists(id) {
343
- const entry = await this.readEntry(id);
344
- return entry !== null;
345
- }
346
- async countAll() {
347
- const ids = await this.getAllIds();
348
- return ids.length;
349
- }
350
- async countWhere(predicate) {
351
- return (await this.getWhere(predicate)).length;
352
- }
353
400
  getFilePath(id) {
354
401
  return path__namespace.join(this.dirpath, `${id}.json`);
355
402
  }
356
- async readEntry(id) {
357
- if (!this.isIdValid(id))
358
- throw new Error(`Invalid id: ${id}`);
359
- try {
360
- const filepath = this.getFilePath(id);
361
- const text = await this.files.read(filepath);
362
- const entry = this.parser.parse(text);
363
- return entry;
364
- }
365
- catch (error) {
366
- console.error('Failed to read entry', error);
367
- // File doesn't exist or invalid JSON
368
- return null;
369
- }
370
- }
371
403
  async writeEntry(entry) {
372
404
  if (!this.isIdValid(entry.id))
373
405
  throw new Error(`Invalid id: ${entry.id}`);
@@ -384,18 +416,26 @@ class MultiEntryFileDb extends MultiEntryDb {
384
416
  return true;
385
417
  }
386
418
  async *iterEntries() {
387
- const ids = await this.getAllIds();
388
- for (const id of ids) {
389
- const entry = await this.readEntry(id);
419
+ for await (const id of this.iterIds()) {
420
+ const entry = await this.getById(id);
390
421
  if (entry)
391
422
  yield entry;
392
423
  }
393
424
  }
425
+ async *iterIds() {
426
+ const filenames = await this.files.list(this.dirpath);
427
+ for (const filename of filenames) {
428
+ if (!filename.endsWith('.json'))
429
+ continue;
430
+ const id = filename.replace(/\.json$/, '');
431
+ if (this.isIdValid(id))
432
+ yield id;
433
+ }
434
+ }
394
435
  }
395
436
 
396
- class SingleEntryFileDb extends SingleEntryDb {
437
+ class SingleEntryFileDb {
397
438
  constructor(filepath, parser = JSON) {
398
- super();
399
439
  this.filepath = filepath;
400
440
  this.parser = parser;
401
441
  this.files = new Files();
@@ -443,108 +483,54 @@ class MultiEntryMemDb extends MultiEntryDb {
443
483
  async getById(id) {
444
484
  return this.entries.get(id) ?? null;
445
485
  }
446
- async getByIdOrThrow(id) {
447
- const entry = await this.getById(id);
448
- if (!entry) {
449
- throw new Error('Entry with id ' + id + ' does not exist');
450
- }
451
- return entry;
452
- }
453
- async getWhere(predicate, max) {
454
- const entries = Array.from(this.entries.values()).filter(predicate);
455
- return max !== undefined ? entries.slice(0, max) : entries;
456
- }
457
- async getAll(whereIds) {
458
- if (whereIds === undefined) {
459
- return Array.from(this.entries.values());
460
- }
461
- const entries = [];
462
- for (const id of whereIds) {
463
- const entry = this.entries.get(id);
464
- if (entry)
465
- entries.push(entry);
466
- }
467
- return entries;
468
- }
469
- async getAllIds() {
470
- return Array.from(this.entries.keys());
471
- }
472
486
  async update(id, updater) {
473
- const entry = this.entries.get(id);
474
- if (!entry) {
475
- throw new Error('Entry with id ' + id + ' does not exist');
476
- }
487
+ const entry = await this.getByIdOrThrow(id);
477
488
  const updatedEntryFields = await updater(entry);
478
489
  const updatedEntry = { ...entry, ...updatedEntryFields };
479
490
  this.entries.set(updatedEntry.id, updatedEntry);
480
- if (updatedEntry.id !== id) {
491
+ if (updatedEntry.id !== id)
481
492
  this.entries.delete(id);
482
- }
483
493
  return updatedEntry;
484
494
  }
485
- async delete(id) {
495
+ async deleteById(id) {
486
496
  return this.entries.delete(id);
487
497
  }
488
- async deleteByIds(ids) {
489
- return this.deleteWhere((entry) => ids.includes(entry.id));
490
- }
491
- async deleteWhere(predicate) {
492
- const deletedIds = [];
493
- const ignoredIds = [];
494
- for (const [id, entry] of this.entries) {
495
- if (!predicate(entry))
496
- continue;
497
- const didDelete = await this.delete(id);
498
- if (didDelete) {
499
- deletedIds.push(id);
500
- }
501
- else {
502
- ignoredIds.push(id);
503
- }
504
- }
505
- return { deletedIds, ignoredIds };
506
- }
507
498
  async destroy() {
508
499
  this.entries.clear();
509
500
  }
510
- async exists(id) {
511
- return this.entries.has(id);
512
- }
513
- async countAll() {
514
- return this.entries.size;
515
- }
516
- async countWhere(predicate) {
517
- return Array.from(this.entries.values()).filter(predicate).length;
518
- }
519
501
  async *iterEntries() {
520
502
  for (const entry of this.entries.values()) {
521
503
  yield entry;
522
504
  }
523
505
  }
506
+ async *iterIds() {
507
+ for (const id of this.entries.keys()) {
508
+ yield id;
509
+ }
510
+ }
524
511
  }
525
512
 
526
- class SingleEntryMemDb extends SingleEntryDb {
513
+ class SingleEntryMemDb {
527
514
  constructor(initialEntry = null) {
528
- super();
529
515
  this.entry = null;
530
516
  this.entry = initialEntry;
531
517
  }
532
- async isInited() {
518
+ isInited() {
533
519
  return this.entry !== null;
534
520
  }
535
- async read() {
521
+ read() {
536
522
  if (this.entry === null)
537
523
  throw new Error('Entry not initialized');
538
524
  return this.entry;
539
525
  }
540
- async write(updaterOrEntry) {
526
+ write(updaterOrEntry) {
541
527
  let entry;
542
528
  if (typeof updaterOrEntry === 'function') {
543
529
  const updater = updaterOrEntry;
544
530
  if (this.entry === null) {
545
531
  throw new Error('Cannot update uninitialized entry. Use write(entry) to initialize first.');
546
532
  }
547
- const updatedFields = await updater(this.entry);
533
+ const updatedFields = updater(this.entry);
548
534
  entry = { ...this.entry, ...updatedFields };
549
535
  }
550
536
  else {
@@ -553,15 +539,13 @@ class SingleEntryMemDb extends SingleEntryDb {
553
539
  this.entry = entry;
554
540
  return entry;
555
541
  }
556
- async delete() {
542
+ delete() {
557
543
  this.entry = null;
558
544
  }
559
545
  }
560
546
 
561
- exports.MultiEntryDb = MultiEntryDb;
562
547
  exports.MultiEntryFileDb = MultiEntryFileDb;
563
548
  exports.MultiEntryMemDb = MultiEntryMemDb;
564
- exports.SingleEntryDb = SingleEntryDb;
565
549
  exports.SingleEntryFileDb = SingleEntryFileDb;
566
550
  exports.SingleEntryMemDb = SingleEntryMemDb;
567
551
  //# sourceMappingURL=index.cjs.map