Skip to content

Commit 5ca3a45

Browse files
committed
Introduce @wry/key-set-map package.
This package provides a `KeySetMap` class similar to the `Trie` API provided by the `@wry/trie` package, except the keys passed to `lookup` or `peek` have _set_ semantics rather than _sequence_ semantics.
1 parent 307b3f7 commit 5ca3a45

File tree

11 files changed

+846
-0
lines changed

11 files changed

+846
-0
lines changed

packages/key-set-map/.gitignore

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
6+
# Runtime data
7+
pids
8+
*.pid
9+
*.seed
10+
11+
# Directory for instrumented libs generated by jscoverage/JSCover
12+
lib-cov
13+
14+
# Coverage directory used by tools like istanbul
15+
coverage
16+
17+
# nyc test coverage
18+
.nyc_output
19+
20+
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21+
.grunt
22+
23+
# node-waf configuration
24+
.lock-wscript
25+
26+
# Compiled binary addons (http://nodejs.org/api/addons.html)
27+
build/Release
28+
29+
# Dependency directories
30+
node_modules
31+
jspm_packages
32+
33+
# Optional npm cache directory
34+
.npm
35+
36+
# Optional REPL history
37+
.node_repl_history
38+
39+
# Ignore generated TypeScript files.
40+
lib
41+
42+
# Cache for rollup-plugin-typescript2
43+
.rpt2_cache

packages/key-set-map/.npmignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/node_modules
2+
/lib/tests
3+
tsconfig.json

packages/key-set-map/README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# @wry/key-set-map
2+
3+
Whereas the `@wry/trie` package associates values with _sequences_ of keys, the
4+
`@wry/key-set-map` package and its `KeySetMap` class provide a similar
5+
capability for _sets_ of keys, so the order of the input keys is no longer
6+
important.
7+
8+
As with a traditional [Trie](https://en.wikipedia.org/wiki/Trie), lookups and
9+
insertions take linear time in the size of the input set, and peek-like
10+
operations can often bail out much more quickly, without having to look at all
11+
the input set elements.
12+
13+
Since JavaScript `Set` and `Map` containers maintain insertion order, two
14+
equivalent sets (containing identical elements) can nevertheless be detectably
15+
different if their keys were inserted in a different order. Deciding which of
16+
the orders is "correct" or "canonical" is a fool's errand, possible only when
17+
there is an inherent total ordering among the elements, suggesting a
18+
sorting-based strategy.
19+
20+
Because sorting is tempting as a strategy for turning sets into
21+
canonically-ordered sequences, it's important to stress: this implementation
22+
works without sorting set elements, and without requiring the elements to be
23+
comparable. In fact, the lookup algorithm is asymptotically faster than it would
24+
be if the keys had to be sorted before lookup.
25+
26+
Finally, to avoid taking any position on which ordering of elements is
27+
canonical, this implementation never grants direct access to any previously
28+
provided sets. Instead of attempting to return a canonical `Set`, the keys of
29+
the set are associated with an arbitrary `TData` value, which is all you get
30+
when you look up a set of keys.
31+
32+
## Memory management
33+
34+
When `WeakRef` and `FinalizationRegistry` are available, the `KeySetMap` class
35+
automatically reclaims internal memory associated with sets containing keys that
36+
have been garbage collected.
37+
38+
To that end, when keys can be garbage collected, the `KeySetMap` takes care not
39+
to retain them strongly, acting like a `WeakMap` for object keys and like a
40+
`Map` for non-object keys. In other words, `KeySetMap` does not prevent its
41+
(object) keys from being garbage collected, if they are otherwise eligible.
42+
43+
By passing `false` for the `weakness` parameter to the `KeySetMap` constructor,
44+
you can disable weak-key-related functionality, so the `KeySetMap` will behave
45+
like a `Map` for all keys, regardless of whether they are objects or primitive.
46+
This mode is not encouraged for production, but may be useful for testing,
47+
debugging, or other diagnostic purposes.
48+
49+
Any `TData` objects allocated by the `KeySetMap` may outlive their associated
50+
sets of keys, and retaining a strong reference to the `TData` object by itself
51+
does not prevent garbage collection and removal of object keys. However, as long
52+
as all keys remain reachable and are not removed from the `KeySetMap` with
53+
`remove` or `removeSet`, the set of keys will remain in the `KeySetMap` and thus
54+
retain a reference to the associated `TData`.

packages/key-set-map/package-lock.json

Lines changed: 31 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/key-set-map/package.json

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"name": "@wry/key-set-map",
3+
"private": true,
4+
"version": "0.0.1",
5+
"author": "Ben Newman <ben@eloper.dev>",
6+
"description": "Trie-like data structure using sets rather than sequences of keys",
7+
"license": "MIT",
8+
"type": "module",
9+
"main": "lib/bundle.cjs",
10+
"module": "lib/index.js",
11+
"types": "lib/index.d.ts",
12+
"keywords": [],
13+
"homepage": "https://github.com/benjamn/wryware",
14+
"repository": {
15+
"type": "git",
16+
"url": "git+https://github.com/benjamn/wryware.git"
17+
},
18+
"bugs": {
19+
"url": "https://github.com/benjamn/wryware/issues"
20+
},
21+
"scripts": {
22+
"build": "npm run clean && npm run tsc && npm run rollup",
23+
"clean": "rimraf lib",
24+
"tsc": "tsc -p tsconfig.json",
25+
"rollup": "rollup -c rollup.config.js",
26+
"prepare": "npm run build",
27+
"test:cjs": "../../shared/test.sh lib/tests/bundle.cjs",
28+
"test:esm": "../../shared/test.sh lib/tests/bundle.js",
29+
"test": "npm run test:esm && npm run test:cjs"
30+
},
31+
"dependencies": {
32+
"tslib": "^2.3.0"
33+
},
34+
"engines": {
35+
"node": ">=8"
36+
}
37+
}

packages/key-set-map/rollup.config.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { build } from "../../shared/rollup.config.js";
2+
3+
// This package doesn't use the lib/es5 directory, so we need to override the
4+
// default export from ../../shared/rollup.config.js (as we also do in the
5+
// @wry/equality package).
6+
export default [
7+
build(
8+
"lib/index.js",
9+
"lib/bundle.cjs",
10+
"cjs"
11+
),
12+
build(
13+
"lib/tests/main.js",
14+
"lib/tests/bundle.js",
15+
"esm"
16+
),
17+
build(
18+
"lib/tests/main.js",
19+
"lib/tests/bundle.cjs",
20+
"cjs"
21+
),
22+
];

packages/key-set-map/src/helpers.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
export const arrayForEach = Array.prototype.forEach;
2+
3+
export const {
4+
prototype: {
5+
hasOwnProperty,
6+
toString: objectToString,
7+
},
8+
} = Object;
9+
10+
// If no makeData function is supplied, the looked-up data will be an empty,
11+
// null-prototype Object.
12+
export function defaultMakeData(): any {
13+
return Object.create(null);
14+
}
15+
16+
export function isObjRef(value: any): value is object {
17+
if (value) {
18+
switch (typeof value) {
19+
case "object":
20+
case "function":
21+
return true;
22+
}
23+
}
24+
return false;
25+
}
26+
27+
const SET_TO_STRING_TAG = objectToString.call(new Set);
28+
29+
export function assertSet(set: any): asserts set is Set<any> {
30+
const toStringTag = objectToString.call(set);
31+
if (toStringTag !== SET_TO_STRING_TAG) {
32+
throw new TypeError(`Not a Set: ${toStringTag}`);
33+
}
34+
}
35+
36+
const KNOWN: unique symbol = Symbol("KeySetMap.KNOWN");
37+
38+
export function makeKnownWeakRef<T extends object>(key: T): WeakRef<T> {
39+
return Object.assign(new WeakRef(key), { [KNOWN]: true });
40+
}
41+
42+
export function isKnownWeakRef(ref: unknown): ref is WeakRef<object> {
43+
return (
44+
ref instanceof WeakRef &&
45+
KNOWN in ref &&
46+
ref[KNOWN] === true
47+
);
48+
}

0 commit comments

Comments
 (0)