typed-path
Advanced tools
Comparing version 2.2.1 to 2.2.2
107
index.ts
@@ -0,20 +1,26 @@ | ||
export type TypedPathKey = string | symbol | number; | ||
function appendStringPathChunk(path: string, chunk: TypedPathKey): string { | ||
if (typeof chunk === 'number') { | ||
return path + `[${chunk}]`; | ||
} else { | ||
return appendStringSymbolChunkToPath(path, chunk); | ||
} | ||
} | ||
function appendStringSymbolChunkToPath(path: string, chunk: string | symbol) { | ||
return path + (path === '' ? chunk.toString() : `.${chunk.toString()}`); | ||
} | ||
function pathToString(path: TypedPathKey[]): string { | ||
return path.reduce<string>((current, next) => { | ||
if (typeof next === 'number') { | ||
current += `[${next}]`; | ||
} else { | ||
current += current === '' ? next.toString() : `.${next.toString()}`; | ||
} | ||
return current; | ||
return appendStringPathChunk(current, next); | ||
}, ''); | ||
} | ||
export type TypedPathKey = string | symbol | number; | ||
export type TypedPathFunction<ResultType> = (...args: any[]) => ResultType; | ||
export type TypedPathFunction<T> = (...args: any[]) => T; | ||
export type TypedPathHandlersConfig = Record< | ||
string, | ||
<T extends TypedPathHandlersConfig = Record<never, never>>(path: TypedPathKey[], additionalHandlers?: T) => any | ||
<T extends TypedPathHandlersConfig>(path: TypedPathKey[], additionalHandlers?: T) => any | ||
>; | ||
@@ -35,53 +41,64 @@ | ||
export type TypedPathHandlers<T extends TypedPathHandlersConfig> = { | ||
[key in keyof T]: ReturnType<T[key]>; | ||
type DefaultHandlers = typeof defaultHandlersConfig; | ||
export type TypedPathHandlers<ConfigType extends TypedPathHandlersConfig> = { | ||
[key in keyof ConfigType]: ReturnType<ConfigType[key]>; | ||
}; | ||
export type TypedPathWrapper<T, TPH extends TypedPathHandlers<Record<never, never>>> = (T extends Array<infer Z> | ||
export type TypedPathWrapper< | ||
OriginalType, | ||
HandlersType extends TypedPathHandlers<Record<never, never>> | ||
> = (OriginalType extends Array<infer OriginalArrayItemType> | ||
? { | ||
[index: number]: TypedPathWrapper<Z, TPH>; | ||
[index: number]: TypedPathWrapper<OriginalArrayItemType, HandlersType>; | ||
} | ||
: T extends TypedPathFunction<infer RET> | ||
: OriginalType extends TypedPathFunction<infer OriginalFunctionResultType> | ||
? { | ||
(): TypedPathWrapper<RET, TPH>; | ||
(): TypedPathWrapper<OriginalFunctionResultType, HandlersType>; | ||
} & { | ||
[P in keyof Required<RET>]: TypedPathWrapper<RET[P], TPH>; | ||
[P in keyof Required<OriginalFunctionResultType>]: TypedPathWrapper< | ||
OriginalFunctionResultType[P], | ||
HandlersType | ||
>; | ||
} | ||
: { | ||
[P in keyof Required<T>]: TypedPathWrapper<T[P], TPH>; | ||
[P in keyof Required<OriginalType>]: TypedPathWrapper<OriginalType[P], HandlersType>; | ||
}) & | ||
TypedPathHandlers<TPH>; | ||
TypedPathHandlers<HandlersType>; | ||
const emptyObject = {}; | ||
export function typedPath<T, K extends TypedPathHandlersConfig = Record<never, never>>( | ||
additionalHandlers?: K, | ||
path: TypedPathKey[] = [], | ||
defaultsApplied: boolean = false | ||
): TypedPathWrapper<T, K & typeof defaultHandlersConfig> { | ||
return <TypedPathWrapper<T, K & typeof defaultHandlersConfig>>new Proxy(emptyObject, { | ||
get(target: T, name: TypedPathKey) { | ||
let handlersConfig: TypedPathHandlersConfig; | ||
function convertNumericKeyToNumber(key: TypedPathKey): TypedPathKey { | ||
if (typeof key === 'string') { | ||
const keyAsNumber = +key; | ||
if (keyAsNumber === keyAsNumber) { | ||
return keyAsNumber; | ||
} | ||
} | ||
if (defaultsApplied) { | ||
handlersConfig = additionalHandlers!; | ||
} else { | ||
handlersConfig = {...(additionalHandlers ?? {}), ...defaultHandlersConfig}; | ||
} | ||
return key; | ||
} | ||
if (handlersConfig.hasOwnProperty(name)) { | ||
return handlersConfig[name as any](path, additionalHandlers); | ||
} | ||
function getHandlerByNameKey<K extends TypedPathHandlersConfig>(name: TypedPathKey, additionalHandlers?: K) { | ||
if (additionalHandlers?.hasOwnProperty(name)) { | ||
return additionalHandlers[name as string]; | ||
} | ||
let newChunk = name; | ||
if (defaultHandlersConfig[name as keyof typeof defaultHandlersConfig]) { | ||
return defaultHandlersConfig[name as keyof typeof defaultHandlersConfig]; | ||
} | ||
} | ||
if (typeof newChunk === 'string') { | ||
const nameAsNumber = +newChunk; | ||
if (nameAsNumber === nameAsNumber) { | ||
newChunk = nameAsNumber; | ||
} | ||
} | ||
const emptyObject = {}; | ||
export function typedPath<OriginalObjectType, HandlersType extends TypedPathHandlersConfig = Record<never, never>>( | ||
additionalHandlers?: HandlersType, | ||
path: TypedPathKey[] = [] | ||
): TypedPathWrapper<OriginalObjectType, HandlersType & DefaultHandlers> { | ||
return <TypedPathWrapper<OriginalObjectType, HandlersType & DefaultHandlers>>new Proxy(emptyObject, { | ||
get(target: unknown, name: TypedPathKey) { | ||
const handler = getHandlerByNameKey(name, additionalHandlers); | ||
return typedPath(handlersConfig, [...path, newChunk], true); | ||
return handler | ||
? handler(path, additionalHandlers) | ||
: typedPath(additionalHandlers, [...path, convertNumericKeyToNumber(name)]); | ||
} | ||
}); | ||
} |
{ | ||
"name": "typed-path", | ||
"version": "2.2.1", | ||
"version": "2.2.2", | ||
"description": "Type safe object string paths for typescript.", | ||
"main": "index.js", | ||
"main": "dist/index.js", | ||
"repository": { | ||
@@ -27,13 +27,15 @@ "type": "git", | ||
"index.ts", | ||
"index.js", | ||
"index.js.map", | ||
"index.d.ts" | ||
"dist/index.js", | ||
"dist/index.js.map", | ||
"dist/index.d.ts" | ||
], | ||
"types": "dist/index.d.ts", | ||
"scripts": { | ||
"test": "jest index.spec.ts", | ||
"test": "yarn tsdlint && jest index.spec.ts", | ||
"lint": "tslint 'src/**/*.ts' 'src/**/*.tsx'", | ||
"tsdlint": "yarn build && tsd", | ||
"format": "prettier --parser typescript --write index.ts index.spec.ts", | ||
"prepush": "yarn lint && yarn build && git diff --exit-code", | ||
"prepush": "yarn prepare", | ||
"prepare": "yarn test && yarn build", | ||
"build": "tsc" | ||
"build": "tsc -p tsconfig.dist.json" | ||
}, | ||
@@ -46,5 +48,9 @@ "devDependencies": { | ||
"ts-node": "^9.0.0", | ||
"tsd": "^0.13.1", | ||
"tslint": "^4.4.2", | ||
"typescript": "^4.0.5" | ||
}, | ||
"tsd": { | ||
"directory": "dist" | ||
} | ||
} |
@@ -6,3 +6,3 @@ # Typed Path | ||
[![Travis](https://img.shields.io/travis/bsalex/typed-path)](https://travis-ci.org/github/bsalex/typed-path) | ||
[![HitCount](http://hits.dwyl.com/bsalex/typed-path.svg)](http://hits.dwyl.com/bsalex/typed-path) | ||
[![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fbsalex%2Ftyped-path&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com) | ||
[![Contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/bsalex/typed-path/issues) | ||
@@ -14,19 +14,46 @@ ![GitHub top language](https://img.shields.io/github/languages/top/bsalex/typed-path) | ||
![Snyk Vulnerabilities for GitHub Repo](https://img.shields.io/snyk/vulnerabilities/github/bsalex/typed-path) | ||
![GitHub issues](https://img.shields.io/github/issues/bsalex/typed-path) | ||
![Code Climate maintainability](https://img.shields.io/codeclimate/maintainability-percentage/bsalex/typed-path) | ||
![Code Climate technical debt](https://img.shields.io/codeclimate/tech-debt/bsalex/typed-path) | ||
[![GitHub issues](https://img.shields.io/github/issues/bsalex/typed-path)](https://github.com/bsalex/typed-path/issues) | ||
[![Code Climate maintainability](https://img.shields.io/codeclimate/maintainability-percentage/bsalex/typed-path)](https://codeclimate.com/github/bsalex/typed-path/) | ||
[![Code Climate technical debt](https://img.shields.io/codeclimate/tech-debt/bsalex/typed-path)](https://codeclimate.com/github/bsalex/typed-path/) | ||
[![codecov](https://codecov.io/gh/bsalex/typed-path/branch/master/graph/badge.svg?token=uzpVtSWKbv)](https://codecov.io/gh/bsalex/typed-path) | ||
--- | ||
## Overview | ||
## Problem | ||
This small utility helps to extract type information from a TypeScript class, interface or type to use it in your code. | ||
Types are lost when string paths are used in typescript. | ||
I.e., `_.get, _.map, _.set, R.pluck` from libraries like [lodash](https://lodash.com), [ramda](http://ramdajs.com/). | ||
It makes those methods dangerous in case of refactoring, the same as JavaScript. | ||
Example: | ||
![](https://res.cloudinary.com/daren64mz/image/upload/v1487457505/string-refactoring_x2tubt.gif) | ||
```js | ||
import {typedPath} from 'typed-path'; | ||
type TestType = { | ||
a: { | ||
testFunc: () => {result: string}; | ||
b: { | ||
arrayOfArrays: string[][]; | ||
c: { | ||
d: number; | ||
}; | ||
}[]; | ||
}; | ||
}; | ||
console.log(typedPath<TestType>().a.b[5].c.d.$rawPath); | ||
/* | ||
Outputs | ||
["a", "b", 5, "c", "d"] | ||
*/ | ||
``` | ||
Please see other path access methods and how to add custom path access methods below. | ||
The utility might also be used to add type protection to such methods as `_.get, _.map, _.set, R.pluck` from libraries like [lodash](https://lodash.com), [ramda](http://ramdajs.com/). | ||
**It is recommended, though, to use [optional chaining](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#optional-chaining) instead.** | ||
--- | ||
## Solution | ||
## Features | ||
@@ -85,3 +112,3 @@ ### Errors | ||
const testAdditionalHandlers = { | ||
$url: (path: string[]) => path.join('/') | ||
$url: (path: TypedPathKey[]) => path.join('/') | ||
} | ||
@@ -96,4 +123,5 @@ | ||
const testAdditionalHandlers = { | ||
$abs: (path: string[]) => typedPath<TestType, typeof testAdditionalHandlers>(testAdditionalHandlers, ['', ...path]), | ||
$url: (path: string[]) => path.join('/') | ||
$abs: (path: TypedPathKey[]) => typedPath<TestType, typeof testAdditionalHandlers>(testAdditionalHandlers, ['', ...path]), | ||
$url: (path: TypedPathKey[]) => path.join('/'), | ||
$length: (path: TypedPathKey[]) => path.length | ||
} | ||
@@ -114,3 +142,3 @@ | ||
Copyright (c) 2020 Oleksandr Beshchuk <[bs.alex.mail@gmail.com](mailto:bs.alex.mail@gmail.com)> | ||
Copyright (c) 2021 Oleksandr Beshchuk <[bs.alex.mail@gmail.com](mailto:bs.alex.mail@gmail.com)> | ||
Licensed under the [Apache License](http://www.apache.org/licenses/LICENSE-2.0). |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
17781
186
140
8
1