Proxy Handler Generic Execution
Proxy handler for tracing property access and function executions.Table of Contents
Technology Stack
Dependencies
0 dependency package!
Installation
npm i -s proxy-handler-generic-execution
Usage
Argument properties
-
onSuccess
- function triggered whenever proxied object function result is returned. -
onError
- function triggered whenever proxied object function throws an error. -
onNonFunction
- function triggered whenever proxied object non functional property is touched (object property getter is triggered). -
callbackEnding
- string used whenever proxied object function use callback style to return value and have callback to promise transformation function. This field define a name of a callback to promise transformation function which should be executed after primary function execution. Example
Callback payloads
onSuccess
fieldValueType: string;
fieldKey: string;
processingStrategy: "synchronous" | "promise async" | "callback ending";
functionArgs: unknown[];
functionResult: any;
processingResult: "succeed"
onError
fieldValueType: string;
fieldKey: string;
processingStrategy: "synchronous" | "promise async" | "callback ending";
functionArgs: unknown[];
functionError: Error;
processingResult: "failed"
onNonFunction
fieldValueType: string;
fieldKey: string;
Callback results
onSuccess
void;
onError
Error | void;
onNonFunction
void;
Examples
Logging function execution result
import { proxyHandlerGenericExecution } from "proxy-handler-generic-execution";
const userRepository = {
find: ({ id }) => ({ id, name: "John" }),
};
const wrappedUserRepository = new Proxy(
userRepository,
proxyHandlerGenericExecution({
onSuccess: console.log,
onNonFunction: () => {},
onError: () => {},
}),
);
wrappedUserRepository.find({ id: 1 });
/*
{
fieldValueType: 'function', <- userRepository.find field value is function
fieldKey: 'find', <- userRepository touched field key
processingStrategy: 'synchronous', <- userRepository.find returns no promise object
functionArgs: [ { id: 1 } ], <- userRepository.find execution arguments
functionResult: { id: 1, name: 'John' }, <- userRepository.find execution result
processingResult: 'succeed' <- userRepository.find did not throw during execution
}
*/
Logging function execution error
import { proxyHandlerGenericExecution } from "proxy-handler-generic-execution";
const userRepository = {
find: async () => {
throw Error("error message");
},
};
const wrappedUserRepository = new Proxy(
userRepository,
proxyHandlerGenericExecution({
onSuccess: () => {},
onNonFunction: () => {},
onError: console.log,
}),
);
wrappedUserRepository.find();
/*
{
fieldValueType: 'function', <- userRepository.find field value is function
fieldKey: 'find', <- userRepository touched field key
processingStrategy: 'promise async', <- userRepository.find returns promise object
functionArgs: [], <- userRepository.find execution arguments
functionError: Error { message: 'error message' }, <- userRepository.find error object
processingResult: 'failed' <- userRepository.find did not throw during execution
}
(node:11867) UnhandledPromiseRejectionWarning: Error: error message
at .../index.ts:112:11
at Generator.next (<anonymous>)
at .../index.ts:8:71
at new Promise (<anonymous>)
at __awaiter (.../index.ts:4:12)
at Object.find (.../index.ts:111:20)
at Proxy.<anonymous> (.../index.ts:72:39)
at Object.<anonymous> (.../index.ts:124:23)
at Module._compile (.../loader.js:999:30)
at Module.m._compile (.../index.ts:1455:23)
(node:11867) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 2)
(node:11867) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
*/
Logging object touched properties
import { proxyHandlerGenericExecution } from "proxy-handler-generic-execution";
const userRepository = {
repositoryName: "user",
};
const wrappedUserRepository = new Proxy(
userRepository,
proxyHandlerGenericExecution({
onSuccess: () => {},
onNonFunction: console.log,
onError: () => {},
}),
);
wrappedUserRepository.repositoryName;
/*
{
fieldValueType: 'string', <- userRepository.get field value is function
fieldKey: 'repositoryName', <- userRepository touched field key
}
*/
Modifying function execution error
import { proxyHandlerGenericExecution } from "proxy-handler-generic-execution";
const userRepository = {
find: async () => {
throw Error("error message");
},
};
const wrappedUserRepository = new Proxy(
userRepository,
proxyHandlerGenericExecution({
onSuccess: () => {},
onNonFunction: () => {},
onError: ({ error, key }) => {
error.message = `userRepository.${key} > ${error.message}`;
return error;
},
}),
);
wrappedUserRepository.find();
/*
(node:11867) UnhandledPromiseRejectionWarning: Error: userRepository.find > error message
at .../index.ts:112:11
at Generator.next (<anonymous>)
at .../index.ts:8:71
at new Promise (<anonymous>)
at __awaiter (.../index.ts:4:12)
at Object.find (.../index.ts:111:20)
at Proxy.<anonymous> (.../index.ts:72:39)
at Object.<anonymous> (.../index.ts:124:23)
at Module._compile (.../loader.js:999:30)
at Module.m._compile (.../index.ts:1455:23)
(node:11867) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 2)
(node:11867) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
*/
Generic logger for object of repositories
import { proxyHandlerGenericExecution } from "proxy-handler-generic-execution";
const proxyHandlerLogger = proxyHandlerGenericExecution({
onSuccess: console.log,
onNonFunction: console.log,
onError: console.log,
});
const repositories = {
user: {
find: ({ id }) => ({ id, name: "John" }),
},
order: {
create: ({ value }) => ({ id: 2, value }),
},
};
const proxiedRepositories = Object.fromEntries(
Object.entries(repositories).map(([repositoryName, repository]) => [
repositoryName,
new Proxy(repository, proxyHandlerLogger),
]),
);
Multiple proxies applied
import { proxyHandlerGenericExecution } from "proxy-handler-generic-execution";
const proxyHandlerErrorDecorator = proxyHandlerGenericExecution({
onSuccess: () => {},
onNonFunction: () => {},
onError: ({ error, key }) => {
error.message = `userRepository.${key} > ${error.message}`;
return error;
},
});
const proxyHandlerLogger = proxyHandlerGenericExecution({
onSuccess: console.log,
onNonFunction: console.log,
onError: console.log,
});
const userRepository = {
find: ({ id }) => ({ id, name: "John" }),
};
const proxiedUserRepository = [proxyHandlerErrorDecorator, proxyHandlerLogger].reduce((acc, handler) => new Proxy(acc, handler), userRepository);
Handling callback to promise transformation
import { StepFunctions } from "aws-sdk";
import { proxyHandlerGenericExecution } from "proxy-handler-generic-execution";
const callbackEnding = "promise";
const stepFunctions = new StepFunctions();
const wrappedStepFunctions = new Proxy(
stepFunctions,
proxyHandlerGenericExecution({
callbackEnding,
onSuccess: () => {},
onNonFunction: () => {},
onError: () => {},
}),
);
(async () => {
await wrappedStepFunctions.startExecution({ stateMachineArn: "ARN" })[callbackEnding]();
})();
Changelog
Contribute
- Suggestions about tests, implementation or others are welcome
- Pull requests are more than welcome
How to start
-
Clone project
git clone https://github.com/czlowiek488/proxy-handler-generic-execution.git
-
Install dependencies
npm install
-
Run all tests
npm run test
-
To run specific test run one of those commands
npm run test-promise
npm run test-callback
npm run test-sync
npm run test-error
npm run test-success
npm run test-common
npm run test-pattern --pattern='no-function'
Information
- Test patterns are stored in typescript enums
- Test patterns will never overlap with each other
- Test patterns must be used in it/describe block names
- Each test case must be executed with common tests and separated test coverage
- Husky is hooked for
- prepublish & prepare
3 parts of tests
- common - running along with each case
- case (describe) - the way strategies are used, sequence per case. Each sequence has own coverage report. New case must not change proxy handler implementation.
- strategy (it) - may be used in multiple not known ways, each way is a case. Each strategy is contained by each of all cases. New strategy may change proxy handler implementation.
Test overview
Common
Common |
---|
get no functional property test name |
get |
✓ |
Case / Strategy
Case | Strategy | |||
---|---|---|---|---|
classic function case | arrow function case | |||
return | throw | return | throw | |
✓ | ✓ | ✓ | ✓ | synchronous strategy |
✓ | ✓ | ✓ | ✓ | promise async strategy |
✓ | ✓ | ✓ | ✓ | callback ending strategy |
Writing tests
-
Case Test
- Add case test name to TestCaseDescribe enum
- Add proper file to test case directory. Must include all strategy tests.
- Typescript will show errors in places where your test is not implemented
- Implement your tests using already existing ones as reference
- Execute tests
-
Strategy Test
- Modify proxy handler implementation
- Add test name to TestStrategyIt enum
- Execute tests, coverage will be less than 100%
-
Common test
- Add common test name to TestCommonDescribe enum
- Add test to common test directory
- Execute tests