atomaric 0.0.68 → 0.0.69

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "atomaric",
3
3
  "description": "Manage your project state",
4
- "version": "0.0.68",
4
+ "version": "0.0.69",
5
5
  "type": "module",
6
6
  "main": "./build/atomaric.umd.cjs",
7
7
  "module": "./build/atomaric.js",
@@ -15,7 +15,8 @@
15
15
  },
16
16
  "files": [
17
17
  "build",
18
- "types"
18
+ "types",
19
+ "src/do.classes"
19
20
  ],
20
21
  "keywords": [
21
22
  "react",
@@ -0,0 +1,103 @@
1
+ import { useSyncExternalStore } from 'react';
2
+ import { atom, configureAtomaric } from '../lib';
3
+ import { wait } from '../utils';
4
+
5
+ configureAtomaric({
6
+ useSyncExternalStore,
7
+ keyPathSeparator: '.',
8
+ });
9
+
10
+ describe('Array', () => {
11
+ test('simple init', async () => {
12
+ const initArray: number[] = [];
13
+ const testAtom = atom(initArray);
14
+ testAtom.set([]);
15
+
16
+ expect(testAtom.get() !== initArray).toBeTruthy();
17
+ });
18
+
19
+ test('do actions', async () => {
20
+ const testAtom = atom((): (number | nil | string | { nums: number[] })[] => [], {
21
+ do: (_set, _get, self) => ({
22
+ switch30(toggleValue: string) {
23
+ self.do.toggle(30);
24
+ self.do.toggle(toggleValue);
25
+ },
26
+ }),
27
+ });
28
+
29
+ testAtom.do.push(1, 2, 5, 2, 5, 5, 5, 5, 1, null, '', '#');
30
+ await wait();
31
+
32
+ expect(testAtom.get()).toEqual([1, 2, 5, 2, 5, 5, 5, 5, 1, null, '', '#']);
33
+
34
+ testAtom.do.add(0);
35
+ testAtom.do.add(5);
36
+ testAtom.do.add(8);
37
+ await wait();
38
+
39
+ expect(testAtom.get()).toEqual([1, 2, 5, 2, 5, 5, 5, 5, 1, null, '', '#', 0, 8]);
40
+
41
+ testAtom.do.remove(5);
42
+ await wait();
43
+
44
+ expect(testAtom.get()).toEqual([1, 2, 2, 5, 5, 5, 5, 1, null, '', '#', 0, 8]);
45
+
46
+ testAtom.do.toggle('+');
47
+ await wait();
48
+
49
+ expect(testAtom.get()).toEqual([1, 2, 2, 5, 5, 5, 5, 1, null, '', '#', 0, 8, '+']);
50
+
51
+ testAtom.do.toggle('+');
52
+ await wait();
53
+
54
+ expect(testAtom.get()).toEqual([1, 2, 2, 5, 5, 5, 5, 1, null, '', '#', 0, 8]);
55
+
56
+ testAtom.do.switch30('@');
57
+ await wait();
58
+
59
+ expect(testAtom.get()).toEqual([1, 2, 2, 5, 5, 5, 5, 1, null, '', '#', 0, 8, 30, '@']);
60
+
61
+ testAtom.do.unshift('**');
62
+ await wait();
63
+
64
+ expect(testAtom.get()).toEqual(['**', 1, 2, 2, 5, 5, 5, 5, 1, null, '', '#', 0, 8, 30, '@']);
65
+
66
+ testAtom.do.filter();
67
+ await wait();
68
+
69
+ expect(testAtom.get()).toEqual(['**', 1, 2, 2, 5, 5, 5, 5, 1, '#', 8, 30, '@']);
70
+
71
+ testAtom.do.filter(val => typeof val === 'string');
72
+ await wait();
73
+
74
+ expect(testAtom.get()).toEqual(['**', '#', '@']);
75
+
76
+ testAtom.do.update(val => val.reverse());
77
+ await wait();
78
+
79
+ expect(testAtom.get()).toEqual(['@', '#', '**']);
80
+
81
+ const nums555 = { nums: [5, 5, 5] };
82
+ const nums123 = { nums: [1, 2, 3] };
83
+
84
+ testAtom.do.add(nums123);
85
+ testAtom.do.add(nums555);
86
+ await wait();
87
+
88
+ expect(testAtom.get()).toEqual(['@', '#', '**', nums123, nums555]);
89
+
90
+ testAtom.do.update(val => {
91
+ if (val[3] && typeof val[3] === 'object' && 'nums' in val[3]) val[3].nums.reverse();
92
+ });
93
+ await wait();
94
+
95
+ expect(testAtom.get()).toEqual(['@', '#', '**', { nums: [3, 2, 1] }, nums555]);
96
+
97
+ expect(testAtom.get()[4] === nums555).toBeTruthy();
98
+ expect(testAtom.get()[3] !== nums123).toBeTruthy();
99
+
100
+ const value = testAtom.get();
101
+ expect(value[3] && typeof value[3] === 'object' && value[3].nums !== nums123.nums).toBeTruthy();
102
+ });
103
+ });
@@ -0,0 +1,59 @@
1
+ import { Atom } from '../../types';
2
+ import { AtomUpdateDoAction } from './_Update';
3
+
4
+ export class AtomArrayDoActions<Value> extends AtomUpdateDoAction {
5
+ constructor(private atom: Atom<Value[]>, actions: Record<string, Function> | nil) {
6
+ super(actions);
7
+ }
8
+
9
+ /** like the Array.prototype.push() method */
10
+ push = (...values: Value[]) => {
11
+ this.atom.set(this.atom.get().concat(values));
12
+ };
13
+
14
+ /** like the Array.prototype.unshift() method */
15
+ unshift = (...values: Value[]) => {
16
+ this.atom.set(values.concat(this.atom.get()));
17
+ };
18
+
19
+ /** transform current taken value */
20
+ update = (updater: (value: Value[]) => void) => {
21
+ const prev = this.atom.get();
22
+ const newValue = this.updateValue(prev, updater);
23
+ if (newValue === prev) return;
24
+ this.atom.set(newValue);
25
+ };
26
+
27
+ /** like the Array.prototype.filter() method, but callback is optional - (it) => !!it */
28
+ filter = (filter?: (value: Value, index: number, Array: Value[]) => any) => {
29
+ this.atom.set(this.atom.get().filter(filter ?? itIt));
30
+ };
31
+
32
+ /** will add value if not exists */
33
+ add = (value: Value) => {
34
+ if (this.atom.get().includes(value)) return;
35
+ this.atom.set(this.atom.get().concat([value]));
36
+ };
37
+
38
+ /** will delete value from array */
39
+ remove = (value: Value) => {
40
+ const index = this.atom.get().indexOf(value);
41
+ if (index < 0) return;
42
+ const newArray = this.atom.get().slice(0);
43
+ newArray.splice(index, 1);
44
+ this.atom.set(newArray);
45
+ };
46
+
47
+ /** will add value if it doesn't exist, otherwise delete */
48
+ toggle = (value: Value, isAddToStart?: boolean) => {
49
+ const newArray = this.atom.get().slice();
50
+ const index = newArray.indexOf(value);
51
+ if (index < 0) {
52
+ if (isAddToStart) newArray.unshift(value);
53
+ else newArray.push(value);
54
+ } else newArray.splice(index, 1);
55
+ this.atom.set(newArray);
56
+ };
57
+ }
58
+
59
+ const itIt = <It>(it: It) => it;
@@ -0,0 +1,27 @@
1
+ import { useSyncExternalStore } from 'react';
2
+ import { atom, configureAtomaric } from '../lib';
3
+ import { wait } from '../utils';
4
+
5
+ configureAtomaric({
6
+ useSyncExternalStore,
7
+ keyPathSeparator: '.',
8
+ });
9
+
10
+ describe('Boolean', () => {
11
+ test('do actions', async () => {
12
+ const testAtom = atom(false, {
13
+ do: (set, get) => ({
14
+ switch(news?: boolean) {
15
+ set(news ?? !get());
16
+ },
17
+ }),
18
+ });
19
+
20
+ testAtom.do.switch(true);
21
+ testAtom.do.switch();
22
+ testAtom.do.toggle();
23
+ await wait();
24
+
25
+ expect(testAtom.get() === true).toBeTruthy();
26
+ });
27
+ });
@@ -0,0 +1,13 @@
1
+ import { Atom } from '../../types';
2
+ import { AtomDoActionsBasic } from './_Basic';
3
+
4
+ export class AtomBooleanDoActions extends AtomDoActionsBasic {
5
+ constructor(private atom: Atom<boolean>, actions: Record<string, Function> | nil) {
6
+ super(actions);
7
+ }
8
+
9
+ /** toggle current value between true/false */
10
+ toggle = () => {
11
+ this.atom.set(!this.atom.get());
12
+ };
13
+ }
@@ -0,0 +1,25 @@
1
+ import { useSyncExternalStore } from 'react';
2
+ import { atom, configureAtomaric } from '../lib';
3
+ import { wait } from '../utils';
4
+
5
+ configureAtomaric({
6
+ useSyncExternalStore,
7
+ keyPathSeparator: '.',
8
+ });
9
+
10
+ describe('Number', () => {
11
+ test('do actions', async () => {
12
+ const testAtom = atom(100, { do: (set, get) => ({ switchSign: () => set(-get()) }) });
13
+ const testFuncAtom = atom(() => 200, { do: (set, get) => ({ switchSign: () => set(-get()) }) });
14
+
15
+ testAtom.do.increment();
16
+ testAtom.do.switchSign();
17
+
18
+ testFuncAtom.do.increment(-3);
19
+ testFuncAtom.do.switchSign();
20
+ await wait();
21
+
22
+ expect(testAtom.get()).toEqual(-101);
23
+ expect(testFuncAtom.get()).toEqual(-197);
24
+ });
25
+ });
@@ -0,0 +1,15 @@
1
+ import { Atom } from '../../types';
2
+ import { AtomDoActionsBasic } from './_Basic';
3
+
4
+ export class AtomNumberDoActions extends AtomDoActionsBasic {
5
+ constructor(private atom: Atom<number>, actions: Record<string, Function> | nil) {
6
+ super(actions);
7
+ }
8
+
9
+ /** pass the 2 to increment on 2, pass the -2 to decrement on 2
10
+ * **default: 1**
11
+ */
12
+ increment = (delta?: number) => {
13
+ this.atom.set(+this.atom.get() + (delta ?? 1));
14
+ };
15
+ }
@@ -0,0 +1,57 @@
1
+ import { useSyncExternalStore } from 'react';
2
+ import { atom, configureAtomaric } from '../lib';
3
+ import { wait } from '../utils';
4
+
5
+ configureAtomaric({
6
+ useSyncExternalStore,
7
+ keyPathSeparator: '.',
8
+ });
9
+
10
+ describe('Object', () => {
11
+ test('setDeepPartial()', async () => {
12
+ const b = { c: [{ d: 8, e: 'e', f: 'F', g: { h: 'HHH' } }] };
13
+ const a = { f: { g: '' }, b };
14
+ const testAtom = atom({ a, b });
15
+
16
+ testAtom.do.setDeepPartial('b.c.0.d', 123, { b: { c: [{}] } });
17
+
18
+ await wait();
19
+
20
+ expect(testAtom.get().b).not.toEqual(b);
21
+ expect(testAtom.get().b.c[0].d).toEqual(123);
22
+ expect(testAtom.get().b.c).not.toEqual(b.c);
23
+ expect(testAtom.get().a).toEqual(a);
24
+
25
+ testAtom.do.setDeepPartial('b+c+8+e', 'EE', null, '+');
26
+
27
+ await wait();
28
+
29
+ expect(testAtom.get().b.c[8].e).toEqual('EE');
30
+ });
31
+
32
+ test('setDeepPartial() with first numeric prop', async () => {
33
+ enum Num {
34
+ num = 123,
35
+ }
36
+ const testAtom = atom({ [Num.num]: { a: 'A' } });
37
+
38
+ testAtom.do.setDeepPartial(`${Num.num}.a`, 'AA', { [Num.num]: {} });
39
+
40
+ await wait();
41
+
42
+ expect(testAtom.get()[Num.num].a).toEqual('AA');
43
+ });
44
+
45
+ test('update()', async () => {
46
+ const init = { a: { b: { c: { d: { e: 'E' } }, f: { g: {} } }, h: { i: { j: {} } } } };
47
+ const testAtom = atom(init);
48
+
49
+ testAtom.do.update(obj => (obj.a.b.c.d.e = 'eE'));
50
+
51
+ await wait();
52
+
53
+ expect(testAtom.get().a.b.c.d.e).toEqual('eE');
54
+ expect(testAtom.get().a.b.c).not.toEqual(init.a.b.c);
55
+ expect(testAtom.get().a.h.i).toEqual(init.a.h.i);
56
+ });
57
+ });
@@ -0,0 +1,75 @@
1
+ import { Atom, ObjectActionsSetDeepPartialDoAction } from '../../types';
2
+ import { configuredOptions } from '../lib';
3
+ import { AtomUpdateDoAction } from './_Update';
4
+
5
+ export class AtomObjectDoActions<Value extends object> extends AtomUpdateDoAction {
6
+ constructor(private atom: Atom<Value>, actions: Record<string, Function> | nil) {
7
+ super(actions);
8
+ }
9
+
10
+ /** pass partial object to update some field values */
11
+ setPartial = (value: Partial<Value> | ((value: Value) => Partial<Value>)) =>
12
+ this.atom.set(prev => ({
13
+ ...prev,
14
+ ...(typeof value === 'function' ? value(this.atom.get()) : value),
15
+ }));
16
+
17
+ /** transform current taken value */
18
+ update = (updater: (value: Value) => void) => {
19
+ const prev = this.atom.get();
20
+ const newValue = this.updateValue(prev, updater);
21
+ if (newValue === prev) return;
22
+ this.atom.set(newValue);
23
+ };
24
+
25
+ /** pass partial value to update some deep values by flat path */
26
+ setDeepPartial: ObjectActionsSetDeepPartialDoAction<Value> = (
27
+ path,
28
+ value,
29
+ donor,
30
+ separator = (configuredOptions.keyPathSeparator || '.') as never,
31
+ ) => {
32
+ if (!separator) return;
33
+
34
+ if (path.includes(separator)) {
35
+ let keys = path.split(separator);
36
+ const lastKey = keys[keys.length - 1];
37
+ keys = keys.slice(0, -1);
38
+ const newObject = { ...this.atom.get() };
39
+ let lastObject = newObject as Record<string, unknown>;
40
+ let lastDonorObject = donor as Record<string, unknown> | nil;
41
+
42
+ for (const key of keys) {
43
+ lastDonorObject = lastDonorObject?.[Array.isArray(lastDonorObject) ? '0' : key] as never;
44
+ const currentObject = lastObject[makeKey(lastObject, key)] ?? (Array.isArray(lastDonorObject) ? [] : {});
45
+
46
+ if (currentObject == null || typeof currentObject !== 'object') {
47
+ if (donor == null) throw 'Incorrect path for setDeepPartial';
48
+
49
+ const newValue = typeof value === 'function' ? (value as (val: undefined) => Value)(undefined) : value;
50
+
51
+ if (this.atom.get()[path as never] !== newValue) this.setPartial({ [path]: newValue } as never);
52
+ return;
53
+ }
54
+
55
+ lastObject = lastObject[makeKey(lastObject, key)] = (
56
+ Array.isArray(currentObject) ? [...currentObject] : { ...currentObject }
57
+ ) as never;
58
+ }
59
+
60
+ const prev = lastObject[lastKey];
61
+ lastObject[lastKey] =
62
+ typeof value === 'function' ? (value as (val: unknown) => Value)(lastObject[lastKey]) : value;
63
+
64
+ if (prev !== lastObject[lastKey]) this.atom.set(newObject);
65
+
66
+ return;
67
+ }
68
+
69
+ const prevValue = this.atom.get()[path as never];
70
+ const newValue = typeof value === 'function' ? (value as (val: Value) => Value)(prevValue) : value;
71
+ if (newValue !== prevValue) this.setPartial({ [path]: newValue } as never);
72
+ };
73
+ }
74
+
75
+ const makeKey = (obj: object, key: string) => (Array.isArray(obj) ? `${+key}` : key);
@@ -0,0 +1,46 @@
1
+ import { useSyncExternalStore } from 'react';
2
+ import { atom, configureAtomaric } from '../lib';
3
+ import { makeFullKey, wait } from '../utils';
4
+
5
+ configureAtomaric({
6
+ useSyncExternalStore,
7
+ keyPathSeparator: '.',
8
+ });
9
+
10
+ describe('Set', () => {
11
+ test('do actions', async () => {
12
+ const testAtom = atom(new Set<string>(), {
13
+ storeKey: 'set:test',
14
+ do: (set, get) => ({
15
+ filterValues: () => {
16
+ const array = Array.from(get());
17
+ set(new Set(array.filter(it => it)));
18
+ },
19
+ }),
20
+ });
21
+
22
+ testAtom.do.add('!');
23
+ testAtom.do.add('@');
24
+ testAtom.do.add('#');
25
+ testAtom.do.add('');
26
+ await wait();
27
+
28
+ expect(localStorage[makeFullKey('set:test')]).toEqual('[["!","@","#",""]]');
29
+
30
+ testAtom.do.filterValues();
31
+ await wait();
32
+
33
+ expect(localStorage[makeFullKey('set:test')]).toEqual('[["!","@","#"]]');
34
+
35
+ testAtom.do.toggle('@');
36
+ await wait();
37
+
38
+ expect(localStorage[makeFullKey('set:test')]).toEqual('[["!","#"]]');
39
+
40
+ testAtom.do.toggle('@');
41
+ testAtom.do.delete('!');
42
+ await wait();
43
+
44
+ expect(localStorage[makeFullKey('set:test')]).toEqual('[["#","@"]]');
45
+ });
46
+ });
@@ -0,0 +1,36 @@
1
+ import { Atom } from '../../types';
2
+ import { AtomDoActionsBasic } from './_Basic';
3
+
4
+ export class AtomSetDoActions<Value> extends AtomDoActionsBasic {
5
+ constructor(private atom: Atom<Set<Value>>, actions: Record<string, Function> | nil) {
6
+ super(actions);
7
+ this.atom = atom;
8
+ }
9
+
10
+ /** like the Set.prototype.add() method */
11
+ add = (value: Value) => {
12
+ this.atom.set(new Set(this.atom.get()).add(value));
13
+ };
14
+
15
+ /** like the Set.prototype.delete() method */
16
+ delete = (value: Value) => {
17
+ const newSet = new Set(this.atom.get());
18
+ newSet.delete(value);
19
+ this.atom.set(newSet);
20
+ };
21
+
22
+ /** will add value if it doesn't exist, otherwise delete */
23
+ toggle = (value: Value) => {
24
+ const newSet = new Set(this.atom.get());
25
+
26
+ if (newSet.has(value)) newSet.delete(value);
27
+ else newSet.add(value);
28
+
29
+ this.atom.set(newSet);
30
+ };
31
+
32
+ /** like the Set.prototype.clear() method */
33
+ clear = () => {
34
+ this.atom.set(new Set());
35
+ };
36
+ }
@@ -0,0 +1,11 @@
1
+ export class AtomDoActionsBasic {
2
+ constructor(actions: Record<string, Function> | nil) {
3
+ if (actions)
4
+ return new Proxy(this, {
5
+ get: (self, p) => {
6
+ if (p in this) return self[p as never];
7
+ return actions[p as never];
8
+ },
9
+ });
10
+ }
11
+ }
@@ -0,0 +1,37 @@
1
+ import { makeDeepProxyObject } from '../makeDeepProxyObject';
2
+ import { AtomDoActionsBasic } from './_Basic';
3
+
4
+ export class AtomUpdateDoAction extends AtomDoActionsBasic {
5
+ protected updateValue = <Object extends object | unknown[]>(
6
+ object: Object,
7
+ updater: (object: Object) => void,
8
+ ): Object => {
9
+ const newObject = Array.isArray(object) ? object.slice(0) : { ...object };
10
+ let isSomeSetted = false;
11
+
12
+ const pro = makeDeepProxyObject(object, {
13
+ onSet: (_, keys, setKey, value, prevValue) => {
14
+ if (value === prevValue) return true;
15
+ let currentObject = newObject as Record<string, unknown> | unknown[];
16
+
17
+ isSomeSetted = true;
18
+
19
+ for (const key of keys) {
20
+ const nextObject = currentObject[key as never] as object;
21
+
22
+ currentObject = currentObject[key as never] = (
23
+ Array.isArray(nextObject) ? nextObject.slice(0) : { ...nextObject }
24
+ ) as never;
25
+ }
26
+
27
+ currentObject[setKey as never] = value as never;
28
+
29
+ return true;
30
+ },
31
+ });
32
+
33
+ updater(pro);
34
+
35
+ return isSomeSetted ? (newObject as never) : object;
36
+ };
37
+ }