Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type: tree-select package #36493

Merged
merged 2 commits into from
Apr 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions packages/tree-select/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"license": "GPL-2.0-or-later",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"calypso:src": "src/index.js",
"calypso:src": "src/index.ts",
"sideEffects": false,
"repository": {
"type": "git",
Expand All @@ -28,9 +28,14 @@
"dist",
"src"
],
"types": "dist/types",
"scripts": {
"clean": "npx rimraf dist",
"build": "transpile",
"prepack": "yarn run clean && yarn run build"
"clean": "tsc --build ./tsconfig.json ./tsconfig-cjs.json --clean",
"build": "tsc --build ./tsconfig.json ./tsconfig-cjs.json",
"prepack": "yarn run clean && yarn run build",
"watch": "tsc --build ./tsconfig.json --watch"
},
"dependencies": {
"tslib": "^1.10.0"
}
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,56 @@
const defaultGetCacheKey = ( ...args ) => args.join();
declare const process: {
env: {
NODE_ENV: unknown;
};
};

interface GenerateCacheKey< A extends unknown[] > {
( ...args: A ): string;
}

const defaultGetCacheKey: GenerateCacheKey< unknown[] > = ( ...args: unknown[] ): string =>
args.join();

const isFunction = ( fn ) => {
return fn && typeof fn === 'function';
const isFunction = ( fn: unknown ): fn is ( ...args: unknown[] ) => unknown => {
return !! fn && typeof fn === 'function';
};

const isObject = ( o ) => {
return o && typeof o === 'object';
const isObject = ( o: unknown ): o is Record< string, unknown > => {
return !! o && typeof o === 'object';
};

type WeakMapKey = object; // eslint-disable-line @typescript-eslint/ban-types

interface Options< A extends unknown[] > {
/** Custom way to compute the cache key from the `args` list */
getCacheKey?: GenerateCacheKey< A >;
}

interface CachedSelector< S, A extends unknown[], R = unknown > {
( state: S, ...args: A ): R;
clearCache: () => void;
}

/**
* Returns a selector that caches values.
*
* @param {Function} getDependents A Function describing the dependent(s) of the selector.
* Must return an array which gets passed as the first arg to the selector
* @param {Function} selector A standard selector for calculating cached result
* @param {object} options Options bag with additional arguments
* @param {Function} options.getCacheKey
* Custom way to compute the cache key from the `args` list
* @returns {Function} Cached selector
* @param getDependents A Function describing the dependent(s) of the selector.
* Must return an array which gets passed as the first arg to the selector.
* @param selector A standard selector for calculating cached result.
* @param options Options bag with additional arguments.
*
* @returns Cached selector
*/
export default function treeSelect( getDependents, selector, options = {} ) {
export default function treeSelect<
State = unknown,
Args extends unknown[] = unknown[],
Deps extends WeakMapKey[] = any[], // eslint-disable-line @typescript-eslint/no-explicit-any
Result = unknown
>(
getDependents: ( state: State, ...args: Args ) => Deps,
selector: ( deps: Deps, ...args: Args ) => Result,
options: Options< Args > = {}
): CachedSelector< State, Args, Result > {
if ( process.env.NODE_ENV !== 'production' ) {
if ( ! isFunction( getDependents ) || ! isFunction( selector ) ) {
throw new TypeError(
Expand All @@ -32,7 +63,10 @@ export default function treeSelect( getDependents, selector, options = {} ) {

const { getCacheKey = defaultGetCacheKey } = options;

const cachedSelector = function ( state, ...args ) {
const cachedSelector: CachedSelector< State, Args, Result > = function (
state: State,
...args: Args
) {
const dependents = getDependents( state, ...args );

if ( process.env.NODE_ENV !== 'production' ) {
Expand All @@ -44,11 +78,11 @@ export default function treeSelect( getDependents, selector, options = {} ) {
// create a dependency tree for caching selector results.
// this is beneficial over standard memoization techniques so that we can
// garbage collect any values that are based on outdated dependents
const leafCache = dependents.reduce( insertDependentKey, cache );
const leafCache: Map< string, Result > = dependents.reduce( insertDependentKey, cache );

const key = getCacheKey( ...args );
if ( leafCache.has( key ) ) {
return leafCache.get( key );
return leafCache.get( key ) as Result;
}

const value = selector( dependents, ...args );
Expand Down Expand Up @@ -76,7 +110,8 @@ const NULLISH_KEY = {};
* Note: Inserts WeakMaps except for the last map which will be a regular Map.
* The last map is a regular one because the the key for the last map is the string results of args.join().
*/
function insertDependentKey( map, key, currentIndex, arr ) {
function insertDependentKey( map: any, key: unknown, currentIndex: number, arr: unknown[] ) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an unusual reducer function, it consumes an array of keys to walk a tree data structure (WeakMaps of WeakMaps) until it reaches a leaf which is a Map< string, CachedSelectorResultValue >.

I've typed map as any because usage suggests this internal function is working well enough and it was difficult to type it as Map<…> | WeakMap<…> here.

// eslint-disable-line @typescript-eslint/no-explicit-any
if ( key != null && Object( key ) !== key ) {
throw new TypeError( 'key must be an object, `null`, or `undefined`' );
}
Expand Down
12 changes: 12 additions & 0 deletions packages/tree-select/tsconfig-cjs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": "./tsconfig",
"compilerOptions": {
"module": "commonjs",
"declaration": false,
"declarationMap": false,
"declarationDir": null,
"outDir": "dist/cjs",
"composite": false,
"incremental": true
}
}
37 changes: 33 additions & 4 deletions packages/tree-select/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,36 @@
{
sirreal marked this conversation as resolved.
Show resolved Hide resolved
"extends": "@automattic/calypso-build/tsconfig",
"exclude": [ "node_modules" ],
"compilerOptions": {
"allowJs": true
}
"target": "ES5",
"lib": [ "ES2015" ],
"module": "ESNEXT",
"allowJs": false,
"declaration": true,
"declarationMap": true,
"declarationDir": "dist/types",
"rootDir": "src",
"outDir": "dist/esm",

"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,

"moduleResolution": "node",
"esModuleInterop": true,
"downlevelIteration": true,

"forceConsistentCasingInFileNames": true,

"importsNotUsedAsValues": "error",

"typeRoots": [ "../../node_modules/@types" ],
"types": [],

"noEmitHelpers": true,
"importHelpers": true,

"composite": true
},
"files": [ "src/index.ts" ]
}
1 change: 1 addition & 0 deletions packages/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
{ "path": "./react-i18n" },
{ "path": "./search" },
{ "path": "./shopping-cart" },
{ "path": "./tree-select" },
{ "path": "./wpcom-checkout" }
]
}