Skip to content

Commit

Permalink
feat: nested access on dynamic getter + enhance object pÃath access
Browse files Browse the repository at this point in the history
  • Loading branch information
tada5hi committed Apr 7, 2023
1 parent eda8a4b commit d828fd5
Show file tree
Hide file tree
Showing 8 changed files with 165 additions and 65 deletions.
2 changes: 1 addition & 1 deletion README.MD
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Continu 🌱
# Continu 🐯

[![npm version](https://badge.fury.io/js/continu.svg)](https://badge.fury.io/js/continu)
[![main](https://github.com/tada5hi/continu/actions/workflows/main.yml/badge.svg)](https://github.com/tada5hi/continu/actions/workflows/main.yml)
Expand Down
8 changes: 8 additions & 0 deletions src/getter/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* Copyright (c) 2023.
* Author Peter Placzek (tada5hi)
* For the full copyright and license information,
* view the LICENSE file that was distributed with this source code.
*/

export * from './module';
46 changes: 46 additions & 0 deletions src/getter/module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright (c) 2023-2023.
* Author Peter Placzek (tada5hi)
* For the full copyright and license information,
* view the LICENSE file that was distributed with this source code.
*/

import type { Context } from '../type';
import { getObjectPathProperty, isObject } from '../utils';

type Output = { success: true, data: any } | { success: false};
export function evaluatePathForDynamicGetters(
data: Record<string, any>,
key: string,
context: Context,
) : Output {
const dotIndex = key.indexOf('.');
const currentKey = dotIndex === -1 ?
key :
key.substring(0, dotIndex);

if (typeof data[currentKey] === 'function') {
const value = data[currentKey](context);

if (dotIndex === -1) {
return {
success: true,
data: value,
};
}

return {
success: true,
data: getObjectPathProperty(value, key.substring(currentKey.length + 1)),
};
}

if (isObject(data[currentKey])) {
const nextKey = key.substring(currentKey.length + 1);
if (nextKey.length > 0) {
return evaluatePathForDynamicGetters(data[currentKey], nextKey, context);
}
}

return { success: false };
}
15 changes: 8 additions & 7 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

import { OptionMissError } from './error';
import { evaluatePathForDynamicGetters } from './getter';
import type {
Context,
ContinuBaseInterface,
Expand Down Expand Up @@ -180,17 +181,17 @@ export class Continu<
return options as O;
}

if (hasObjectPathProperty(this.options, key as any)) {
return getObjectPathProperty(this.options, key as any) as O[K];
if (hasObjectPathProperty(this.options, key)) {
return getObjectPathProperty(this.options, key) as O[K];
}

const getter = this.getters[key];
if (getter) {
return getter(this);
const dynamicGetter = evaluatePathForDynamicGetters(this.getters, key, this);
if (dynamicGetter.success) {
return dynamicGetter.data;
}

if (hasObjectPathProperty(this.defaults, key as any)) {
return getObjectPathProperty(this.defaults, key as any) as O[K];
if (hasObjectPathProperty(this.defaults, key)) {
return getObjectPathProperty(this.defaults, key) as O[K];
}

if (this.errorOnMiss) {
Expand Down
2 changes: 1 addition & 1 deletion src/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export type Getters<T extends ObjectLiteral> = {
[K in keyof FlattenObject<T>]?: Getter<T, FlattenObject<T>[K]>
};

export type Context<T extends ObjectLiteral> = {
export type Context<T extends ObjectLiteral = ObjectLiteral> = {
defaults?: Partial<T>,
getters?: Getters<T>,
options?: Partial<T>,
Expand Down
111 changes: 55 additions & 56 deletions src/utils/object-path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,91 +5,90 @@
* view the LICENSE file that was distributed with this source code.
*/

import type { FlattenObject, ObjectLiteral } from '../type';
import type { ObjectLiteral } from '../type';
import { hasOwnProperty } from './has-property';
import { isObject } from './object';

export function setObjectPathProperty(
record: ObjectLiteral,
key: string,
value: unknown,
) {
const parts = key.split('.');
if (parts.length === 1) {
record[key] = value;
const dotIndex = key.indexOf('.');
const currentKey = dotIndex === -1 ?
key :
key.substring(0, dotIndex);

if (dotIndex === -1) {
record[currentKey] = value;
return;
}

const prefix = parts.shift();
if (prefix) {
if (!Object.prototype.hasOwnProperty.call(record, prefix)) {
record[prefix] = {};
}

setObjectPathProperty(record[prefix], parts.join('.') as any, value as any);
if (!Object.prototype.hasOwnProperty.call(record, currentKey)) {
record[currentKey] = {};
}

const nextKey = key.substring(currentKey.length + 1);
setObjectPathProperty(record[currentKey], nextKey, value);
}

export function hasObjectPathProperty<
O extends ObjectLiteral,
K extends keyof FlattenObject<O>,
>(record: O, key: K) : boolean {
const parts = key.split('.');
if (parts.length === 1) {
return hasOwnProperty(record, key);
}
export function hasObjectPathProperty(data: ObjectLiteral, key: string) : boolean {
const dotIndex = key.indexOf('.');
const currentKey = dotIndex === -1 ?
key :
key.substring(0, dotIndex);

const prefix = parts.shift();
if (prefix) {
if (!hasOwnProperty(record, prefix)) {
return false;
}
if (dotIndex === -1) {
return !!hasOwnProperty(data, currentKey);
}

return hasObjectPathProperty(record[prefix], parts.join('.'));
if (!isObject(data[currentKey])) {
return false;
}

return false;
const nextKey = key.substring(currentKey.length + 1);
return hasObjectPathProperty(data[currentKey], nextKey);
}

export function removeObjectPathProperty<
O extends ObjectLiteral,
K extends keyof FlattenObject<O>,
>(record: O, key: K) {
const parts = key.split('.');
if (parts.length === 1) {
if (hasOwnProperty(record, key)) {
delete record[key];
export function removeObjectPathProperty(
data: Record<string, any>,
key: string,
) {
const dotIndex = key.indexOf('.');
const currentKey = dotIndex === -1 ?
key :
key.substring(0, dotIndex);

if (dotIndex === -1) {
if (hasOwnProperty(data, currentKey)) {
delete data[currentKey];
}

return;
}

const prefix = parts.shift();
if (prefix) {
if (!hasOwnProperty(record, prefix)) {
return;
}

removeObjectPathProperty(record[prefix], parts.join('.'));
if (!isObject(data[currentKey])) {
return;
}

const nextKey = key.substring(currentKey.length + 1);
getObjectPathProperty(data[currentKey], nextKey);
}

export function getObjectPathProperty<
O extends ObjectLiteral,
K extends keyof FlattenObject<O>,
>(record: O, key: K) : FlattenObject<O>[K] | undefined {
const parts = key.split('.');
if (parts.length === 1) {
return record[key];
}
export function getObjectPathProperty(data: Record<string, any>, key: string) : any {
const dotIndex = key.indexOf('.');
const currentKey = dotIndex === -1 ?
key :
key.substring(0, dotIndex);

const prefix = parts.shift();
if (prefix) {
if (!hasOwnProperty(record, prefix)) {
return undefined;
}
if (dotIndex === -1) {
return data[currentKey];
}

return getObjectPathProperty(record[prefix], parts.join('.'));
if (!isObject(data[currentKey])) {
return undefined;
}

return undefined;
const nextKey = key.substring(currentKey.length + 1);
return getObjectPathProperty(data[currentKey], nextKey);
}
19 changes: 19 additions & 0 deletions test/unit/module.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,4 +218,23 @@ describe('src/module.ts', () => {
expect(continu.get('foo')).toEqual('bar');
expect(continu.get('bar')).toEqual('bar:baz');
});

it('should access property of dynamic getter', () => {
const continu = new Continu<Options>({
defaults: {
foo: 'bar',
},
getters: {
nested: (context) => ({
key: 'bar',
}),
},
});

expect(continu.has('nested')).toBeFalsy();
expect(continu.has('nested.key')).toBeFalsy();

expect(continu.get('nested')).toEqual({ key: 'bar' });
expect(continu.get('nested.key')).toEqual('bar');
});
});
27 changes: 27 additions & 0 deletions test/unit/object-path.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { hasObjectPathProperty, setObjectPathProperty } from '../../src';

describe('src/utils/object-path', () => {
it('should set object path property', () => {
let data : Record<string, any> = {};
setObjectPathProperty(data, 'foo', 'bar');
expect(data).toEqual({ foo: 'bar' });

data = {};
setObjectPathProperty(data, 'foo.bar', 'baz');
expect(data).toEqual({ foo: { bar: 'baz' } });
});

it('should check object path property', () => {
const data = {
foo: {
bar: {
baz: 'boz',
},
},
};

expect(hasObjectPathProperty(data, 'foo.bar')).toBeTruthy();
expect(hasObjectPathProperty(data, 'foo.bar.baz')).toBeTruthy();
expect(hasObjectPathProperty(data, 'foo.boz')).toBeFalsy();
});
});

0 comments on commit d828fd5

Please sign in to comment.