weak-ref-collections
Advanced tools
Comparing version 1.1.0 to 1.2.0
145
index.js
@@ -30,16 +30,23 @@ // In Node.js this is packaged into a module | ||
set (key, value) { | ||
const oldRef = super.get(key) | ||
if (typeof oldRef !== 'undefined') { | ||
this.#registry.unregister(oldRef) | ||
const oldValue = super.get(key) | ||
if (oldValue instanceof WeakRef) { | ||
this.#registry.unregister(oldValue) | ||
} | ||
const ref = new WeakRef(value) | ||
this.#registry.register(value, key, ref) | ||
return super.set(key, ref) | ||
// If its an object wrap it in a weakref | ||
if (typeof value === 'object' && value !== null) { | ||
const ref = new WeakRef(value) | ||
this.#registry.register(value, key, ref) | ||
return super.set(key, ref) | ||
} | ||
// If its not an object just set it directly | ||
else { | ||
return super.set(key, value) | ||
} | ||
} | ||
get (key) { | ||
const value = super.get(key)?.deref() | ||
if (typeof value === 'undefined') { | ||
return | ||
} | ||
let value = super.get(key); | ||
// If its a weakRef then unwrap it first | ||
// No need to check for GCd stuff because its meant to be undefined anyway | ||
if (value instanceof WeakRef) value = super.get(key)?.deref() | ||
return value | ||
@@ -49,20 +56,32 @@ } | ||
has (key) { | ||
const value = super.get(key)?.deref() | ||
if (typeof value === 'undefined') { | ||
return false | ||
let value = super.get(key); | ||
// If its a weakRef then unwrap it first | ||
if (value instanceof WeakRef) { | ||
value = super.get(key)?.deref() | ||
// If its been GC'd then return false | ||
if (typeof value === 'undefined') return false | ||
return true | ||
} | ||
return true | ||
// If it's a normal object use the super | ||
// Do this to account for the edge case of setting a key with value undefined | ||
return super.has(key) | ||
} | ||
delete (key) { | ||
const ref = super.get(key) | ||
// Early return if nothing defined | ||
if (typeof ref === 'undefined') return false | ||
const value = super.get(key) | ||
// If there is a ref then unregister first to avoid | ||
// finalization deleting any new values later | ||
this.#registry.unregister(ref) | ||
super.delete(key) | ||
// Only return a successful delete if ref was still live | ||
if (typeof ref.deref() === 'undefined') return false | ||
return true | ||
if (value instanceof WeakRef) { | ||
this.#registry.unregister(value) | ||
super.delete(key) | ||
// Only return a successful delete if ref was still live | ||
if (typeof value.deref() === 'undefined') return false | ||
else return true | ||
} | ||
// Getting here means it is a valid primitive | ||
// return the super.delete call to account for | ||
// edge case of valid undefined value | ||
else { | ||
return super.delete(key) | ||
} | ||
} | ||
@@ -84,5 +103,9 @@ | ||
* [Symbol.iterator] () { | ||
for (const [key, ref] of super[Symbol.iterator]()) { | ||
const value = ref.deref() | ||
if (typeof value !== 'undefined') yield [key, value] | ||
for (let [key, value] of super[Symbol.iterator]()) { | ||
if (value instanceof WeakRef) { | ||
value = value.deref() | ||
if (typeof value !== 'undefined') yield [key, value] | ||
} else { | ||
yield [key, value] | ||
} | ||
} | ||
@@ -111,2 +134,4 @@ } | ||
// Delete the corresponding ref when object is collected | ||
// No need to remove from membership because it would already be gone from there | ||
// By the time we hit finalization | ||
#registry = new FinalizationRegistry(ref => { | ||
@@ -120,6 +145,4 @@ super.delete(ref) | ||
super() | ||
if (iterable) { | ||
for (const value of iterable) { | ||
this.add(value) | ||
} | ||
if (iterable) for (const value of iterable) { | ||
this.add(value) | ||
} | ||
@@ -135,26 +158,45 @@ } | ||
// and store the reference | ||
const ref = new WeakRef(value) | ||
this.#membership.set(value, ref) | ||
this.#registry.register(value, ref, ref) | ||
return super.add(ref) | ||
if (typeof value === 'object' && value !== null) { | ||
const ref = new WeakRef(value) | ||
this.#membership.set(value, ref) | ||
this.#registry.register(value, ref, ref) | ||
return super.add(ref) | ||
} | ||
// For primitives then just process it normally | ||
else { | ||
return super.add(value) | ||
} | ||
} | ||
has (value) { | ||
const ref = this.#membership.get(value) | ||
if (typeof ref === 'undefined') return false | ||
if (typeof ref.deref() === 'undefined') return false | ||
return true | ||
// If its an object then check the refs | ||
if (typeof value === 'object' && value !== null) { | ||
const ref = this.#membership.get(value) | ||
if (typeof ref === 'undefined') return false | ||
if (typeof ref.deref() === 'undefined') return false | ||
return true | ||
} | ||
// If it's a primitive then do a normal has check | ||
else { | ||
return super.has(value) | ||
} | ||
} | ||
delete (value) { | ||
const ref = this.#membership.get(value) | ||
// Early return if nothing defined | ||
if (typeof ref === 'undefined') return false | ||
// Otherwise an entry was found | ||
this.#membership.delete(value) | ||
this.#registry.unregister(ref) | ||
super.delete(ref) | ||
// Only return a successful delete if ref was still live | ||
if (typeof ref.deref() === 'undefined') return false | ||
return true | ||
if (typeof value === 'object' && value !== null) { | ||
const ref = this.#membership.get(value) | ||
// Early return if nothing defined | ||
if (typeof ref === 'undefined') return false | ||
// Otherwise an entry was found | ||
this.#membership.delete(value) | ||
this.#registry.unregister(ref) | ||
super.delete(ref) | ||
// Only return a successful delete if ref was still live | ||
if (typeof ref.deref() === 'undefined') return false | ||
return true | ||
} | ||
else { | ||
return super.delete(value) | ||
} | ||
} | ||
@@ -181,5 +223,8 @@ | ||
* [Symbol.iterator] () { | ||
for (const ref of super[Symbol.iterator]()) { | ||
const value = ref.deref() | ||
if (typeof value !== 'undefined') yield value | ||
for (let value of super[Symbol.iterator]()) { | ||
if (value instanceof WeakRef) { | ||
value = value.deref() | ||
if (typeof value !== 'undefined') yield value | ||
} | ||
else { yield value } | ||
} | ||
@@ -186,0 +231,0 @@ } |
{ | ||
"name": "weak-ref-collections", | ||
"version": "1.1.0", | ||
"version": "1.2.0", | ||
"description": "Iterable WeakMaps and WeakSets. Provides WeakRefMap and WeakRefSet which store values using WeakRefs and clean themselves up when garbage collected.", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
# Weak Ref Collections | ||
Iterable WeakMaps and WeakSets. Provides WeakRefMap and WeakRefSet which store values using WeakRefs and clean themselves up when garbage collected. | ||
Iterable WeakMaps and WeakSets. Provides WeakRefMap and WeakRefSet which store object values using WeakRefs and clean themselves up when garbage collected. Supports both objects and primitives simultaneously. Behaves like normal Map and Set for primitives. | ||
@@ -4,0 +4,0 @@ Unlike WeakMap which stores keys weakly, WeakRefMap stores keys strongly but stores values using WeakRefs. Is fully iterable. Works just like a normal Map object but holds its values weakly and cleans itself up when its values are garbage collected. Follows the [Map API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) |
88
test.js
@@ -18,3 +18,4 @@ /* global describe, it */ | ||
['foo', { qoo: 1 }], | ||
['bar', { qar: 2 }] | ||
['bar', { qar: 2 }], | ||
['box', 'boop'] | ||
] | ||
@@ -32,2 +33,29 @@ weakRefMap = new WeakRefMap(iterableSeed) | ||
it('can set an existing key', () => { | ||
weakRefMap.set('bar', 'bark') | ||
regularMap.set('bar', 'bark') | ||
assert(normalizeMap(weakRefMap) === normalizeMap(regularMap)) | ||
}) | ||
it('can set an existing non-object value', () => { | ||
weakRefMap.set('bar', 'bonk') | ||
regularMap.set('bar', 'bonk') | ||
assert(normalizeMap(weakRefMap) === normalizeMap(regularMap)) | ||
}) | ||
if('can set a value to undefined', () => { | ||
weakRefMap.set('beep', undefined) | ||
regularMap.set('beep', undefined) | ||
assert(weakRefMap.has('beep')) | ||
assert(weakRefMap.has('beep') === regularMap.has('beep')) | ||
let weakRefDeleteReturn = weakRefMap.delete('beep') | ||
let regularDeleteReturn = regularMap.delete('beep') | ||
assert(weakRefDeleteReturn) | ||
assert(weakRefDeleteReturn === regularDeleteReturn) | ||
weakRefDeleteReturn = weakRefMap.delete('beep') | ||
regularDeleteReturn = regularMap.delete('beep') | ||
assert(weakRefDeleteReturn === false) | ||
assert(weakRefDeleteReturn === regularDeleteReturn) | ||
}); | ||
it('can have a value gotten', () => { | ||
@@ -60,3 +88,3 @@ const weakRefResult = weakRefMap.get('foo') | ||
regularMap.delete('foo') | ||
assert(normalizeMap(weakRefMap) === '[["bar",{"qar":2}],["baz",{"qux":3}]]') | ||
assert(normalizeMap(weakRefMap) === '[["bar","bonk"],["box","boop"],["baz",{"qux":3}]]') | ||
assert(normalizeMap(weakRefMap) === normalizeMap(regularMap)) | ||
@@ -72,6 +100,18 @@ }) | ||
it('can set a value to a WeakRef', () => { | ||
const dummyWeakRef = new WeakRef({}) | ||
weakRefMap.set('weakRef', dummyWeakRef) | ||
regularMap.set('weakRef', dummyWeakRef) | ||
const weakRefResult = weakRefMap.get('weakRef'); | ||
const regularResult = regularMap.get('weakRef'); | ||
assert(weakRefResult instanceof WeakRef) | ||
assert(weakRefResult === regularResult) | ||
}); | ||
it('can be iterated over', () => { | ||
const seedData = [ | ||
['foo', { qoo: 1 }], | ||
['bar', { qar: 2 }] | ||
['bar', { qar: 2 }], | ||
['boop', 'beep'], | ||
['oops', undefined] | ||
] | ||
@@ -88,3 +128,3 @@ let weakRefResult = '' | ||
} | ||
assert(weakRefResult === '["foo",{"qoo":1}]["bar",{"qar":2}]') | ||
assert(weakRefResult === '["foo",{"qoo":1}]["bar",{"qar":2}]["boop","beep"]["oops",null]') | ||
assert(weakRefResult === regularResult) | ||
@@ -102,3 +142,3 @@ }) | ||
}) | ||
assert(weakRefResult === '["foo",{"qoo":1}]["bar",{"qar":2}]') | ||
assert(weakRefResult === '["foo",{"qoo":1}]["bar",{"qar":2}]["boop","beep"]["oops",null]') | ||
assert(weakRefResult === regularResult) | ||
@@ -116,3 +156,3 @@ }) | ||
} | ||
assert(weakRefResult === '["foo",{"qoo":1}]["bar",{"qar":2}]') | ||
assert(weakRefResult === '["foo",{"qoo":1}]["bar",{"qar":2}]["boop","beep"]["oops",null]') | ||
assert(weakRefResult === regularResult) | ||
@@ -130,3 +170,3 @@ }) | ||
} | ||
assert(weakRefResult === '"foo""bar"') | ||
assert(weakRefResult === '"foo""bar""boop""oops"') | ||
assert(weakRefResult === regularResult) | ||
@@ -144,5 +184,6 @@ }) | ||
} | ||
assert(weakRefResult === '{"qoo":1}{"qar":2}') | ||
assert(weakRefResult === '{"qoo":1}{"qar":2}"beep"undefined') | ||
assert(weakRefResult === regularResult) | ||
}) | ||
}) | ||
@@ -161,3 +202,3 @@ | ||
it('can be created with iterable', () => { | ||
const seedData = [{ foo: 1 }, { bar: 2 }, { baz: 3 }] | ||
const seedData = [{ foo: 1 }, { bar: 2 }, { baz: 3 }, 'boop'] | ||
weakRefSet = new WeakRefSet(seedData) | ||
@@ -171,3 +212,3 @@ regularSet = new Set(seedData) | ||
regularSet.add({ qaz: 4 }) | ||
assert(normalizeSet(weakRefSet) === '[{"foo":1},{"bar":2},{"baz":3},{"qaz":4}]') | ||
assert(normalizeSet(weakRefSet) === '[{"foo":1},{"bar":2},{"baz":3},"boop",{"qaz":4}]') | ||
assert(normalizeSet(weakRefSet) === normalizeSet(regularSet)) | ||
@@ -182,3 +223,3 @@ }) | ||
regularSet.add(newElement) | ||
assert(normalizeSet(weakRefSet) === '[{"foo":1},{"bar":2},{"baz":3},{"qaz":4},{"qux":5}]') | ||
assert(normalizeSet(weakRefSet) === '[{"foo":1},{"bar":2},{"baz":3},"boop",{"qaz":4},{"qux":5}]') | ||
assert(normalizeSet(weakRefSet) === normalizeSet(regularSet)) | ||
@@ -198,3 +239,3 @@ }) | ||
let regularDidDelete = regularSet.delete(newElement) | ||
assert(normalizeSet(weakRefSet) === '[{"foo":1},{"bar":2},{"baz":3},{"qaz":4}]') | ||
assert(normalizeSet(weakRefSet) === '[{"foo":1},{"bar":2},{"baz":3},"boop",{"qaz":4}]') | ||
assert(normalizeSet(weakRefSet) === normalizeSet(regularSet)) | ||
@@ -215,4 +256,15 @@ assert(weakDidDelete === true) | ||
it('can add undefined', () => { | ||
assert(weakRefSet.has(undefined) === false) | ||
assert(regularSet.has(undefined) === false) | ||
weakRefSet.add(undefined) | ||
regularSet.add(undefined) | ||
assert(normalizeSet(weakRefSet) === '[null]') | ||
assert(normalizeSet(weakRefSet) === normalizeSet(regularSet)) | ||
assert(weakRefSet.has(undefined) === true) | ||
assert(regularSet.has(undefined) === true) | ||
}) | ||
it('can forEach', () => { | ||
const seedData = [{ foo: 1 }, { bar: 2 }, { baz: 3 }] | ||
const seedData = [{ foo: 1 }, { bar: 2 }, { baz: 3 }, "boop", "undefined"] | ||
weakRefSet = new WeakRefSet(seedData) | ||
@@ -230,3 +282,3 @@ regularSet = new Set(seedData) | ||
}) | ||
assert(weakRefResult === '{"foo":1}{"foo":1}{"bar":2}{"bar":2}{"baz":3}{"baz":3}') | ||
assert(weakRefResult === '{"foo":1}{"foo":1}{"bar":2}{"bar":2}{"baz":3}{"baz":3}"boop""boop""undefined""undefined"') | ||
assert(weakRefResult === regularResult) | ||
@@ -244,3 +296,3 @@ }) | ||
} | ||
assert(weakRefResult === '{"foo":1}{"bar":2}{"baz":3}') | ||
assert(weakRefResult === '{"foo":1}{"bar":2}{"baz":3}"boop""undefined"') | ||
assert(weakRefResult === regularResult) | ||
@@ -258,3 +310,3 @@ }) | ||
} | ||
assert(weakRefResult === '[{"foo":1},{"foo":1}][{"bar":2},{"bar":2}][{"baz":3},{"baz":3}]') | ||
assert(weakRefResult === '[{"foo":1},{"foo":1}][{"bar":2},{"bar":2}][{"baz":3},{"baz":3}]["boop","boop"]["undefined","undefined"]') | ||
assert(weakRefResult === regularResult) | ||
@@ -272,3 +324,3 @@ }) | ||
} | ||
assert(weakRefResult === '{"foo":1}{"bar":2}{"baz":3}') | ||
assert(weakRefResult === '{"foo":1}{"bar":2}{"baz":3}"boop""undefined"') | ||
assert(weakRefResult === regularResult) | ||
@@ -286,5 +338,5 @@ }) | ||
} | ||
assert(weakRefResult === '{"foo":1}{"bar":2}{"baz":3}') | ||
assert(weakRefResult === '{"foo":1}{"bar":2}{"baz":3}"boop""undefined"') | ||
assert(weakRefResult === regularResult) | ||
}) | ||
}) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
20458
503