Skip to content

Commit cd982b1

Browse files
committed
Initial commit
0 parents  commit cd982b1

10 files changed

+4994
-0
lines changed

.babelrc

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"env": {
3+
"test": {
4+
"presets": [],
5+
"plugins": []
6+
}
7+
}
8+
}

.eslintrc

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"env": {
3+
"es6": true,
4+
"node": true,
5+
"browser": true
6+
},
7+
"extends": [
8+
"eslint:recommended"
9+
],
10+
"parser": "babel-eslint",
11+
"parserOptions": {
12+
"ecmaVersion": 6,
13+
"sourceType": "module",
14+
"ecmaFeatures": {
15+
"experimentalObjectRestSpread": true
16+
}
17+
},
18+
"plugins": [],
19+
"rules": {
20+
"comma-style": [ "warn", "first", { "exceptions": { "ArrayExpression": true, "ObjectExpression": true } } ],
21+
"indent": [ "error", 2, { "SwitchCase": 1 } ],
22+
"no-empty": "warn",
23+
"no-unused-vars": [ "warn", { "vars": "all", "varsIgnorePattern": "^_+?$", "args": "all", "argsIgnorePattern": "^_+?$" } ],
24+
"quote-props": [ "error", "as-needed", { "keywords": false, "numbers": true } ],
25+
"quotes": [ "warn", "single" ],
26+
"semi": [ "warn", "never" ]
27+
}
28+
}

.gitignore

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.DS_Store
2+
.idea
3+
.nyc_output
4+
.vscode
5+
bin
6+
build
7+
coverage
8+
dist
9+
node_modules
10+
*.env
11+
*.log

jest.config.js

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
module.exports = {
2+
preset: 'ts-jest/presets/js-with-ts',
3+
testEnvironment: 'node',
4+
rootDir: './',
5+
globals: {
6+
'ts-jest': {
7+
babelConfig: true,
8+
diagnostics: {
9+
warnOnly: true,
10+
},
11+
},
12+
},
13+
collectCoverage: true,
14+
coverageDirectory: '<rootDir>/coverage',
15+
coverageReporters: [ 'lcov', 'text' ],
16+
coverageThreshold: {
17+
global: {
18+
branches: 90,
19+
functions: 90,
20+
lines: 90,
21+
statements: 90,
22+
},
23+
},
24+
transformIgnorePatterns: [
25+
'/coverage/',
26+
'/dist/',
27+
'/node_modules/',
28+
],
29+
testMatch: null,
30+
testRegex: 'tests/.*?\\.(spec|test)\\.(j|t)sx?$',
31+
moduleFileExtensions: [
32+
'ts',
33+
'tsx',
34+
'js',
35+
'json',
36+
'jsx',
37+
],
38+
}

package.json

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"name": "sfobj",
3+
"version": "1.0.0",
4+
"main": "./index.js",
5+
"description": "JS proxy for safe property access.",
6+
"repository": "https://github.com/devMYC/sfobj.git",
7+
"author": "Martin <mycha0@hotmail.com>",
8+
"license": "MIT",
9+
"engines": {
10+
"node": ">= 6.0"
11+
},
12+
"scripts": {
13+
"build": "rm -fr ./dist && tsc",
14+
"show:coverage": "open ./coverage/lcov-report/index.html",
15+
"test": "jest --runInBand"
16+
},
17+
"devDependencies": {
18+
"@types/jest": "^23.3.9",
19+
"@types/node": "^10.12.3",
20+
"@types/sinon": "^5.0.5",
21+
"babel-eslint": "^10.0.1",
22+
"eslint": "^5.16.0",
23+
"jest": "^23.6.0",
24+
"nodemon": "^1.19.1",
25+
"sinon": "^7.3.2",
26+
"source-map-support": "^0.5.12",
27+
"ts-jest": "^23.10.4",
28+
"ts-node": "^8.2.0",
29+
"typescript": "^3.5.1"
30+
},
31+
"peerDependencies": {
32+
"source-map-support": "*"
33+
},
34+
"keywords": [
35+
"safe",
36+
"proxy",
37+
"property",
38+
"access",
39+
"es6"
40+
]
41+
}

src/index.ts

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
export const unwrap = Symbol('unwrap')
2+
3+
export type SafeWrapper<T> = {
4+
[unwrap]: T;
5+
}
6+
7+
type SafeProxy<T> = {
8+
[P in keyof T]: SafeProxy<T[P]> & (
9+
T[P] extends ((...args: any[]) => infer R)
10+
? (...args: any[]) => SafeWrapper<R>
11+
: SafeWrapper<T[P]>
12+
)
13+
}
14+
15+
export function Safe<T>(v: T): SafeProxy<T> {
16+
let curr = v
17+
const proxy: any = new Proxy(NeverFail, {
18+
apply() {
19+
return proxy
20+
},
21+
get(_, prop) {
22+
switch (true) {
23+
case prop === unwrap:
24+
const val = curr
25+
curr = v
26+
return val
27+
case prop === Symbol.toPrimitive: return () => Safe.name
28+
case curr == null: break
29+
default:
30+
curr = (curr as any)[prop]
31+
typeof curr === 'function' && (curr = curr())
32+
break
33+
}
34+
return proxy
35+
},
36+
})
37+
return proxy
38+
}
39+
40+
export default Safe
41+
42+
/* istanbul ignore next */
43+
function NeverFail() {}

tests/index.spec.ts

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import Safe, { SafeWrapper, unwrap } from '../src/index'
2+
3+
test(Safe.name, () => {
4+
const obj = {
5+
a: 100,
6+
b: {
7+
c: [
8+
{ d: 1e3 },
9+
{ e: () => 1e4 },
10+
],
11+
},
12+
f: null,
13+
g: undefined,
14+
}
15+
16+
const safeObj = Safe(obj)
17+
18+
expect(String(safeObj)).toBe(Safe.name)
19+
expect(safeObj.a[unwrap]).toBe(100)
20+
expect((safeObj.f as SafeWrapper<null>)[unwrap]).toBe(null)
21+
expect((safeObj.g as SafeWrapper<undefined>)[unwrap]).toBe(undefined)
22+
expect(safeObj.b.c[0].d[unwrap]).toBe(1e3)
23+
expect(safeObj.b.c[1].e()[unwrap]).toBe(1e4)
24+
expect((safeObj.b.c[100] as any).xx().yy.zz().or[1].whatever['key'][unwrap]).toBe(undefined)
25+
})

tsconfig.json

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"compilerOptions": {
3+
"target": "es6",
4+
"module": "commonjs",
5+
"allowJs": false,
6+
"checkJs": false,
7+
"declaration": true,
8+
"sourceMap": true,
9+
"sourceRoot": "./src",
10+
"outDir": "./dist",
11+
"rootDir": "./src",
12+
"removeComments": true,
13+
"strict": true,
14+
"noImplicitAny": true,
15+
"strictNullChecks": true,
16+
"alwaysStrict": true,
17+
"noUnusedLocals": true,
18+
"noImplicitReturns": true,
19+
"noFallthroughCasesInSwitch": true,
20+
"moduleResolution": "node",
21+
"preserveConstEnums": true,
22+
"pretty": true
23+
},
24+
"include": [
25+
"src/**/*"
26+
]
27+
}

tslint.json

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
{
2+
"defaultSeverity": "error",
3+
"extends": [
4+
"tslint:recommended"
5+
],
6+
"jsRules": {},
7+
"rules": {
8+
"arrow-parens": false,
9+
"arrow-return-shorthand": true,
10+
"callable-types": true,
11+
"class-name": true,
12+
"comment-format": [
13+
true,
14+
"check-space"
15+
],
16+
"curly": true,
17+
"deprecation": {
18+
"severity": "warn"
19+
},
20+
"eofline": true,
21+
"forin": true,
22+
"import-blacklist": [
23+
true,
24+
"rxjs/Rx"
25+
],
26+
"import-spacing": true,
27+
"indent": [
28+
true,
29+
"spaces"
30+
],
31+
"interface-over-type-literal": false,
32+
"label-position": true,
33+
"max-line-length": [
34+
true,
35+
140
36+
],
37+
"member-access": false,
38+
"member-ordering": [
39+
true,
40+
{
41+
"order": [
42+
"static-field",
43+
"instance-field",
44+
"static-method",
45+
"instance-method"
46+
]
47+
}
48+
],
49+
"no-arg": true,
50+
"no-bitwise": true,
51+
"no-console": [
52+
true,
53+
"debug",
54+
"info",
55+
"time",
56+
"timeEnd",
57+
"trace"
58+
],
59+
"no-construct": true,
60+
"no-debugger": true,
61+
"no-duplicate-super": true,
62+
"no-empty": false,
63+
"no-empty-interface": true,
64+
"no-eval": true,
65+
"no-inferrable-types": [
66+
true,
67+
"ignore-params"
68+
],
69+
"no-misused-new": true,
70+
"no-non-null-assertion": true,
71+
"no-redundant-jsdoc": true,
72+
"no-shadowed-variable": true,
73+
"no-string-literal": false,
74+
"no-string-throw": true,
75+
"no-switch-case-fall-through": true,
76+
"no-trailing-whitespace": true,
77+
"no-unnecessary-initializer": true,
78+
"no-unused-expression": false,
79+
"no-use-before-declare": true,
80+
"no-var-keyword": true,
81+
"object-literal-sort-keys": false,
82+
"one-line": [
83+
true,
84+
"check-open-brace",
85+
"check-catch",
86+
"check-else",
87+
"check-whitespace"
88+
],
89+
"prefer-const": true,
90+
"quotemark": [
91+
true,
92+
"single"
93+
],
94+
"radix": true,
95+
"semicolon": [
96+
true,
97+
"never"
98+
],
99+
"triple-equals": [
100+
true,
101+
"allow-null-check"
102+
],
103+
"typedef-whitespace": [
104+
true,
105+
{
106+
"call-signature": "nospace",
107+
"index-signature": "nospace",
108+
"parameter": "nospace",
109+
"property-declaration": "nospace",
110+
"variable-declaration": "nospace"
111+
}
112+
],
113+
"unified-signatures": true,
114+
"variable-name": false,
115+
"whitespace": [
116+
true,
117+
"check-branch",
118+
"check-decl",
119+
"check-operator",
120+
"check-separator",
121+
"check-type"
122+
]
123+
},
124+
"rulesDirectory": []
125+
}

0 commit comments

Comments
 (0)