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

Add .appendToArray() method #124

Closed
wants to merge 4 commits into from
Closed
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
15 changes: 14 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ The instance is [`iterable`](https://developer.mozilla.org/en/docs/Web/JavaScrip

Set an item.

The `value` must be JSON serializable. Trying to set the type `undefined`, `function`, or `symbol` will result in a TypeError.
The `value` must be JSON serializable. Trying to set the type like `undefined`, `function`, or `symbol` will result in a `TypeError`.

#### .set(object)

Expand Down Expand Up @@ -338,6 +338,19 @@ const unsubscribe = conf.onDidAnyChange(key, callback);
unsubscribe();
```

#### .appendToArray(key, value)

Append `value` to an array item. If the item (`key`) doesn't exist, a new array with the `value` will be created. If item exists and is not an array, it will result in a `TypeError`.

The `value` must be JSON serializable. Trying to set the type like `undefined`, `function`, or `symbol` will result in a `TypeError`.

```js
config.appendToArray('foo.unicorns', '🦄');
config.appendToArray('foo.unicorns', '🦄');
console.log(config.get('foo.unicorns'));
//=> ['🦄', '🦄']
```

#### .size

Get the item count.
Expand Down
21 changes: 20 additions & 1 deletion source/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ class Conf<T extends Record<string, any> = Record<string, unknown>> implements I
@param defaultValue - The default value if the item does not exist.
*/
get<Key extends keyof T>(key: Key): T[Key];
get<Key extends keyof T>(key: Key, defaultValue: Required<T>[Key]): Required<T>[Key];
get<Key extends keyof T>(key: Key, defaultValue: Required<T>[Key] | Required<unknown>): Required<T>[Key];
// This overload is used for dot-notation access.
// We exclude `keyof T` as an incorrect type for the default value should not fall through to this overload.
get<Key extends string, Value = unknown>(key: Exclude<Key, keyof T>, defaultValue?: Value): Value;
Expand Down Expand Up @@ -223,6 +223,25 @@ class Conf<T extends Record<string, any> = Record<string, unknown>> implements I
this.store = store;
}

/**
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The types here should limit it to only allow using appendToArray on a key defined to have an array value or unknown.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm new to typescript, I tried to understand much from the existing code to make this one. Sorry, I'm not sure how to do what you mean 😅 Can you suggest one please ?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Being new to something is a great opportunity to learn more about it. Give it a shot at least. I can help if you really cannot do it.

Copy link
Author

@subins2000 subins2000 Aug 22, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can I know if the overload definition I set is correct, I tried doing

store.set('a', 1)
store.appendToArray('a', 2)

These lines didn't show any error in VSCode, it should right ?, that's how typescript should work right ?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests would fail if it's correct, as TS would refuse to compile. You need to make it TS fail, and then you can use the @ts-expect-error comment to ignore the error just for the test.

Copy link
Author

@subins2000 subins2000 Aug 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried many, but can't figure it out. Is there any examples that I can look for ? I've been at this for hours now 😅. The best I could come up to do "limit it to only allow using appendToArray on a key defined to have an array value". Each line is a separate try and not a group :

appendToArray<Key extends keyof T, Index extends keyof any[]>(key: Key, value: T[Key][Index]): void
appendToArray<Key extends keyof T, Index extends keyof T[Key]>(key: Key, value: T[Key][Index]): void
appendToArray<Key extends keyof T, Index extends T[Key][keyof T[Key]]>(key: Key, value: T[Key][Index]): void

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sindresorhus I'm not able to make it, would like to know the solution please

Append an item to array

@param {key|object} - You can use [dot-notation](https://github.com/sindresorhus/dot-prop) in a key to access nested properties. Or a hashmap of items to set at once.
@param value - Must be JSON serializable. Trying to set the type `undefined`, `function`, or `symbol` will result in a `TypeError`.
*/
sindresorhus marked this conversation as resolved.
Show resolved Hide resolved
appendToArray<Key extends keyof T, Value = T[Key][0]>(key: Key, value: Value): void;
appendToArray(key: string, value: unknown): void;
// This overload is used for dot-notation access.
appendToArray<Key extends string, Value = T[Key][0]>(key: Key, value: Value): void {
const array: [] = this.get(key, [] as Array<typeof value>);

if (!Array.isArray(array)) {
throw new TypeError(`The key \`${key}\` is already set to a non-array value`);
}

this.set(key, [...array, value] as T[Key]);
}

/**
Check if an item exists.

Expand Down
40 changes: 40 additions & 0 deletions test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,46 @@ test('.set() - invalid key', t => {
}, {message: 'Expected `key` to be of type `string` or `object`, got number'});
});

test('.appendToArray()', t => {
sindresorhus marked this conversation as resolved.
Show resolved Hide resolved
t.context.config.set('foo', [fixture]);
t.context.config.set('baz.boo', [fixture]);
t.context.config.appendToArray('foo', '🐴');
t.context.config.appendToArray('baz.boo', '🐴');
t.context.config.appendToArray('bar', '🐴'); // Will create new item with array
t.deepEqual(t.context.config.get('foo'), [fixture, '🐴']);
t.deepEqual(t.context.config.get('baz.boo'), [fixture, '🐴']);
t.deepEqual(t.context.config.get('bar'), ['🐴']);
});

test('.appendToArray() - array of objects', t => {
t.context.config.appendToArray('foo', {name: 'a'});
t.context.config.appendToArray('foo', {name: 'b'});
t.context.config.appendToArray('foo', {name: 'c'});
t.deepEqual(t.context.config.get('foo'), [
{name: 'a'},
{name: 'b'},
{name: 'c'}
]);
});

test('.appendToArray() - array of arrays', t => {
t.context.config.appendToArray('foo', [1]);
t.context.config.appendToArray('foo', [1, 2]);
t.context.config.appendToArray('foo', [{name: 'a'}]);
t.deepEqual(t.context.config.get('foo'), [
[1],
[1, 2],
[{name: 'a'}]
]);
});

test('.appendToArray() - value already set', t => {
t.context.config.set('foo', fixture);
t.throws(() => {
t.context.config.appendToArray('foo', fixture);
}, {message: 'The key `foo` is already set to a non-array value'});
});

test('.has()', t => {
t.context.config.set('foo', fixture);
t.context.config.set('baz.boo', fixture);
Expand Down