@incoqnito.io/bajadab 1.2.2 → 1.3.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/README.md +192 -174
- package/dist/base.d.ts +13 -9
- package/dist/base.js.map +1 -1
- package/dist/basics.d.ts +27 -10
- package/dist/basics.js +378 -251
- package/dist/basics.js.map +1 -1
- package/dist/fileio.d.ts +7 -0
- package/dist/fileio.js +65 -14
- package/dist/fileio.js.map +1 -1
- package/package.json +2 -2
- package/src/main/base.ts +235 -231
- package/src/main/basics.ts +1058 -912
- package/src/main/fileio.ts +270 -207
package/src/main/basics.ts
CHANGED
|
@@ -1,913 +1,1059 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
} from "./
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
export
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
public
|
|
73
|
-
this.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
public async
|
|
122
|
-
const
|
|
123
|
-
return
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
const
|
|
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
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
private
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
private
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
private async
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
if (
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
if (
|
|
608
|
-
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
if (
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { uuidv7, } from "uuidv7";
|
|
3
|
+
import { runReentrantProtected } from "@incoqnito.io/ts-iq-core";
|
|
4
|
+
|
|
5
|
+
import type { IODatabase, IPreparedWrite } from "./fileio.js";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
EBArrayDeletionStrategy,
|
|
9
|
+
EBAutoFlushStrategy,
|
|
10
|
+
EBAutoIDStrategy,
|
|
11
|
+
EBDirtyUnloadStrategy,
|
|
12
|
+
EBDuplicateIDStrategy,
|
|
13
|
+
EBHandlerMode,
|
|
14
|
+
EBMemStorageStrategy,
|
|
15
|
+
EBModificationType,
|
|
16
|
+
IBCollection,
|
|
17
|
+
IBCollectionConfig,
|
|
18
|
+
IBDatabase,
|
|
19
|
+
IBDatabaseConfig,
|
|
20
|
+
IBFunctionRegistry,
|
|
21
|
+
IBSnapshotOptions,
|
|
22
|
+
IBTransaction,
|
|
23
|
+
IBTransactionCollection,
|
|
24
|
+
IHandlerRegistration,
|
|
25
|
+
TAddHandler,
|
|
26
|
+
TDeleteHandler,
|
|
27
|
+
TGetIDFunction,
|
|
28
|
+
TIndexKeyFunction,
|
|
29
|
+
TSetIDFunction,
|
|
30
|
+
TUpdateHandler,
|
|
31
|
+
} from "./base.js";
|
|
32
|
+
import { canonicalName } from "./utils.js";
|
|
33
|
+
|
|
34
|
+
/** Default {@link TGetIDFunction}: reads `v.id`, `undefined` if absent. */
|
|
35
|
+
export const DEFAULT_GET_ID: TGetIDFunction = (v: any) => (v !== null && typeof v === 'object' && "id" in v) ? v.id : undefined;
|
|
36
|
+
/** Default {@link TSetIDFunction}: assigns to `v.id`. */
|
|
37
|
+
export const DEFAULT_SET_ID: TSetIDFunction = async (v: any, id: string) => { v.id = id; };
|
|
38
|
+
|
|
39
|
+
function normalizeID(id: unknown): string | undefined {
|
|
40
|
+
return id === undefined || id === null || id === "" ? undefined : String(id);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface IPreparedTransactionCommit {
|
|
44
|
+
readonly write: IPreparedWrite;
|
|
45
|
+
apply(): void;
|
|
46
|
+
fire(): Promise<void>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** {@link IBCollectionConfig} used when a collection is created without an explicit one. */
|
|
50
|
+
export const DEFAULT_COLLECTION_CONFIG: IBCollectionConfig = {
|
|
51
|
+
arrayDeletionStrategy: EBArrayDeletionStrategy.IN_PLACE,
|
|
52
|
+
autoIDStrategy: EBAutoIDStrategy.AUTO_ID_UUID,
|
|
53
|
+
duplicateIDStrategy: EBDuplicateIDStrategy.OVERWRITE,
|
|
54
|
+
memStorageStrategy: EBMemStorageStrategy.MAP,
|
|
55
|
+
dirtyUnloadStrategy: EBDirtyUnloadStrategy.FLUSH,
|
|
56
|
+
autoFlushStrategy: EBAutoFlushStrategy.ALWAYS_AUTO_FLUSH,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** {@link IBDatabaseConfig} used when `getDatabase()` creates a fresh database. */
|
|
60
|
+
export const DEFAULT_DATABASE_CONFIG: IBDatabaseConfig = {
|
|
61
|
+
defaultCollectionConfig: DEFAULT_COLLECTION_CONFIG,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export class BDatabase implements IBDatabase {
|
|
65
|
+
public readonly config: IBDatabaseConfig;
|
|
66
|
+
private collections: Map<string, BCollection> = new Map();
|
|
67
|
+
readonly io: IODatabase;
|
|
68
|
+
readonly functionRegistry?: IBFunctionRegistry;
|
|
69
|
+
readonly iid = uuidv7();
|
|
70
|
+
private readonly transactionScope = new AsyncLocalStorage<ReadonlySet<string>>();
|
|
71
|
+
|
|
72
|
+
public constructor(io: IODatabase, config?: IBDatabaseConfig, functionRegistry?: IBFunctionRegistry) {
|
|
73
|
+
this.config = { ...(config ?? DEFAULT_DATABASE_CONFIG) };
|
|
74
|
+
this.io = io;
|
|
75
|
+
this.functionRegistry = functionRegistry;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public async runExclusive<R>(fn: () => Promise<R>): Promise<R> {
|
|
79
|
+
return runReentrantProtected(this.iid, fn);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public isInTransaction(collectionName: string): boolean {
|
|
83
|
+
return this.transactionScope.getStore()?.has(collectionName) ?? false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
public addCollection(collection: BCollection): void {
|
|
87
|
+
this.collections.set(collection.collectionName, collection);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
public async getCollection<T>(collectionName: string, collectionConfig?: IBCollectionConfig): Promise<IBCollection<T>> {
|
|
91
|
+
return this.runExclusive(async () => {
|
|
92
|
+
const cname = canonicalName(collectionName);
|
|
93
|
+
let ret: BCollection<T> | undefined = this.collections.get(cname);
|
|
94
|
+
if (!ret) {
|
|
95
|
+
ret = await this.createCollection(collectionName, collectionConfig);
|
|
96
|
+
}
|
|
97
|
+
return ret;
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public async createCollection<T = any>(collectionName: string, collectionConfig?: IBCollectionConfig): Promise<BCollection<T>> {
|
|
102
|
+
return this.runExclusive(async () => {
|
|
103
|
+
const cname = canonicalName(collectionName);
|
|
104
|
+
if (this.collections.has(cname)) {
|
|
105
|
+
throw new Error(`cannot create already existing collection ${collectionName}`);
|
|
106
|
+
}
|
|
107
|
+
const id = uuidv7();
|
|
108
|
+
const ret: BCollection<T> = new BCollection<T>(this, id, collectionName, { ...(collectionConfig ?? this.config.defaultCollectionConfig) });
|
|
109
|
+
await this.io.createCollection<T>(ret);
|
|
110
|
+
this.addCollection(ret);
|
|
111
|
+
return ret;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
public async hasCollection(collectionName: string): Promise<boolean> {
|
|
116
|
+
return this.runExclusive(async () => {
|
|
117
|
+
return this.collections.has(canonicalName(collectionName));
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
public async dropCollection(collectionName: string): Promise<void> {
|
|
122
|
+
const cname = canonicalName(collectionName);
|
|
123
|
+
return this.runExclusive(async () => {
|
|
124
|
+
const coll: BCollection = this.collections.get(cname) as BCollection;
|
|
125
|
+
if (coll) {
|
|
126
|
+
coll.markDropped();
|
|
127
|
+
this.collections.delete(cname);
|
|
128
|
+
return this.io.deleteCollection(cname);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
throw new Error(`no such collection ${collectionName}`);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
public async snapshot(targetDir: string, options?: IBSnapshotOptions): Promise<string> {
|
|
137
|
+
return this.runExclusive(async () => {
|
|
138
|
+
for (const collection of this.collections.values()) {
|
|
139
|
+
await collection.flush();
|
|
140
|
+
}
|
|
141
|
+
return this.io.snapshot(targetDir, options);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
public async transaction(collectionNames: string[], fn: (tx: IBTransaction) => Promise<void>): Promise<void> {
|
|
146
|
+
if (this.transactionScope.getStore() !== undefined) {
|
|
147
|
+
throw new Error("nested transactions are not supported");
|
|
148
|
+
}
|
|
149
|
+
const canonicalNames = Array.from(new Set(collectionNames.map(canonicalName)));
|
|
150
|
+
return this.runExclusive(async () => {
|
|
151
|
+
const originals = new Map<string, BCollection>();
|
|
152
|
+
const clones = new Map<string, BCollection>();
|
|
153
|
+
for (const name of canonicalNames) {
|
|
154
|
+
const original = this.collections.get(name);
|
|
155
|
+
if (!original) {
|
|
156
|
+
throw new Error(`no such collection ${name}`);
|
|
157
|
+
}
|
|
158
|
+
originals.set(name, original);
|
|
159
|
+
}
|
|
160
|
+
for (const [name, original] of originals) {
|
|
161
|
+
clones.set(name, await original.cloneForTransaction());
|
|
162
|
+
}
|
|
163
|
+
const tx: IBTransaction = {
|
|
164
|
+
getCollection: async <T>(collectionName: string): Promise<IBTransactionCollection<T>> => {
|
|
165
|
+
const clone = clones.get(canonicalName(collectionName));
|
|
166
|
+
if (!clone) {
|
|
167
|
+
throw new Error(`collection "${collectionName}" was not declared to this transaction`);
|
|
168
|
+
}
|
|
169
|
+
return clone as IBTransactionCollection<T>;
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
try {
|
|
173
|
+
await this.transactionScope.run(new Set(canonicalNames), () => fn(tx));
|
|
174
|
+
const prepared: IPreparedTransactionCommit[] = [];
|
|
175
|
+
try {
|
|
176
|
+
for (const name of canonicalNames) {
|
|
177
|
+
prepared.push(await originals.get(name)!.prepareTransactionCommit(clones.get(name)!));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
await Promise.all(prepared.map(p => p.write.discard()));
|
|
182
|
+
throw err;
|
|
183
|
+
}
|
|
184
|
+
let renamed = 0;
|
|
185
|
+
try {
|
|
186
|
+
for (const p of prepared) {
|
|
187
|
+
await p.write.commit();
|
|
188
|
+
renamed++;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
finally {
|
|
192
|
+
await Promise.all(prepared.slice(renamed).map(p => p.write.discard()));
|
|
193
|
+
const applied = prepared.slice(0, renamed);
|
|
194
|
+
applied.forEach(p => p.apply());
|
|
195
|
+
for (const p of applied) {
|
|
196
|
+
await p.fire();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
finally {
|
|
201
|
+
clones.forEach(clone => clone.markDropped());
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export class BCollection<T = any> implements IBCollection<T> {
|
|
209
|
+
readonly database: BDatabase;
|
|
210
|
+
readonly collectionConfig: IBCollectionConfig;
|
|
211
|
+
readonly collectionName: string;
|
|
212
|
+
readonly collectionID: string;
|
|
213
|
+
|
|
214
|
+
private mapData: Map<string, T> = new Map();
|
|
215
|
+
private arrayData: Array<T> = [];
|
|
216
|
+
private loaded: boolean = false;
|
|
217
|
+
private dirty: boolean = false;
|
|
218
|
+
private dropped: boolean = false;
|
|
219
|
+
private transactionClone: boolean = false;
|
|
220
|
+
|
|
221
|
+
private addHandlers: Array<{ handler: TAddHandler<T>; mode: EBHandlerMode }> = [];
|
|
222
|
+
private updateHandlers: Array<{ handler: TUpdateHandler<T>; mode: EBHandlerMode }> = [];
|
|
223
|
+
private deleteHandlers: Array<{ handler: TDeleteHandler<T>; mode: EBHandlerMode }> = [];
|
|
224
|
+
|
|
225
|
+
private indexDefinitions: Map<string, TIndexKeyFunction<T>> = new Map();
|
|
226
|
+
private indexRuntime: Map<string, Map<string, Set<T>>> = new Map();
|
|
227
|
+
private indexedKeys: Map<T, Array<[string, string[]]>> = new Map();
|
|
228
|
+
private txChanges?: Map<string, EBModificationType>;
|
|
229
|
+
|
|
230
|
+
public constructor(database: BDatabase, collectionID: string, collectionName: string, collectionConfig: IBCollectionConfig) {
|
|
231
|
+
this.database = database;
|
|
232
|
+
this.collectionID = collectionID;
|
|
233
|
+
this.collectionName = canonicalName(collectionName);
|
|
234
|
+
this.collectionConfig = collectionConfig;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private async checkLoaded(): Promise<void> {
|
|
238
|
+
if (!this.loaded) {
|
|
239
|
+
await this.reload();
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private async runExclusive<R>(fn: () => Promise<R>): Promise<R> {
|
|
244
|
+
return this.database.runExclusive(fn);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private async runProtected<R>(fn: () => Promise<R>): Promise<R> {
|
|
248
|
+
return this.runExclusive(async () => {
|
|
249
|
+
if (this.dropped) {
|
|
250
|
+
throw new Error(`collection "${this.collectionName}" has been dropped`);
|
|
251
|
+
}
|
|
252
|
+
return fn();
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private async runMutation<R>(fn: () => Promise<R>): Promise<R> {
|
|
257
|
+
return this.runProtected(async () => {
|
|
258
|
+
if (!this.transactionClone && this.database.isInTransaction(this.collectionName)) {
|
|
259
|
+
throw new Error(`collection "${this.collectionName}" is part of a running transaction; write through tx.getCollection() instead`);
|
|
260
|
+
}
|
|
261
|
+
return fn();
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* call this only within the protecting mutex
|
|
267
|
+
*/
|
|
268
|
+
public markDropped(): void {
|
|
269
|
+
this.dropped = true;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
public async get(predicate?: (v: T) => boolean): Promise<T[]> {
|
|
273
|
+
return this.runProtected(async () => {
|
|
274
|
+
await this.checkLoaded();
|
|
275
|
+
switch (this.collectionConfig.memStorageStrategy) {
|
|
276
|
+
case EBMemStorageStrategy.MAP:
|
|
277
|
+
return !predicate ?
|
|
278
|
+
this.mapData.values().toArray() :
|
|
279
|
+
this.mapData.values().filter(predicate).toArray();
|
|
280
|
+
default:
|
|
281
|
+
case EBMemStorageStrategy.ARRAY:
|
|
282
|
+
return !predicate ?
|
|
283
|
+
this.arrayData.slice() :
|
|
284
|
+
this.arrayData.filter(predicate);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
public async count(predicate?: (v: T) => boolean): Promise<number> {
|
|
290
|
+
return this.runProtected(async () => {
|
|
291
|
+
await this.checkLoaded();
|
|
292
|
+
switch (this.collectionConfig.memStorageStrategy) {
|
|
293
|
+
case EBMemStorageStrategy.MAP:
|
|
294
|
+
return !predicate ?
|
|
295
|
+
this.mapData.size :
|
|
296
|
+
this.mapData.values().filter(predicate).reduce((n) => n + 1, 0);
|
|
297
|
+
default:
|
|
298
|
+
case EBMemStorageStrategy.ARRAY:
|
|
299
|
+
return !predicate ?
|
|
300
|
+
this.arrayData.length :
|
|
301
|
+
this.arrayData.filter(predicate).length;
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
public async getByIndex(indexKey: string, key: string): Promise<T[]> {
|
|
307
|
+
return this.runProtected(async () => {
|
|
308
|
+
await this.checkLoaded();
|
|
309
|
+
const byKey = this.indexRuntime.get(indexKey);
|
|
310
|
+
if (!byKey) {
|
|
311
|
+
throw new Error(`index "${indexKey}" is not configured`);
|
|
312
|
+
}
|
|
313
|
+
return Array.from(byKey.get(key) ?? []);
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private async invokeHandler(fn: () => void | Promise<void>, modificationType: EBModificationType): Promise<void> {
|
|
318
|
+
try {
|
|
319
|
+
await fn();
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
// TODO switch logging to ILogging where necessary
|
|
323
|
+
console.error(`bajadab: collection "${this.collectionName}" ${modificationType} handler failed`, error);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
private async fireAdd(added: T[]): Promise<void> {
|
|
328
|
+
for (const v of added) {
|
|
329
|
+
for (const { handler, mode } of this.addHandlers) {
|
|
330
|
+
const call = this.invokeHandler(() => handler(v), EBModificationType.ADD);
|
|
331
|
+
if (mode === EBHandlerMode.SYNC) {
|
|
332
|
+
await call;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
private async fireUpdate(pairs: Array<[T, T]>): Promise<void> {
|
|
339
|
+
for (const [previous, updated] of pairs) {
|
|
340
|
+
for (const { handler, mode } of this.updateHandlers) {
|
|
341
|
+
const call = this.invokeHandler(() => handler(previous, updated), EBModificationType.UPDATE);
|
|
342
|
+
if (mode === EBHandlerMode.SYNC) {
|
|
343
|
+
await call;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
private async fireDelete(deleted: T[]): Promise<void> {
|
|
350
|
+
for (const v of deleted) {
|
|
351
|
+
for (const { handler, mode } of this.deleteHandlers) {
|
|
352
|
+
const call = this.invokeHandler(() => handler(v), EBModificationType.DELETE);
|
|
353
|
+
if (mode === EBHandlerMode.SYNC) {
|
|
354
|
+
await call;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
public async on(modificationType: EBModificationType.ADD, handler: TAddHandler<T>, mode?: EBHandlerMode): Promise<IHandlerRegistration>;
|
|
361
|
+
public async on(modificationType: EBModificationType.UPDATE, handler: TUpdateHandler<T>, mode?: EBHandlerMode): Promise<IHandlerRegistration>;
|
|
362
|
+
public async on(modificationType: EBModificationType.DELETE, handler: TDeleteHandler<T>, mode?: EBHandlerMode): Promise<IHandlerRegistration>;
|
|
363
|
+
public async on(modificationType: EBModificationType, handler: TAddHandler<T> | TUpdateHandler<T> | TDeleteHandler<T>, mode: EBHandlerMode = EBHandlerMode.SYNC): Promise<IHandlerRegistration> {
|
|
364
|
+
return this.runProtected(async () => {
|
|
365
|
+
switch (modificationType) {
|
|
366
|
+
case EBModificationType.ADD: {
|
|
367
|
+
const h = handler as TAddHandler<T>;
|
|
368
|
+
this.addHandlers.push({ handler: h, mode });
|
|
369
|
+
return { cancel: async () => this.runExclusive(async () => { this.addHandlers = this.addHandlers.filter(x => x.handler !== h); }) };
|
|
370
|
+
}
|
|
371
|
+
case EBModificationType.UPDATE: {
|
|
372
|
+
const h = handler as TUpdateHandler<T>;
|
|
373
|
+
this.updateHandlers.push({ handler: h, mode });
|
|
374
|
+
return { cancel: async () => this.runExclusive(async () => { this.updateHandlers = this.updateHandlers.filter(x => x.handler !== h); }) };
|
|
375
|
+
}
|
|
376
|
+
case EBModificationType.DELETE: {
|
|
377
|
+
const h = handler as TDeleteHandler<T>;
|
|
378
|
+
this.deleteHandlers.push({ handler: h, mode });
|
|
379
|
+
return { cancel: async () => this.runExclusive(async () => { this.deleteHandlers = this.deleteHandlers.filter(x => x.handler !== h); }) };
|
|
380
|
+
}
|
|
381
|
+
default:
|
|
382
|
+
throw new Error(`unknown modification type ${modificationType}`);
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
public async delete(predicate: (v: T) => boolean): Promise<number> {
|
|
388
|
+
return this.runMutation(async () => {
|
|
389
|
+
await this.checkLoaded();
|
|
390
|
+
let ret = 0;
|
|
391
|
+
const trackDeletes = this.deleteHandlers.length > 0;
|
|
392
|
+
const collectDeleted = trackDeletes || this.txChanges !== undefined;
|
|
393
|
+
const indexDeletes = this.indexDefinitions.size > 0;
|
|
394
|
+
const deleted: T[] = [];
|
|
395
|
+
switch (this.collectionConfig.memStorageStrategy) {
|
|
396
|
+
case EBMemStorageStrategy.MAP:
|
|
397
|
+
for (const [key, value] of this.mapData) {
|
|
398
|
+
if (predicate(value)) {
|
|
399
|
+
ret++;
|
|
400
|
+
if (indexDeletes) {
|
|
401
|
+
this.removeFromIndexes(value);
|
|
402
|
+
}
|
|
403
|
+
this.mapData.delete(key);
|
|
404
|
+
if (collectDeleted) {
|
|
405
|
+
deleted.push(value);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
break;
|
|
410
|
+
default:
|
|
411
|
+
case EBMemStorageStrategy.ARRAY:
|
|
412
|
+
switch (this.collectionConfig.arrayDeletionStrategy) {
|
|
413
|
+
case EBArrayDeletionStrategy.IN_PLACE:
|
|
414
|
+
let writeIndex = 0;
|
|
415
|
+
for (let readIndex = 0; readIndex < this.arrayData.length; readIndex++) {
|
|
416
|
+
const item = this.arrayData[readIndex];
|
|
417
|
+
if (!predicate(item!)) {
|
|
418
|
+
this.arrayData[writeIndex] = item!;
|
|
419
|
+
writeIndex++;
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
ret++;
|
|
423
|
+
if (indexDeletes) {
|
|
424
|
+
this.removeFromIndexes(item!);
|
|
425
|
+
}
|
|
426
|
+
if (collectDeleted) {
|
|
427
|
+
deleted.push(item!);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
this.arrayData.length = writeIndex;
|
|
432
|
+
break;
|
|
433
|
+
default:
|
|
434
|
+
case EBArrayDeletionStrategy.NEW_ARRAY:
|
|
435
|
+
if (collectDeleted || indexDeletes) {
|
|
436
|
+
const kept: T[] = [];
|
|
437
|
+
for (const v of this.arrayData) {
|
|
438
|
+
if (predicate(v)) {
|
|
439
|
+
ret++;
|
|
440
|
+
if (indexDeletes) {
|
|
441
|
+
this.removeFromIndexes(v);
|
|
442
|
+
}
|
|
443
|
+
if (collectDeleted) {
|
|
444
|
+
deleted.push(v);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
kept.push(v);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
this.arrayData = kept;
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
const ol = this.arrayData.length;
|
|
455
|
+
this.arrayData = this.arrayData.filter(v => !predicate(v));
|
|
456
|
+
ret += (ol - this.arrayData.length);
|
|
457
|
+
}
|
|
458
|
+
break;
|
|
459
|
+
}
|
|
460
|
+
break;
|
|
461
|
+
}
|
|
462
|
+
if (ret > 0) {
|
|
463
|
+
await this.doOnDirty();
|
|
464
|
+
}
|
|
465
|
+
await this.recordTxChanges(EBModificationType.DELETE, deleted);
|
|
466
|
+
if (trackDeletes && deleted.length > 0) {
|
|
467
|
+
await this.fireDelete(deleted);
|
|
468
|
+
}
|
|
469
|
+
return ret;
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
public async has(predicate: (v: T) => boolean): Promise<boolean> {
|
|
474
|
+
return this.runProtected(async () => {
|
|
475
|
+
await this.checkLoaded();
|
|
476
|
+
switch (this.collectionConfig.memStorageStrategy) {
|
|
477
|
+
case EBMemStorageStrategy.MAP:
|
|
478
|
+
return this.mapData.values().some(predicate);
|
|
479
|
+
default:
|
|
480
|
+
case EBMemStorageStrategy.ARRAY:
|
|
481
|
+
return this.arrayData.some(predicate);
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
private resolveIndexDefinitions(): void {
|
|
487
|
+
this.indexDefinitions = new Map();
|
|
488
|
+
if (!this.collectionConfig.indexes) {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
for (const [indexKey, registryKey] of Object.entries(this.collectionConfig.indexes)) {
|
|
492
|
+
const extractor = this.database.functionRegistry?.fetch(registryKey) as TIndexKeyFunction<T> | undefined;
|
|
493
|
+
if (!extractor) {
|
|
494
|
+
throw new Error(`index key function "${registryKey}" is not registered`);
|
|
495
|
+
}
|
|
496
|
+
this.indexDefinitions.set(indexKey, extractor);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
private async indexKeysFor(v: T): Promise<Array<[string, string[]]>> {
|
|
501
|
+
const result: Array<[string, string[]]> = [];
|
|
502
|
+
for (const [indexKey, extractor] of this.indexDefinitions) {
|
|
503
|
+
const extracted = await extractor(v);
|
|
504
|
+
if (extracted === undefined) {
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
result.push([indexKey, Array.isArray(extracted) ? extracted : [extracted]]);
|
|
508
|
+
}
|
|
509
|
+
return result;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
private async addToIndexes(v: T): Promise<void> {
|
|
513
|
+
this.addKeysToIndexes(v, await this.indexKeysFor(v));
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
private async indexKeysForAll(vs: T[]): Promise<Array<Array<[string, string[]]>>> {
|
|
517
|
+
const ret: Array<Array<[string, string[]]>> = [];
|
|
518
|
+
for (const v of vs) {
|
|
519
|
+
ret.push(await this.indexKeysFor(v));
|
|
520
|
+
}
|
|
521
|
+
return ret;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
private addKeysToIndexes(v: T, indexKeys: Array<[string, string[]]>): void {
|
|
525
|
+
for (const [indexKey, keys] of indexKeys) {
|
|
526
|
+
const byKey = this.indexRuntime.get(indexKey)!;
|
|
527
|
+
for (const key of keys) {
|
|
528
|
+
let bucket = byKey.get(key);
|
|
529
|
+
if (!bucket) {
|
|
530
|
+
bucket = new Set();
|
|
531
|
+
byKey.set(key, bucket);
|
|
532
|
+
}
|
|
533
|
+
bucket.add(v);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
if (indexKeys.length > 0) {
|
|
537
|
+
this.indexedKeys.set(v, indexKeys);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
private removeFromIndexes(v: T): void {
|
|
542
|
+
const indexKeys = this.indexedKeys.get(v);
|
|
543
|
+
if (!indexKeys) {
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
this.indexedKeys.delete(v);
|
|
547
|
+
for (const [indexKey, keys] of indexKeys) {
|
|
548
|
+
const byKey = this.indexRuntime.get(indexKey)!;
|
|
549
|
+
for (const key of keys) {
|
|
550
|
+
const bucket = byKey.get(key);
|
|
551
|
+
if (!bucket) {
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
bucket.delete(v);
|
|
555
|
+
if (bucket.size === 0) {
|
|
556
|
+
byKey.delete(key);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* full rebuild from currently loaded data; only needed right after (re)load, since every
|
|
564
|
+
* value then is a freshly deserialized object the incremental add/update/delete hooks never saw
|
|
565
|
+
*/
|
|
566
|
+
private async rebuildIndexRuntime(): Promise<void> {
|
|
567
|
+
// every configured index gets an entry up front, even if empty, so getByIndex()/
|
|
568
|
+
// addToIndexes()/removeFromIndexes() can assume it's there instead of checking twice
|
|
569
|
+
this.indexRuntime = new Map(Array.from(this.indexDefinitions.keys(), (indexKey) => [indexKey, new Map()]));
|
|
570
|
+
this.indexedKeys = new Map();
|
|
571
|
+
if (this.indexDefinitions.size === 0) {
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
const values = this.collectionConfig.memStorageStrategy === EBMemStorageStrategy.MAP ?
|
|
575
|
+
[...this.mapData.values()] : this.arrayData;
|
|
576
|
+
for (const v of values) {
|
|
577
|
+
await this.addToIndexes(v);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
private resolveIDGetter(): TGetIDFunction {
|
|
582
|
+
if (this.collectionConfig.getID) {
|
|
583
|
+
const found = this.database.functionRegistry?.fetch(this.collectionConfig.getID) as TGetIDFunction | undefined;
|
|
584
|
+
if (!found) {
|
|
585
|
+
throw new Error(`getID function "${this.collectionConfig.getID}" is not registered`);
|
|
586
|
+
}
|
|
587
|
+
return found;
|
|
588
|
+
}
|
|
589
|
+
return DEFAULT_GET_ID;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* id -> value for every stored item, resolved via the plain id getter (no auto-id assignment - every
|
|
594
|
+
* stored item already has one). Used to diff a transaction's working copy against the pre-transaction
|
|
595
|
+
* state in {@link prepareTransactionCommit}.
|
|
596
|
+
*/
|
|
597
|
+
private async idKeyedSnapshot(): Promise<Map<string, T>> {
|
|
598
|
+
switch (this.collectionConfig.memStorageStrategy) {
|
|
599
|
+
case EBMemStorageStrategy.MAP:
|
|
600
|
+
return this.mapData;
|
|
601
|
+
default:
|
|
602
|
+
case EBMemStorageStrategy.ARRAY:
|
|
603
|
+
const getter = this.resolveIDGetter();
|
|
604
|
+
const ret = new Map<string, T>();
|
|
605
|
+
for (const v of this.arrayData) {
|
|
606
|
+
const id = normalizeID(await getter(v));
|
|
607
|
+
if (id) {
|
|
608
|
+
ret.set(id, v);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return ret;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
private resolveIDSetter(): TSetIDFunction {
|
|
616
|
+
if (this.collectionConfig.setID) {
|
|
617
|
+
const found = this.database.functionRegistry?.fetch(this.collectionConfig.setID) as TSetIDFunction | undefined;
|
|
618
|
+
if (!found) {
|
|
619
|
+
throw new Error(`setID function "${this.collectionConfig.setID}" is not registered`);
|
|
620
|
+
}
|
|
621
|
+
return found;
|
|
622
|
+
}
|
|
623
|
+
return DEFAULT_SET_ID;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
private async resolveIncomingIDs(vs: T[]): Promise<Array<string | undefined>> {
|
|
627
|
+
const getter = this.resolveIDGetter();
|
|
628
|
+
const ids: Array<string | undefined> = [];
|
|
629
|
+
for (const v of vs) {
|
|
630
|
+
const id = normalizeID(await getter(v));
|
|
631
|
+
if (!id && this.collectionConfig.autoIDStrategy === EBAutoIDStrategy.NO_AUTO_ID) {
|
|
632
|
+
throw new Error("cannot omit auto-ids and than trying to add a value without an id");
|
|
633
|
+
}
|
|
634
|
+
ids.push(id);
|
|
635
|
+
}
|
|
636
|
+
return ids;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
private async assignMissingIDs(vs: T[], ids: Array<string | undefined>): Promise<string[]> {
|
|
640
|
+
let setter: TSetIDFunction | undefined;
|
|
641
|
+
const ret: string[] = [];
|
|
642
|
+
for (let i = 0; i < vs.length; i++) {
|
|
643
|
+
let id = ids[i];
|
|
644
|
+
if (!id) {
|
|
645
|
+
setter ??= this.resolveIDSetter();
|
|
646
|
+
id = uuidv7();
|
|
647
|
+
await setter(vs[i], id);
|
|
648
|
+
}
|
|
649
|
+
ret.push(id);
|
|
650
|
+
}
|
|
651
|
+
return ret;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
private async storedArrayIDs(): Promise<Array<string | undefined>> {
|
|
655
|
+
const getter = this.resolveIDGetter();
|
|
656
|
+
const ids: Array<string | undefined> = [];
|
|
657
|
+
for (const v of this.arrayData) {
|
|
658
|
+
ids.push(normalizeID(await getter(v)));
|
|
659
|
+
}
|
|
660
|
+
return ids;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
private async recordTxChanges(modificationType: EBModificationType, values: T[]): Promise<void> {
|
|
664
|
+
if (!this.txChanges || values.length === 0) {
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
const getter = this.resolveIDGetter();
|
|
668
|
+
for (const v of values) {
|
|
669
|
+
const id = normalizeID(await getter(v));
|
|
670
|
+
if (id) {
|
|
671
|
+
this.txChanges.set(id, modificationType);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
public async add(...vs: T[]): Promise<number> {
|
|
677
|
+
return this.runMutation(async () => {
|
|
678
|
+
await this.checkLoaded();
|
|
679
|
+
const trackAdds = this.addHandlers.length > 0;
|
|
680
|
+
const collectAdded = trackAdds || this.txChanges !== undefined;
|
|
681
|
+
const indexAdds = this.indexDefinitions.size > 0;
|
|
682
|
+
const incomingIDs = await this.resolveIncomingIDs(vs);
|
|
683
|
+
const added: T[] = [];
|
|
684
|
+
let ret = 0;
|
|
685
|
+
switch (this.collectionConfig.memStorageStrategy) {
|
|
686
|
+
case EBMemStorageStrategy.MAP: {
|
|
687
|
+
if (this.collectionConfig.duplicateIDStrategy === EBDuplicateIDStrategy.ERROR) {
|
|
688
|
+
const batchIDs = new Set<string>();
|
|
689
|
+
for (const id of incomingIDs) {
|
|
690
|
+
if (id === undefined) {
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
if (this.mapData.has(id) || batchIDs.has(id)) {
|
|
694
|
+
throw new Error(`duplicate id ${id}`);
|
|
695
|
+
}
|
|
696
|
+
batchIDs.add(id);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
const ids = await this.assignMissingIDs(vs, incomingIDs);
|
|
700
|
+
const indexKeys = indexAdds ? await this.indexKeysForAll(vs) : [];
|
|
701
|
+
for (let i = 0; i < vs.length; i++) {
|
|
702
|
+
const id = ids[i]!;
|
|
703
|
+
const v = vs[i]!;
|
|
704
|
+
if (this.mapData.has(id)) {
|
|
705
|
+
switch (this.collectionConfig.duplicateIDStrategy) {
|
|
706
|
+
case EBDuplicateIDStrategy.ERROR:
|
|
707
|
+
throw new Error(`duplicate id ${id}`);
|
|
708
|
+
case EBDuplicateIDStrategy.IGNORE:
|
|
709
|
+
// ignore the new value
|
|
710
|
+
break;
|
|
711
|
+
default:
|
|
712
|
+
case EBDuplicateIDStrategy.OVERWRITE:
|
|
713
|
+
// replace the new value
|
|
714
|
+
ret++;
|
|
715
|
+
if (indexAdds) {
|
|
716
|
+
this.removeFromIndexes(this.mapData.get(id)!);
|
|
717
|
+
}
|
|
718
|
+
this.mapData.set(id, v);
|
|
719
|
+
if (indexAdds) {
|
|
720
|
+
this.addKeysToIndexes(v, indexKeys[i]!);
|
|
721
|
+
}
|
|
722
|
+
if (collectAdded) {
|
|
723
|
+
added.push(v);
|
|
724
|
+
}
|
|
725
|
+
break;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
else {
|
|
729
|
+
ret++;
|
|
730
|
+
this.mapData.set(id, v);
|
|
731
|
+
if (indexAdds) {
|
|
732
|
+
this.addKeysToIndexes(v, indexKeys[i]!);
|
|
733
|
+
}
|
|
734
|
+
if (collectAdded) {
|
|
735
|
+
added.push(v);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
break;
|
|
740
|
+
}
|
|
741
|
+
default:
|
|
742
|
+
case EBMemStorageStrategy.ARRAY: {
|
|
743
|
+
const storedIDs = await this.storedArrayIDs();
|
|
744
|
+
switch (this.collectionConfig.duplicateIDStrategy) {
|
|
745
|
+
case EBDuplicateIDStrategy.ERROR: {
|
|
746
|
+
const haveIDs = new Set(storedIDs.filter((id): id is string => id !== undefined));
|
|
747
|
+
for (const id of incomingIDs) {
|
|
748
|
+
if (id === undefined) {
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
if (haveIDs.has(id)) {
|
|
752
|
+
throw new Error(`duplicate id ${id}`);
|
|
753
|
+
}
|
|
754
|
+
haveIDs.add(id);
|
|
755
|
+
}
|
|
756
|
+
await this.assignMissingIDs(vs, incomingIDs);
|
|
757
|
+
const indexKeys = indexAdds ? await this.indexKeysForAll(vs) : [];
|
|
758
|
+
vs.forEach((v, i) => {
|
|
759
|
+
ret++;
|
|
760
|
+
this.arrayData.push(v);
|
|
761
|
+
if (indexAdds) {
|
|
762
|
+
this.addKeysToIndexes(v, indexKeys[i]!);
|
|
763
|
+
}
|
|
764
|
+
if (collectAdded) {
|
|
765
|
+
added.push(v);
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
break;
|
|
769
|
+
}
|
|
770
|
+
case EBDuplicateIDStrategy.IGNORE: {
|
|
771
|
+
const ids = await this.assignMissingIDs(vs, incomingIDs);
|
|
772
|
+
const seenIDs = new Set(storedIDs.filter((id): id is string => id !== undefined));
|
|
773
|
+
const toAdd: T[] = [];
|
|
774
|
+
ids.forEach((id, i) => {
|
|
775
|
+
if (!seenIDs.has(id)) {
|
|
776
|
+
seenIDs.add(id);
|
|
777
|
+
toAdd.push(vs[i]!);
|
|
778
|
+
}
|
|
779
|
+
});
|
|
780
|
+
const indexKeys = indexAdds ? await this.indexKeysForAll(toAdd) : [];
|
|
781
|
+
toAdd.forEach((v, i) => {
|
|
782
|
+
ret++;
|
|
783
|
+
this.arrayData.push(v);
|
|
784
|
+
if (indexAdds) {
|
|
785
|
+
this.addKeysToIndexes(v, indexKeys[i]!);
|
|
786
|
+
}
|
|
787
|
+
if (collectAdded) {
|
|
788
|
+
added.push(v);
|
|
789
|
+
}
|
|
790
|
+
});
|
|
791
|
+
break;
|
|
792
|
+
}
|
|
793
|
+
default:
|
|
794
|
+
case EBDuplicateIDStrategy.OVERWRITE: {
|
|
795
|
+
const ids = await this.assignMissingIDs(vs, incomingIDs);
|
|
796
|
+
const indexKeys = indexAdds ? await this.indexKeysForAll(vs) : [];
|
|
797
|
+
const positions = new Map<string, number>();
|
|
798
|
+
storedIDs.forEach((id, i) => {
|
|
799
|
+
if (id !== undefined) {
|
|
800
|
+
positions.set(id, i);
|
|
801
|
+
}
|
|
802
|
+
});
|
|
803
|
+
vs.forEach((v, i) => {
|
|
804
|
+
const id = ids[i]!;
|
|
805
|
+
const existingIndex = positions.get(id);
|
|
806
|
+
ret++;
|
|
807
|
+
if (existingIndex !== undefined) {
|
|
808
|
+
if (indexAdds) {
|
|
809
|
+
this.removeFromIndexes(this.arrayData[existingIndex]!);
|
|
810
|
+
}
|
|
811
|
+
this.arrayData[existingIndex] = v;
|
|
812
|
+
}
|
|
813
|
+
else {
|
|
814
|
+
positions.set(id, this.arrayData.length);
|
|
815
|
+
this.arrayData.push(v);
|
|
816
|
+
}
|
|
817
|
+
if (indexAdds) {
|
|
818
|
+
this.addKeysToIndexes(v, indexKeys[i]!);
|
|
819
|
+
}
|
|
820
|
+
if (collectAdded) {
|
|
821
|
+
added.push(v);
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
break;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
break;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
if (ret > 0) {
|
|
831
|
+
await this.doOnDirty();
|
|
832
|
+
}
|
|
833
|
+
await this.recordTxChanges(EBModificationType.ADD, added);
|
|
834
|
+
if (trackAdds && added.length > 0) {
|
|
835
|
+
await this.fireAdd(added);
|
|
836
|
+
}
|
|
837
|
+
return ret;
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
public async update(predicate: (v: T) => boolean, updater: (v: T) => Promise<T>): Promise<number> {
|
|
842
|
+
return this.runMutation(async () => {
|
|
843
|
+
await this.checkLoaded();
|
|
844
|
+
const trackUpdates = this.updateHandlers.length > 0;
|
|
845
|
+
const indexUpdates = this.indexDefinitions.size > 0;
|
|
846
|
+
const planned: Array<{
|
|
847
|
+
previous: T;
|
|
848
|
+
replacement: T;
|
|
849
|
+
replacementKeys: Array<[string, string[]]>;
|
|
850
|
+
store: (replacement: T) => void;
|
|
851
|
+
}> = [];
|
|
852
|
+
const plan = async (previous: T, store: (replacement: T) => void): Promise<void> => {
|
|
853
|
+
const replacement = await updater(previous);
|
|
854
|
+
if (replacement === undefined) {
|
|
855
|
+
throw new Error("updater gave undefined");
|
|
856
|
+
}
|
|
857
|
+
const replacementKeys = indexUpdates ? await this.indexKeysFor(replacement) : [];
|
|
858
|
+
planned.push({ previous, replacement, replacementKeys, store });
|
|
859
|
+
};
|
|
860
|
+
switch (this.collectionConfig.memStorageStrategy) {
|
|
861
|
+
case EBMemStorageStrategy.MAP:
|
|
862
|
+
for (const [key, value] of this.mapData) {
|
|
863
|
+
if (predicate(value)) {
|
|
864
|
+
await plan(value, (replacement) => { this.mapData.set(key, replacement); });
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
break;
|
|
868
|
+
default:
|
|
869
|
+
case EBMemStorageStrategy.ARRAY:
|
|
870
|
+
for (let i = 0; i < this.arrayData.length; i++) {
|
|
871
|
+
const previous = this.arrayData[i]!;
|
|
872
|
+
if (predicate(previous)) {
|
|
873
|
+
await plan(previous, (replacement) => { this.arrayData[i] = replacement; });
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
break;
|
|
877
|
+
}
|
|
878
|
+
for (const { previous, replacement, replacementKeys, store } of planned) {
|
|
879
|
+
if (indexUpdates) {
|
|
880
|
+
this.removeFromIndexes(previous);
|
|
881
|
+
}
|
|
882
|
+
store(replacement);
|
|
883
|
+
if (indexUpdates) {
|
|
884
|
+
this.addKeysToIndexes(replacement, replacementKeys);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
if (planned.length > 0) {
|
|
888
|
+
await this.doOnDirty();
|
|
889
|
+
}
|
|
890
|
+
await this.recordTxChanges(EBModificationType.UPDATE, planned.map(({ replacement }) => replacement));
|
|
891
|
+
if (trackUpdates && planned.length > 0) {
|
|
892
|
+
await this.fireUpdate(planned.map(({ previous, replacement }) => [previous, replacement]));
|
|
893
|
+
}
|
|
894
|
+
return planned.length;
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
private async doOnDirty(): Promise<void> {
|
|
899
|
+
this.dirty = true;
|
|
900
|
+
if (this.collectionConfig.autoFlushStrategy == EBAutoFlushStrategy.ALWAYS_AUTO_FLUSH) {
|
|
901
|
+
await this.flush();
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
public async flush(): Promise<void> {
|
|
906
|
+
return this.runProtected(async () => {
|
|
907
|
+
if (this.loaded) {
|
|
908
|
+
switch (this.collectionConfig.memStorageStrategy) {
|
|
909
|
+
case EBMemStorageStrategy.MAP:
|
|
910
|
+
await this.database.io.writeMapData(this.collectionID, this.mapData);
|
|
911
|
+
break;
|
|
912
|
+
default:
|
|
913
|
+
case EBMemStorageStrategy.ARRAY:
|
|
914
|
+
await this.database.io.writeArrayData(this.collectionID, this.arrayData);
|
|
915
|
+
break;
|
|
916
|
+
}
|
|
917
|
+
this.dirty = false;
|
|
918
|
+
}
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
public async reload(): Promise<void> {
|
|
923
|
+
return this.runProtected(async () => {
|
|
924
|
+
switch (this.collectionConfig.memStorageStrategy) {
|
|
925
|
+
case EBMemStorageStrategy.MAP:
|
|
926
|
+
this.mapData = await this.database.io.loadMapData<T>(this.collectionID);
|
|
927
|
+
break;
|
|
928
|
+
default:
|
|
929
|
+
case EBMemStorageStrategy.ARRAY:
|
|
930
|
+
this.arrayData = await this.database.io.loadArrayData<T>(this.collectionID);
|
|
931
|
+
break;
|
|
932
|
+
}
|
|
933
|
+
this.resolveIndexDefinitions();
|
|
934
|
+
await this.rebuildIndexRuntime();
|
|
935
|
+
this.loaded = true;
|
|
936
|
+
this.dirty = false;
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
public async unload(): Promise<void> {
|
|
941
|
+
return this.runProtected(async () => {
|
|
942
|
+
if (this.dirty) {
|
|
943
|
+
switch (this.collectionConfig.dirtyUnloadStrategy) {
|
|
944
|
+
case EBDirtyUnloadStrategy.FLUSH:
|
|
945
|
+
await this.flush();
|
|
946
|
+
break;
|
|
947
|
+
case EBDirtyUnloadStrategy.ERROR:
|
|
948
|
+
throw new Error("cannot unload unwritten collection");
|
|
949
|
+
default:
|
|
950
|
+
case EBDirtyUnloadStrategy.IGNORE:
|
|
951
|
+
// do nothing
|
|
952
|
+
break;
|
|
953
|
+
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
switch (this.collectionConfig.memStorageStrategy) {
|
|
957
|
+
case EBMemStorageStrategy.MAP:
|
|
958
|
+
this.mapData = new Map();
|
|
959
|
+
break;
|
|
960
|
+
default:
|
|
961
|
+
case EBMemStorageStrategy.ARRAY:
|
|
962
|
+
this.arrayData = [];
|
|
963
|
+
break;
|
|
964
|
+
}
|
|
965
|
+
this.indexRuntime.clear();
|
|
966
|
+
this.indexedKeys.clear();
|
|
967
|
+
this.loaded = false;
|
|
968
|
+
this.dirty = false;
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* Creates a transaction-scoped working copy: same identity/config as this collection, but with
|
|
974
|
+
* independently mutable in-memory state (auto-flush forced off, regardless of this collection's own
|
|
975
|
+
* `autoFlushStrategy`) and no change-event handlers of its own. Call only while already holding this
|
|
976
|
+
* collection's mutex for the whole transaction (see {@link BDatabase.transaction}).
|
|
977
|
+
*/
|
|
978
|
+
public async cloneForTransaction(): Promise<BCollection<T>> {
|
|
979
|
+
return this.runProtected(async () => {
|
|
980
|
+
await this.checkLoaded();
|
|
981
|
+
const txConfig: IBCollectionConfig = { ...this.collectionConfig, autoFlushStrategy: EBAutoFlushStrategy.NO_AUTO_FLUSH };
|
|
982
|
+
const clone = new BCollection<T>(this.database, this.collectionID, this.collectionName, txConfig);
|
|
983
|
+
clone.mapData = new Map(this.mapData);
|
|
984
|
+
clone.arrayData = this.arrayData.slice();
|
|
985
|
+
clone.loaded = true;
|
|
986
|
+
clone.transactionClone = true;
|
|
987
|
+
if (this.addHandlers.length > 0 || this.updateHandlers.length > 0 || this.deleteHandlers.length > 0) {
|
|
988
|
+
clone.txChanges = new Map();
|
|
989
|
+
}
|
|
990
|
+
clone.resolveIndexDefinitions();
|
|
991
|
+
await clone.rebuildIndexRuntime();
|
|
992
|
+
return clone;
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
/**
|
|
997
|
+
* Writes `clone`'s state to a temp file (see {@link IPreparedWrite}) and computes the handler events for the
|
|
998
|
+
* net changes against this collection's current state: one per id the transaction touched, typed by the last
|
|
999
|
+
* operation on it (`DELETE` if gone, `ADD` if new or last stored via `add()`, `UPDATE` otherwise). Nothing is
|
|
1000
|
+
* applied until `apply()`; call only while already holding this collection's mutex for the whole transaction.
|
|
1001
|
+
*/
|
|
1002
|
+
public async prepareTransactionCommit(clone: BCollection<T>): Promise<IPreparedTransactionCommit> {
|
|
1003
|
+
return this.runProtected(async () => {
|
|
1004
|
+
await this.checkLoaded();
|
|
1005
|
+
const added: T[] = [];
|
|
1006
|
+
const updated: Array<[T, T]> = [];
|
|
1007
|
+
const deleted: T[] = [];
|
|
1008
|
+
if (clone.txChanges && clone.txChanges.size > 0) {
|
|
1009
|
+
const before = await this.idKeyedSnapshot();
|
|
1010
|
+
const after = await clone.idKeyedSnapshot();
|
|
1011
|
+
for (const [id, modificationType] of clone.txChanges) {
|
|
1012
|
+
if (!after.has(id)) {
|
|
1013
|
+
if (before.has(id)) {
|
|
1014
|
+
deleted.push(before.get(id)!);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
else if (!before.has(id) || modificationType === EBModificationType.ADD) {
|
|
1018
|
+
added.push(after.get(id)!);
|
|
1019
|
+
}
|
|
1020
|
+
else {
|
|
1021
|
+
updated.push([before.get(id)!, after.get(id)!]);
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
let write: IPreparedWrite;
|
|
1026
|
+
switch (this.collectionConfig.memStorageStrategy) {
|
|
1027
|
+
case EBMemStorageStrategy.MAP:
|
|
1028
|
+
write = await this.database.io.prepareMapData(this.collectionID, clone.mapData);
|
|
1029
|
+
break;
|
|
1030
|
+
default:
|
|
1031
|
+
case EBMemStorageStrategy.ARRAY:
|
|
1032
|
+
write = await this.database.io.prepareArrayData(this.collectionID, clone.arrayData);
|
|
1033
|
+
break;
|
|
1034
|
+
}
|
|
1035
|
+
return {
|
|
1036
|
+
write,
|
|
1037
|
+
apply: () => {
|
|
1038
|
+
this.mapData = clone.mapData;
|
|
1039
|
+
this.arrayData = clone.arrayData;
|
|
1040
|
+
this.indexRuntime = clone.indexRuntime;
|
|
1041
|
+
this.indexedKeys = clone.indexedKeys;
|
|
1042
|
+
this.dirty = false;
|
|
1043
|
+
},
|
|
1044
|
+
fire: () => this.runProtected(async () => {
|
|
1045
|
+
if (added.length > 0) {
|
|
1046
|
+
await this.fireAdd(added);
|
|
1047
|
+
}
|
|
1048
|
+
if (updated.length > 0) {
|
|
1049
|
+
await this.fireUpdate(updated);
|
|
1050
|
+
}
|
|
1051
|
+
if (deleted.length > 0) {
|
|
1052
|
+
await this.fireDelete(deleted);
|
|
1053
|
+
}
|
|
1054
|
+
}),
|
|
1055
|
+
};
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
|
|
913
1059
|
}
|