rxjs-idsets
v3.0.0
Published
A number of Typescript Sets that publish changes in their state (entries added, created, updated or deleted) using RxJS Observables.
Downloads
7
Maintainers
Readme
rxjs-idsets
If you like to work with Typescript, RxJS and whished you could observe changes to Set
or Map
like objects or classes, this library might be for you because that is exactly what it does.
Set operations
It also provides 'live' union, intersection and difference set operations. I.e. if a source set of a set operation changes, the result of that set operation is updated automatically.
Opinionated?
It requires values that implement an IdObject
interface (which is simply an object with an id
property, see the introduction for more details).
It encourages the use of immutable objects as it only detects updates of existing values if the new value is a different object (the id
is the same, but for the IdObject newIdValue !== existingIdValue
is true).
Otherwise it provides all the functionality (and more) from Typescript Sets and adds several RxJS Observables to keep you informed of changes.
Dependencies
Apart from Typescripts tslib
there are no external dependencies.
Table of contents
Introduction
This library provides a number of IdSet
classes, which are observable extensions of the javascript Set
object.
The values in an IdSet
are objects that implement the IdObject
interface:
interface IdObject<Id = string> {
id: Id
}
The uniqueness of a value in an IdSet is defined by the value of its id. It functions as a javascript Map
where the key is the id
property of the value (under water it actually is).
If you add an element to an IdSet with an id that already exists in the set, the existing value will be updated to the new value and the change to the IdSet will be published through the appropriate Observables.
This library provides the following IdSet
classes:
IdSet
is the basic observableSet
UnionIdSet
is a computedIdSet
that perfoms the union set operation. It computes and automatically updates the union of multiple sourceIdSet
's.IntersectionIdSet
is a computedIdSet
that performs the intersection set operation. It computes and automatically updates the intersection of multiple sourceIdSet
's.SubtractionIdSet
is a computedIdSet
that performs the subtract set operation. It computes and automatically updates the subtraction of multiple subtractIdSet
's from the sourceIdSet
.ContainerIdSet
is anIdSet
where you add values to one or more containers. You can add, remove and update containers and values independant of one another and be notified of changes. It is a powerful concept, see theContainerIdSet
section for more details.ReadOnlyIdSet
is a readonly version of theIdSet
. It can be used as base class to create your own computedIdSet
's.
Changes to the above classes can be observed with the following RxJS Observable properties:
all$
returns all values that are currently in the set.create$
returns only the new values, values with an id that does not already exist in the set.update$
returns only the changed values, values with an id that does exist in the set and is not a reference to to the existing value (needs to benewvalue !== existingvalue
).delete$
returns the values that will be deleted from the set.
For convenience the following Observable properties are also provided:
add$
returns all values that will be added (both created and updated) to the set.allAdd$
returns all values currently in the set and then switches toadd$
.delta$
returns a structure that can contain one or more create'd, update'd and/or delete'd value's.
This library was created as a more 'pure' Observable version of my rxjs-supersets library.
For the latest changes and the current version see the Change log.
Examples
Nothing explains a library better than a few well documented examples, so here they come (I hope the examples are indeed documented enough).
The included examples are basic examples on how to use the IdSet
classes, a more elaborate 'real life' example is planned for the future.
Example 1, IdSet
The IdSet
is the basic class this library is built upon, it provides all normal Typescript Set operations
// create a new set containing 3 values, values implement IdObject interface
const exampleSet = new IdSet([value1, value2, value3]);
// subscribe to an observable that emits all values added to the set
exampleSet.add$.subscribe(value => console.log('created or updated', value));
// add a new value to the set
// where exampleSet already contains [value1, value2, value3]
exampleSet.add(value4);
// exampleset now contains [value1, value2, value3, value4]
// the exampleset.create$, .add$ and .allAdd$ observables publish value4
// the exampleset.delta$ and allDelta$ publish { create: value4 }
// update an existing value
exampleSet.add(value1update);
// exampleset now contains [value1update, value2, value3, value4]
// the exampleset.update$, .add$ and .allAdd$ observables publish value1update
// the exampleset.delta$ and allDelta$ publish { update: value1update }
// delete a value from the set
exampleSet.delete(value1.id);
// exampleset now contains [value2, value3, value4]
// the exampleset.delete$ observable publishes value1
// the exampleset.delta$ and .allDelta$ publish { delete: value1 }
// replace the contents of the set
exampleSet.replace([value1, value2update, value3]);
// exampleset now contains [value1, value2update, value3]
// the exampleset.create$ observable publishes value1
// the exampleset.update$ observable publishes value2update
// the exampleset.add$ and .allAdd$ observables publish value1 and value2update
// the exampleset.delete$ observable publishes value4
// delete all content from the set
exampleSet.clear();
// exampleset now contains []
// the exampleset.delete$ observable publishes value1, value2update and value3
// close all subscritions to the set
exampleSet.complete();
// completes all existing and new subscriptions ('unsubscribes' them)
// all existing and new subscriptions will no longer receive added, updated or deleted values
A more complete example for the IdSet can be found in example1.ts
Example 2, UnionIdSet
The UnionIdSet
is a computed IdSet that is the live representation of the mathematical union of two
or more IdSets.
Updates in one of the source sets are immediately processed in the union set. The UnionIdSet observables publish the changes.
const set1 = new IdSet([value1, value2]);
const set2 = new IdSet([value2, value3]);
const unionSet = new UnionIdSet([set1, set2]);
// unionSet: [value1, value2, value3]
// subscribe to allAdd$
unionSet.allAdd$.subscribe(value => console.log('already present, created or updated', value));
set1.add(value4);
// unionSet: [value1, value2, value3, value4] value4 added to the union
set1.delete(value2);
// unionSet: [value1, value2, value3, value4] because value2 is still in set2
set1.delete(value1);
// unionSet: [value2, value3, value4] because value1 is not in another union source it is deleted
Example 3, IntersectionIdSet
The IntersectionIdSet
is a computed IdSet that is the live representation of the mathematical intersection of two or more IdSets.
Updates in one of the source sets are immediately processed in the intersection set. The IntersectionIdSet observables publish the changes.
const set1 = new IdSet([value1, value2]);
const set2 = new IdSet([value2, value3]);
const intersectionSet = new IntersectionIdSet([set1, set2]);
// unionSet: [value2]
// subscribe to delete$
intersectionSet.delete$.subscribe(value => console.log('deleted', value));
set1.add(value4);
// intersectionSet: [value2] value4 is not in all sources, so not added to the intersection
set1.delete(value1);
// intersectionSet: [value2] because value3 was not in the intersection
set1.add(value3);
// intersectionSet: [value2, value3] because value3 is now in all intersection sources
Example 4, DifferenceIdSet
The DifferenceIdSet
is a computed IdSet that is the live representation of the a source IdSet from which the elements contained in one or more other IdSets are subtracted.
Updates in one of the sets are immediately processed in the difference set. The DifferenceIdSet observables publish the changes.
const sourceSet = new IdSet([value1, value2, value3])
const subtractSet1 = new IdSet([value1]);
const subtractSet2 = new IdSet([value2]);
const differenceResultSet = new DifferenceIdSet(sourceSet, [set1, set2]);
// differenceResultSet: [value3]
// subscribe to create$
differenceResultSet.create$.subscribe(value => console.log('created new', value));
differenceResultSet.add(value4);
// differenceResultSet: [value3, value4] because value4 is not present in one of the subtractSets
subtractSet1.add(value4);
// differenceResultSet: [value3] because value4 is now present in one of the subtractSets
subtractSet1.delete(value1);
// differenceResultSet: [value1, value3] because value1 is no longer present in one of the subtractSets
Example 5, ContainerIdSet
The ContainerIdSet
is an IdSet that consists of named subsets
const container = new ContainerIdSet();
// add value1 to the IdSet 'set1', create the IdSet if it does not altready exist
container.add(value1, 'set1');
// create a new empty IdSet inside the container if a set with that name does not exist
const set2 = container.getSet('set2');
// if the set already contains values get the existing IdSet
const set1 = container.getSet('set1');
// you can add multiple values to multiple IdSets at once if you want to
container.add([value2, value3], ['set1', 'set2', 'set3']);
// container now contains [value1, value2 value3]
// set1 now contains [value1, value2, value3]
// set2 now contains [value2, value3]
// there is also a 'set3' in the container containing [value2, value3]
// add or replace value3 only in the specified IdSets
// remove from all other sets if it already exists there
container.addExclusive(value3, ['set2', 'set3']);
// container still contains [value1, value2 value3]
// set1 now contains [value1, value2]
// set2 now contains [value2, value3]
// there is now also a 'set3' in the container with [value3]
container.delete(value2.id, 'set2');
// set2 now contains [value3]
container.delete(value1.id);
// container now contains [value2 value3]
// set2 now contains []
container.setsBelongedTo(value3.id);
// should return a Set containing ['set1', 'set3']
// there are methods to create union, intersection and subtraction sets from sets
const unionIdSet = container.union(['set1', 'set2', 'set3']);
const intersectionIdSet = container.intersection(['set1', 'set2', 'set3']);
const differenceIdSet = container.difference('set1', ['set2', 'set3']);
const complementIdSet = container.complement(['set2', 'set3']);
// complement returns a differenceIdSet of the specified sets with the container
Reference
The all important 'if all else fails, read the manual' command reference.
I have tried to make the IdSets
as self explaining as possible from within an IDE
(VS Code in my case), but this reference might help.
This reference is a 'minimal' reference as in only Class specific properties and methods and overridden methods with changed or extended functionality will be described here, unchanged parent methods and properties will be described in the parent class only.
BaseIdSet
The BaseIdSet
is not very useful in itself, but it contains all the basic functionality needed for the IdSet
and other subclasses to function, it can be used as a base for your own custom IdSets.
It is an IdSet that is readonly. This means that it provides no way to change its contents by itself. when you create a subclass, you can use the addValue
and deleteId
protected methods to add and delete values and automatically publish relevant updates through the obserbvables.
Class specific public properties and methods
The methods and properties that define the basic functionality of the IdSet
classes are described below.
constructor(values?: Iterable<IdValue>, cloneValues = false)
- Creates a new Set based the values given. If no values are supplied an empty Set is created.
- It will deep clone the values using
structuredClone()
ifcloneValues
is true.
all$: Observable<IdValue>
- Observable that returns all values currently in the set one by one and then completes.
create$: Observable<IdValue>
- If a new value is added to the Set, this Observable returns that value (placeholder to be used by subclasses).
update$: Observable<IdValue>
- If an existing value is updated in the set, this Observable returns that value (placeholder to be used by subclasses).
delete$: Observable<IdValue>
- If a value is deleted from the set, this Observable returns that value (placeholder to be used by subclasses).
add$: Observable<IdValue>
- If a value is added to the set (created or updated), this Observable returns that value (placeholder to be used by subclasses).
allAdd$: Observable<IdValue>
- Observable that returns all values currently in the set one by one and hands it over to the
add$
. - Useful observable when you need to monitor all additions from the beginnig of the Set, but you start when the Set is already populated with values.
delta$(): Observable<Readonly<DeltaValue<IdValue>>>
- observable that returns a
DeltaValue
structure. ADeltaValue
structure combines one or morecreate
,update
anddelete
values.
allDelta$(): Observable<Readonly<DeltaValue<IdValue>>>
- observable that returns a
DeltaValue
structure. ADeltaValue
structure combines one or morecreate
,update
anddelete
values. The first result contains all current values of the set in thecreate
property of theDeltaValue
.
observed
true
when the BaseIdSet is observed (subscribed to),false
otherwise.
complete()
- Completes all Observables in the set, modifications to the set will no longer be propageted through these observables. Only the
all$
Observable will still function.
Standard Set
properties and methods
The methods and properties that are more or less identical to the default Set
classes are given below. No description apart from the type annotation is given.
size: number
values(): IterableIterator<IdValue>
forEach(fn: (...) => void)
get(key: Id): IdValue
has(key: Id): boolean
keys(): IterableIterator<Id>
entries(): IterableIterator<[Id, IdValue]>
[Symbol.iterator](): IterableIterator<IdValue>
protected methods
There are a few protected methods that can be used when creating your own IdSet subclass.
protected addValue(value: IdValue)
- Adds a single value to the set, updating observables when needed.
protected deleteId(id: Id)
- Deletes a single value from the set, updating observables when needed.
protected clear()
- Clears all values from the set, updating observables when needed.
protected pause()
- Pauses updating observables, but keeps track of all changes since paused.
protected resume()
- Publishes the changes since paused to the observables and resumes updating observables.
IdSet
This is the basic 'bread and butter' class of the IdSet
classes (that is why it is called IdSet
).
It extends the BaseIdSet
.
See the example1.ts file for a complete example of the IdSet
.
Additional properties and methods
The IdSet
class extends the BaseIdClass
with the methods described below.
add(values: OneOrMore<IdValue>)
- Add one or more values to the set.
delete(ids: OneOrMore<Id>): boolean
- Deletes one or more values from the set.
replace(values: OneOrMore<IdValue>, cloneValues = false)
- Replaces the existing set with the defined values.
- It will deep clone the values using
structuredClone()
ifcloneValues
is true.
clear()
- Removes all existing values from the set.
- Alle existing values are deleted on by one and each deleted value is published to the corresponding Observables.
pause()
- Pauses updating observables, but keeps track of all changes since paused.
resume()
- Publishes the changes since paused to the observables and resumes updating observables.
UnionIdSet
The UnionIdSet
is a live union of the source IdSets defined in the constructor.
It extends the BaseIdSet
.
The UnionIdSet
is a 'live' representation of that union. I.e. if the content of a source IdSet changes it automatically updates the content of the UnionIdSet
, see the example below:
source1 = new IdSet([value1, value2]);
source2 = new IdSet([value2, value3]);
unionIdSet = new UnionIdSet([source1, source2]); //contains [value1, value2, value3]
source2.add(value4);
// unionIdSet now contains [value1, value2, value3, value4]
Additional properties and methods
constructor(sourceSets: Iterable<BaseIdSet>)
- Define the source
IdSets
theUnionIdSet
operates upon at construction.
readonly sourceSets: Iterable<BaseIdSet>
- The sourceSets the
UnionIdSet
operates upon
IntersectionIdSet
The IntersectionIdSet
is a live intersection of the source IdSets defined in the constructor.
It extends the BaseIdSet
.
The IntersectionIdSet
is a 'live' representation of that intersection. I.e. if the content of a source IdSet changes it automatically updates the content of the IntersectionIdSet
, see the example below:
source1 = new IdSet([value1, value2]);
source2 = new IdSet([value2, value3]);
intersectionIdSet = new IntersectionIdSet([source1, source2]); //contains [value2]
source2.add(value1);
// intersectionIdSet now contains [value1, value2]
Additional properties and methods
constructor(sourceSets: Iterable<BaseIdSet>)
- Define the source
IdSets
theIntersectionIdSet
operates upon at construction.
readonly sourceSets: Iterable<BaseIdSet>
- The sourceSets the
IntersectionIdSet
operates upon
DifferenceIdSet
The DifferenceIdSet
is the live difference between the source IdSet and other sets defined in the constructor.
It extends the BaseIdSet
.
The DifferenceIdSet
is a 'live' representation of that difference. I.e. if the content of a source or other IdSet changes it automatically updates the content of the DifferenceIdSet
, see the example below:
source = new IdSet([value1, value2, value3, value4]);
other1 = new IdSet([value3]);
other2 = new IdSet([value3, value4]);
intersectionIdSet = new DifferenceIdSet(source, [other1, other2]); //contains [value1, value2]
other1.add(value1);
// DifferenceIdSet now contains [value2]
Additional properties and methods
constructor(sourceSet: IdSet, otherSets: Iterable<BaseIdSet>)
- Define the source and other
IdSets
theDifferenceIdSet
operates upon at construction.
readonly sourceSet: BaseIdSet
- The sourceSet the
DifferenceIdSet
operates upon
readonly othersets: Iterable<BaseIdSet>
- The otherSets the
DifferenceIdSet
operates upon
ContainerIdSet
A ContainerIdSet
consists of one or more IdSets
identified by a SetId
.
The ContainerIdSet
itself is also an IdSet
where the values are the union of all the sets it contains.
It extends the IdSet
.
It can add
, delete
, replace
values etc. and subscribe to changes with the create$
, delete$
etc.
Observables.
A value in a ContainerIdSet
only exists if the value is alse present in one or more of its contained
sets.
A few lines of example code:
const container = new ContainerIdSet();
container.add([value1, value2], 'set1');
container.add(value3, ['set2', 'set3']);
const set1 = container.getSet('set1');
set1.delete(value2.id);
set1.allAdd$.subscribe(value => console.log(`Added to set2: ${value}`));
Additional and overridden properties and methods
constructor(values?: OneOrMore<[IdValue, Iterable<SetId>]>, cloneValues = false)
- You can use the
export()
method to create values for the constructor to duplicate an existingContainerIdSet
. - It will deep clone the values using
structuredClone()
ifcloneValues
is true.
sets: ReadonlyMap<SetId, IdSet>
- A Map containing all SetIds with their corresponding IdSet.
add(values: OneOrMore<IdValue>, setIds?: OneOrMore<SetId>)
- Add one or more values to the specified sets.
delete(ids: OneOrMore<Id>, setIds?: OneOrMore<SetId>)
- Delete values, specified by their Id from the specified sets.
- If no sets are specified, they are removed from all sets in the
ContainerIdSet
. - If a value no longer belongs to any set in the
ContainerIdSet
it will also be removed from theContainerIdSet
.
replace(values: OneOrMore<[IdValue, Iterable<SetId>]>, cloneValues = false)
- Replace the contents of the
ContainerIdSet
with the specified values. - If no values are specified the
ContainerIdSet
and all its existing sets will be cleared.
export(): IterableIterator<[IdValue, Iterable<SetId>]>
- Export the contents of the
ContainerIdSet
in a format that theconstructor
andreplace
method understand.
addExclusive(values: OneOrMore<IdValue>, sets?: OneOrMore<SetId>)
- Add the values only to the specified sets, remove from all other sets.
complete()
- Completes all subscriptions of this
ContainerIdSet
and all category IdSets
setsBelongedTo(id: Id): ReadonlySet<SetId> | undefined
- Return a
Set
containing the SetIds for the IdSets the value with this id is member of or undefined if it is not member of any contained set.
clear(sets?: OneOrMore<SetId>)
- Clear specified sets, if no set is specified all contained sets are cleared.
- If a value no longer exists in any set it will also be removed from the
ContainerIdSet
.
detachSet(setIds: OneOrMore<SetId>)
- Remove a contained set from the collection of sets.
- Remove all values that are not present in another contained set from the
ContainerIdSet
getSet(setId: SetId): IdSet
- Uses the existing IdSet for the SetId if the set exists.
- Creates a new empty IdSet for the SetId if the set does not exist.
- Returns the IdSet of the specified SetId.
union(sets: Iterable<SetId>)
- Return a UnionIdSet that is the union of the specified sets
intersection(sets: Iterable<SetId>)
- Return an IntersectionIdSet that is the intersection of the specified sets
difference(category: SetId, subtractedCategories: OneOrMore<SetId>)
- Return a DifferenceIdSet that subtracts the other sets from the specified category
complement(subtractedCategories: OneOrMore<SetId>)
- Return a ComplementIdSet that returns a set containing the CategorizedSet minus the subtracted sets