RFC: upsertItem state operator API #1796
Replies: 3 comments 6 replies
-
|
An example of a proposed API:
Please note that if you propose an API, it is good to identify the gaps as well as the advantages (as demonstrated here). |
Beta Was this translation helpful? Give feedback.
-
|
I agree that the API is not descriptive enough as to what the output will be, i do think though, that given we already have
i think the rest of functionality should not became part of this operator, and rather become part of that said, i think we should support operator for the scenario an item already exists, and in case item doesn't exist throw error? |
Beta Was this translation helpful? Give feedback.
-
|
Following this comment: #1796 (reply in thread) The signature would be this: export function upsertItem<T>(
selector: number | ((item: T, index: number) => boolean),
upsertOptions: UpsertOptions<T>
): StateOperator<T[]>;
export function upsertItem<T>(
selector: number | ((item: T, index: number) => boolean),
upsertOptions: UpdateOptions<T> & InsertOptions<T>
): StateOperator<T[]>;
// export function upsertItem<T>( /*...*/): StateOperator<T[]> { /* ...implementation */ }So, you would either use just the interface AllUpsertOptions<T> {
value: T;
}
interface AllInsertOptions<T> {
prependNewItem: T;
appendNewItem: T;
// Debatable if this option should be included (see comment):
customInsert: StateOperator<T[]>;
}
interface AllUpdateOptions<T> {
replaceItem: T;
updateItem: StateOperator<T>;
}
type InsertOptions<T> = MutuallyExclusiveProps<AllInsertOptions<T>>;
type UpdateOptions<T> = MutuallyExclusiveProps<AllUpdateOptions<T>>;
type UpsertOptions<T> = AllUpsertOptions<T>;Here is a playground I used to flesh this out: https://stackblitz.com/edit/typescript-ngxs-upsert-item-proposal-1?file=index.ts Usage best explained through examples: /** By default this replaces or inserts the item at the provided index */
upsertItem<string>(2, { value: 'UPDATED' });
// 2. [Exists] Replace item if it matches the provided index
// ['zero', 'one', 'two'] => ['zero', 'one', 'UPDATED']
// 14. [Doesn't exist] Insert item at provided index which is one past the last index of the array (array grows by 1)
// ['zero', 'one'] => ['zero', 'one', 'NEW']
// 15. [Doesn't exist] Insert item at provided index which is a more than one past the last index of the array (array grows to new index length)
// ['zero'] => ['zero', undefined, 'NEW']
/** By default this replaces or appends the item */
upsertItem<string>((item) => item === 'two', { value: 'UPDATED' });
// 1. [Exists] Replace item if it matches the provided predicate
// ['zero', 'one', 'two'] => ['zero', 'one', 'UPDATED']
// 11. [Doesn't exist] Append item if no items match the provided predicate
// ['zero', 'one'] => ['zero', 'one', 'NEW']
// 11. [Doesn't exist] Append item if no items match the provided predicate
// ['zero'] => ['zero', 'NEW'
upsertItem<string>(2, { replaceItem: 'UPDATED', appendNewItem: 'NEW' });
// 2. [Exists] Replace item if it matches the provided index
// ['zero', 'one', 'two'] => ['zero', 'one', 'UPDATED']
// 14. [Doesn't exist] Insert item at provided index which is one past the last index of the array (array grows by 1)
// ['zero', 'one'] => ['zero', 'one', 'NEW']
// 15. [Doesn't exist] Insert item at provided index which is a more than one past the last index of the array (array grows to new index length)
// ['zero'] => ['zero', undefined, 'NEW']
upsertItem<string>((item) => item === 'two', {
replaceItem: 'UPDATED',
appendNewItem: 'NEW',
});
// 1. [Exists] Replace item if it matches the provided predicate
// ['zero', 'one', 'two'] => ['zero', 'one', 'UPDATED']
// 11. [Doesn't exist] Append item if no items match the provided predicate
// ['zero', 'one'] => ['zero', 'one', 'NEW']
// 11. [Doesn't exist] Append item if no items match the provided predicate
// ['zero'] => ['zero', 'NEW']
upsertItem<string>((item) => item === 'two', {
replaceItem: 'UPDATED',
prependNewItem: 'NEW',
});
// 1. [Exists] Replace item if it matches the provided predicate
// ['zero', 'one', 'two'] => ['zero', 'one', 'UPDATED']
// 10. [Doesn't exist] Prepend item if no items match the provided predicate
// ['zero', 'one'] => ['NEW', 'zero', 'one']
// 10. [Doesn't exist] Prepend item if no items match the provided predicate
// ['zero'] => ['NEW', 'zero']
upsertItem<string>(2, {
updateItem: (val) => `${val} UPDATED`,
appendNewItem: 'NEW',
});
// 4. [Exists] Patch item if it matches the provided index
// 6. [Exists] Apply a state operator to item if it matches the provided index
// ['zero', 'one', 'two'] => ['zero', 'one', 'two UPDATED']
// 11. [Doesn't exist] Append item if no items match the provided predicate
// ['zero', 'one'] => ['zero', 'one', 'NEW']
// 11. [Doesn't exist] Append item if no items match the provided predicate
// ['zero'] => ['zero', 'NEW']
upsertItem<string>(2, {
updateItem: (val) => `${val} UPDATED`,
prependNewItem: 'NEW',
});
// 4. [Exists] Patch item if it matches the provided index
// 6. [Exists] Apply a state operator to item if it matches the provided index
// ['zero', 'one', 'two'] => ['zero', 'one', 'two UPDATED']
// 10. [Doesn't exist] Prepend item if no items match the provided predicate
// ['zero', 'one'] => ['NEW', 'zero', 'one']
// 10. [Doesn't exist] Prepend item if no items match the provided predicate
// ['zero'] => ['NEW', 'zero']
upsertItem<string>((item) => item === 'two', {
updateItem: (val) => `${val} UPDATED`,
appendNewItem: 'NEW',
});
// 3. [Exists] Patch item if it matches the provided predicate
// 5. [Exists] Apply a state operator to item if it matches the provided predicate
// ['zero', 'one', 'two'] => ['zero', 'one', 'two UPDATED']
// 11. [Doesn't exist] Append item if no items match the provided predicate
// ['zero', 'one'] => ['zero', 'one', 'NEW']
// 11. [Doesn't exist] Append item if no items match the provided predicate
// ['zero'] => ['zero', 'NEW']
/*
I am in two minds if the `customInsert` option should be part of the operator
If we don't include it then we dont support the following scenarios:
// 12. [Doesn't exist] Insert item at a specified index if no items match the provided predicate
// 13. [Doesn't exist] Insert item at a position determined by a function if no items match the provided predicate (think adding item to a sorted list... IMO this should not be part of this operator, but worth mentioning to be thorough)
I am ok to not support these to be honest, because it is a really specific requirement as opposed to general purpose
That being said, if we were to include that operator then these scenarios can be handled like this:
*/
upsertItem<string>((item) => item === 'two', {
replaceItem: 'UPDATED',
customInsert: insertItem('NEW', 1),
});
// 1. [Exists] Replace item if it matches the provided predicate
// ['zero', 'one', 'two'] => ['zero', 'one', 'UPDATED']
// 12. [Doesn't exist] Insert item at a specified index if no items match the provided predicate
// ['zero', 'one'] => ['zero', 'one', 'NEW']
// 12. [Doesn't exist] Insert item at a specified index if no items match the provided predicate
// ['zero'] => ['zero', 'NEW']
upsertItem<string>((item) => item === 'c', {
replaceItem: 'UPDATED',
customInsert: (val) => {
const i = val.findIndex((item) => item > 'c');
if (i < 0) {
return [...val, 'NEW'];
}
return [...val.slice(0, i), 'NEW', ...val.slice(i)];
},
});
// 1. [Exists] Replace item if it matches the provided predicate
// ['a', 'b', 'c', 'd'] => ['a', 'b', 'UPDATED', 'd']
// 13. [Doesn't exist] Insert item at a position determined by a function if no items match the provided predicate
// ['a', 'b', 'd'] => ['a', 'b', 'NEW', 'd']
// 13. [Doesn't exist] Insert item at a position determined by a function if no items match the provided predicate
// ['a', 'b'] => ['a', 'b', 'NEW']Thoughts? |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
It has been proposed that an
upsertItemstate operator is added to the standard NGXS state operators.PR #1795 proposes an approach, but I think we need to be more deliberate with the API.
It is based on my comment here: #926 (comment)
(but potentially over-engineered by me at the time)
But there is another simpler operator proposed by @marcjulian here: #926 (comment)
We really need to think about scenarios that are best handled by this operator and which would be better handled by another operator (instead of overloading this one too much).
Some scenarios in this problem space that we can think about how we will handle:
Item exists
Item doesn't exist
Numbered items for reference in the discussion, a gap is left in case items are added later.
If you think of another scenario, then please mention it here.
Beta Was this translation helpful? Give feedback.
All reactions