Skip to content
This repository has been archived by the owner on May 8, 2021. It is now read-only.

Commit

Permalink
- Initial commit and v1.0.0 🎉🎉🎉
Browse files Browse the repository at this point in the history
  • Loading branch information
RIP21 committed Mar 12, 2019
1 parent dc42b53 commit 1d138a7
Show file tree
Hide file tree
Showing 10 changed files with 5,852 additions and 1 deletion.
57 changes: 57 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.

# dependencies
/node_modules

# testing
/coverage

# production
/build

# misc
.DS_Store
npm-debug.log*
yarn-debug.log*


# Runtime data
pids
*.pid
*.seed

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov


# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Debug log from npm
npm-debug.log

# sass
.sass-cache/

#ngrok
ngrok
/.idea
/reports
/screenshots
/dist
.project
npm-debug.log.*
/selenium-debug.log
/grocerycop-website-3.1-ui.iml
/.vscode/
/lib/
/.env.local
/styleguide/
/storybook-static/
/compiled/
6 changes: 6 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"printWidth": 90,
"semi": false,
"singleQuote": true,
"trailingComma": "all"
}
7 changes: 7 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
language: node_js
node_js:
- 10
install:
- npm install
script:
- yarn test
39 changes: 38 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,39 @@
# ts-get
Alternative to lodash.get that makes it typed and cool as if optional typing proposal is there

Alternative to `lodash.get` that makes it typed and cool as if optional chaining proposal is there.

## Usage and features

```typescript
import get from 'ts-get'

type SomeType = {
optionalField?: string
nested?: {
dangerousAccess?: string
}
} | undefined | null

const input = {}
const input2 = {
optionalField: "value",
}

get(input, it => it.optionalField, "default") // -> "default"
get(input2, it => it.optionalField, "default") // -> "value"
get(input2, it => it.nested.dangerousAccess, "default") // -> "default"
get(input2, it => it.unknownField, "default") // -> Type error, `unknownField` doesn't exist on type
get(input2, it => it.optionalField, 5) // -> Type error, third argument is not assignable to type `string`
```

## Difference with `lodash.get` behavior

- If your path gets `null` at the end, it will bail out to `defaultValue` or `undefined`.
If you would like to get `null` returned anyway, just pass it as a `defaultValue`
- If your type field is of type `null` and only `null` or `undefined` your field will be of type `{}[]`.
I have no idea how to fix it 🤷‍♂️ PR Welcome 😇🙏
```typescript
type A = {
field: null | undefined// -> {}[] inside of the callback and as return type too
}
```
5 changes: 5 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
"transform": {
"^.+\\.(ts|tsx)$": "ts-jest"
}
}
50 changes: 50 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"name": "ts-get",
"version": "0.0.1",
"description": "Alternative to lodash.get that makes it typed and cool as if optional chaining proposal is there",
"main": "lib/index.node.js",
"module": "lib/index.js",
"types": "lib/index.d.ts",
"repository": "https://github.com/RIP21/ts-get.git",
"author": "Andrii Los <puha212@gmail.com>",
"license": "MIT",
"private": false,
"scripts": {
"lint": "prettier --write ./**/*.ts",
"prebuild": "rimraf lib",
"build": "tsc && babel src/index.ts --out-file lib/index.node.js",
"test": "jest src --coverage",
"release": "np"
},
"devDependencies": {
"np": "^4.0.2",
"@babel/cli": "^7.2.3",
"@babel/core": "^7.3.4",
"@babel/plugin-syntax-typescript": "^7.3.3",
"@babel/preset-env": "^7.3.4",
"@babel/preset-typescript": "^7.3.3",
"@babel/runtime": "^7.3.4",
"@types/jest": "^24.0.11",
"@types/lodash": "^4.14.123",
"jest": "^24.5.0",
"lodash": "^4.17.11",
"prettier": "^1.16.4",
"rimraf": "^2.6.3",
"ts-jest": "^24.0.0",
"typescript": "^3.3.3333"
},
"babel": {
"comments": false,
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "10"
}
}
],
"@babel/preset-typescript"
]
}
}
80 changes: 80 additions & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { get } from './'
import { get as _get } from 'lodash'

describe('get', function() {
type InputType = {
a: string
b?: {
nested?: string | null
}
}
const inputAllPresent: InputType = {
a: 'Value',
b: {
nested: 'Value',
},
}
const inputOptionalsMissing: InputType = {
a: 'Value',
}
const inputOptionalsSafeAccessMissing: InputType = {
a: 'Value',
b: {} // accessing b.nested will return undefined and will not throw error
}
it('should return value', () => {
const result = get(inputAllPresent, it => it.a)
expect(result).toBe('Value')
})
it('should return value on existing optional field', () => {
const result = get(inputAllPresent, it => it.b.nested, 'Default')
expect(result).toBe('Value')
})
it('should return undefined on non existing optional field', () => {
const result = get(inputOptionalsMissing, it => it.b.nested)
expect(result).toBeUndefined()
})
it('should return defaultValue on non existing optional field', () => {
const result = get(inputOptionalsMissing, it => it.b.nested, 'Default')
expect(result).toBe('Default')
})
it('should return defaultValue on non existing optional field', () => {
const result = get(inputOptionalsMissing, it => it.b.nested, 'Default')
expect(result).toBe('Default')
})

it('should replica lodash interface in cases below', () => {
const result1 = get(inputAllPresent, it => it.a)
const result1_ = _get(inputAllPresent, 'a')

const result2 = get(inputAllPresent, it => it.b.nested, 'Default')
const result2_ = _get(inputAllPresent, 'b.nested', 'Default')

const result3 = get(inputOptionalsMissing, it => it.b.nested)
const result3_ = _get(inputOptionalsMissing, 'b.nested')

const result4 = get(inputOptionalsSafeAccessMissing, it => it.b.nested, 'Default')
const result4_ = _get(inputOptionalsSafeAccessMissing, 'b.nested', 'Default')


expect(result1).toBe(result1_)
expect(result2).toBe(result2_)
expect(result3).toBe(result3_)
expect(result4).toBe(result4_)
})
it('should return undefined and default value in case of null (different from _.get)', function() {
const inputWithNull: InputType = {
a: 'Value',
b: {
nested: null,
},
}
const defaultedNull = get<InputType, string | null>(inputOptionalsMissing, it => it.b.nested, null)
expect(defaultedNull).toBe(null)

const undefinedFromNullValue = get(inputWithNull, it => it.b.nested)
expect(undefinedFromNullValue).toBeUndefined()

const defaultedStringFromNullValue = get(inputWithNull, it => it.b.nested, "Default")
expect(defaultedStringFromNullValue).toBe("Default")
})
})
67 changes: 67 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/** Removes all undefined and nulls from the nested types making all nested fields
* non-nullable hence you can simply chain through it and autosuggestion will work
* and will not complain on undefiness and nullability of some of the nested fields
* including the root
*
* @example
* type AllOpt = {
* a?: string
* arr?: string[] | null
* b?: {
* c?: string | undefined | null
* n?:
* | {
* a?: string | null
* b: object | null
* }
* | null
* | undefined
* }
* c?: string
* }
*
* type AllReq = RequireRecursively<AllOpt | undefined | null>
*
* const obj: AllReq = ({} as unknown) as AllReq
*
* obj.b.c // -> string
* obj.b.n.a // -> string
* obj.b.n.b // -> object
* obj.arr // -> string[]
*
*
*/
export type RequiredRecursively<T> = Exclude<
{
[P in keyof T]-?: T[P] extends (infer U)[]
? RequiredRecursively<U>[]
: RequiredRecursively<T[P]>
},
null | undefined
>

export type AccessorFunction<T, R> = (object: RequiredRecursively<T>) => R

export function get<T, R>(
object: T,
accessorFn: AccessorFunction<T, R>,
): R | undefined
export function get<T, R>(
object: T,
accessorFn: AccessorFunction<T, R>,
defaultValue: R,
): R
export function get<T, R>(
object: T,
accessorFn: AccessorFunction<T, R>,
defaultValue?: R,
): R | undefined {
try {
const result = accessorFn((object as unknown) as RequiredRecursively<T>)
return result === undefined || result === null ? defaultValue : result
} catch (e) {
return defaultValue === undefined ? undefined : defaultValue
}
}

export default get
16 changes: 16 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"compilerOptions": {
"baseUrl": "./src",
"lib": ["es6", "dom", "es2016", "es2017"],
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"outDir": "lib",
"declaration": true,
"esModuleInterop": true,
"sourceMap": true
},
"exclude": [
"src/index.test.ts"
]
}
Loading

0 comments on commit 1d138a7

Please sign in to comment.