Skip to content

Commit

Permalink
[Refactor] extract implementations to side-channel-weakmap, `side-c…
Browse files Browse the repository at this point in the history
…hannel-map`, `side-channel-list`
  • Loading branch information
ljharb committed Dec 11, 2024
1 parent c01d2d3 commit ada5955
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 239 deletions.
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"extends": "@ljharb",

"rules": {
"id-length": 0,
"max-lines-per-function": 0,
"multiline-comment-style": 1,
"new-cap": [2, { "capIsNewExceptions": ["GetIntrinsic"] }],
Expand Down
17 changes: 9 additions & 8 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import getSideChannelList from 'side-channel-list';
import getSideChannelMap from 'side-channel-map';
import getSideChannelWeakMap from 'side-channel-weakmap';

declare namespace getSideChannel {
type Channel<V, K> = {
assert: (key: K) => void;
has: (key: K) => boolean;
get: (key: K) => V | undefined;
set: (key: K, value: V) => void;
delete: (key: K) => boolean;
}
type Channel<K, V> =
| getSideChannelList.Channel<K, V>
| ReturnType<Exclude<typeof getSideChannelMap<K, V>, false>>
| ReturnType<Exclude<typeof getSideChannelWeakMap<K, V>, false>>;
}

declare function getSideChannel<V, K>(): getSideChannel.Channel<V, K>;
declare function getSideChannel<K, V>(): getSideChannel.Channel<K, V>;

export = getSideChannel;
169 changes: 14 additions & 155 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,107 +1,18 @@
'use strict';

var GetIntrinsic = require('get-intrinsic');
var callBound = require('call-bind/callBound');
var inspect = require('object-inspect');

var $TypeError = require('es-errors/type');
var $WeakMap = GetIntrinsic('%WeakMap%', true);
var $Map = GetIntrinsic('%Map%', true);

/** @template T @typedef {<T extends (this: any, ...args: any[]) => any>(this: ThisParameterType<T>, ...args: Parameters<T>) => ReturnType<T>} CallBind */

/** @type {CallBind<<K extends object, V>(this: WeakMap<K, V>, key: K) => V>} */
var $weakMapGet = callBound('WeakMap.prototype.get', true);
/** @type {CallBind<typeof WeakMap.prototype.set>} */
var $weakMapSet = callBound('WeakMap.prototype.set', true);
/** @type {CallBind<typeof WeakMap.prototype.has>} */
var $weakMapHas = callBound('WeakMap.prototype.has', true);
/** @type {CallBind<typeof WeakMap.prototype.delete>} */
var $weakMapDelete = callBound('WeakMap.prototype.delete', true);
/** @type {CallBind<typeof Map.prototype.get>} */
var $mapGet = callBound('Map.prototype.get', true);
/** @type {CallBind<typeof Map.prototype.set>} */
var $mapSet = callBound('Map.prototype.set', true);
/** @type {CallBind<typeof Map.prototype.has>} */
var $mapHas = callBound('Map.prototype.has', true);
/** @type {CallBind<typeof Map.prototype.delete>} */
var $mapDelete = callBound('Map.prototype.delete', true);

/*
* This function traverses the list returning the node corresponding to the given key.
*
* That node is also moved to the head of the list, so that if it's accessed again we don't need to traverse the whole list.
* By doing so, all the recently used nodes can be accessed relatively quickly.
*/
/** @type {import('./list.d.ts').listGetNode} */
// eslint-disable-next-line consistent-return
var listGetNode = function (list, key, isDelete) {
/** @type {typeof list | NonNullable<(typeof list)['next']>} */
var prev = list;
/** @type {(typeof list)['next']} */
var curr;
// eslint-disable-next-line eqeqeq
for (; (curr = prev.next) != null; prev = curr) {
if (curr.key === key) {
prev.next = curr.next;
if (!isDelete) {
// eslint-disable-next-line no-extra-parens
curr.next = /** @type {NonNullable<typeof list.next>} */ (list.next);
list.next = curr; // eslint-disable-line no-param-reassign
}
return curr;
}
}
};
var inspect = require('object-inspect');
var getSideChannelList = require('side-channel-list');
var getSideChannelMap = require('side-channel-map');
var getSideChannelWeakMap = require('side-channel-weakmap');

/** @type {import('./list.d.ts').listGet} */
// eslint-disable-next-line consistent-return
var listGet = function (objects, key) {
if (objects) {
var node = listGetNode(objects, key);
return node && node.value;
}
};
/** @type {import('./list.d.ts').listSet} */
var listSet = function (objects, key, value) {
if (objects) {
var node = listGetNode(objects, key);
if (node) {
node.value = value;
} else {
// Prepend the new node to the beginning of the list
objects.next = /** @type {import('./list.d.ts').ListNode<typeof value, typeof key>} */ ({ // eslint-disable-line no-param-reassign, no-extra-parens
key: key,
next: objects.next,
value: value
});
}
}
};
/** @type {import('./list.d.ts').listHas} */
var listHas = function (objects, key) {
if (!objects) {
return false;
}
return !!listGetNode(objects, key);
};
/** @type {import('./list.d.ts').listDelete} */
// eslint-disable-next-line consistent-return
var listDelete = function (objects, key) {
if (objects) {
return listGetNode(objects, key, true);
}
};
var makeChannel = getSideChannelWeakMap || getSideChannelMap || getSideChannelList;

/** @type {import('.')} */
module.exports = function getSideChannel() {
/** @typedef {ReturnType<typeof getSideChannel>} Channel */
/** @typedef {Parameters<Channel['get']>[0]} K */
/** @typedef {Parameters<Channel['set']>[1]} V */

/** @type {WeakMap<K & object, V> | undefined} */ var $wm;
/** @type {Map<K, V> | undefined} */ var $m;
/** @type {import('./list.d.ts').RootNode<V, K> | undefined} */ var $o;
/** @type {Channel | undefined} */ var $channelData;

/** @type {Channel} */
var channel = {
Expand All @@ -111,72 +22,20 @@ module.exports = function getSideChannel() {
}
},
'delete': function (key) {
if ($WeakMap && key && (typeof key === 'object' || typeof key === 'function')) {
if ($wm) {
return $weakMapDelete($wm, key);
}
} else if ($Map) {
if ($m) {
return $mapDelete($m, key);
}
} else {
if ($o) { // eslint-disable-line no-lonely-if
var root = $o && $o.next;
var deletedNode = listDelete($o, key);
if (deletedNode && root && root === deletedNode) {
$o = void undefined;
}
return !!deletedNode;
}
}
return false;
return !!$channelData && $channelData['delete'](key);
},
get: function (key) { // eslint-disable-line consistent-return
if ($WeakMap && key && (typeof key === 'object' || typeof key === 'function')) {
if ($wm) {
return $weakMapGet($wm, key);
}
} else if ($Map) {
if ($m) {
return $mapGet($m, key);
}
} else {
return listGet($o, key);
}
get: function (key) {
return $channelData && $channelData.get(key);
},
has: function (key) {
if ($WeakMap && key && (typeof key === 'object' || typeof key === 'function')) {
if ($wm) {
return $weakMapHas($wm, key);
}
} else if ($Map) {
if ($m) {
return $mapHas($m, key);
}
} else {
return listHas($o, key);
}
return false;
return !!$channelData && $channelData.has(key);
},
set: function (key, value) {
if ($WeakMap && key && (typeof key === 'object' || typeof key === 'function')) {
if (!$wm) {
$wm = new $WeakMap();
}
$weakMapSet($wm, key, value);
} else if ($Map) {
if (!$m) {
$m = new $Map();
}
$mapSet($m, key, value);
} else {
if (!$o) {
// Initialize the linked list as an empty node, so that we don't have to special-case handling of the first node: we can always refer to it as (previous node).next, instead of something like (list).head
$o = { next: void undefined };
}
// eslint-disable-next-line no-extra-parens
listSet(/** @type {NonNullable<typeof $o>} */ ($o), key, value);
if (!$channelData) {
$channelData = makeChannel();
}

$channelData.set(key, value);
}
};
// @ts-expect-error TODO: figure out why this is erroring
Expand Down
14 changes: 0 additions & 14 deletions list.d.ts

This file was deleted.

13 changes: 6 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"description": "Store information about any JS value in a side channel. Uses WeakMap if available.",
"main": "index.js",
"exports": {
"./package.json": "./package.json",
".": "./index.js"
".": "./index.js",
"./package.json": "./package.json"
},
"types": "./index.d.ts",
"scripts": {
Expand Down Expand Up @@ -43,17 +43,16 @@
},
"homepage": "https://github.com/ljharb/side-channel#readme",
"dependencies": {
"call-bind": "^1.0.8",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3"
"object-inspect": "^1.13.3",
"side-channel-list": "^1.0.0",
"side-channel-map": "^1.0.1",
"side-channel-weakmap": "^1.0.2"
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.17.1",
"@ljharb/eslint-config": "^21.1.1",
"@ljharb/tsconfig": "^0.2.2",
"@types/call-bind": "^1.0.5",
"@types/get-intrinsic": "^1.2.3",
"@types/object-inspect": "^1.13.0",
"@types/tape": "^5.6.5",
"auto-changelog": "^2.5.0",
Expand Down
Loading

0 comments on commit ada5955

Please sign in to comment.