typeomatica 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.editorconfig ADDED
@@ -0,0 +1,11 @@
1
+ root = true
2
+
3
+ [*]
4
+ trim_trailing_whitespace = false
5
+ indent_style = tab
6
+ end_of_line = lf
7
+ insert_final_newline = true
8
+
9
+ [{package*.json,*.yml}]
10
+ indent_style = space
11
+ indent_size = 2
package/.eslintignore ADDED
File without changes
package/.eslintrc.js ADDED
@@ -0,0 +1,76 @@
1
+ module.exports = {
2
+ env: {
3
+ node: true,
4
+ es6: true,
5
+ },
6
+ extends: ['eslint:recommended'],
7
+ parserOptions: {
8
+ ecmaVersion: 2018,
9
+ },
10
+ rules: {
11
+ 'indent': ['error', 'tab'],
12
+ 'key-spacing': [
13
+ 'warn',
14
+ {
15
+ beforeColon: true,
16
+ afterColon: true,
17
+ align: 'colon',
18
+ },
19
+ ],
20
+ 'linebreak-style': ['error', 'unix'],
21
+ quotes: ['error', 'single'],
22
+ semi: ['error', 'always'],
23
+ 'no-unused-vars': 'warn',
24
+ 'no-shadow': [
25
+ 'error',
26
+ {
27
+ builtinGlobals: true,
28
+ hoist: 'all',
29
+ allow: [],
30
+ },
31
+ ],
32
+ 'space-before-function-paren': [
33
+ 'warn', {
34
+ 'anonymous': 'always',
35
+ 'named': 'always',
36
+ 'asyncArrow': 'always'
37
+ }
38
+ ],
39
+ 'prefer-template': 'warn',
40
+ 'prefer-spread': 'warn',
41
+ 'no-useless-concat': 'warn',
42
+ 'prefer-rest-params': 'warn',
43
+ 'prefer-destructuring': 'warn',
44
+ 'no-useless-computed-key': 'warn',
45
+ 'no-useless-constructor': 'warn',
46
+ 'no-useless-rename': 'warn',
47
+ 'no-this-before-super': 'warn',
48
+ 'no-new-symbol': 'warn',
49
+ 'no-duplicate-imports': 'warn',
50
+ 'no-confusing-arrow': 'warn',
51
+ 'no-multi-assign': 'warn',
52
+ 'no-lonely-if': 'warn',
53
+ 'newline-per-chained-call': 'warn',
54
+ 'new-cap': 'warn',
55
+ 'func-name-matching': 'error',
56
+ // 'consistent-this' : 'error',
57
+ 'line-comment-position': [
58
+ 'warn',
59
+ {
60
+ position: 'above',
61
+ },
62
+ ],
63
+ quotes: ['error', 'single'],
64
+ yoda: 'warn',
65
+ },
66
+ 'overrides': [
67
+ {
68
+ 'files': ['lib/**/*.js'],
69
+ 'rules': {
70
+ 'prefer-rest-params': 0,
71
+ 'no-redeclare': 0
72
+ }
73
+ }
74
+ ]
75
+
76
+ };
package/.gitattributes ADDED
@@ -0,0 +1,22 @@
1
+
2
+ # Automatically normalize line endings for all text-based files
3
+ # http://git-scm.com/docs/gitattributes#_end_of_line_conversion
4
+
5
+ # For the following file types, normalize line endings to LF on
6
+ # checkin and prevent conversion to CRLF when they are checked out
7
+ # (this is required in order to prevent newline related issues like,
8
+ # for example, after the build script is run)
9
+ *.html text eol=lf
10
+ *.css text eol=lf
11
+ *.less text eol=lf
12
+ *.scss text eol=lf
13
+ *.sss text eol=lf
14
+ *.sass text eol=lf
15
+ *.js text eol=lf
16
+ *.json text eol=lf
17
+ *.yml text eol=lf
18
+ *.yaml text eol=lf
19
+ *.md text eol=lf
20
+ *.sh text eol=lf
21
+ *.txt text eol=lf
22
+ *.xml text eol=lf
@@ -0,0 +1,62 @@
1
+ # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2
+ # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3
+
4
+ name: Node.js CI
5
+
6
+ on:
7
+ push:
8
+ branches: [ main ]
9
+ pull_request:
10
+ branches: [ main ]
11
+
12
+ jobs:
13
+ build:
14
+
15
+ runs-on: ubuntu-latest
16
+
17
+ strategy:
18
+ matrix:
19
+ node-version: [12.x, 14.x]
20
+
21
+ steps:
22
+ - uses: actions/checkout@v2
23
+ - name: Use Node.js ${{ matrix.node-version }}
24
+ uses: actions/setup-node@v1
25
+ with:
26
+ node-version: ${{ matrix.node-version }}
27
+ - run: npm ci
28
+ - run: npm run build --if-present
29
+ - run: npm test
30
+
31
+
32
+ test:
33
+ runs-on: ubuntu-latest
34
+
35
+ steps:
36
+ - uses: actions/checkout@master
37
+ - name: Use Node.js 12.x
38
+ uses: actions/setup-node@master
39
+ with:
40
+ node-version: 12.x
41
+
42
+ - name: npm install
43
+ run: npm install
44
+
45
+ - name: Test
46
+ run: npm run test:cov
47
+
48
+ - name: Coveralls Parallel
49
+ uses: coverallsapp/github-action@master
50
+ with:
51
+ github-token: ${{ secrets.github_token }}
52
+ parallel: true
53
+
54
+ finish:
55
+ needs: test
56
+ runs-on: ubuntu-latest
57
+ steps:
58
+ - name: Coveralls Finished
59
+ uses: coverallsapp/github-action@master
60
+ with:
61
+ github-token: ${{ secrets.github_token }}
62
+ parallel-finished: true
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Mythographica → went.out@gmail.com
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # Type ø matica
2
+
3
+ [![Coverage Status](https://coveralls.io/repos/github/wentout/typeomatica/badge.svg?branch=main)](https://coveralls.io/github/wentout/typeomatica?branch=main)
4
+
5
+ ![NPM](https://img.shields.io/npm/l/typeomatica)
6
+ ![GitHub package.json version](https://img.shields.io/github/package-json/v/wentout/typeomatica)
7
+ ![GitHub last commit](https://img.shields.io/github/last-commit/wentout/typeomatica)
8
+
9
+ [**$ npm install <u>typeomatica</u>**](https://www.npmjs.com/package/typeomatica)
10
+
11
+
12
+ This package is a part of [mnemonica](https://www.npmjs.com/package/mnemonica) project.
13
+
14
+ Strict Types checker for objects which represent Data Types.
15
+
16
+ # how it works
17
+
18
+ see `test/index.ts`
19
+
20
+ ```js
21
+
22
+ class SimpleBase extends BasePrototype {
23
+ stringProp = '123';
24
+ };
25
+
26
+ // nect code line will work properly
27
+ simpleInstance.stringProp = '321';
28
+
29
+ // but next code line will throw TypeError('Type Mismatch')
30
+ // @ts-ignore
31
+ simpleInstance.stringProp = 123;
32
+
33
+ ```
34
+
35
+ That is it. It will be impossible to assign anything else except of:
36
+
37
+ ```js
38
+ typeof something === 'string'
39
+ ```
40
+
41
+ to `stringProp` in runtime.
42
+
43
+ As we describe Data Types &mdash; please take a peek for tests directory:
44
+ [HERE](https://github.com/wentout/typeomatica/blob/main/test/index.ts).
package/jest.config.js ADDED
@@ -0,0 +1,10 @@
1
+ module.exports = {
2
+ preset: 'ts-jest',
3
+ testEnvironment: 'node',
4
+ testMatch: ['**/test/**/index.ts', '**/test/**/addition.js'],
5
+ globals: {
6
+ 'ts-jest': {
7
+ tsconfig: './test/tsconfig.json'
8
+ }
9
+ },
10
+ };
package/lib/index.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ export declare type IDEF<T, P = {}, R = {}> = {
2
+ new (...args: any[]): T;
3
+ (this: T, ...args: any[]): R;
4
+ prototype: P;
5
+ };
6
+ declare const BaseConstructor: ObjectConstructor;
7
+ export declare class BaseClass extends BaseConstructor {
8
+ }
9
+ export {};
package/lib/index.js ADDED
@@ -0,0 +1,198 @@
1
+ 'use strict';
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BaseClass = void 0;
4
+ const ErrorsNames = {
5
+ TYPE_MISMATCH: 'Type Mismatch',
6
+ ACCESS_DENIED: 'Value Access Denied',
7
+ MISSING_PROP: 'Attempt to Access to Undefined Prop',
8
+ RIP_FUNCTIONS: 'Functions are Restricted'
9
+ };
10
+ const PRIMITIVE_TYPES = [
11
+ 'string',
12
+ 'number',
13
+ 'boolean',
14
+ ];
15
+ const isPrimitive = (value) => {
16
+ return PRIMITIVE_TYPES.includes(typeof value);
17
+ };
18
+ const primitives = (initialValue) => {
19
+ let value = Object(initialValue);
20
+ const initialType = typeof initialValue;
21
+ return {
22
+ get() {
23
+ const proxyAsValue = new Proxy(value, {
24
+ get(_, prop) {
25
+ if (prop === Symbol.toPrimitive) {
26
+ return function (hint) {
27
+ if (hint !== initialType) {
28
+ throw new ReferenceError(ErrorsNames.ACCESS_DENIED);
29
+ }
30
+ return value.valueOf();
31
+ };
32
+ }
33
+ if (prop === 'valueOf') {
34
+ return function () {
35
+ return value.valueOf();
36
+ };
37
+ }
38
+ if (value[prop] instanceof Function) {
39
+ return value[prop].bind(value);
40
+ }
41
+ return value[prop];
42
+ }
43
+ });
44
+ return proxyAsValue;
45
+ },
46
+ set(replacementValue) {
47
+ if (replacementValue instanceof value.constructor) {
48
+ value = replacementValue;
49
+ return value;
50
+ }
51
+ const prevalue = Object(replacementValue);
52
+ if (prevalue instanceof value.constructor) {
53
+ value = prevalue;
54
+ return value;
55
+ }
56
+ const error = new TypeError(ErrorsNames.TYPE_MISMATCH);
57
+ throw error;
58
+ }
59
+ };
60
+ };
61
+ const special = (value) => {
62
+ return {
63
+ get() {
64
+ return value;
65
+ },
66
+ set(replacementValue) {
67
+ if (typeof replacementValue === typeof value) {
68
+ value = replacementValue;
69
+ return value;
70
+ }
71
+ const error = new TypeError(ErrorsNames.TYPE_MISMATCH);
72
+ throw error;
73
+ }
74
+ };
75
+ };
76
+ const nullish = (value) => {
77
+ return {
78
+ get() {
79
+ return value;
80
+ },
81
+ set() {
82
+ const error = new TypeError(ErrorsNames.TYPE_MISMATCH);
83
+ throw error;
84
+ }
85
+ };
86
+ };
87
+ const objects = (value) => {
88
+ return {
89
+ get() {
90
+ return value;
91
+ },
92
+ set(replacementValue) {
93
+ if (replacementValue instanceof Object && replacementValue.constructor === value.constructor) {
94
+ value = replacementValue;
95
+ return value;
96
+ }
97
+ const error = new TypeError(ErrorsNames.TYPE_MISMATCH);
98
+ throw error;
99
+ }
100
+ };
101
+ };
102
+ const functions = () => {
103
+ throw new TypeError(ErrorsNames.RIP_FUNCTIONS);
104
+ };
105
+ const resolver = Object.entries({
106
+ primitives,
107
+ special,
108
+ nullish,
109
+ objects,
110
+ functions
111
+ }).reduce((obj, [key, _handler]) => {
112
+ obj[key] = function (initialValue, receiver) {
113
+ const handler = _handler(initialValue);
114
+ return {
115
+ get() {
116
+ const invocationThis = this;
117
+ if (invocationThis !== receiver) {
118
+ throw new ReferenceError(ErrorsNames.ACCESS_DENIED);
119
+ }
120
+ return handler.get();
121
+ },
122
+ set(replacementValue) {
123
+ const invocationThis = this;
124
+ if (invocationThis !== receiver) {
125
+ throw new ReferenceError(ErrorsNames.ACCESS_DENIED);
126
+ }
127
+ return handler.set(replacementValue);
128
+ }
129
+ };
130
+ };
131
+ return obj;
132
+ }, {});
133
+ const createProperty = (propName, initialValue, receiver) => {
134
+ const value = initialValue;
135
+ const valueIsPrimitive = isPrimitive(initialValue);
136
+ const isObject = typeof initialValue === 'object';
137
+ const isFunction = initialValue instanceof Function;
138
+ const isNull = initialValue === null;
139
+ const type = valueIsPrimitive ? 'primitives' : (isObject ? (isNull ? 'nullish' : 'objects') : (isFunction ? 'functions' : 'special'));
140
+ const descriptor = resolver[type](value, receiver);
141
+ const result = Reflect.defineProperty(receiver, propName, Object.assign(Object.assign({}, descriptor), { enumerable: true }));
142
+ return result;
143
+ };
144
+ const handlers = {
145
+ get(target, prop, receiver) {
146
+ const result = Reflect.get(target, prop, receiver);
147
+ if (result !== undefined) {
148
+ return result;
149
+ }
150
+ if (prop === 'toJSON') {
151
+ return function () {
152
+ return JSON.stringify(Object.entries(this).reduce((obj, [key, value]) => {
153
+ obj[key] = value.valueOf();
154
+ return obj;
155
+ }, {}));
156
+ };
157
+ }
158
+ throw new Error(`${ErrorsNames.MISSING_PROP}: [ ${String(prop).valueOf()} ]`);
159
+ },
160
+ set(_, prop, value, receiver) {
161
+ const result = createProperty(prop, value, receiver);
162
+ return result;
163
+ },
164
+ };
165
+ const BaseTarget = Object.create(null);
166
+ const BaseConstructor = function (InstanceTarget = BaseTarget) {
167
+ if (!new.target) {
168
+ const constructor = BaseConstructor.bind(this, InstanceTarget);
169
+ constructor.prototype = {
170
+ constructor: BaseConstructor
171
+ };
172
+ return constructor;
173
+ }
174
+ const InstancePrototype = new Proxy(InstanceTarget, handlers);
175
+ let protoPointer = this;
176
+ let protoConstrcutor;
177
+ do {
178
+ protoPointer = Reflect.getPrototypeOf(protoPointer);
179
+ protoConstrcutor = Reflect.getOwnPropertyDescriptor(protoPointer, 'constructor').value;
180
+ } while (protoConstrcutor !== BaseConstructor);
181
+ Reflect.setPrototypeOf(protoPointer, InstancePrototype);
182
+ };
183
+ Object.defineProperty(module, 'exports', {
184
+ get() {
185
+ return BaseConstructor;
186
+ },
187
+ enumerable: true
188
+ });
189
+ class BaseClass extends BaseConstructor {
190
+ }
191
+ exports.BaseClass = BaseClass;
192
+ ;
193
+ Object.defineProperty(module.exports, 'BaseClass', {
194
+ get() {
195
+ return BaseClass;
196
+ },
197
+ enumerable: true
198
+ });
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "typeomatica",
3
+ "version": "0.2.5",
4
+ "description": "type logic against javascript metaprogramming",
5
+ "main": "lib/index.js",
6
+ "scripts": {
7
+ "build": "rm -rf ./lib && npx tsc --pretty",
8
+ "buildlint": "rm -rf ./lib && npx tsc --pretty && npm run lint",
9
+ "lint": "npx eslint --fix --ignore-path .gitignore ./lib",
10
+ "test": "npx jest",
11
+ "test:cov": "npx jest --collectCoverage",
12
+ "debug": "npx --node-arg=--inspect-brk jest"
13
+ },
14
+ "keywords": [
15
+ "strict",
16
+ "types",
17
+ "runtime",
18
+ "javascript",
19
+ "metaprogramming"
20
+ ],
21
+ "author": "went.out@gmail.com",
22
+ "license": "MIT",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/wentout/typeomatica.git"
26
+ },
27
+ "devDependencies": {
28
+ "@types/jest": "^27.0.1",
29
+ "@types/node": "^16.9.2",
30
+ "eslint": "^7.32.0",
31
+ "husky": "^7.0.2",
32
+ "jest": "^27.2.0",
33
+ "lint-staged": "^11.1.2",
34
+ "set-value": "^4.1.0",
35
+ "ts-jest": "^27.0.5",
36
+ "ts-node": "^10.2.1",
37
+ "tslint": "^6.1.3",
38
+ "typescript": "^4.4.3"
39
+ }
40
+ }
package/src/index.ts ADDED
@@ -0,0 +1,270 @@
1
+ 'use strict';
2
+
3
+ const ErrorsNames = {
4
+ TYPE_MISMATCH: 'Type Mismatch',
5
+ ACCESS_DENIED: 'Value Access Denied',
6
+ MISSING_PROP: 'Attempt to Access to Undefined Prop',
7
+ RIP_FUNCTIONS: 'Functions are Restricted'
8
+ };
9
+
10
+ const PRIMITIVE_TYPES = [
11
+ 'string',
12
+ 'number',
13
+ 'boolean',
14
+ ];
15
+
16
+ const isPrimitive = (value: unknown) => {
17
+ return PRIMITIVE_TYPES.includes(typeof value);
18
+ };
19
+
20
+ const primitives = (initialValue: object) => {
21
+ let value = Object(initialValue);
22
+ const initialType = typeof initialValue;
23
+
24
+ return {
25
+ get() {
26
+ const proxyAsValue = new Proxy(value, {
27
+ // get(target, prop, receiver) {
28
+ get(_, prop) {
29
+
30
+ if (prop === Symbol.toPrimitive) {
31
+ return function (hint: string) {
32
+ if (hint !== initialType) {
33
+ throw new ReferenceError(ErrorsNames.ACCESS_DENIED);
34
+ }
35
+ return value.valueOf();
36
+ }
37
+ }
38
+
39
+ if (prop === 'valueOf') {
40
+ return function () {
41
+ return value.valueOf();
42
+ }
43
+ }
44
+
45
+ // @ts-ignore
46
+ if (value[prop] instanceof Function) {
47
+ return value[prop].bind(value);
48
+ }
49
+
50
+ return value[prop];
51
+ }
52
+ });
53
+ return proxyAsValue;
54
+ },
55
+ // get() {
56
+ // const preparedValue = {
57
+ // [Symbol.toPrimitive]() {
58
+ // return function () {
59
+ // throw new ReferenceError(ErrorsNames.ACCESS_DENIED);
60
+ // };
61
+ // }
62
+ // };
63
+ // Reflect.setPrototypeOf(preparedValue, value);
64
+ // debugger;
65
+ // return preparedValue;
66
+ // },
67
+ set(replacementValue: unknown) {
68
+ if (replacementValue instanceof value.constructor) {
69
+ value = replacementValue;
70
+ return value;
71
+ }
72
+
73
+ const prevalue = Object(replacementValue);
74
+
75
+ if (prevalue instanceof value.constructor) {
76
+ value = prevalue;
77
+ return value;
78
+ }
79
+
80
+ const error = new TypeError(ErrorsNames.TYPE_MISMATCH);
81
+ throw error;
82
+ }
83
+ };
84
+ };
85
+
86
+ const special = (value: object) => {
87
+ return {
88
+ get() {
89
+ return value;
90
+ },
91
+ set(replacementValue: object) {
92
+ if (typeof replacementValue === typeof value) {
93
+ value = replacementValue;
94
+ return value;
95
+
96
+ }
97
+ const error = new TypeError(ErrorsNames.TYPE_MISMATCH);
98
+ throw error;
99
+ }
100
+ }
101
+ };
102
+
103
+ const nullish = (value: object) => {
104
+ return {
105
+ get() {
106
+ return value;
107
+ },
108
+ set() {
109
+ const error = new TypeError(ErrorsNames.TYPE_MISMATCH);
110
+ throw error;
111
+ }
112
+ }
113
+ };
114
+
115
+ const objects = (value: object) => {
116
+ return {
117
+ get() {
118
+ return value;
119
+ },
120
+ set(replacementValue: unknown) {
121
+ if (replacementValue instanceof Object && replacementValue.constructor === value.constructor) {
122
+ value = replacementValue;
123
+ return value;
124
+ }
125
+ const error = new TypeError(ErrorsNames.TYPE_MISMATCH);
126
+ throw error;
127
+ }
128
+ }
129
+ };
130
+
131
+ const functions = () => {
132
+ throw new TypeError(ErrorsNames.RIP_FUNCTIONS);
133
+ };
134
+
135
+ const resolver = Object.entries({
136
+ primitives,
137
+ special,
138
+ nullish,
139
+ objects,
140
+ functions
141
+ }).reduce((obj: object, [key, _handler]) => {
142
+ // @ts-ignore
143
+ obj[key] = function (initialValue: object, receiver: object) {
144
+ const handler = _handler(initialValue);
145
+ return {
146
+ get() {
147
+ const invocationThis = this;
148
+ if (invocationThis !== receiver) {
149
+ throw new ReferenceError(ErrorsNames.ACCESS_DENIED);
150
+ }
151
+ return handler.get();
152
+ },
153
+ set(replacementValue: unknown) {
154
+ const invocationThis = this;
155
+ if (invocationThis !== receiver) {
156
+ throw new ReferenceError(ErrorsNames.ACCESS_DENIED);
157
+ }
158
+ return handler.set(replacementValue);
159
+ }
160
+ }
161
+ };
162
+
163
+ return obj;
164
+ }, {});
165
+
166
+ const createProperty = (propName: string, initialValue: any, receiver: object) => {
167
+
168
+ const value = initialValue;
169
+ const valueIsPrimitive = isPrimitive(initialValue);
170
+ const isObject = typeof initialValue === 'object';
171
+ const isFunction = initialValue instanceof Function;
172
+ const isNull = initialValue === null;
173
+
174
+ const type = valueIsPrimitive ? 'primitives' : (
175
+ isObject ? (
176
+ isNull ? 'nullish' : 'objects'
177
+ ) : (
178
+ isFunction ? 'functions' : 'special'));
179
+
180
+ // @ts-ignore
181
+ const descriptor = resolver[type](value, receiver);
182
+
183
+ const result = Reflect.defineProperty(receiver, propName, {
184
+ ...descriptor,
185
+ enumerable: true
186
+ });
187
+
188
+ return result;
189
+ };
190
+
191
+
192
+ const handlers = {
193
+ get(target: object, prop: string | symbol, receiver: object) {
194
+ const result = Reflect.get(target, prop, receiver);
195
+ if (result !== undefined) {
196
+ return result;
197
+ }
198
+ if (prop === 'toJSON') {
199
+ return function (this: typeof target) {
200
+ return JSON.stringify(Object.entries(this).reduce((obj, [key, value]) => {
201
+ // @ts-ignore
202
+ obj[key] = value.valueOf();
203
+ return obj;
204
+ }, {}));
205
+ }
206
+ }
207
+ throw new Error(`${ErrorsNames.MISSING_PROP}: [ ${String(prop).valueOf()} ]`);
208
+ },
209
+ set(_: object, prop: string, value: unknown, receiver: object) {
210
+ const result = createProperty(prop, value, receiver);
211
+ return result;
212
+ },
213
+ // defineProperty(target: object, key: string, descriptor: object) {
214
+ // Reflect.defineProperty(target, key, descriptor);
215
+ // return true;
216
+ // }
217
+ };
218
+
219
+ // user have to precisely define all props
220
+ const BaseTarget = Object.create(null);
221
+
222
+ // const BasePrototype = new Proxy(BaseTarget, handlers);
223
+
224
+ export type IDEF<T, P = {}, R = {}> = {
225
+ new(...args: any[]): T;
226
+ (this: T, ...args: any[]): R;
227
+ prototype: P;
228
+ };
229
+
230
+ // @ts-ignore
231
+ const BaseConstructor = function (this: object, InstanceTarget = BaseTarget) {
232
+ if (!new.target) {
233
+ const constructor = BaseConstructor.bind(this, InstanceTarget);
234
+ constructor.prototype = {
235
+ constructor: BaseConstructor
236
+ };
237
+ return constructor;
238
+ }
239
+
240
+ const InstancePrototype = new Proxy(InstanceTarget, handlers);
241
+
242
+ let protoPointer = this;
243
+ let protoConstrcutor;
244
+ do {
245
+ protoPointer = Reflect.getPrototypeOf(protoPointer) as object;
246
+ protoConstrcutor = Reflect.getOwnPropertyDescriptor(protoPointer, 'constructor')!.value;
247
+ } while (protoConstrcutor !== BaseConstructor);
248
+ Reflect.setPrototypeOf(protoPointer, InstancePrototype);
249
+
250
+ } as ObjectConstructor;
251
+ // } as IDEF;
252
+
253
+ // Reflect.setPrototypeOf(BaseConstructor.prototype, BasePrototype);
254
+
255
+ Object.defineProperty(module, 'exports', {
256
+ get() {
257
+ return BaseConstructor;
258
+ },
259
+ enumerable: true
260
+ });
261
+
262
+ // @ts-ignore
263
+ export class BaseClass extends BaseConstructor { };
264
+
265
+ Object.defineProperty(module.exports, 'BaseClass', {
266
+ get() {
267
+ return BaseClass;
268
+ },
269
+ enumerable: true
270
+ });
@@ -0,0 +1,3 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`props tests correct JSON.stringify 1`] = `"\\"{\\\\\\"numberValue\\\\\\":123,\\\\\\"stringValue\\\\\\":\\\\\\"123\\\\\\",\\\\\\"booleanValue\\\\\\":false,\\\\\\"objectValue\\\\\\":{}}\\""`;
@@ -0,0 +1,43 @@
1
+ 'use strict';
2
+
3
+ const BasePrototype = require('..');
4
+
5
+ class Base extends BasePrototype({
6
+ additionalProp: 321,
7
+ }) {
8
+ numberValue = 123;
9
+ constructor() {
10
+ super();
11
+ this.stringValue = '123';
12
+ this.booleanValue = true;
13
+ this.objectValue = {};
14
+ }
15
+ };
16
+
17
+ const baseInstance = new Base;
18
+
19
+ describe('props tests', () => {
20
+
21
+ test('base instance has props', () => {
22
+ expect(Object.keys(baseInstance)).toEqual(["numberValue", "stringValue", "booleanValue", "objectValue"]);
23
+ });
24
+
25
+ test('JavaScript class fields allow re-definition', () => {
26
+ baseInstance.numberValue = '123';
27
+ expect(baseInstance.numberValue).toEqual('123');
28
+ });
29
+
30
+ test('everything the rest is the same', () => {
31
+ expect(baseInstance.additionalProp).toEqual(321);
32
+ expect(() => {
33
+ baseInstance.stringValue = 123;
34
+ }).toThrow(new TypeError('Type Mismatch'));
35
+ expect(() => {
36
+ baseInstance.booleanValue = 123;
37
+ }).toThrow(new TypeError('Type Mismatch'));
38
+ expect(() => {
39
+ baseInstance.objectValue = null;
40
+ }).toThrow(new TypeError('Type Mismatch'));
41
+ });
42
+
43
+ });
package/test/index.ts ADDED
@@ -0,0 +1,410 @@
1
+ 'use strict';
2
+
3
+ // BasePrototype & BaseClass are the same function
4
+ // go as you want for being meaningfull
5
+ // or meaningless
6
+
7
+ const BasePrototype = require('..');
8
+ import { BaseClass, IDEF } from '..';
9
+
10
+ class Base extends BasePrototype({
11
+ additionalProp: 321,
12
+ someMethod() {
13
+ return this.numberValue.valueOf();
14
+ },
15
+ }) {
16
+ numberValue = 123;
17
+ constructor() {
18
+ super();
19
+ this.stringValue = '123';
20
+ this.booleanValue = true;
21
+ this.objectValue = {};
22
+ }
23
+ };
24
+
25
+ const baseInstance = new Base;
26
+
27
+ const upperInstance = Object.create(baseInstance);
28
+
29
+ class SimpleBase extends BaseClass {
30
+ stringProp = '123';
31
+ };
32
+ const simpleInstance = new SimpleBase;
33
+
34
+ type MyFunctionalConstructorInstance = {
35
+ stringProp: string
36
+ };
37
+
38
+ const MyFunctionalConstructor = function () {
39
+ this.stringProp = '123';
40
+ } as IDEF<MyFunctionalConstructorInstance>;
41
+
42
+ Reflect.setPrototypeOf(MyFunctionalConstructor.prototype, new BasePrototype);
43
+
44
+ const myFunctionalInstance = new MyFunctionalConstructor();
45
+
46
+ class SecondaryExtend extends Base { second = 123 };
47
+ class TripleExtend extends SecondaryExtend { };
48
+ const tiripleExtendInstance = new TripleExtend;
49
+
50
+ class NetworkedExtention extends BasePrototype(tiripleExtendInstance) { };
51
+
52
+ const networkedInstance = new NetworkedExtention;
53
+
54
+ class ExtendedArray extends BasePrototype([1, 2, 3]) { };
55
+ class ExtendedSet extends BasePrototype(new Set([1, 2, 3])) { };
56
+
57
+ const extendedArrayInstance = new ExtendedArray;
58
+ const extendedSetInstance = new ExtendedSet;
59
+
60
+ const MUTATION_VALUE = -2;
61
+
62
+ describe('props tests', () => {
63
+
64
+ test('base instance has props', () => {
65
+ expect(Object.keys(baseInstance)).toEqual(["numberValue", "stringValue", "booleanValue", "objectValue"]);
66
+ });
67
+
68
+ test('simple instance works & strings too', () => {
69
+ expect(simpleInstance.stringProp.toString()).toBe('123');
70
+ expect(simpleInstance.stringProp.length).toBe(3);
71
+ expect(/String$/.test(simpleInstance.stringProp.constructor.name)).toBe(true);
72
+ expect(() => {
73
+
74
+ // @ts-ignore
75
+ simpleInstance.stringProp = 123;
76
+
77
+ }).toThrow(new TypeError('Type Mismatch'));
78
+ });
79
+
80
+ test('correct boolean comparison with type coercion', () => {
81
+ expect(() => {
82
+
83
+ const { booleanValue } = baseInstance;
84
+ booleanValue != false;
85
+
86
+ }).toThrow(new TypeError('Value Access Denied'));
87
+ });
88
+
89
+ test('fails boolean arithmetics', () => {
90
+ expect(() => {
91
+
92
+ baseInstance.booleanValue + 5;
93
+
94
+ }).toThrow(new ReferenceError('Value Access Denied'));
95
+ });
96
+
97
+ test('correct boolean assignment', () => {
98
+
99
+ let { booleanValue } = baseInstance;
100
+ expect(booleanValue.valueOf()).toEqual(true);
101
+
102
+ // warning!
103
+ // booleanValue does not rely on baseInstance anymore!
104
+ booleanValue = new Boolean(false);
105
+
106
+ let value = baseInstance.booleanValue.valueOf();
107
+ expect(value).toEqual(true);
108
+
109
+
110
+ baseInstance.booleanValue = new Boolean(false);
111
+ value = baseInstance.booleanValue.valueOf();
112
+ expect(value).toEqual(false);
113
+
114
+ });
115
+
116
+ test('correct JSON.stringify', () => {
117
+ const stringifyResult = JSON.stringify(baseInstance);
118
+ expect(stringifyResult).toMatchSnapshot();
119
+ });
120
+
121
+ test('correct boolean constructor', () => {
122
+ expect(baseInstance.booleanValue).toBeInstanceOf(Boolean);
123
+ });
124
+
125
+ test('correct object assignment', () => {
126
+ baseInstance.objectValue = { a: 123 };
127
+ expect(baseInstance.objectValue.a).toEqual(123);
128
+ });
129
+
130
+ test('wrong assignment to objects', () => {
131
+
132
+ expect(() => {
133
+
134
+ baseInstance.objectValue = 123;
135
+
136
+ }).toThrow(new TypeError('Type Mismatch'));
137
+
138
+ expect(() => {
139
+
140
+ baseInstance.objectValue = new Set();
141
+
142
+ }).toThrow(new TypeError('Type Mismatch'));
143
+
144
+ });
145
+
146
+ test('fails number arithmetics', () => {
147
+ expect(() => {
148
+
149
+ baseInstance.numberValue + 5;
150
+
151
+ }).toThrow();
152
+ });
153
+
154
+ test('correct number arithmetics using .valueOf()', () => {
155
+
156
+ baseInstance.numberValue = MUTATION_VALUE;
157
+
158
+ const result = baseInstance.numberValue.valueOf() + 5;
159
+ expect(result).toStrictEqual(3);
160
+
161
+ });
162
+
163
+ test('correct number arithmetics using hinting for Symbol.toPrimitive (hint)', () => {
164
+
165
+ const result = 3 + +baseInstance.numberValue;
166
+ expect(result).toStrictEqual(1);
167
+
168
+ });
169
+
170
+ test('correct number value', () => {
171
+ expect(baseInstance.numberValue.toString()).toStrictEqual(MUTATION_VALUE.toString());
172
+ expect(/Number$/.test(baseInstance.numberValue.constructor.name)).toBe(true);
173
+ });
174
+
175
+
176
+ test('wrong assignment', () => {
177
+ expect(() => {
178
+
179
+ baseInstance.booleanValue = 123;
180
+
181
+ }).toThrow(new TypeError('Type Mismatch'));
182
+ });
183
+
184
+ test('correct null value', () => {
185
+ baseInstance.nullValue = null;
186
+ expect(baseInstance.nullValue).toEqual(null);
187
+ });
188
+
189
+ test('any assignment to null value', () => {
190
+ expect(() => {
191
+
192
+ baseInstance.nullValue = 123;
193
+
194
+ }).toThrow(new TypeError('Type Mismatch'));
195
+ });
196
+
197
+
198
+ test('correct undefined value', () => {
199
+ baseInstance.undefinedValue = undefined;
200
+ expect(baseInstance.undefinedValue).toEqual(undefined);
201
+ });
202
+
203
+ test('wrong assignment to undefined value', () => {
204
+ expect(() => {
205
+
206
+ baseInstance.undefinedValue = 123;
207
+
208
+ }).toThrow(new TypeError('Type Mismatch'));
209
+ });
210
+
211
+ test('correct BigInt value', () => {
212
+ baseInstance.BigIntValue = BigInt(1);
213
+ expect(baseInstance.BigIntValue).toEqual(BigInt(1));
214
+ });
215
+
216
+ test('correct assignment to BigInt value', () => {
217
+ baseInstance.BigIntValue = BigInt(2);
218
+ expect(baseInstance.BigIntValue).toEqual(BigInt(2));
219
+ });
220
+
221
+ test('wrong assignment to BigInt value', () => {
222
+ expect(() => {
223
+
224
+ baseInstance.BigIntValue = 123;
225
+
226
+ }).toThrow(new TypeError('Type Mismatch'));
227
+ });
228
+
229
+ test('correct Symbol value', () => {
230
+ baseInstance.SymbolValue = Symbol('test');
231
+ expect(typeof baseInstance.SymbolValue).toEqual('symbol');
232
+ });
233
+
234
+ test('wrong assignment to BigInt value', () => {
235
+ expect(() => {
236
+
237
+ baseInstance.SymbolValue = 123;
238
+
239
+ }).toThrow(new TypeError('Type Mismatch'));
240
+ });
241
+
242
+ test('correct prototype correction', () => {
243
+ expect(baseInstance.additionalProp).toEqual(321);
244
+ expect(baseInstance.toString).toBeInstanceOf(Function);
245
+ });
246
+
247
+ test('missing value fails', () => {
248
+ expect(() => {
249
+
250
+ baseInstance.missingValue > 1;
251
+
252
+ }).toThrow(new TypeError('Attempt to Access to Undefined Prop: [ missingValue ]'));
253
+ });
254
+
255
+ });
256
+
257
+ describe('prototype mutations tests', () => {
258
+
259
+ test('incorrect prototype invocation number get', () => {
260
+ expect(() => {
261
+
262
+ upperInstance.numberValue > 1;
263
+
264
+ }).toThrow(new ReferenceError('Value Access Denied'));
265
+ });
266
+
267
+ test('incorrect prototype invocation number set', () => {
268
+ expect(() => {
269
+
270
+ upperInstance.numberValue = new Number(1);
271
+
272
+ }).toThrow(new ReferenceError('Value Access Denied'));
273
+ });
274
+
275
+
276
+ test('incorrect prototype invocation string get', () => {
277
+ expect(() => {
278
+
279
+ upperInstance.stringValue > '1';
280
+
281
+ }).toThrow(new ReferenceError('Value Access Denied'));
282
+ });
283
+
284
+ test('incorrect prototype invocation string set', () => {
285
+ expect(() => {
286
+
287
+ upperInstance.stringValue = new String(1);
288
+
289
+ }).toThrow(new ReferenceError('Value Access Denied'));
290
+ });
291
+
292
+ });
293
+
294
+ describe('methods tests', () => {
295
+
296
+ test('functions are restricted for data type', () => {
297
+
298
+ expect(() => {
299
+
300
+ baseInstance.someMethod = function () { };
301
+
302
+ }).toThrow(new TypeError('Functions are Restricted'));
303
+
304
+ });
305
+
306
+ test('proxy proto methods are SOLID', () => {
307
+
308
+ const result = baseInstance.someMethod();
309
+ expect(result).toBe(MUTATION_VALUE);
310
+
311
+ });
312
+
313
+ });
314
+
315
+ describe('property re-definition works', () => {
316
+
317
+ test('exact prototype invocation for correct property extraction', () => {
318
+
319
+ Object.defineProperty(upperInstance, 'stringProp', {
320
+ get() {
321
+ const target = Reflect.getPrototypeOf(upperInstance) as {
322
+ stringValue: string
323
+ };
324
+ return target.stringValue;
325
+ }
326
+ });
327
+
328
+ const value = upperInstance.stringProp;
329
+ expect(`${value}`).toEqual('123');
330
+
331
+ });
332
+ });
333
+
334
+
335
+ describe('functional constructors tests', () => {
336
+ test('construction made properly', () => {
337
+
338
+ const { stringProp,
339
+ constructor: {
340
+ name
341
+ }
342
+ } = myFunctionalInstance;
343
+ expect(name).toEqual('MyFunctionalConstructor');
344
+ expect(stringProp.valueOf()).toEqual('123');
345
+
346
+ });
347
+ });
348
+
349
+ describe('instanceof works', () => {
350
+ test('for class construction', () => {
351
+
352
+ expect(baseInstance).toBeInstanceOf(Base);
353
+
354
+ });
355
+ test('for simple construction', () => {
356
+
357
+ expect(simpleInstance).toBeInstanceOf(SimpleBase);
358
+
359
+ });
360
+ test('for functional construction', () => {
361
+
362
+ expect(myFunctionalInstance).toBeInstanceOf(MyFunctionalConstructor);
363
+
364
+ });
365
+ });
366
+
367
+ describe('deep extend works', () => {
368
+ test('class extended three times construction', () => {
369
+
370
+ expect(tiripleExtendInstance).toBeInstanceOf(Base);
371
+ expect(tiripleExtendInstance).toBeInstanceOf(SecondaryExtend);
372
+ expect(tiripleExtendInstance).toBeInstanceOf(TripleExtend);
373
+ expect(`${tiripleExtendInstance.stringValue}`).toEqual('123');
374
+ expect(tiripleExtendInstance.second).toBeInstanceOf(Number);
375
+
376
+ });
377
+
378
+ test('network extention works', () => {
379
+ expect(networkedInstance).toBeInstanceOf(NetworkedExtention);
380
+
381
+ expect(() => {
382
+
383
+ `${networkedInstance.stringValue}`;
384
+
385
+ }).toThrow(new ReferenceError('Value Access Denied'));
386
+
387
+ });
388
+
389
+ test('builtin types works', () => {
390
+ expect(extendedArrayInstance).toBeInstanceOf(Array);
391
+ expect(extendedArrayInstance).toBeInstanceOf(ExtendedArray);
392
+ expect(extendedArrayInstance[0]).toBe(1);
393
+ extendedArrayInstance.unshift(0);
394
+ extendedArrayInstance.push(5);
395
+ expect(+extendedArrayInstance.length).toBe(5);
396
+ expect(+extendedArrayInstance[0]).toBe(0);
397
+ expect(+extendedArrayInstance[4]).toBe(5);
398
+
399
+ expect(extendedSetInstance).toBeInstanceOf(Set);
400
+ expect(extendedSetInstance).toBeInstanceOf(ExtendedSet);
401
+
402
+ // JS Object.create(new Set([1, 2, 3])) is doing the same error!
403
+ expect(() => {
404
+
405
+ extendedSetInstance.has(0);
406
+
407
+ }).toThrow(new TypeError('Method Set.prototype.has called on incompatible receiver [object Object]'));
408
+
409
+ });
410
+ });
@@ -0,0 +1,30 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "commonjs",
4
+ "target": "ES2015",
5
+ "lib": [
6
+ "ES6",
7
+ ],
8
+ "strict": true,
9
+ "alwaysStrict": true,
10
+ "strictFunctionTypes": true,
11
+ "diagnostics": true,
12
+ "noImplicitThis": true,
13
+ "extendedDiagnostics": true,
14
+ "noImplicitReturns": true,
15
+ "noFallthroughCasesInSwitch": true,
16
+ "noUnusedLocals": true,
17
+ "noUnusedParameters": true,
18
+ "declaration": true,
19
+ "sourceMap": false,
20
+ "outDir": "lib",
21
+ "traceResolution": false,
22
+ "removeComments": true,
23
+ "experimentalDecorators": true,
24
+ "forceConsistentCasingInFileNames": true,
25
+ "types": [
26
+ "jest",
27
+ "node"
28
+ ]
29
+ },
30
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "commonjs",
4
+ "target": "ES2015",
5
+ "lib": [
6
+ "ES6",
7
+ ],
8
+ "strict": true,
9
+ "alwaysStrict": true,
10
+ "strictFunctionTypes": true,
11
+ "diagnostics": true,
12
+ "noImplicitThis": true,
13
+ "extendedDiagnostics": true,
14
+ "noImplicitReturns": true,
15
+ "noFallthroughCasesInSwitch": true,
16
+ "noUnusedLocals": true,
17
+ "noUnusedParameters": true,
18
+ "declaration": true,
19
+ "sourceMap": false,
20
+ "outDir": "lib",
21
+ "traceResolution": false,
22
+ "removeComments": true,
23
+ "experimentalDecorators": true,
24
+ "forceConsistentCasingInFileNames": true,
25
+ "types": ["node"]
26
+ },
27
+ "include": [
28
+ "./src/**/*.ts",
29
+ ],
30
+ "exclude": [
31
+ "./lib/**/*",
32
+ "./coverage/**/*",
33
+ ]
34
+ }