Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix typescript circular ref errors and typing improvements #2

Merged
merged 2 commits into from
Oct 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
.vscode
node_modules
node_modules
*.map
*.js
*.d.ts
31 changes: 29 additions & 2 deletions docs/testFail.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import {typedPath as tp} from '../index';

type TestType = {
a: {
testFunc: () => { result: string },
b: {
arrayOfArrays: string[][]
c: number;
f: { test: string, blah: { path?: string }, arr: string[] }[];
}
}
};
Expand All @@ -10,7 +15,29 @@ function getField(path: string, object: any) {
return 'Here should be some logic to get field by path';
}

const testObject: TestType = {a: {b: {c: 5}}};
const field = getField('a.b.c', testObject); // <- Look! No error and type protection here!
const testObject: TestType = {
a: {
b: {
arrayOfArrays: [['hi']],
c: 5,
f: [{test: 'tes123t', blah: {path: '123123'}, arr: ['tt']}]
}
}
};

// Look! No error and type protection here!
const field = getField('a.b.c', testObject);

// f is an array you cant access it directly
console.log(tp<TestType>().a.b.f.blah.$path);

// f is not an array of arrays just a plain string array so double accessor fails
console.log(tp<TestType>().a.b.f[0][0].$path);

// a is not a func
console.log(tp<TestType>().a().$path);

// bad is not a field in the function return type
console.log(tp<TestType>().a.testFunc.bad);

export default TestType;
8 changes: 0 additions & 8 deletions index.d.ts

This file was deleted.

32 changes: 0 additions & 32 deletions index.js

This file was deleted.

1 change: 0 additions & 1 deletion index.js.map

This file was deleted.

41 changes: 34 additions & 7 deletions index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,43 @@
import {expect} from 'chai';
import {typedPath} from './index';
import {typedPath as tp, typedPath} from './index';

type TestType = {
a: {
testFunc: () => { result: string },
b: {
arrayOfArrays: string[][]
c: number;
f: { test: string, blah: { path?: string }, arr: string[] }[];
}
}
};

describe('Typed path', () => {
it('should get field path', () => {
type TestType = {a: {b: {c: number}}};

expect(typedPath<TestType>().a.b.c.path()).to.equal('a.b.c');
expect(typedPath<TestType>().a.b.c.$path).to.equal('a.b.c');
});

it('should get index path', () => {
type TestType = {a: {b: [{c: number}]}};
expect(tp<TestType>().a.b.f[3].$path).to.equal('a.b.f[3]');
});

it('should get path for toString()', () => {
expect(tp<TestType>().a.b.f[3].blah.path.toString()).to.equal('a.b.f[3].blah.path');
});

it('should get index of index for array of array ', () => {
expect(tp<TestType>().a.b.arrayOfArrays[3][3].$path).to.equal('a.b.arrayOfArrays[3][3]');
});

it('should get array node', () => {
expect(tp<TestType>().a.b.f.$path).to.equal('a.b.f');
});

it('should get function return type path', () => {
expect(tp<TestType>().a.testFunc.result.$path).to.equal('a.testFunc.result');
});

expect(typedPath<TestType>().a.b[5].c.path()).to.equal('a.b[5].c');
it('should get function path', () => {
expect(tp<TestType>().a.testFunc.$path).to.equal('a.testFunc');
});
});
});
33 changes: 24 additions & 9 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
export type TypedPathWrapper<T> = {
[P in keyof T]: TypedPathWrapper<T[P]>;
} &
{
(): T;
[index: number]: TypedPathWrapper<T[any]>;
path: () => string;
export type TypedPathNode<T> = {
$path: string;
};

export type TypedPathFunction<T> = (...args: any[]) => T;

export type TypedPathWrapper<T> = (T extends Array<infer Z>
? {
[index: number]: TypedPathWrapper<Z>;
}
: T extends TypedPathFunction<infer RET>
? {
(): TypedPathWrapper<RET>;
} & {
[P in keyof RET]: TypedPathWrapper<RET[P]>;
}
: {
[P in keyof T]: TypedPathWrapper<T[P]>;
}
) & TypedPathNode<T>;

const toStringMethods: (string | symbol | number)[] = [
'toString',
'path',
Symbol.toStringTag,
'valueOf'
];

function pathToString (path: string[]): string {
function pathToString(path: string[]): string {
return path.reduce((current, next) => {
if (+next === +next) {
current += `[${next}]`;
Expand All @@ -29,6 +40,10 @@ function pathToString (path: string[]): string {
export function typedPath<T>(path: string[] = []): TypedPathWrapper<T> {
return <TypedPathWrapper<T>>new Proxy({}, {
get(target: T, name: string | symbol | number) {
if (name === '$path') {
return pathToString(path);
}

if (toStringMethods.includes(name)) {
return () => pathToString(path);
}
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "typed-path",
"version": "1.0.3",
"version": "2.0.0",
"description": "Type safe object string paths for typescript.",
"main": "index.js",
"repository": {
Expand Down Expand Up @@ -37,9 +37,9 @@
"@types/mocha": "^2.2.39",
"chai": "^3.5.0",
"husky": "^0.13.1",
"mocha": "^3.2.0",
"ts-node": "^2.1.0",
"mocha": "^6.2.0",
"ts-node": "8.3.0",
"tslint": "^4.4.2",
"typescript": "^2.2.1"
"typescript": "3.6.4"
}
}
Loading