Skip to content
This repository has been archived by the owner on Oct 30, 2024. It is now read-only.

Commit

Permalink
Migrate to path utilities from pxth package (#101)
Browse files Browse the repository at this point in the history
* Migrated to new pxth utilities

* Created PxthMap abstraction

* Fixed tests

* Used getPxthSegments in tests

* Removed 'values' function
  • Loading branch information
AlexShukel authored Mar 7, 2022
1 parent e515234 commit 9504df2
Show file tree
Hide file tree
Showing 19 changed files with 156 additions and 424 deletions.
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
"dependencies": {
"lodash": "^4.17.20",
"lodash-es": "^4.17.15",
"pxth": "^0.4.0",
"pxth": "^0.5.0",
"tiny-invariant": "^1.1.0"
}
}
5 changes: 3 additions & 2 deletions src/hooks/useMappingProxy.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useEffect, useMemo, useRef } from 'react';
import { Pxth, pxthToString } from 'pxth';
import { Pxth } from 'pxth';

import { MappingProxy, ProxyMapSource } from '../typings';
import { areProxyMapsEqual } from '../utils/areProxyMapsEqual';
import { hashPxth } from '../utils/hashPxth';

const useMapMemo = <V>(map: ProxyMapSource<V>): ProxyMapSource<V> => {
const mapRef = useRef(map);
Expand All @@ -23,5 +24,5 @@ export const useMappingProxy = <V>(mapSource: ProxyMapSource<V>, path: Pxth<V>)
const proxy = new MappingProxy(realMap, path);
proxy.activate();
return proxy;
}, [pxthToString(path), realMap]);
}, [hashPxth(path), realMap]);
};
65 changes: 25 additions & 40 deletions src/hooks/useObservers.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useCallback, useRef } from 'react';
import { createPxth, deepGet, parseSegmentsFromString, Pxth, pxthToString, RootPath } from 'pxth';
import { createPxth, deepGet, isInnerPxth, Pxth, samePxth } from 'pxth';
import invariant from 'tiny-invariant';

import { BatchUpdate, Observer } from '../typings';
import { PxthMap } from '../typings/PxthMap';
import { ObserverArray, ObserverKey } from '../utils/ObserverArray';
import { isInnerPath } from '../utils/pathUtils';
import { useLazyRef } from '../utils/useLazyRef';

export type ObserversControl<T> = {
Expand All @@ -24,19 +24,9 @@ export type ObserversControl<T> = {

/** Hook, wraps functionality of observers storage (add, remove, notify tree of observers, etc.) */
export const useObservers = <T>(): ObserversControl<T> => {
const observers = useRef<Record<string | RootPath, ObserverArray<unknown>>>(
{} as Record<string | RootPath, ObserverArray<unknown>>
);
const observersMap = useRef<PxthMap<ObserverArray<unknown>>>(new PxthMap());
const batchUpdateObservers = useLazyRef<ObserverArray<BatchUpdate<T>>>(() => new ObserverArray());

const getObserversKeys = useCallback(
() => [
...Object.keys(observers.current),
...(Object.getOwnPropertySymbols(observers.current) as unknown as string[]),
],
[]
);

const batchUpdate = useCallback(
(update: BatchUpdate<T>) => {
batchUpdateObservers.current.call(update);
Expand All @@ -55,25 +45,23 @@ export const useObservers = <T>(): ObserversControl<T> => {
);

const observe = useCallback(<V>(path: Pxth<V>, observer: Observer<V>) => {
const pathKey = pxthToString(path);

if (!Object.prototype.hasOwnProperty.call(observers.current, pathKey)) {
observers.current[pathKey] = new ObserverArray();
if (!observersMap.current.has(path)) {
observersMap.current.set(path, new ObserverArray());
}

return observers.current[pathKey].add(observer as Observer<unknown>);
return observersMap.current.get(path).add(observer as Observer<unknown>);
}, []);

const stopObserving = useCallback(<V>(path: Pxth<V>, observerKey: ObserverKey) => {
const pathKey = pxthToString(path);

const currentObservers = observers.current[pathKey];
const currentObservers = observersMap.current.get(path);

invariant(currentObservers, 'Cannot remove observer from value, which is not observing');

currentObservers.remove(observerKey);

if (currentObservers.isEmpty()) delete observers.current[pathKey];
if (currentObservers.isEmpty()) {
observersMap.current.remove(path);
}
}, []);

const watch = useCallback(
Expand All @@ -95,17 +83,14 @@ export const useObservers = <T>(): ObserversControl<T> => {
[observeBatchUpdates, stopObservingBatchUpdates]
);

const isObserved = useCallback(
<V>(path: Pxth<V>) => Object.prototype.hasOwnProperty.call(observers.current, pxthToString(path)),
[]
);
const isObserved = useCallback(<V>(path: Pxth<V>) => observersMap.current.has(path), []);

const notifyPaths = useCallback(
(origin: Pxth<unknown>, paths: string[], values: T) => {
(origin: Pxth<unknown>, paths: Pxth<unknown>[], values: T) => {
batchUpdate({ paths, origin, values });
paths.forEach(path => {
const observer = observers.current[path];
const subValue = deepGet(values, createPxth(parseSegmentsFromString(path)));
const observer = observersMap.current.get(path);
const subValue = deepGet(values, path);
observer.call(subValue);
});
},
Expand All @@ -114,23 +99,23 @@ export const useObservers = <T>(): ObserversControl<T> => {

const notifySubTree = useCallback(
<V>(path: Pxth<V>, values: T) => {
const stringifiedPath = pxthToString(path);

const subPaths = getObserversKeys().filter(
tempPath =>
isInnerPath(stringifiedPath, tempPath) ||
stringifiedPath === tempPath ||
isInnerPath(tempPath, stringifiedPath)
);
const subPaths = observersMap.current
.keys()
.filter(
tempPath =>
isInnerPxth(path as Pxth<unknown>, tempPath) ||
samePxth(path as Pxth<unknown>, tempPath) ||
isInnerPxth(tempPath, path as Pxth<unknown>)
);

notifyPaths(path as Pxth<unknown>, subPaths, values);
},
[notifyPaths, getObserversKeys]
[notifyPaths]
);

const notifyAll = useCallback(
(values: T) => notifyPaths(createPxth([]), getObserversKeys(), values),
[notifyPaths, getObserversKeys]
(values: T) => notifyPaths(createPxth([]), observersMap.current.keys(), values),
[notifyPaths]
);

return {
Expand Down
2 changes: 0 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,3 @@ export * from './components';
export * from './utils/ObserverArray';

export { intercept } from './utils/useInterceptors';

export * from './utils/pathUtils';
2 changes: 1 addition & 1 deletion src/typings/BatchUpdate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Pxth } from 'pxth';
/** Object, in which "stocked" calls observers */
export type BatchUpdate<T> = {
/** which paths should be updated */
paths: string[];
paths: Pxth<unknown>[];
/** path, which triggered subtree update */
origin: Pxth<unknown>;
/** all values, which should be sent to observers */
Expand Down
48 changes: 19 additions & 29 deletions src/typings/MappingProxy.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import isNil from 'lodash/isNil';
import { createPxth, deepGet, deepSet, parseSegmentsFromString, Pxth, pxthToString } from 'pxth';
import { deepGet, deepSet, isInnerPxth, joinPxths, longestCommonPxth, Pxth, relativePxth, samePxth } from 'pxth';
import invariant from 'tiny-invariant';

import { Observer } from './Observer';
import { ProxyMap } from './ProxyMap';
import { PxthMap } from './PxthMap';
import { StockProxy } from './StockProxy';
import { ProxyMapSource } from '.';
import { createProxyMap } from '../utils/createProxyMap';
import { getInnerPaths, hasMappedParentPaths } from '../utils/mappingProxyUtils';
import { isInnerPath, joinPaths, longestCommonPath, relativePath, samePxth } from '../utils/pathUtils';

/**
* Simple example of StockProxy.
Expand All @@ -22,15 +21,15 @@ import { isInnerPath, joinPaths, longestCommonPath, relativePath, samePxth } fro
* }
*/
export class MappingProxy<T> extends StockProxy<T> {
private readonly proxyMap: ProxyMap;
private readonly proxyMap: PxthMap<Pxth<unknown>>;

public constructor(mapSource: ProxyMapSource<T>, path: Pxth<T>) {
super(path);
this.proxyMap = createProxyMap(mapSource);
}

public setValue = <V>(path: Pxth<V>, value: V, defaultSetValue: <U>(path: Pxth<U>, value: U) => void) => {
const relativeValuePath = relativePath(this.path, path);
const relativeValuePath = relativePxth(this.path as Pxth<unknown>, path as Pxth<unknown>);

if (hasMappedParentPaths(relativeValuePath, this.proxyMap)) {
const normalPath = this.getNormalPath(path);
Expand All @@ -42,7 +41,7 @@ export class MappingProxy<T> extends StockProxy<T> {

innerPaths.forEach(
([to, from]) =>
from !== undefined && defaultSetValue(from, deepGet(value, relativePath(relativeValuePath, to)))
from !== undefined && defaultSetValue(from, deepGet(value, relativePxth(relativeValuePath, to)))
);
};

Expand All @@ -61,7 +60,7 @@ export class MappingProxy<T> extends StockProxy<T> {
};

private mapValue = <V>(value: V, path: Pxth<V>, normalPath: Pxth<V>): V => {
path = relativePath(this.path, path);
path = relativePxth(this.path as Pxth<unknown>, path as Pxth<unknown>) as Pxth<V>;

if (hasMappedParentPaths(path, this.proxyMap)) {
return value;
Expand All @@ -73,8 +72,8 @@ export class MappingProxy<T> extends StockProxy<T> {
(acc, [to, from]) =>
deepSet(
acc as unknown as object,
relativePath(path, to),
deepGet(value, relativePath(normalPath, from!))
relativePxth(path as Pxth<unknown>, to),
deepGet(value, relativePxth(normalPath as Pxth<unknown>, from!))
) as V,
{} as V
);
Expand All @@ -88,42 +87,33 @@ export class MappingProxy<T> extends StockProxy<T> {
'Mapping proxy error: trying to get normal path of proxied path, which is not defined in proxy map'
);

return joinPaths<V>(this.path as Pxth<unknown>, normalPath);
return joinPxths(this.path as Pxth<unknown>, normalPath) as Pxth<V>;
};

public getNormalPath = <V>(path: Pxth<V>): Pxth<V> => {
const normalPath = relativePath(this.path, path);
const stringifiedPath = pxthToString(normalPath);
const normalPath = relativePxth(this.path as Pxth<unknown>, path as Pxth<unknown>);

const isIndependent = this.proxyMap.has(normalPath);

if (isIndependent) {
return this.proxyMap.get(normalPath) as Pxth<V>;
}

const hasMappedChildrenPaths = this.proxyMap
.entries()
.some(([mappedPath]) => isInnerPath(stringifiedPath, pxthToString(mappedPath)));
const hasMappedChildrenPaths = this.proxyMap.keys().some(mappedPath => isInnerPxth(normalPath, mappedPath));

if (hasMappedChildrenPaths) {
return createPxth(
parseSegmentsFromString(
longestCommonPath(
this.proxyMap
.entries()
.filter(([to]) => isInnerPath(stringifiedPath, pxthToString(to)))
.map(([, from]) => pxthToString(from!) as string)
)
)
);
return longestCommonPxth(
this.proxyMap
.entries()
.filter(([to]) => isInnerPxth(normalPath, to))
.map(([, from]) => from)
) as Pxth<V>;
}

if (hasMappedParentPaths(normalPath, this.proxyMap)) {
const [to, from] = this.proxyMap
.entries()
.find(([mappedPath]) => isInnerPath(pxthToString(mappedPath), stringifiedPath))!;
const [to, from] = this.proxyMap.entries().find(([mappedPath]) => isInnerPxth(mappedPath, normalPath))!;

return joinPaths<V>(from!, relativePath(to, normalPath as Pxth<unknown>));
return joinPxths(from!, relativePxth(to, normalPath)) as Pxth<V>;
}

invariant(false, 'Mapping proxy error: trying to proxy value, which is not defined in proxy map.');
Expand Down
25 changes: 0 additions & 25 deletions src/typings/ProxyMap.ts

This file was deleted.

33 changes: 33 additions & 0 deletions src/typings/PxthMap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Pxth } from 'pxth';

import { hashPxth } from '../utils/hashPxth';

export class PxthMap<Value> {
private _values: Record<string, Value> = {};
private _keys: Record<string, Pxth<unknown>> = {};

public get = <V>(key: Pxth<V>): Value => {
return this._values[hashPxth(key)];
};

public set = <V>(key: Pxth<V>, value: Value) => {
const stingifiedKey = hashPxth(key);
this._values[stingifiedKey] = value;
this._keys[stingifiedKey] = key as Pxth<unknown>;
};

public remove = <V>(key: Pxth<V>) => {
const stingifiedKey = hashPxth(key);
delete this._values[stingifiedKey];
delete this._keys[stingifiedKey];
};

public has = <V>(key: Pxth<V>) => {
return hashPxth(key) in this._values;
};

public keys = (): Pxth<unknown>[] => Object.values(this._keys);

public entries = (): Array<[key: Pxth<unknown>, value: Value]> =>
Object.entries(this._keys).map(([key, value]) => [value, this._values[key]]);
}
3 changes: 2 additions & 1 deletion src/utils/areProxyMapsEqual.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { samePxth } from 'pxth';

import { createProxyMap } from './createProxyMap';
import { samePxth } from './pathUtils';
import { ProxyMapSource } from '../typings/ProxyMapSource';

export const areProxyMapsEqual = (map1: ProxyMapSource<unknown>, map2: ProxyMapSource<unknown>) => {
Expand Down
Loading

0 comments on commit 9504df2

Please sign in to comment.