ixfx
Version:
A framework for programming interactivity
689 lines (679 loc) • 18.8 kB
JavaScript
import {
NumberMap,
add,
del,
has,
immutable,
set
} from "./chunk-XFNQJV53.js";
import {
MapOfSimpleBase,
SimpleEventEmitter,
defaultKeyer,
firstEntry,
firstEntryByValue,
intervalToMs,
lengthMax,
ofSimpleMutable
} from "./chunk-72EKR3DZ.js";
import {
addKeepingExisting,
filter,
find,
hasAnyValue,
sortByValueProperty,
toArray
} from "./chunk-KVLT2D4E.js";
import {
without
} from "./chunk-LKZ4HZTV.js";
import {
isEqualDefault,
toStringDefault
} from "./chunk-6UZ3OSJO.js";
import {
throwIntegerTest
} from "./chunk-UC4AQMTL.js";
import {
__export
} from "./chunk-L5EJU35C.js";
// src/collections/Map/index.ts
var Map_exports = {};
__export(Map_exports, {
MapOfMutableImpl: () => MapOfMutableImpl,
MapOfSimple: () => MapOfSimple,
NumberMap: () => NumberMap,
expiringMap: () => create,
firstEntry: () => firstEntry,
firstEntryByValue: () => firstEntryByValue,
immutable: () => immutable,
lengthMax: () => lengthMax,
mapOfSimpleMutable: () => ofSimpleMutable,
mutable: () => mutable,
ofArrayMutable: () => ofArrayMutable,
ofCircularMutable: () => ofCircularMutable,
ofSetMutable: () => ofSetMutable,
ofSimple: () => ofSimple
});
// src/collections/Map/ExpiringMap.ts
var create = (options = {}) => new ExpiringMap(options);
var ExpiringMap = class extends SimpleEventEmitter {
constructor(opts = {}) {
super();
this.disposed = false;
this.capacity = opts.capacity ?? -1;
throwIntegerTest(this.capacity, `nonZero`, `capacity`);
this.store = /* @__PURE__ */ new Map();
if (opts.evictPolicy && this.capacity <= 0) {
throw new Error(`evictPolicy is set, but no capacity limit is set`);
}
this.evictPolicy = opts.evictPolicy ?? `none`;
this.autoDeleteElapsedMs = opts.autoDeleteElapsedMs ?? -1;
this.autoDeletePolicy = opts.autoDeletePolicy ?? `none`;
if (this.autoDeleteElapsedMs > 0) {
this.autoDeleteTimer = setInterval(
() => {
this.#maintain();
},
Math.max(1e3, this.autoDeleteElapsedMs * 2)
);
}
}
dispose() {
if (this.disposed) return;
this.disposed = true;
if (this.autoDeleteTimer) {
clearInterval(this.autoDeleteTimer);
this.autoDeleteTimer = void 0;
}
}
/**
* Returns the number of keys being stored.
*/
get keyLength() {
return this.store.size;
}
*entries() {
for (const entry of this.store.entries()) {
yield [entry[0], entry[1].value];
}
}
*values() {
for (const v of this.store.values()) {
yield v.value;
}
}
*keys() {
yield* this.store.keys();
}
/**
* Returns the elapsed time since `key`
* was set. Returns _undefined_ if `key`
* does not exist
*/
elapsedSet(key) {
const v = this.store.get(key);
if (!v) return v;
return Date.now() - v.lastSet;
}
/**
* Returns the elapsed time since `key`
* was accessed. Returns _undefined_ if `key`
* does not exist
*/
elapsedGet(key) {
const v = this.store.get(key);
if (!v) return v;
return Date.now() - v.lastGet;
}
/**
* Returns true if `key` is stored.
* Does not affect the key's last access time.
* @param key
* @returns
*/
has(key) {
return this.store.has(key);
}
/**
* Gets an item from the map by key, returning
* undefined if not present
* @param key Key
* @returns Value, or undefined
*/
get(key) {
const v = this.store.get(key);
if (v) {
if (this.autoDeletePolicy === `either` || this.autoDeletePolicy === `get`) {
this.store.set(key, {
...v,
lastGet: performance.now()
});
}
return v.value;
}
}
/**
* Deletes the value under `key`, if present.
*
* Returns _true_ if something was removed.
* @param key
* @returns
*/
delete(key) {
const value = this.store.get(key);
if (!value) return false;
const d = this.store.delete(key);
this.fireEvent(`removed`, {
key,
value: value.value
});
return d;
}
/**
* Clears the contents of the map.
* Note: does not fire `removed` event
*/
clear() {
this.store.clear();
}
/**
* Updates the lastSet/lastGet time for a value
* under `k`.
*
* Returns false if key was not found
* @param key
* @returns
*/
touch(key) {
const v = this.store.get(key);
if (!v) return false;
this.store.set(key, {
...v,
lastSet: Date.now(),
lastGet: Date.now()
});
return true;
}
findEvicteeKey() {
if (this.evictPolicy === `none`) return;
let sortBy = ``;
if (this.evictPolicy === `oldestGet`) sortBy = `lastGet`;
else if (this.evictPolicy === `oldestSet`) sortBy = `lastSet`;
else throw new Error(`Unknown eviction policy ${this.evictPolicy}`);
const sorted = sortByValueProperty(this.store, sortBy);
return sorted[0][0];
}
#maintain() {
if (this.autoDeletePolicy === `none`) return;
this.deleteWithElapsed(this.autoDeleteElapsedMs, this.autoDeletePolicy);
}
/**
* Deletes all values where elapsed time has past
* for get/set or either.
* ```js
* // Delete all keys (and associated values) not accessed for a minute
* em.deleteWithElapsed({mins:1}, `get`);
* // Delete things that were set 1s ago
* em.deleteWithElapsed(1000, `set`);
* ```
*
* @param interval Interval
* @param property Basis for deletion 'get','set' or 'either'
* @returns Items removed
*/
deleteWithElapsed(interval, property) {
const entries = [...this.store.entries()];
const prune = [];
const intervalMs = intervalToMs(interval, 1e3);
const now = performance.now();
for (const entry of entries) {
const elapsedGet = now - entry[1].lastGet;
const elapsedSet = now - entry[1].lastSet;
const elapsed = property === `get` ? elapsedGet : property === `set` ? elapsedSet : Math.max(elapsedGet, elapsedSet);
if (elapsed >= intervalMs) {
prune.push([entry[0], entry[1].value]);
}
}
for (const entry of prune) {
this.store.delete(entry[0]);
const eventArguments = {
key: entry[0],
value: entry[1]
};
this.fireEvent(`expired`, eventArguments);
this.fireEvent(`removed`, eventArguments);
}
return prune;
}
/**
* Sets the `key` to be `value`.
*
* If the key already exists, it is updated.
*
* If the map is full, according to its capacity,
* another value is selected for removal.
* @param key
* @param value
* @returns
*/
set(key, value) {
const existing = this.store.get(key);
if (existing) {
this.store.set(key, {
...existing,
lastSet: performance.now()
});
return;
}
if (this.keyLength === this.capacity && this.capacity > 0) {
const key2 = this.findEvicteeKey();
if (!key2) {
throw new Error(`ExpiringMap full (capacity: ${this.capacity})`);
}
const existing2 = this.store.get(key2);
this.store.delete(key2);
if (existing2) {
const eventArguments = { key: key2, value: existing2.value };
this.fireEvent(`expired`, eventArguments);
this.fireEvent(`removed`, eventArguments);
}
}
this.store.set(key, {
lastGet: 0,
lastSet: performance.now(),
value
});
this.fireEvent(`newKey`, { key, value });
}
};
// src/collections/Map/MapMutable.ts
var mutable = (...data) => {
let m = add(/* @__PURE__ */ new Map(), ...data);
return {
add: (...data2) => {
m = add(m, ...data2);
},
delete: (key) => {
m = del(m, key);
},
clear: () => {
m = add(/* @__PURE__ */ new Map());
},
set: (key, value) => {
m = set(m, key, value);
},
get: (key) => m.get(key),
entries: () => m.entries(),
values: () => m.values(),
isEmpty: () => m.size === 0,
has: (key) => has(m, key)
};
};
// src/collections/Map/MapOfMultiImpl.ts
var MapOfMutableImpl = class extends SimpleEventEmitter {
/* eslint-disable-next-line functional/prefer-readonly-type */
#map = /* @__PURE__ */ new Map();
constructor(type, opts = {}) {
super();
this.type = type;
this.groupBy = opts.groupBy ?? toStringDefault;
}
/**
* Returns the type name. For in-built implementations, it will be one of: array, set or circular
*/
get typeName() {
return this.type.name;
}
/**
* Returns the number of keys
*/
get lengthKeys() {
return this.#map.size;
}
/**
* Returns the length of the longest child list
*/
get lengthMax() {
let m = 0;
for (const v of this.#map.values()) {
m = Math.max(m, this.type.count(v));
}
return m;
}
debugString() {
const keys = [...this.#map.keys()];
let r = `Keys: ${keys.join(`, `)}\r
`;
for (const k of keys) {
const v = this.#map.get(k);
if (v === void 0) {
r += ` - ${k} (undefined)\r
`;
} else {
const asArray = this.type.toArray(v);
if (asArray !== void 0) {
r += ` - ${k} (${this.type.count(v)}) = ${JSON.stringify(
asArray
)}\r
`;
}
}
}
;
return r;
}
get isEmpty() {
return this.#map.size === 0;
}
clear() {
this.#map.clear();
super.fireEvent(`clear`, true);
}
//eslint-disable-next-line functional/prefer-immutable-types
addKeyedValues(key, ...values) {
const set2 = this.#map.get(key);
if (set2 === void 0) {
this.#map.set(key, this.type.add(void 0, values));
super.fireEvent(`addedKey`, { key });
super.fireEvent(`addedValues`, { values });
} else {
this.#map.set(key, this.type.add(set2, values));
super.fireEvent(`addedValues`, { values });
}
}
//eslint-disable-next-line functional/prefer-immutable-types
set(key, values) {
this.addKeyedValues(key, ...values);
return this;
}
addValue(...values) {
for (const v of values) this.addKeyedValues(this.groupBy(v), v);
}
hasKeyValue(key, value, eq) {
const m = this.#map.get(key);
if (m === void 0) return false;
return this.type.has(m, value, eq);
}
//eslint-disable-next-line functional/prefer-tacit
has(key) {
return this.#map.has(key);
}
deleteKeyValue(key, value) {
const a = this.#map.get(key);
if (a === void 0) return false;
return this.deleteKeyValueFromMap(a, key, value);
}
deleteKeyValueFromMap(map, key, value) {
const preCount = this.type.count(map);
const filtered = this.type.without(map, value);
const postCount = filtered.length;
this.#map.set(key, this.type.add(void 0, filtered));
return preCount > postCount;
}
deleteByValue(value) {
let something = false;
[...this.#map.keys()].filter((key) => {
const a = this.#map.get(key);
if (!a) throw new Error(`Bug: map could not be accessed`);
if (this.deleteKeyValueFromMap(a, key, value)) {
something = true;
if (this.count(key) === 0) this.delete(key);
}
});
return something;
}
delete(key) {
const a = this.#map.get(key);
if (a === void 0) return false;
this.#map.delete(key);
this.fireEvent(`deleteKey`, { key });
return true;
}
firstKeyByValue(value, eq = isEqualDefault) {
const keys = [...this.#map.keys()];
const found = keys.find((key) => {
const a = this.#map.get(key);
if (a === void 0) throw new Error(`Bug: map could not be accessed`);
const r = this.type.has(a, value, eq);
return r;
});
return found;
}
count(key) {
const entry = this.#map.get(key);
if (entry === void 0) return 0;
return this.type.count(entry);
}
/**
* Iterates over values stored under `key`
* An empty array is returned if there are no values
*/
*get(key) {
const m = this.#map.get(key);
if (m === void 0) return;
yield* this.type.iterable(m);
}
/**
* Iterate over the values stored under `key`.
* If key does not exist, iteration is essentially a no-op
* @param key
* @returns
*/
*valuesFor(key) {
const m = this.#map.get(key);
if (m === void 0) return;
yield* this.type.iterable(m);
}
//eslint-disable-next-line functional/prefer-tacit
getSource(key) {
return this.#map.get(key);
}
/* eslint-disable-next-line functional/prefer-readonly-type */
*keys() {
yield* this.#map.keys();
}
*entriesFlat() {
for (const entry of this.#map.entries()) {
for (const v of this.type.iterable(entry[1])) {
yield [entry[0], v];
}
}
}
*valuesFlat() {
for (const entry of this.#map.entries()) {
yield* this.type.iterable(entry[1]);
}
}
*entries() {
for (const [k, v] of this.#map.entries()) {
const temporary = [...this.type.iterable(v)];
yield [k, temporary];
}
}
/* eslint-disable-next-line functional/prefer-readonly-type */
*keysAndCounts() {
for (const key of this.keys()) {
yield [key, this.count(key)];
}
}
merge(other) {
for (const key of other.keys()) {
const data = other.get(key);
this.addKeyedValues(key, ...data);
}
}
get size() {
return this.#map.size;
}
get [Symbol.toStringTag]() {
return this.#map[Symbol.toStringTag];
}
};
// src/collections/Map/MapOfSetMutable.ts
var ofSetMutable = (options) => {
const hash = options?.hash ?? toStringDefault;
const comparer = (a, b) => hash(a) === hash(b);
const t = {
get name() {
return `set`;
},
iterable: (source) => source.values(),
add: (dest, values) => addKeepingExisting(dest, hash, ...values),
count: (source) => source.size,
find: (source, predicate) => find(source, predicate),
filter: (source, predicate) => filter(source, predicate),
toArray: (source) => toArray(source),
has: (source, value) => hasAnyValue(source, value, comparer),
without: (source, value) => without(toArray(source), value, comparer)
};
const m = new MapOfMutableImpl(t, options);
return m;
};
// src/collections/CircularArray.ts
var CircularArray = class _CircularArray extends Array {
// ✔ Class is unit tested!
/* eslint-disable-next-line functional/prefer-readonly-type */
#capacity;
/* eslint-disable-next-line functional/prefer-readonly-type */
#pointer;
constructor(capacity = 0) {
super();
throwIntegerTest(capacity, `positive`, `capacity`);
this.#capacity = capacity;
this.#pointer = 0;
}
/**
* Add to array
* @param value Thing to add
* @returns
*/
add(value) {
const ca = _CircularArray.from(this);
ca[this.#pointer] = value;
ca.#capacity = this.#capacity;
if (this.#capacity > 0) {
ca.#pointer = this.#pointer + 1 === this.#capacity ? 0 : this.#pointer + 1;
} else {
ca.#pointer = this.#pointer + 1;
}
return ca;
}
get pointer() {
return this.#pointer;
}
get isFull() {
if (this.#capacity === 0) return false;
return this.length === this.#capacity;
}
};
var circularArray = (capacity) => new CircularArray(capacity);
// src/collections/Map/MapOfCircularMutable.ts
var ofCircularMutable = (options) => {
const comparer = isEqualDefault;
const t = {
get name() {
return `circular`;
},
add: (destination, values) => {
if (destination === void 0) destination = circularArray(options.capacity);
for (const v of values) {
destination = destination.add(v);
}
return destination;
},
count: (source) => source.length,
find: (source, predicate) => source.find(predicate),
filter: (source, predicate) => source.filter(predicate),
toArray: (source) => source,
iterable: (source) => source.values(),
has: (source, value) => source.find((v) => comparer(v, value)) !== void 0,
without: (source, value) => source.filter((v) => !comparer(v, value))
};
return new MapOfMutableImpl(t, options);
};
// src/collections/Map/MapOfArrayMutable.ts
var ofArrayMutable = (options = {}) => {
const convertToString = options.convertToString;
const toStringFunction = typeof convertToString === `undefined` ? isEqualDefault : (a, b) => convertToString(a) === convertToString(b);
const comparer = options.comparer ?? toStringFunction;
const t = {
get name() {
return `array`;
},
add: (destination, values) => {
if (destination === void 0) return [...values];
return [...destination, ...values];
},
iterable: (source) => source.values(),
count: (source) => source.length,
find: (source, predicate) => source.find((f) => predicate(f)),
filter: (source, predicate) => source.filter((f) => predicate(f)),
toArray: (source) => source,
has: (source, value) => source.some((v) => comparer(v, value)),
without: (source, value) => source.filter((v) => !comparer(v, value))
//[Symbol.iterator]: (source) => source[Symbol.iterator]()
};
const m = new MapOfMutableImpl(t, options);
return m;
};
// src/collections/Map/MapOfSimple.ts
var MapOfSimple = class _MapOfSimple extends MapOfSimpleBase {
addKeyedValues(key, ...values) {
return this.addBatch([[key, values]]);
}
addValue(...values) {
const asEntries = values.map((v) => [this.groupBy(v), v]);
return this.addBatch(asEntries);
}
//eslint-disable-next-line functional/prefer-immutable-types
addBatch(entries) {
const temporary = new Map(
[...this.map.entries()].map((e) => [e[0], [...e[1]]])
);
for (const [key, list] of entries) {
const existingList = temporary.get(key);
if (typeof existingList === `undefined`) {
temporary.set(key, list);
} else {
existingList.push(...list);
}
}
return new _MapOfSimple(this.groupBy, this.valueEq, [...temporary.entries()]);
}
clear() {
return new _MapOfSimple(this.groupBy, this.valueEq);
}
deleteKeyValue(_key, _value) {
throw new Error(`Method not implemented.`);
}
deleteByValue(value, eq) {
const entries = [...this.map.entries()];
const eqFunction = eq ?? this.valueEq;
const x = entries.map((entry) => {
const key = entry[0];
const values = entry[1].filter((vv) => !eqFunction(vv, value));
return [key, values];
});
return new _MapOfSimple(this.groupBy, this.valueEq, x);
}
delete(key) {
const entries = [...this.map.entries()].filter((e) => e[0] !== key);
return new _MapOfSimple(this.groupBy, this.valueEq, entries);
}
};
var ofSimple = (groupBy = defaultKeyer, valueEq = isEqualDefault) => new MapOfSimple(groupBy, valueEq);
export {
circularArray,
create,
ExpiringMap,
mutable,
MapOfMutableImpl,
ofSetMutable,
ofCircularMutable,
ofArrayMutable,
MapOfSimple,
ofSimple,
Map_exports
};
//# sourceMappingURL=chunk-YGPAJ4I6.js.map