Skip to content

Commit

Permalink
feat(utilities): added trace function
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewcourtice committed Aug 4, 2021
1 parent 744ba80 commit a343a8a
Show file tree
Hide file tree
Showing 11 changed files with 118 additions and 9 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,6 @@ module.exports = {
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-inferrable-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/ban-types': 'warn',
},
};
11 changes: 8 additions & 3 deletions packages/utilities/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
export { default as Task } from './task';
export { default as Task } from './task/task';

export { default as clone } from './clone';
export { default as overwrite } from './overwrite';
export { default as clone } from './object/clone';
export { default as overwrite } from './object/overwrite';
export { default as trace } from './object/trace';

export { default as getType } from './type/get-type';
export { default as isArray } from './type/is-array';
export { default as isObject } from './type/is-object';

export * from './types';
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import getType from './get-type';
import getType from '../type/get-type';

import type {
Constructable,
RuntimeType,
} from './types';
} from '../types';

function cloneIdentity(input: unknown): unknown {
return input;
Expand Down
File renamed without changes.
93 changes: 93 additions & 0 deletions packages/utilities/src/object/trace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import isArray from '../type/is-array';
import isObject from '../type/is-object';
import clone from './clone';

type TraceGate<TValue extends object> = keyof ProxyHandler<TValue>;
type TraceCallback<TValue extends object> = (result: TraceResult<TValue>) => void;

interface TraceOptions<TValue extends object> {
gates: TraceGate<TValue>[];
paths: PropertyKey[];
hasGetGate: boolean;
}

interface TraceResult<TValue extends object> {
gate: TraceGate<TValue>;
paths: PropertyKey[];
oldValue: unknown;
newValue: unknown;
}

type GateMap<TValue extends object = any> = {
[TGate in TraceGate<TValue>]?: (callback: TraceCallback<TValue>, options: TraceOptions<TValue>) => ProxyHandler<TValue>[TGate];
}

const GATE_MAP = {
get: (callback, { hasGetGate, gates, paths }) => (target, prop, receiver) => {
const value = Reflect.get(target, prop, receiver);

if (hasGetGate) {
defaultCallback(callback, 'get', paths, prop, value, value);
}

return deepTrace(value, callback, {
gates,
hasGetGate,
paths: paths.concat(prop),
});
},
set: (callback, { paths }) => (target, prop, value, receiver) => {
defaultCallback(callback, 'set', paths, prop, target[prop], value);
return Reflect.set(target, prop, value, receiver);
},
deleteProperty: (callback, { paths }) => (target, prop) => {
defaultCallback(callback, 'deleteProperty', paths, prop, target[prop]);
return Reflect.deleteProperty(target, prop);
},
} as GateMap;

function defaultCallback<TValue extends object>(callback: TraceCallback<TValue>, gate: TraceGate<TValue>, paths: PropertyKey[], key: PropertyKey, oldValue: unknown, newValue?: unknown) {
try {
callback({
gate,
newValue,
paths: paths.concat(key),
oldValue: newValue === oldValue ? oldValue : clone(oldValue),
});
} catch {
console.warn('Trace callback failed');
}
}

function deepTrace<TValue extends object>(value: TValue, callback: TraceCallback<TValue>, options: TraceOptions<TValue>): TValue {
if (!isObject(value) && !isArray(value)) {
return value;
}

const {
gates,
} = options;

const handler = gates.reduce((output, gate) => {
const gateHandler = (GATE_MAP as GateMap<TValue>)[gate];

if (gateHandler) {
output[gate] = gateHandler(callback, options)?.bind(output) as any;
}

return output;
}, {} as ProxyHandler<TValue>);

return new Proxy(value, handler);
}

export default function trace<TValue extends object>(value: TValue, gates: TraceGate<TValue> | TraceGate<TValue>[], callback: TraceCallback<TValue>): TValue {
const allGates = ([] as TraceGate<TValue>[]).concat(gates);
const hasGetGate = allGates.includes('get');

return deepTrace(value, callback, {
hasGetGate,
gates: hasGetGate ? allGates : allGates.concat('get'),
paths: [],
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type {
Product,
TaskAbortCallback,
TaskExecutor,
} from './types';
} from '../types';

function safeRun<TResult>(bodyInvokee: Product<TResult>, finallyInvokee: Product): Product<TResult> {
return (...args: any[]) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type {
RuntimeType,
} from './types';
} from '../types';

export default function getType(input: unknown): RuntimeType {
return Object.prototype.toString.call(input).slice(8, -1).toLowerCase() as RuntimeType;
Expand Down
5 changes: 5 additions & 0 deletions packages/utilities/src/type/is-array.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import getType from './get-type';

export default function isArray(value: unknown): value is unknown[] {
return getType(value) === 'array';
}
5 changes: 5 additions & 0 deletions packages/utilities/src/type/is-object.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import getType from './get-type';

export default function isObject(value: unknown): value is object {
return getType(value) === 'object';
}
2 changes: 1 addition & 1 deletion packages/utilities/test/clone.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import clone from '../src/clone';
import clone from '../src/object/clone';

function getSimpleTypes(): Record<string, unknown> {
return {
Expand Down
2 changes: 1 addition & 1 deletion packages/utilities/test/overwrite.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import overwrite from '../src/overwrite';
import overwrite from '../src/object/overwrite';

describe('Utilities', () => {

Expand Down

0 comments on commit a343a8a

Please sign in to comment.