@fireflysemantics/slice 15.2.1 → 16.2.12

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.
@@ -1,1494 +0,0 @@
1
- import { ReplaySubject, fromEvent, of } from 'rxjs';
2
- import { map, debounceTime, distinctUntilChanged, pairwise, switchMap, filter, takeWhile } from 'rxjs/operators';
3
- import { nanoid } from 'nanoid';
4
-
5
- ;
6
-
7
- /**
8
- * Abstract Entity base class with the
9
- * gid and id properties declared.
10
- */
11
- class Entity {
12
- }
13
-
14
- const SCROLL_UP_DEBOUNCE_TIME_20 = 20;
15
- const SEARCH_DEBOUNCE_TIME_300 = 300;
16
-
17
- const { freeze } = Object;
18
- const ESTORE_DEFAULT_ID_KEY = "id";
19
- const ESTORE_DEFAULT_GID_KEY = "gid";
20
- const ESTORE_CONFIG_DEFAULT = freeze({
21
- idKey: ESTORE_DEFAULT_ID_KEY,
22
- guidKey: ESTORE_DEFAULT_GID_KEY
23
- });
24
- class AbstractStore {
25
- constructor(config) {
26
- /**
27
- * Notifies observers of the store query.
28
- */
29
- this.notifyQuery = new ReplaySubject(1);
30
- /**
31
- * The current query state.
32
- */
33
- this._query = '';
34
- /**
35
- * Primary index for the stores elements.
36
- */
37
- this.entries = new Map();
38
- /**
39
- * The element entries that are keyed by
40
- * an id generated on the server.
41
- */
42
- this.idEntries = new Map();
43
- /**
44
- * Create notifications that broacast
45
- * the entire set of entries.
46
- */
47
- this.notify = new ReplaySubject(1);
48
- /**
49
- * Create notifications that broacast
50
- * store or slice delta state changes.
51
- */
52
- this.notifyDelta = new ReplaySubject(1);
53
- /**
54
- * An Observable<E[]> reference
55
- * to the entities in the store or
56
- * Slice instance.
57
- *
58
- */
59
- this.obs = this.observe();
60
- this.config = config
61
- ? freeze(Object.assign(Object.assign({}, ESTORE_CONFIG_DEFAULT), config))
62
- : ESTORE_CONFIG_DEFAULT;
63
- }
64
- /**
65
- * Sets the current query state and notifies observers.
66
- */
67
- set query(query) {
68
- this._query = query;
69
- this.notifyQuery.next(this._query);
70
- }
71
- /**
72
- * @return A snapshot of the query state.
73
- */
74
- get query() {
75
- return this._query;
76
- }
77
- /**
78
- * Observe the query.
79
- * @example
80
- <pre>
81
- let query$ = source.observeQuery();
82
- </pre>
83
- */
84
- observeQuery() {
85
- return this.notifyQuery.asObservable();
86
- }
87
- /**
88
- * The current id key for the EStore instance.
89
- * @return this.config.idKey;
90
- */
91
- get ID_KEY() {
92
- return this.config.idKey;
93
- }
94
- /**
95
- * The current guid key for the EStore instance.
96
- * @return this.config.guidKey;
97
- */
98
- get GUID_KEY() {
99
- return this.config.guidKey;
100
- }
101
- /**
102
- * Call all the notifiers at once.
103
- *
104
- * @param v
105
- * @param delta
106
- */
107
- notifyAll(v, delta) {
108
- this.notify.next(v);
109
- this.notifyDelta.next(delta);
110
- }
111
- /**
112
- * Observe store state changes.
113
- * @param sort Optional sorting function yielding a sorted observable.
114
- * @example
115
- ```
116
- let todos$ = source.observe();
117
- //or with a sort by title function
118
- let todos$ = source.observe((a, b)=>(a.title > b.title ? -1 : 1));
119
- ```
120
- */
121
- observe(sort) {
122
- if (sort) {
123
- return this.notify.pipe(map((e) => e.sort(sort)));
124
- }
125
- return this.notify.asObservable();
126
- }
127
- /**
128
- * Observe delta updates.
129
- * @example
130
- <pre>
131
- let todos$ = source.observeDelta();
132
- </pre>
133
- */
134
- observeDelta() {
135
- return this.notifyDelta.asObservable();
136
- }
137
- /**
138
- * Check whether the store is empty.
139
- *
140
- * @return A hot {@link Observable} that indicates whether the store is empty.
141
- *
142
- * @example
143
- <pre>
144
- source.isEmpty();
145
- </pre>
146
- */
147
- isEmpty() {
148
- return this.notify.pipe(map((entries) => entries.length == 0));
149
- }
150
- /**
151
- * Check whether the store is empty.
152
- *
153
- * @return A snapshot that indicates whether the store is empty.
154
- *
155
- * @example
156
- <pre>
157
- source.isEmpty();
158
- </pre>
159
- */
160
- isEmptySnapshot() {
161
- return Array.from(this.entries.values()).length == 0;
162
- }
163
- /**
164
- * Returns the number of entries contained.
165
- * @param p The predicate to apply in order to filter the count
166
- */
167
- count(p) {
168
- if (p) {
169
- return this.notify.pipe(map((e) => e.reduce((total, e) => total + (p(e) ? 1 : 0), 0)));
170
- }
171
- return this.notify.pipe(map((entries) => entries.length));
172
- }
173
- /**
174
- * Returns a snapshot of the number of entries contained in the store.
175
- * @param p The predicate to apply in order to filter the count
176
- */
177
- countSnapshot(p) {
178
- if (p) {
179
- return Array.from(this.entries.values()).filter(p).length;
180
- }
181
- return Array.from(this.entries.values()).length;
182
- }
183
- /**
184
- * Snapshot of all entries.
185
- *
186
- * @return Snapshot array of all the elements the entities the store contains.
187
- *
188
- * @example Observe a snapshot of all the entities in the store.
189
- ```
190
- let selectedTodos:Todo[] = source.allSnapshot();
191
- ```
192
- */
193
- allSnapshot() {
194
- return Array.from(this.entries.values());
195
- }
196
- /**
197
- * Returns true if the entries contain the identified instance.
198
- *
199
- * @param target Either an instance of type `E` or a `guid` identifying the instance.
200
- * @param byId Whether the lookup should be performed with the `id` key rather than the `guid`.
201
- * @returns true if the instance identified by the guid exists, false otherwise.
202
- *
203
- * @example
204
- <pre>
205
- let contains:boolean = source.contains(guid);
206
- </pre>
207
- */
208
- contains(target) {
209
- if (typeof target === "string") {
210
- return this.entries.get(target) ? true : false;
211
- }
212
- const guid = target[this.config.guidKey];
213
- return this.entries.get(guid) ? true : false;
214
- }
215
- /**
216
- * Returns true if the entries contain the identified instance.
217
- *
218
- * @param target Either an instance of type `E` or a `id` identifying the instance.
219
- * @returns true if the instance identified by the `id` exists, false otherwise.
220
- *
221
- * @example
222
- <pre>
223
- let contains:boolean = source.contains(guid);
224
- </pre>
225
- */
226
- containsById(target) {
227
- if (typeof target === "string") {
228
- return this.idEntries.get(target) ? true : false;
229
- }
230
- const id = target[this.config.idKey];
231
- return this.idEntries.get(id) ? true : false;
232
- }
233
- /**
234
- * Find and return the entity identified by the GUID parameter
235
- * if it exists and return it.
236
- *
237
- * @param guid
238
- * @return The entity instance if it exists, null otherwise
239
- */
240
- findOne(guid) {
241
- return this.entries.get(guid);
242
- }
243
- /**
244
- * Find and return the entity identified by the ID parameter
245
- * if it exists and return it.
246
- *
247
- * @param id
248
- * @return The entity instance if it exists, null otherwise
249
- */
250
- findOneByID(id) {
251
- return this.idEntries.get(id);
252
- }
253
- /**
254
- * Snapshot of the entries that match the predicate.
255
- *
256
- * @param p The predicate used to query for the selection.
257
- * @return A snapshot array containing the entities that match the predicate.
258
- *
259
- * @example Select all the `Todo` instance where the `title` length is greater than 100.
260
- ```
261
- let todos:Todo[]=store.select(todo=>todo.title.length>100);
262
- ```
263
- */
264
- select(p) {
265
- const selected = [];
266
- Array.from(this.entries.values()).forEach(e => {
267
- if (p(e)) {
268
- selected.push(e);
269
- }
270
- });
271
- return selected;
272
- }
273
- /**
274
- * Compare entities by GUID
275
- * @param e1 The first entity
276
- * @param e2 The second entity
277
- * @return true if the two entities have equal GUID ids
278
- * @example Compare `todo1` with `todo2` by `gid`.
279
- ```
280
- if (equalsByGUID(todo1, todo2)){...};
281
- ```
282
- */
283
- equalsByGUID(e1, e2) {
284
- return e1[this.GUID_KEY] == e2[this.GUID_KEY];
285
- }
286
- /**
287
- * Compare entities by ID
288
- * @param e1 The first entity
289
- * @param e2 The second entity
290
- * @return true if the two entities have equal ID ids
291
- * @example Compare `todo1` with `todo2` by `id`.
292
- ```
293
- if (equalsByID(todo1, todo2)){...};
294
- ```
295
- */
296
- equalsByID(e1, e2) {
297
- return e1[this.ID_KEY] == e2[this.ID_KEY];
298
- }
299
- /**
300
- * Calls complete on all {@link BehaviorSubject} instances.
301
- *
302
- * Call destroy when disposing of the store.
303
- */
304
- destroy() {
305
- this.notify.complete();
306
- this.notifyDelta.complete();
307
- this.notifyQuery.complete();
308
- }
309
- }
310
-
311
- /**
312
- * Returns all the entities are distinct by the
313
- * `property` value argument.
314
- *
315
- * Note that the implementation uses a `Map<string, E>` to
316
- * index the entities by key. Therefore the more recent occurences
317
- * matching a key instance will overwrite the previous ones.
318
- *
319
- * @param property The name of the property to check for distinct values by.
320
- * @param entities The entities in the array.
321
- *
322
- * @example
323
- ```
324
- let todos: Todo[] = [
325
- { id: 1, title: "Lets do it!" },
326
- { id: 1, title: "Lets do it again!" },
327
- { id: 2, title: "All done!" }
328
- ];
329
-
330
- let todos2: Todo[] = [
331
- { id: 1, title: "Lets do it!" },
332
- { id: 2, title: "All done!" }
333
- ];
334
-
335
- expect(distinct(todos, "id").length).toEqual(2);
336
- expect(distinct(todos2, "id").length).toEqual(2);
337
-
338
- ```
339
- */
340
- function distinct(entities, property) {
341
- const entitiesByProperty = new Map(entities.map(e => [e[property], e]));
342
- return Array.from(entitiesByProperty.values());
343
- }
344
- /**
345
- * Returns true if all the entities are distinct by the
346
- * `property` value argument.
347
- *
348
- * @param property The name of the property to check for distinct values by.
349
- * @param entities The entities in the array.
350
- *
351
- * @example
352
- *
353
- ```
354
- let todos: Todo[] = [
355
- { id: 1, title: "Lets do it!" },
356
- { id: 1, title: "Lets do it again!" },
357
- { id: 2, title: "All done!" }
358
- ];
359
-
360
- let todos2: Todo[] = [
361
- { id: 1, title: "Lets do it!" },
362
- { id: 2, title: "All done!" }
363
- ];
364
-
365
- expect(unique(todos, "id")).toBeFalsy();
366
- expect(unique(todos2, "id")).toBeTruthy();
367
- ```
368
- */
369
- function unique(entities, property) {
370
- return entities.length == distinct(entities, property).length ? true : false;
371
- }
372
- /**
373
- * Create a global ID
374
- * @return The global id.
375
- *
376
- * @example
377
- * let e.guid = GUID();
378
- */
379
- function GUID() {
380
- return nanoid();
381
- }
382
- /**
383
- * Set the global identfication property on the instance.
384
- *
385
- * @param e Entity we want to set the global identifier on.
386
- * @param gid The name of the `gid` property. If not specified it defaults to `ESTORE_CONFIG_DEFAULT.guidKey`.
387
- */
388
- function attachGUID(e, gid) {
389
- const guidKey = gid ? gid : ESTORE_CONFIG_DEFAULT.guidKey;
390
- let id = nanoid();
391
- e[guidKey] = id;
392
- return id;
393
- }
394
- /**
395
- * Set the global identfication property on the instance.
396
- *
397
- * @param e[] Entity array we want to set the global identifiers on.
398
- * @param gid The name of the `gid` property. If not specified it defaults to `gid`.
399
- */
400
- function attachGUIDs(e, gid) {
401
- e.forEach(e => {
402
- attachGUID(e, gid);
403
- });
404
- }
405
- /**
406
- * Create a shallow copy of the argument.
407
- * @param o The object to copy
408
- */
409
- function shallowCopy(o) {
410
- return Object.assign({}, o);
411
- }
412
- /**
413
- * Create a deep copy of the argument.
414
- * @param o The object to copy
415
- */
416
- function deepCopy(o) {
417
- return JSON.parse(JSON.stringify(o));
418
- }
419
- /**
420
- * Gets the current active value from the `active`
421
- * Map.
422
- *
423
- * This is used for the scenario where we are managing
424
- * a single active instance. For example
425
- * when selecting a book from a collection of books.
426
- *
427
- * The selected `Book` instance becomes the active value.
428
- *
429
- * @example
430
- * const book:Book = getActiveValue(bookStore.active);
431
- * @param m
432
- */
433
- function getActiveValue(m) {
434
- if (m.size) {
435
- return m.entries().next().value[1];
436
- }
437
- return null;
438
- }
439
- /**
440
- * The method can be used to exclude keys from an instance
441
- * of type `E`.
442
- *
443
- * We can use this to exclude values when searching an object.
444
- *
445
- * @param entity An instance of type E
446
- * @param exclude The keys to exclude
447
- *
448
- * @example
449
- * todo = { id: '1', description: 'Do it!' }
450
- * let keys = excludeKeys<Todo>(todo, ['id]);
451
- * // keys = ['description']
452
- */
453
- function excludeKeys(entity, exclude) {
454
- const keys = Object.keys(entity);
455
- return keys.filter((key) => {
456
- return exclude.indexOf(key) < 0;
457
- });
458
- }
459
- /**
460
- *
461
- * @param entities The entity to search
462
- * @param exclude Keys to exclude from each entity
463
- *
464
- * @return E[] Array of entities with properties containing the search term.
465
- */
466
- function search(query = '', entities, exclude = []) {
467
- const { isArray } = Array;
468
- query = query.toLowerCase();
469
- return entities.filter(function (e) {
470
- //Do the keys calculation on each instance e:E
471
- //because an instance can have optional parameters,
472
- //and thus we have to check each instance, not just
473
- //the first one in the array.
474
- const keys = excludeKeys(e, exclude);
475
- return keys.some((key) => {
476
- const value = e[key];
477
- if (!value) {
478
- return false;
479
- }
480
- if (isArray(value)) {
481
- return value.some(v => {
482
- return String(v).toLowerCase().includes(query);
483
- });
484
- }
485
- else {
486
- return String(value).toLowerCase().includes(query);
487
- }
488
- });
489
- });
490
- }
491
- /**
492
- * @param scrollable The element being scrolled
493
- * @param debounceMS The number of milliseconds to debounce scroll events
494
- * @param sp The function returning the scroll position coordinates.
495
- * @return A boolean valued observable indicating whether the element is scrolling up or down
496
- */
497
- function scrollingUp(scrollable, debounceMS, sp) {
498
- return fromEvent(scrollable, 'scroll').pipe(debounceTime(debounceMS), distinctUntilChanged(), map(v => sp()), pairwise(), switchMap(p => {
499
- const y1 = p[0][1];
500
- const y2 = p[1][1];
501
- return y1 - y2 > 0 ? of(false) : of(true);
502
- }));
503
- }
504
- /**
505
- * Filters the entities properties to the set contained in the
506
- * `keys` array.
507
- *
508
- * @param keys The array of keys that the entity be limited to
509
- * @param entity The entity to map
510
- * @return An entity instance that has only the keys provided in the keys array
511
- */
512
- function mapEntity(keys, entity) {
513
- const result = {};
514
- keys.forEach(k => {
515
- result[k] = entity[k];
516
- });
517
- return result;
518
- }
519
- /**
520
- * Returns an `Observable<E>` instance that
521
- * filters for arguments where the property
522
- * value matches the provided value.
523
- *
524
- * @param value The value targeted
525
- * @param propertyName The name of the property to contain the value
526
- * @param obs The Slice Object Store Observable
527
- * @returns Observable<E>
528
- */
529
- function onFilteredEvent(value, propertyName, obs) {
530
- return obs.pipe(filter((e) => !!(e && e[propertyName] === value)));
531
- }
532
-
533
- const { isArray } = Array;
534
- class Slice extends AbstractStore {
535
- /**
536
- * perform initial notification to all observers,
537
- * such that operations like {@link combineLatest}{}
538
- * will execute at least once.
539
- *
540
- * @param label The slice label
541
- * @param predicate The slice predicate
542
- * @param eStore The EStore instance containing the elements considered for slicing
543
- *
544
- * @example
545
- <pre>
546
- //Empty slice
547
- new Slice<Todo>(Todo.COMPLETE, todo=>!todo.complete);
548
-
549
- //Initialized slice
550
- let todos = [new Todo(false, "You complete me!"),
551
- new Todo(true, "You completed me!")];
552
- new Slice<Todo>(Todo.COMPLETE, todo=>!todo.complete, todos);
553
- </pre>
554
- */
555
- constructor(label, predicate, eStore) {
556
- super();
557
- this.label = label;
558
- this.predicate = predicate;
559
- this.eStore = eStore;
560
- /* The slice element entries */
561
- this.entries = new Map();
562
- const entities = eStore.allSnapshot();
563
- this.config = eStore.config;
564
- let passed = this.test(predicate, entities);
565
- const delta = { type: "Initialize" /* ActionTypes.INTIALIZE */, entries: passed };
566
- this.post(passed);
567
- this.notifyDelta.next(delta);
568
- }
569
- /**
570
- * Add the element if it satisfies the predicate
571
- * and notify subscribers that an element was added.
572
- *
573
- * @param e The element to be considered for slicing
574
- */
575
- post(e) {
576
- if (isArray(e)) {
577
- this.postA(e);
578
- }
579
- else {
580
- if (this.predicate(e)) {
581
- const id = e[this.config.guidKey];
582
- this.entries.set(id, e);
583
- const delta = { type: "Post" /* ActionTypes.POST */, entries: [e] };
584
- this.notifyAll([...Array.from(this.entries.values())], delta);
585
- }
586
- }
587
- }
588
- /**
589
- * Add the elements if they satisfy the predicate
590
- * and notify subscribers that elements were added.
591
- *
592
- * @param e The element to be considered for slicing
593
- */
594
- postN(...e) {
595
- this.postA(e);
596
- }
597
- /**
598
- * Add the elements if they satisfy the predicate
599
- * and notify subscribers that elements were added.
600
- *
601
- * @param e The element to be considered for slicing
602
- */
603
- postA(e) {
604
- const d = [];
605
- e.forEach(e => {
606
- if (this.predicate(e)) {
607
- const id = e[this.config.guidKey];
608
- this.entries.set(id, e);
609
- d.push(e);
610
- }
611
- });
612
- const delta = { type: "Post" /* ActionTypes.POST */, entries: d };
613
- this.notifyAll([...Array.from(this.entries.values())], delta);
614
- }
615
- /**
616
- * Delete an element from the slice.
617
- *
618
- * @param e The element to be deleted if it satisfies the predicate
619
- */
620
- delete(e) {
621
- if (isArray(e)) {
622
- this.deleteA(e);
623
- }
624
- else {
625
- if (this.predicate(e)) {
626
- const id = e[this.config.guidKey];
627
- this.entries.delete(id);
628
- const delta = { type: "Delete" /* ActionTypes.DELETE */, entries: [e] };
629
- this.notifyAll(Array.from(this.entries.values()), delta);
630
- }
631
- }
632
- }
633
- /**
634
- * @param e The elements to be deleted if it satisfies the predicate
635
- */
636
- deleteN(...e) {
637
- this.deleteA(e);
638
- }
639
- /**
640
- * @param e The elements to be deleted if they satisfy the predicate
641
- */
642
- deleteA(e) {
643
- const d = [];
644
- e.forEach(e => {
645
- if (this.predicate(e)) {
646
- const id = e[this.config.guidKey];
647
- d.push(this.entries.get(id));
648
- this.entries.delete(id);
649
- }
650
- });
651
- const delta = { type: "Delete" /* ActionTypes.DELETE */, entries: d };
652
- this.notifyAll([...Array.from(this.entries.values())], delta);
653
- }
654
- /**
655
- * Update the slice when an Entity instance mutates.
656
- *
657
- * @param e The element to be added or deleted depending on predicate reevaluation
658
- */
659
- put(e) {
660
- if (isArray(e)) {
661
- this.putA(e);
662
- }
663
- else {
664
- const id = e[this.config.guidKey];
665
- if (this.entries.get(id)) {
666
- if (!this.predicate(e)) {
667
- //Note that this is a ActionTypes.DELETE because we are removing the
668
- //entity from the slice.
669
- const delta = { type: "Delete" /* ActionTypes.DELETE */, entries: [e] };
670
- this.entries.delete(id);
671
- this.notifyAll([...Array.from(this.entries.values())], delta);
672
- }
673
- }
674
- else if (this.predicate(e)) {
675
- this.entries.set(id, e);
676
- const delta = { type: "Put" /* ActionTypes.PUT */, entries: [e] };
677
- this.notifyAll([...Array.from(this.entries.values())], delta);
678
- }
679
- }
680
- }
681
- /**
682
- * Update the slice with mutated Entity instances.
683
- *
684
- * @param e The elements to be deleted if it satisfies the predicate
685
- */
686
- putN(...e) {
687
- this.putA(e);
688
- }
689
- /**
690
- * @param e The elements to be put
691
- */
692
- putA(e) {
693
- const d = []; //instances to delete
694
- const u = []; //instances to update
695
- e.forEach(e => {
696
- const id = e[this.config.guidKey];
697
- if (this.entries.get(id)) {
698
- if (!this.predicate(e)) {
699
- d.push(this.entries.get(id));
700
- }
701
- }
702
- else if (this.predicate(e)) {
703
- u.push(e);
704
- }
705
- });
706
- if (d.length > 0) {
707
- d.forEach(e => {
708
- this.entries.delete(e[this.config.guidKey]);
709
- });
710
- const delta = { type: "Delete" /* ActionTypes.DELETE */, entries: d };
711
- this.notifyAll([...Array.from(this.entries.values())], delta);
712
- }
713
- if (u.length > 0) {
714
- u.forEach(e => {
715
- this.entries.set(e[this.config.guidKey], e);
716
- });
717
- const delta = { type: "Put" /* ActionTypes.PUT */, entries: u };
718
- this.notifyAll([...Array.from(this.entries.values())], delta);
719
- }
720
- }
721
- /**
722
- * Resets the slice to empty.
723
- */
724
- reset() {
725
- let delta = {
726
- type: "Reset" /* ActionTypes.RESET */,
727
- entries: [...Array.from(this.entries.values())]
728
- };
729
- this.notifyAll([], delta);
730
- this.entries = new Map();
731
- }
732
- /**
733
- * Utility method that applies the predicate to an array
734
- * of entities and return the ones that pass the test.
735
- *
736
- * Used to create an initial set of values
737
- * that should be part of the `Slice`.
738
- *
739
- * @param p
740
- * @param e
741
- * @return The the array of entities that pass the predicate test.
742
- */
743
- test(p, e) {
744
- let v = [];
745
- e.forEach((e) => {
746
- if (p(e)) {
747
- v.push(e);
748
- }
749
- });
750
- return v;
751
- }
752
- }
753
-
754
- /**
755
- * This `todoFactory` code will be used to illustrate the API examples. The following
756
- * utilities are used in the tests and the API Typedoc examples contained here.
757
- * @example Utilities for API Examples
758
- ```
759
- export const enum TodoSliceEnum {
760
- COMPLETE = "Complete",
761
- INCOMPLETE = "Incomplete"
762
- }
763
-
764
- export class Todo {
765
- constructor(public complete: boolean, public title: string,public gid?:string, public id?:string) {}
766
- }
767
-
768
- export let todos = [new Todo(false, "You complete me!"), new Todo(true, "You completed me!")];
769
-
770
- export function todosFactory():Todo[] {
771
- return [new Todo(false, "You complete me!"), new Todo(true, "You completed me!")];
772
- }
773
- ```
774
- */
775
- class EStore extends AbstractStore {
776
- /**
777
- * Store constructor (Initialization with element is optional)
778
- *
779
- * perform initial notification to all observers,
780
- * such that function like {@link combineLatest}{}
781
- * will execute at least once.
782
- * @param entities
783
- * @example Dynamic `EStore<Todo>` Creation
784
- ```
785
- // Initialize the Store
786
- let store: EStore<Todo> = new EStore<Todo>(todosFactory());
787
- ```*/
788
- constructor(entities = [], config) {
789
- super(config);
790
- /**
791
- * Notifies observers when the store is empty.
792
- */
793
- this.notifyActive = new ReplaySubject(1);
794
- /**
795
- * `Map` of active entties. The instance is public and can be used
796
- * directly to add and remove active entities, however we recommend
797
- * using the {@link addActive} and {@link deleteActive} methods.
798
- */
799
- this.active = new Map();
800
- /**
801
- * Notifies observers when the store is loading.
802
- *
803
- * This is a common pattern found when implementing
804
- * `Observable` data sources.
805
- */
806
- this.notifyLoading = new ReplaySubject(1);
807
- /**
808
- * The current loading state. Use loading when fetching new
809
- * data for the store. The default loading state is `true`.
810
- *
811
- * This is such that if data is fetched asynchronously
812
- * in a service, components can wait on loading notification
813
- * before attempting to retrieve data from the service.
814
- *
815
- * Loading could be based on a composite response. For example
816
- * when the stock and mutual funds have loaded, set loading to `false`.
817
- */
818
- this._loading = true;
819
- /**
820
- * Notifies observers that a search is in progress.
821
- *
822
- * This is a common pattern found when implementing
823
- * `Observable` data sources.
824
- */
825
- this.notifySearching = new ReplaySubject(1);
826
- /**
827
- * The current `searching` state. Use `searching`
828
- * for example to display a spinnner
829
- * when performing a search.
830
- * The default `searching` state is `false`.
831
- */
832
- this._searching = false;
833
- /**
834
- * Store slices
835
- */
836
- this.slices = new Map();
837
- const delta = { type: "Initialize" /* ActionTypes.INTIALIZE */, entries: entities };
838
- this.post(entities);
839
- this.notifyDelta.next(delta);
840
- }
841
- /**
842
- * Calls complete on all {@link BehaviorSubject} instances.
843
- *
844
- * Call destroy when disposing of the store.
845
- */
846
- destroy() {
847
- super.destroy();
848
- this.notifyLoading.complete();
849
- this.notifyActive.complete();
850
- this.slices.forEach(slice => slice.destroy());
851
- }
852
- /**
853
- * Toggles the entity:
854
- *
855
- * If the store contains the entity
856
- * it will be deleted. If the store
857
- * does not contains the entity,
858
- * it is added.
859
- * @param e
860
- * @example Toggle the `Todo` instance
861
- ```
862
- estore.post(todo);
863
- // Remove todo
864
- estore.toggle(todo);
865
- // Add it back
866
- estore.toggle(todo);
867
-
868
- ```
869
- */
870
- toggle(e) {
871
- if (this.contains(e)) {
872
- this.delete(e);
873
- }
874
- else {
875
- this.post(e);
876
- }
877
- }
878
- /**
879
- * Add multiple entity entities to active.
880
- *
881
- * If the entity is not contained in the store it is added
882
- * to the store before it is added to `active`.
883
- *
884
- * Also we clone the map prior to broadcasting it with
885
- * `notifyActive` to make sure we will trigger Angular
886
- * change detection in the event that it maintains
887
- * a reference to the `active` state `Map` instance.
888
- *
889
- * @example Add a `todo1` and `todo2` as active
890
- ```
891
- addActive(todo1);
892
- addActive(todo2);
893
- ```
894
- */
895
- addActive(e) {
896
- if (this.contains(e)) {
897
- this.active.set(e.gid, e);
898
- this.notifyActive.next(new Map(this.active));
899
- }
900
- else {
901
- this.post(e);
902
- this.active.set(e.gid, e);
903
- this.notifyActive.next(new Map(this.active));
904
- }
905
- }
906
- /**
907
- * Delete an entity as active.
908
- *
909
- * Also we clone the map prior to broadcasting it with
910
- * `notifyActive` to make sure we will trigger Angular
911
- * change detection in the event that it maintains
912
- * a reference to the `active` state `Map` instance.
913
- *
914
- * @example Mark a `todo` instance as active
915
- ```
916
- deleteActive(todo1);
917
- deleteActive(todo2);
918
- ```
919
- */
920
- deleteActive(e) {
921
- this.active.delete(e.gid);
922
- this.notifyActive.next(new Map(this.active));
923
- }
924
- /**
925
- * Clear / reset the active entity map.
926
- *
927
- * Also we clone the map prior to broadcasting it with
928
- * `notifyActive` to make sure we will trigger Angular
929
- * change detection in the event that it maintains
930
- * a reference to the `active` state `Map` instance.
931
- *
932
- * @example Mark a `todo` instance as active
933
- ```
934
- deleteActive(todo1);
935
- deleteActive(todo2);
936
- ```
937
- */
938
- clearActive() {
939
- this.active.clear();
940
- this.notifyActive.next(new Map(this.active));
941
- }
942
- /**
943
- * Observe the active entity.
944
- * @example
945
- <pre>
946
- let active$ = source.observeActive();
947
- </pre>
948
- */
949
- observeActive() {
950
- return this.notifyActive.asObservable();
951
- }
952
- /**
953
- * Observe the active entity.
954
- * @example
955
- <pre>
956
- let active$ = source.activeSnapshot();
957
- </pre>
958
- */
959
- activeSnapshot() {
960
- return Array.from(this.active.values());
961
- }
962
- /**
963
- * Sets the current loading state and notifies observers.
964
- */
965
- set loading(loading) {
966
- this._loading = loading;
967
- this.notifyLoading.next(this._loading);
968
- }
969
- /**
970
- * @return A snapshot of the loading state.
971
- */
972
- get loading() {
973
- return this._loading;
974
- }
975
- /**
976
- * Observe loading.
977
- * @example
978
- <pre>
979
- let loading$ = source.observeLoading();
980
- </pre>
981
-
982
- Note that this obverable piped through
983
- `takeWhile(v->v, true), such that it will
984
- complete after each emission.
985
-
986
- See:
987
- https://medium.com/@ole.ersoy/waiting-on-estore-to-load-8dcbe161613c
988
-
989
- For more details.
990
- */
991
- observeLoading() {
992
- return this.notifyLoading.asObservable().
993
- pipe(takeWhile(v => v, true));
994
- }
995
- /**
996
- * Notfiies when loading has completed.
997
- */
998
- observeLoadingComplete() {
999
- return this.observeLoading().pipe(filter(loading => loading == false), switchMap(() => of(true)));
1000
- }
1001
- /**
1002
- * Sets the current searching state and notifies observers.
1003
- */
1004
- set searching(searching) {
1005
- this._searching = searching;
1006
- this.notifySearching.next(this._searching);
1007
- }
1008
- /**
1009
- * @return A snapshot of the searching state.
1010
- */
1011
- get searching() {
1012
- return this._searching;
1013
- }
1014
- /**
1015
- * Observe searching.
1016
- * @example
1017
- <pre>
1018
- let searching$ = source.observeSearching();
1019
- </pre>
1020
-
1021
- Note that this obverable piped through
1022
- `takeWhile(v->v, true), such that it will
1023
- complete after each emission.
1024
-
1025
- See:
1026
- https://medium.com/@ole.ersoy/waiting-on-estore-to-load-8dcbe161613c
1027
-
1028
- For more details.
1029
- */
1030
- observeSearching() {
1031
- return this.notifySearching.asObservable().
1032
- pipe(takeWhile(v => v, true));
1033
- }
1034
- /**
1035
- * Notfiies when searching has completed.
1036
- */
1037
- observeSearchingComplete() {
1038
- return this.observeSearching().pipe(filter(searching => searching == false), switchMap(() => of(true)));
1039
- }
1040
- /**
1041
- * Adds a slice to the store and keys it by the slices label.
1042
- *
1043
- * @param p
1044
- * @param label
1045
- *
1046
- * @example Setup a Todo Slice for COMPLETE Todos
1047
- ```
1048
- source.addSlice(todo => todo.complete, TodoSlices.COMPLETE);
1049
- ```
1050
- */
1051
- addSlice(p, label) {
1052
- const slice = new Slice(label, p, this);
1053
- this.slices.set(slice.label, slice);
1054
- }
1055
- /**
1056
- * Remove a slice
1057
- * @param label The label identifying the slice
1058
- *
1059
- * @example Remove the TodoSlices.COMPLETE Slice
1060
- ```
1061
- source.removeSlice(TodoSlices.COMPLETE);
1062
- ```
1063
- */
1064
- removeSlice(label) {
1065
- this.slices.delete(label);
1066
- }
1067
- /**
1068
- * Get a slice
1069
- * @param label The label identifying the slice
1070
- * @return The Slice instance or undefined
1071
- *
1072
- * @example Get the TodoSlices.COMPLETE slice
1073
- ```
1074
- source.getSlice(TodoSlices.COMPLETE);
1075
- ```
1076
- */
1077
- getSlice(label) {
1078
- return this.slices.get(label);
1079
- }
1080
- /**
1081
- * Post (Add a new) element(s) to the store.
1082
- * @param e An indiidual entity or an array of entities
1083
- * @example Post a `todo`.
1084
- ```
1085
- store.post(todo);
1086
- ```
1087
- */
1088
- post(e) {
1089
- if (!Array.isArray(e)) {
1090
- const guid = e[this.GUID_KEY]
1091
- ? e[this.GUID_KEY]
1092
- : GUID();
1093
- e[this.GUID_KEY] = guid;
1094
- this.entries.set(guid, e);
1095
- this.updateIDEntry(e);
1096
- Array.from(this.slices.values()).forEach(s => {
1097
- s.post(e);
1098
- });
1099
- //Create a new array reference to trigger Angular change detection.
1100
- let v = [...Array.from(this.entries.values())];
1101
- const delta = { type: "Post" /* ActionTypes.POST */, entries: [e] };
1102
- this.notifyAll(v, delta);
1103
- }
1104
- else {
1105
- this.postA(e);
1106
- }
1107
- }
1108
- /**
1109
- * Post elements to the store.
1110
- * @param ...e
1111
- * @example Post two `Todo` instances.
1112
- ```
1113
- store.post(todo1, todo2);
1114
- ```
1115
- */
1116
- postN(...e) {
1117
- e.forEach(e => {
1118
- const guid = e[this.GUID_KEY]
1119
- ? e[this.GUID_KEY]
1120
- : GUID();
1121
- e[this.GUID_KEY] = guid;
1122
- this.entries.set(guid, e);
1123
- this.updateIDEntry(e);
1124
- });
1125
- Array.from(this.slices.values()).forEach(s => {
1126
- s.postA(e);
1127
- });
1128
- //Create a new array reference to trigger Angular change detection.
1129
- let v = [...Array.from(this.entries.values())];
1130
- const delta = { type: "Post" /* ActionTypes.POST */, entries: e };
1131
- this.notifyAll(v, delta);
1132
- }
1133
- /**
1134
- * Post (Add) an array of elements to the store.
1135
- * @param e
1136
- * @example Post a `Todo` array.
1137
- ```
1138
- store.post([todo1, todo2]);
1139
- ```
1140
- */
1141
- postA(e) {
1142
- this.postN(...e);
1143
- }
1144
- /**
1145
- * Put (Update) an element.
1146
- * @param e
1147
- * @example Put a Todo instance.
1148
- ```
1149
- store.put(todo1);
1150
- ```
1151
- */
1152
- put(e) {
1153
- if (!Array.isArray(e)) {
1154
- let id = e[this.GUID_KEY];
1155
- this.entries.set(id, e);
1156
- this.updateIDEntry(e);
1157
- let v = [...Array.from(this.entries.values())];
1158
- this.notify.next(v);
1159
- const delta = { type: "Put" /* ActionTypes.PUT */, entries: [e] };
1160
- this.notifyDelta.next(delta);
1161
- Array.from(this.slices.values()).forEach(s => {
1162
- s.put(e);
1163
- });
1164
- }
1165
- else {
1166
- this.putA(e);
1167
- }
1168
- }
1169
- /**
1170
- * Put (Update) an element or add an element that was read from a persistence source
1171
- * and thus already has an assigned global id`.
1172
- * @param e
1173
- * @example Put Todo instances.
1174
- ```
1175
- store.put(todo1, todo2);
1176
- ```
1177
- */
1178
- putN(...e) {
1179
- this.putA(e);
1180
- }
1181
- /**
1182
- * Put (Update) the array of elements.
1183
- * @param e
1184
- * @example Put Todo instances.
1185
- ```
1186
- store.put([todo1, todo2]);
1187
- ```
1188
- */
1189
- putA(e) {
1190
- e.forEach(e => {
1191
- let guid = e[this.GUID_KEY];
1192
- this.entries.set(guid, e);
1193
- this.updateIDEntry(e);
1194
- });
1195
- //Create a new array reference to trigger Angular change detection.
1196
- let v = [...Array.from(this.entries.values())];
1197
- this.notify.next(v);
1198
- const delta = { type: "Put" /* ActionTypes.PUT */, entries: e };
1199
- this.notifyDelta.next(delta);
1200
- Array.from(this.slices.values()).forEach(s => {
1201
- s.putA(e);
1202
- });
1203
- }
1204
- /**
1205
- * Delete (Update) the array of elements.
1206
- * @param e
1207
- * @example Delete todo1.
1208
- ```
1209
- store.delete(todo1]);
1210
- ```
1211
- */
1212
- delete(e) {
1213
- if (!Array.isArray(e)) {
1214
- this.deleteActive(e);
1215
- const guid = e[this.GUID_KEY];
1216
- this.entries.delete(guid);
1217
- this.deleteIDEntry(e);
1218
- Array.from(this.slices.values()).forEach(s => {
1219
- s.entries.delete(guid);
1220
- });
1221
- //Create a new array reference to trigger Angular change detection.
1222
- let v = [...Array.from(this.entries.values())];
1223
- const delta = { type: "Delete" /* ActionTypes.DELETE */, entries: [e] };
1224
- this.notifyAll(v, delta);
1225
- Array.from(this.slices.values()).forEach(s => {
1226
- s.delete(e);
1227
- });
1228
- }
1229
- else {
1230
- this.deleteA(e);
1231
- }
1232
- }
1233
- /**
1234
- * Delete N elements.
1235
- * @param ...e
1236
- * @example Put Todo instances.
1237
- ```
1238
- store.delete(todo1, todo2);
1239
- ```
1240
- */
1241
- deleteN(...e) {
1242
- this.deleteA(e);
1243
- }
1244
- /**
1245
- * Delete N elements.
1246
- * @param ...e
1247
- * @example Put Todo instances.
1248
- ```
1249
- store.delete(todo1, todo2);
1250
- ```
1251
- */
1252
- deleteA(e) {
1253
- e.forEach(e => {
1254
- this.deleteActive(e);
1255
- const guid = e[this.GUID_KEY];
1256
- this.entries.delete(guid);
1257
- this.deleteIDEntry(e);
1258
- Array.from(this.slices.values()).forEach(s => {
1259
- s.entries.delete(guid);
1260
- });
1261
- });
1262
- //Create a new array reference to trigger Angular change detection.
1263
- let v = [...Array.from(this.entries.values())];
1264
- const delta = { type: "Delete" /* ActionTypes.DELETE */, entries: e };
1265
- this.notifyAll(v, delta);
1266
- Array.from(this.slices.values()).forEach(s => {
1267
- s.deleteA(e);
1268
- });
1269
- }
1270
- /**
1271
- * Delete elements by {@link Predicate}.
1272
- * @param p The predicate.
1273
- * @example Put Todo instances.
1274
- ```
1275
- store.delete(todo1, todo2);
1276
- ```
1277
- */
1278
- deleteP(p) {
1279
- const d = [];
1280
- Array.from(this.entries.values()).forEach(e => {
1281
- if (p(e)) {
1282
- d.push(e);
1283
- const id = e[this.GUID_KEY];
1284
- this.entries.delete(id);
1285
- this.deleteActive(e);
1286
- this.deleteIDEntry(e);
1287
- }
1288
- });
1289
- //Create a new array reference to trigger Angular change detection.
1290
- let v = [...Array.from(this.entries.values())];
1291
- const delta = { type: "Delete" /* ActionTypes.DELETE */, entries: d };
1292
- this.notifyAll(v, delta);
1293
- Array.from(this.slices.values()).forEach(s => {
1294
- s.deleteA(d);
1295
- });
1296
- }
1297
- /**
1298
- * If the entity has the `id` key initialized with a value,
1299
- * then also add the entity to the `idEntries`.
1300
- *
1301
- * @param e The element to be added to the `idEntries`.
1302
- */
1303
- updateIDEntry(e) {
1304
- if (e[this.ID_KEY]) {
1305
- this.idEntries.set(e[this.ID_KEY], e);
1306
- }
1307
- }
1308
- /**
1309
- * If the entity has the `id` key initialized with a value,
1310
- * then also delete the entity to the `idEntries`.
1311
- *
1312
- * @param e The element to be added to the `idEntries`.
1313
- */
1314
- deleteIDEntry(e) {
1315
- if (e[this.ID_KEY]) {
1316
- this.idEntries.delete(e[this.ID_KEY]);
1317
- }
1318
- }
1319
- /**
1320
- * Resets the store and all contained slice instances to empty.
1321
- * Also perform delta notification that sends all current store entries.
1322
- * The ActionType.RESET code is sent with the delta notification. Slices
1323
- * send their own delta notification.
1324
- *
1325
- * @example Reset the store.
1326
- ```
1327
- store.reset();
1328
- ```
1329
- */
1330
- reset() {
1331
- const delta = {
1332
- type: "Reset" /* ActionTypes.RESET */,
1333
- entries: Array.from(this.entries.values())
1334
- };
1335
- this.notifyAll([], delta);
1336
- this.entries = new Map();
1337
- Array.from(this.slices.values()).forEach(s => {
1338
- s.reset();
1339
- });
1340
- }
1341
- /**
1342
- * Call all the notifiers at once.
1343
- *
1344
- * @param v
1345
- * @param delta
1346
- */
1347
- notifyAll(v, delta) {
1348
- super.notifyAll(v, delta);
1349
- this.notifyLoading.next(this.loading);
1350
- }
1351
- }
1352
-
1353
- class OStore {
1354
- constructor(start) {
1355
- /**
1356
- * Map of Key Value pair entries
1357
- * containing values store in this store.
1358
- */
1359
- this.entries = new Map();
1360
- /**
1361
- * Map of replay subject id to `ReplaySubject` instance.
1362
- */
1363
- this.subjects = new Map();
1364
- if (start) {
1365
- this.S = start;
1366
- const keys = Object.keys(start);
1367
- keys.forEach((k) => {
1368
- const ovr = start[k];
1369
- this.post(ovr, ovr.value);
1370
- ovr.obs = this.observe(ovr);
1371
- });
1372
- }
1373
- }
1374
- /**
1375
- * Reset the state of the OStore to the
1376
- * values or reset provided in the constructor
1377
- * {@link OStoreStart} instance.
1378
- */
1379
- reset() {
1380
- if (this.S) {
1381
- const keys = Object.keys(this.S);
1382
- keys.forEach((k) => {
1383
- const ovr = this.S[k];
1384
- this.put(ovr, ovr.reset ? ovr.reset : ovr.value);
1385
- });
1386
- }
1387
- }
1388
- /**
1389
- * Clear all entries
1390
- */
1391
- clear() {
1392
- this.entries.clear();
1393
- }
1394
- /**
1395
- * Set create a key value pair entry and creates a
1396
- * corresponding replay subject instance that will
1397
- * be used to broadcast updates.
1398
- *
1399
- * @param key The key identifying the value
1400
- * @param value The value
1401
- */
1402
- post(key, value) {
1403
- this.entries.set(key, value);
1404
- this.subjects.set(key, new ReplaySubject(1));
1405
- //Emit immediately so that Observers can receive
1406
- //the value straight away.
1407
- const subject = this.subjects.get(key);
1408
- if (subject) {
1409
- subject.next(value);
1410
- }
1411
- }
1412
- /**
1413
- * Update a value and notify subscribers.
1414
- *
1415
- * @param key
1416
- * @param value
1417
- */
1418
- put(key, value) {
1419
- this.entries.set(key, value);
1420
- const subject = this.subjects.get(key);
1421
- if (subject) {
1422
- subject.next(value);
1423
- }
1424
- }
1425
- /**
1426
- * Deletes both the value entry and the corresponding {@link ReplaySubject}.
1427
- * Will unsubscribe the {@link ReplaySubject} prior to deleting it,
1428
- * severing communication with corresponding {@link Observable}s.
1429
- *
1430
- * @param key
1431
- */
1432
- delete(key) {
1433
- this.entries.delete(key);
1434
- this.subjects.delete(key);
1435
- const subject = this.subjects.get(key);
1436
- if (subject) {
1437
- subject.unsubscribe();
1438
- }
1439
- }
1440
- /**
1441
- * Observe changes to the values.
1442
- *
1443
- * @param key
1444
- * @return An {@link Observable} of the value
1445
- */
1446
- observe(key) {
1447
- return this.subjects.get(key).asObservable();
1448
- }
1449
- /**
1450
- * Check whether a value exists.
1451
- *
1452
- * @param key
1453
- * @return True if the entry exists ( Is not null or undefined ) and false otherwise.
1454
- */
1455
- exists(key) {
1456
- return this.entries.get(key) != null;
1457
- }
1458
- /**
1459
- * Retrieve a snapshot of the
1460
- * value.
1461
- *
1462
- * @param key
1463
- * @return A snapshot of the value corresponding to the key.
1464
- */
1465
- snapshot(key) {
1466
- return this.entries.get(key);
1467
- }
1468
- /**
1469
- * Indicates whether the store is empty.
1470
- * @return true if the store is empty, false otherwise.
1471
- */
1472
- isEmpty() {
1473
- return Array.from(this.entries.values()).length == 0;
1474
- }
1475
- /**
1476
- * Returns the number of key value pairs contained.
1477
- *
1478
- * @return the number of entries in the store.
1479
- */
1480
- count() {
1481
- return Array.from(this.entries.values()).length;
1482
- }
1483
- }
1484
-
1485
- /*
1486
- * Public API Surface of slice
1487
- */
1488
-
1489
- /**
1490
- * Generated bundle index. Do not edit.
1491
- */
1492
-
1493
- export { AbstractStore, ESTORE_CONFIG_DEFAULT, EStore, Entity, GUID, OStore, SCROLL_UP_DEBOUNCE_TIME_20, SEARCH_DEBOUNCE_TIME_300, Slice, attachGUID, attachGUIDs, deepCopy, distinct, excludeKeys, getActiveValue, mapEntity, onFilteredEvent, scrollingUp, search, shallowCopy, unique };
1494
- //# sourceMappingURL=fireflysemantics-slice.mjs.map