Skip to content
Open
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
6 changes: 3 additions & 3 deletions dist/index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type TestType = {

const testAdditionalHandlers = {
$abs: (path: TypedPathKey[]) =>
typedPath<TestType, typeof testAdditionalHandlers>(testAdditionalHandlers, ['', ...path]),
typedPath<TestType, TestType, typeof testAdditionalHandlers>(testAdditionalHandlers, ['', ...path]),
$url: (path: TypedPathKey[]) => path.join('/'),
$length: (path: TypedPathKey[]) => path.length
};
Expand All @@ -42,5 +42,5 @@ expectError(typedPath<TestType>().a.W.c.$rawPath);


// Types for additional handlers
expectType<number>(typedPath<TestType, typeof testAdditionalHandlers>(testAdditionalHandlers).a.b.c.$length);
// Types for additional handlers
expectType<number>(typedPath<TestType, TestType, typeof testAdditionalHandlers>(testAdditionalHandlers).a.b.c.$length);
// Types for additional handlers
6 changes: 3 additions & 3 deletions index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ interface OptionalThing {

const testAdditionalHandlers = {
$abs: (path: TypedPathKey[]) =>
typedPath<TestType, typeof testAdditionalHandlers>(testAdditionalHandlers, ['', ...path]),
typedPath<TestType, TestType, typeof testAdditionalHandlers>(testAdditionalHandlers, ['', ...path]),
$url: (path: TypedPathKey[]) => path.join('/')
};

Expand Down Expand Up @@ -124,11 +124,11 @@ describe('Typed path', () => {
});

it('should work with extended handlers', () => {
expect(typedPath<TestType, typeof testAdditionalHandlers>(testAdditionalHandlers).a.b.c.$url).toEqual('a/b/c');
expect(typedPath<TestType, TestType, typeof testAdditionalHandlers>(testAdditionalHandlers).a.b.c.$url).toEqual('a/b/c');
});

it('should work with chained extended handlers', () => {
expect(typedPath<TestType, typeof testAdditionalHandlers>(testAdditionalHandlers).a.b.c.$abs.$url).toEqual(
expect(typedPath<TestType, TestType, typeof testAdditionalHandlers>(testAdditionalHandlers).a.b.c.$abs.$url).toEqual(
'/a/b/c'
);
});
Expand Down
75 changes: 45 additions & 30 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,43 +25,48 @@ export type TypedPathHandlersConfig = Record<
<T extends TypedPathHandlersConfig>(path: TypedPathKey[], additionalHandlers?: T) => any
>;

const defaultHandlersConfig = {
$path: (path: TypedPathKey[]) => pathToString(path),
/**
* @deprecated This method transforms all path chunks to strings.
* If you need the path with numbers and Symbols - use $rawPath
*/
$raw: (path: TypedPathKey[]) => path.map((chunk) => chunk.toString()),
$rawPath: (path: TypedPathKey[]) => path,
toString: (path: TypedPathKey[]) => () => pathToString(path),
[Symbol.toStringTag]: (path: TypedPathKey[]) => pathToString(path),
valueOf: (path: TypedPathKey[]) => () => pathToString(path)
};
function defaultHandlersConfig<RootType>() {
return {
$rootType: () => ({} as RootType),
$path: (path: TypedPathKey[]) => pathToString(path),
/**
* @deprecated This method transforms all path chunks to strings.
* If you need the path with numbers and Symbols - use $rawPath
*/
$raw: (path: TypedPathKey[]) => path.map((chunk) => chunk.toString()),
$rawPath: (path: TypedPathKey[]) => path,
toString: (path: TypedPathKey[]) => () => pathToString(path),
[Symbol.toStringTag]: (path: TypedPathKey[]) => pathToString(path),
valueOf: (path: TypedPathKey[]) => () => pathToString(path)
};
}

export type DefaultHandlers = typeof defaultHandlersConfig;
export type DefaultHandlers<RootType> = ReturnType<typeof defaultHandlersConfig<RootType>>;

export type TypedPathHandlers<ConfigType extends TypedPathHandlersConfig> = {
[key in keyof ConfigType]: ReturnType<ConfigType[key]>;
};

export type TypedPathWrapper<
RootType,
OriginalType,
HandlersType extends TypedPathHandlers<Record<never, never>>
> = (OriginalType extends Array<infer OriginalArrayItemType>
? {
[index: number]: TypedPathWrapper<OriginalArrayItemType, HandlersType>;
[index: number]: TypedPathWrapper<RootType, OriginalArrayItemType, HandlersType>;
}
: OriginalType extends TypedPathFunction<infer OriginalFunctionResultType>
? {
(): TypedPathWrapper<OriginalFunctionResultType, HandlersType>;
(): TypedPathWrapper<RootType, OriginalFunctionResultType, HandlersType>;
} & {
[P in keyof Required<OriginalFunctionResultType>]: TypedPathWrapper<
RootType,
OriginalFunctionResultType[P],
HandlersType
>;
}
: {
[P in keyof Required<OriginalType>]: TypedPathWrapper<OriginalType[P], HandlersType>;
[P in keyof Required<OriginalType>]: TypedPathWrapper<RootType, OriginalType[P], HandlersType>;
}) &
TypedPathHandlers<HandlersType>;

Expand All @@ -76,28 +81,38 @@ function convertNumericKeyToNumber(key: TypedPathKey): TypedPathKey {
return key;
}

function getHandlerByNameKey<K extends TypedPathHandlersConfig>(name: TypedPathKey, additionalHandlers?: K) {
function getHandlerByNameKey<RootType, K extends TypedPathHandlersConfig>(name: TypedPathKey, additionalHandlers?: K) {
if (additionalHandlers?.hasOwnProperty(name)) {
return additionalHandlers[name as string];
}

if (defaultHandlersConfig[name as keyof typeof defaultHandlersConfig]) {
return defaultHandlersConfig[name as keyof typeof defaultHandlersConfig];
const defaultHandlers = defaultHandlersConfig<RootType>();

if (defaultHandlers[name as keyof typeof defaultHandlersConfig]) {
return defaultHandlers[name as keyof typeof defaultHandlersConfig];
}

return null;
}

const emptyObject = {};
export function typedPath<OriginalObjectType, HandlersType extends TypedPathHandlersConfig = Record<never, never>>(
export function typedPath<
RootObjectType,
OriginalObjectType = RootObjectType,
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 handler
? handler(path, additionalHandlers)
: typedPath(additionalHandlers, [...path, convertNumericKeyToNumber(name)]);
}
});
): TypedPathWrapper<RootObjectType, OriginalObjectType, HandlersType & DefaultHandlers<RootObjectType>> {
return <TypedPathWrapper<RootObjectType, OriginalObjectType, HandlersType & DefaultHandlers<RootObjectType>>>(
new Proxy(emptyObject, {
get(target: unknown, name: TypedPathKey) {
const handler = getHandlerByNameKey(name, additionalHandlers);

return handler
? handler(path, additionalHandlers)
: typedPath(additionalHandlers, [...path, convertNumericKeyToNumber(name)]);
}
})
);
}
13 changes: 7 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,15 @@
"build": "tsc -p tsconfig.dist.json"
},
"devDependencies": {
"@types/jest": "^29.5.4",
"husky": "^0.13.1",
"jest": "^26.6.3",
"jest": "^29.6.4",
"prettier": "2.2.0",
"ts-jest": "^26.4.4",
"ts-node": "^9.0.0",
"tsd": "^0.13.1",
"tslint": "^4.4.2",
"typescript": "^4.0.5"
"ts-jest": "^29.1.1",
"ts-node": "^10.9.1",
"tsd": "^0.29.0",
"tslint": "^6.1.3",
"typescript": "^5.2.2"
},
"tsd": {
"directory": "dist"
Expand Down
Loading