UNPKG

@electric-sql/d2ts

Version:

D2TS is a TypeScript implementation of Differential Dataflow.

170 lines 5.46 kB
import murmurhash from 'murmurhash-js'; /** * A map that uses WeakRefs to store objects, and automatically removes them when * they are no longer referenced. */ export class WeakRefMap { cacheMap = new Map(); finalizer = new AnyFinalizationRegistry((key) => { this.cacheMap.delete(key); }); set(key, value) { const cache = this.get(key); if (cache) { if (cache === value) return; this.finalizer.unregister(cache); } this.cacheMap.set(key, new WeakRef(value)); this.finalizer.register(value, key, value); } get(key) { return this.cacheMap.get(key)?.deref() ?? null; } } /** * A map that returns a default value for keys that are not present. */ export class DefaultMap extends Map { defaultValue; constructor(defaultValue, entries) { super(entries); this.defaultValue = defaultValue; } get(key) { if (!this.has(key)) { this.set(key, this.defaultValue()); } return super.get(key); } /** * Update the value for a key using a function. */ update(key, updater) { const value = this.get(key); const newValue = updater(value); this.set(key, newValue); return newValue; } } // JS engines have various limits on how many args can be passed to a function // with a spread operator, so we need to split the operation into chunks // 32767 is the max for Chrome 14, all others are higher // TODO: investigate the performance of this and other approaches const chunkSize = 30000; export function chunkedArrayPush(array, other) { if (other.length <= chunkSize) { array.push(...other); } else { for (let i = 0; i < other.length; i += chunkSize) { const chunk = other.slice(i, i + chunkSize); array.push(...chunk); } } } const hashCache = new WeakMap(); /** * Replacer function for JSON.stringify that converts unsupported types to strings */ function hashReplacer(_key, value) { if (typeof value === 'bigint') { return String(value); } else if (typeof value === 'symbol') { return String(value); } else if (typeof value === 'function') { return String(value); } else if (value === undefined) { return 'undefined'; } else if (value instanceof Map) { return `Map(${JSON.stringify(Array.from(value.entries()), hashReplacer)})`; } else if (value instanceof Set) { return `Set(${JSON.stringify(Array.from(value.values()), hashReplacer)})`; } return value; } /** * A hash method that caches the hash of a value in a week map */ export function hash(data) { if (data === null || data === undefined || (typeof data !== 'object' && typeof data !== 'function')) { // Can't be cached in the weak map because it's not an object const serialized = JSON.stringify(data, hashReplacer); return murmurhash.murmur3(serialized); } if (hashCache.has(data)) { return hashCache.get(data); } const serialized = JSON.stringify(data, hashReplacer); const hashValue = murmurhash.murmur3(JSON.stringify(serialized)); hashCache.set(data, hashValue); return hashValue; } /** * This is a mock implementation of FinalizationRegistry which uses WeakRef to * track the target objects. It's used in environments where FinalizationRegistry * is not available but WeakRef is (e.g. React Native >=0.75 on New Architecture). * Based on https://gist.github.com/cray0000/abecb1ca71fd28a1d8efff2be9e0f6c5 * MIT License - Copyright Cray0000 */ export class WeakRefBasedFinalizationRegistry { counter = 0; registrations = new Map(); sweepTimeout; finalize; sweepIntervalMs = 10_000; constructor(finalize, sweepIntervalMs) { this.finalize = finalize; if (sweepIntervalMs !== undefined) { this.sweepIntervalMs = sweepIntervalMs; } } register(target, value, token) { this.registrations.set(this.counter, { targetRef: new WeakRef(target), tokenRef: token != null ? new WeakRef(token) : undefined, value, }); this.counter++; this.scheduleSweep(); } unregister(token) { if (token == null) return; this.registrations.forEach((registration, key) => { if (registration.tokenRef?.deref() === token) { this.registrations.delete(key); } }); } // Bound so it can be used directly as setTimeout callback. sweep = () => { clearTimeout(this.sweepTimeout); this.sweepTimeout = undefined; this.registrations.forEach((registration, key) => { if (registration.targetRef.deref() !== undefined) return; const value = registration.value; this.registrations.delete(key); this.finalize(value); }); if (this.registrations.size > 0) this.scheduleSweep(); }; scheduleSweep() { if (this.sweepTimeout) return; this.sweepTimeout = setTimeout(this.sweep, this.sweepIntervalMs); } } const AnyFinalizationRegistry = typeof FinalizationRegistry !== 'undefined' ? FinalizationRegistry : WeakRefBasedFinalizationRegistry; //# sourceMappingURL=utils.js.map