@ocelotjungle/collections
v1.0.1
Published
Enhanced collections
Downloads
2
Maintainers
Readme
@ocelotjungle/collections
This package contains 7 collections that are subtypes of a standard Set and Map, and one complex collection DataPond, which can be used to manage some micro-lakes of data, relating entities to each other.
Collections
ComplexSet<K - key, I - item>
This collection acts like a common Set except ComplexSet allows to use object values. It compares equality not of items, but of something returned by selector (keys). Actually, this is just a map with selector.
Creating a ComplexSet with item type { foo: number, bar: string }
and key type number
. First argument is a selector, that selects foo from item type. In this case, foo
is a key and it will refleсt equality of items, so if a.foo == b.foo, then a == b. If updateExisting
is true, then adding an item with already existing key will replace the existing item.
new ComplexSet<number, { foo: number, bar: string }>(({ foo }) => foo, [updateExisting])
.add({ foo: 42, bar: "question" })
.add({ foo: 42, bar: "not question" }) // replaces previous if updateExisting
.add({ foo: 100, bar: "IOO" })
EnsuredMap<K - key, V - value>
Does nothing except get
returns X
instead of X | undefined
. No real checking, no safety, only types. Use it only when you are absolutely sure here is a value for key.
new EnsuredMap<number, string>([entries])
.set(42, "question") // { 42 => "question" }
.get(42) // "question"
.get(43) // undefined as V
ArrayEnsuredMap<K - key, V - value>
The same as EnsuredMap, but stores V[]. Also allows to push/clear array by a key. Other array functions are not implemented.
new ArrayEnsuredMap<number, string>([entries])
.set(42, []) // { 42 => [] }
.push(42, "question") // { 42 => ["question"] }
.clear(42) // { 42 => [] }
.clear() // { }
StrongMap<K - key, V - value>
This collection is a map that guarantees an existence of required key by using a callback that creates some default V.
new StrongMap<number, string>(() => "default value", [entries])
.set(42, "question") // { 42 => "question" }
.get(42) // "question"
.get(43) // "default value"
ArrayStrongMap<K - key, V - value>
The same as StrongMap, but stores V[] and default value is always an empty array. Also allows to push/clear array by a key. Other array functions are not implemented.
new ArrayStrongMap<number, string>([entries])
.push(42, "foo") // { 42 => ["foo"] }
.get(42) // ["foo"]
.get(43) // []
ComplexKeyMap<K - key, V - value>
This collection is a Map that allows to correctly use complex objects. When you use get
, it won't compare object's reference equality, but its structure. Also, likely StrongMap, guarantees that get
returns a defined value. Requires passing default value callback.
new ComplexKeyMap<{ foo: number, bar: string }, boolean>(() => false, [entries])
.set({ foo: 42, bar: "question" }, true) // { { foo: 42, bar: "question" } => true }
.get({ foo: 42, bar: "question" }) // true
.get(43) // false
ArrayComplexKeyMap<K - key, V - value>
The same as ComplexKeyMap, but stores V[] and default value is always an empty array. Also allows to push/clear array by a key. Other array functions are not implemented.
new ArrayComplexKeyMap<{ foo: number, bar: string }, boolean>([entries])
.push({ foo: 42, bar: "question" }, true) // { { foo: 42, bar: "question" } => [true] }
.get({ foo: 42, bar: "question" }) // [true]
.get({ foo: 43, bar: "no value" }) // []
DataPond<P - pond scheme>
This collection is like a micro-database with preconfigured JOINs. It realizes a crude state machine on types for user's convenience (that makes impossible to correctly extend this class, preserving the state machine).
Creating
DataPond.create<P>(idExtractors)
P
is a scheme of data, it looks like{ [name]: object }
idExtractors
is an object like{ [name]: extractor }
extractor
is a selector function that accepts corresponding type object and returns some identifier
Example:
DataPond.create<{ foo: { bar: number, baz: Date } }>({ foo: ({ bar }) => bar })
foo
is a pond entity, it's type{ bar: number, baz: Date }
({ bar }) => bar
is anextractor
, it selectsbar
fromfoo
Using
Add entities (add(name, entity)
)
Initially the pond has state 0
(next DataPond<0>
), that means no cache.
Adding one entity returns DataPond<1>
, that means there's owner
in the cache.
Adding one more entity returns DataPond<2>
, that means there're owner
and item
in the cache.
Adding more entities won't affect state of the pond. The pond "remembers" only last 2 added entities.
Relate entities (relate(item, owner)
or relate({ owner, item })
)
owner
and item
can be either pointers to the entity ([entity_name, entity_id]) or arrays of pointers or even undefined
, if applicable (depending on cache size).
DataPond<0>
.relate
:
- requires both
owner
anditem
; - returns
DataPond<0>
;
DataPond<1>
.relate
:
- requires either:
- passing
item
inrelate(item, owner)
and in this caseowner
is optional (if pass noowner
, will be usedowner
from cache) - or passing
owner
oritem
inrelate({ owner, item })
and in this case the second argument will be optional (will be used from cache);
- passing
- returns
DataPond<1>
, that allows add one entity and then call multiplerelate
in chain with different arguments;
DataPond<2>
.relate
:
- requires no arguments and if called without them, will use
owner
anditem
from cache; - passing one or both arguments will lead to using them instead of cache;
- returns
DataPond<0>
;
Fetching entities (fetch(name, id)
or fetch(name, id, array_of_names)
)
fetch(name, id)
returns entity with corresponding name and id.
fetch(name, id, array_of_names)
acts like fetch(name, id)
, but augments the result with related entities, which names are in array_of_names
: { _[name]: related_entity[] }
Examples
type Foo = { id: number, value: string };
type Bar = { name: string };
type Schema = { foo: Foo, bar: Bar };
// Creating
const dp = new DataPond<Schema>({
foo: ({ id }) => id,
bar: ({ name }) => name,
});
// Adding
dp.add("foo", { id: 42, value: "question" });
dp.add("bar", { name: "baz" });
dp.relate(["bar", "baz"], ["foo", 42]); // relating item to owner, foo-42 owns bar-baz
dp
.add("foo", { id: 43, value: "not question" }) // state 1, owner in cache
.relate(["bar", "baz"]) // relates item to cached owner, foo-43 also owns bar-baz
dp
.add("foo", { id: 44, value: "idk" }) // state 1, owner in cache
.add("bar", { name: "buzz" }) // state 2, owner and item in cache
.relate() // relates cached item to cached owner, foo-44 owns bar-buzz
// Fetching
dp.fetch("foo", 42) // { id: 42, value: "question" }
dp.fetch("foo", 44, ["bar"]) // { id: 44, value: "idk", _bar: [{ name: "buzz" }] }