@oscarpalmer/mora 0.30.0 → 0.31.0
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/dist/constants.d.mts +2 -1
- package/dist/constants.mjs +3 -1
- package/dist/helpers/is.d.mts +11 -10
- package/dist/helpers/is.mjs +15 -12
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +2 -2
- package/dist/models.d.mts +84 -18
- package/dist/mora.full.mjs +121 -40
- package/dist/value/array.mjs +33 -17
- package/dist/value/readonly.d.mts +7 -0
- package/dist/value/readonly.mjs +37 -0
- package/dist/value/signal.mjs +11 -5
- package/dist/value/store.mjs +24 -8
- package/package.json +1 -1
- package/src/constants.ts +9 -1
- package/src/helpers/is.ts +28 -13
- package/src/index.ts +18 -2
- package/src/models.ts +110 -22
- package/src/value/array.ts +76 -40
- package/src/value/readonly.ts +71 -0
- package/src/value/signal.ts +15 -6
- package/src/value/store.ts +63 -17
package/src/value/array.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {select} from '@oscarpalmer/atoms/array';
|
|
2
2
|
import {filter} from '@oscarpalmer/atoms/array/filter';
|
|
3
3
|
import {noop} from '@oscarpalmer/atoms/function';
|
|
4
|
+
import {isPlainObject} from '@oscarpalmer/atoms/is';
|
|
4
5
|
import type {GenericCallback} from '@oscarpalmer/atoms/models';
|
|
5
6
|
import {
|
|
6
7
|
METHODS_AFFECTING_LENGTH,
|
|
@@ -22,11 +23,13 @@ import type {
|
|
|
22
23
|
ReactiveArray,
|
|
23
24
|
ReactiveOptions,
|
|
24
25
|
ReactiveState,
|
|
26
|
+
ReadonlyInstances,
|
|
25
27
|
Signal,
|
|
26
28
|
} from '../models';
|
|
27
29
|
import {subscribe, unsubscribe} from '../subscription';
|
|
28
30
|
import {computed} from './computed';
|
|
29
31
|
import {reactive} from './reactive';
|
|
32
|
+
import {getReadonlyInstance} from './readonly';
|
|
30
33
|
import {signal} from './signal';
|
|
31
34
|
|
|
32
35
|
/**
|
|
@@ -70,6 +73,9 @@ export function array<Item>(
|
|
|
70
73
|
|
|
71
74
|
const indiced = new Map<number, [Computed<unknown>, ComputedEffect]>();
|
|
72
75
|
const length = signal(0);
|
|
76
|
+
const readonlies: ReadonlyInstances<Item[]> = {};
|
|
77
|
+
|
|
78
|
+
let instance: Record<string, unknown> = {};
|
|
73
79
|
|
|
74
80
|
state.value = new Proxy([], {
|
|
75
81
|
get: (target: Item[], property: PropertyKey) =>
|
|
@@ -96,43 +102,23 @@ export function array<Item>(
|
|
|
96
102
|
return getArrayValue(instance as ReactiveArray<Item>, indiced, state, length, value);
|
|
97
103
|
}
|
|
98
104
|
|
|
99
|
-
|
|
105
|
+
function set(first?: unknown, second?: unknown): void {
|
|
106
|
+
setProxyValue<Item[], Item>(
|
|
107
|
+
true,
|
|
108
|
+
state,
|
|
109
|
+
isArrayValue,
|
|
110
|
+
isArrayIndex,
|
|
111
|
+
setArray,
|
|
112
|
+
setAtIndex,
|
|
113
|
+
first,
|
|
114
|
+
second,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const handlers = {
|
|
100
119
|
...rx,
|
|
101
|
-
length: state.value.length,
|
|
102
|
-
at: (index: number): Item | undefined => get(index),
|
|
103
|
-
clear: () => {
|
|
104
|
-
state.value.length = 0;
|
|
105
|
-
},
|
|
106
|
-
filter: (callback: (item: Item, index: number, array: Item[]) => boolean) =>
|
|
107
|
-
computed(() => filter(get(), callback)),
|
|
108
120
|
get: (value?: number | typeof PROPERTY_LENGTH) => get(value),
|
|
109
|
-
|
|
110
|
-
computed(() => (get() as Item[]).map(callback)),
|
|
111
|
-
notify: () => {
|
|
112
|
-
emityProxyValues(state, indiced);
|
|
113
|
-
},
|
|
114
|
-
peek: (value?: unknown) => peekArrayValue(state, length, value),
|
|
115
|
-
pop: () => state.value.pop(),
|
|
116
|
-
push: (...items: Item[]) => state.value.push(...items),
|
|
117
|
-
select: <Mapped>(
|
|
118
|
-
filter: (item: Item, index: number, array: Item[]) => boolean,
|
|
119
|
-
map: (item: Item, index: number, array: Item[]) => Mapped,
|
|
120
|
-
) => computed(() => select(get() as Item[], filter, map)),
|
|
121
|
-
set: (first?: unknown, second?: unknown) => {
|
|
122
|
-
setProxyValue<Item[], Item>(
|
|
123
|
-
true,
|
|
124
|
-
state,
|
|
125
|
-
isArrayValue,
|
|
126
|
-
isArrayIndex,
|
|
127
|
-
setArray,
|
|
128
|
-
setAtIndex,
|
|
129
|
-
first,
|
|
130
|
-
second,
|
|
131
|
-
);
|
|
132
|
-
},
|
|
133
|
-
shift: () => state.value.shift(),
|
|
134
|
-
splice: (from: number, to?: number, ...items: Item[]) =>
|
|
135
|
-
state.value.splice(from, to ?? state.value.length, ...items),
|
|
121
|
+
peek: (first?: unknown, second?: boolean) => peekArrayValue(state, length, first, second),
|
|
136
122
|
subscribe: (first: number | GenericCallback, second?: GenericCallback) => {
|
|
137
123
|
if (typeof first === 'number' && typeof second === 'function') {
|
|
138
124
|
return getReactiveValueInProxy(
|
|
@@ -145,7 +131,6 @@ export function array<Item>(
|
|
|
145
131
|
|
|
146
132
|
return typeof first === 'function' ? subscribe(state, first) : noop;
|
|
147
133
|
},
|
|
148
|
-
unshift: (...items: Item[]) => state.value.unshift(...items),
|
|
149
134
|
unsubscribe: (first: number | GenericCallback, second?: GenericCallback) => {
|
|
150
135
|
if (typeof first === 'number' && typeof second === 'function') {
|
|
151
136
|
getReactiveValueInProxy(instance as ReactiveArray<Item>, indiced, first, true)?.unsubscribe(
|
|
@@ -155,6 +140,36 @@ export function array<Item>(
|
|
|
155
140
|
unsubscribe(state, first);
|
|
156
141
|
}
|
|
157
142
|
},
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
instance = {
|
|
146
|
+
...handlers,
|
|
147
|
+
asReadonly: (frozen?: unknown) =>
|
|
148
|
+
getReadonlyInstance(state, readonlies, handlers, frozen === true),
|
|
149
|
+
at: (index: number): Item | undefined => get(index),
|
|
150
|
+
clear: () => {
|
|
151
|
+
state.value.length = 0;
|
|
152
|
+
},
|
|
153
|
+
filter: (callback: (item: Item, index: number, array: Item[]) => boolean) =>
|
|
154
|
+
computed(() => filter(get(), callback)),
|
|
155
|
+
map: <Mapped>(callback: (item: Item, index: number, array: Item[]) => Mapped) =>
|
|
156
|
+
computed(() => get().map(callback)),
|
|
157
|
+
notify: () => {
|
|
158
|
+
emityProxyValues(state, indiced);
|
|
159
|
+
},
|
|
160
|
+
pop: () => state.value.pop(),
|
|
161
|
+
push: (...items: Item[]) => state.value.push(...items),
|
|
162
|
+
select: <Mapped>(
|
|
163
|
+
filter: (item: Item, index: number, array: Item[]) => boolean,
|
|
164
|
+
map: (item: Item, index: number, array: Item[]) => Mapped,
|
|
165
|
+
) => computed(() => select(get() as Item[], filter, map)),
|
|
166
|
+
set: (first?: unknown, second?: unknown) => {
|
|
167
|
+
set(first, second);
|
|
168
|
+
},
|
|
169
|
+
shift: () => state.value.shift(),
|
|
170
|
+
splice: (from: number, to?: number, ...items: Item[]) =>
|
|
171
|
+
state.value.splice(from, to ?? state.value.length, ...items),
|
|
172
|
+
unshift: (...items: Item[]) => state.value.unshift(...items),
|
|
158
173
|
update: (callback: (value: Item[]) => Item[]) =>
|
|
159
174
|
updateArrayValue(instance as ReactiveArray<Item>, state, callback),
|
|
160
175
|
};
|
|
@@ -171,7 +186,7 @@ export function array<Item>(
|
|
|
171
186
|
},
|
|
172
187
|
});
|
|
173
188
|
|
|
174
|
-
|
|
189
|
+
set(value);
|
|
175
190
|
|
|
176
191
|
return Object.freeze(instance) as ReactiveArray<Item>;
|
|
177
192
|
}
|
|
@@ -201,13 +216,34 @@ function isArrayValue<Item>(value: unknown): value is Item[] | undefined {
|
|
|
201
216
|
function peekArrayValue<Item>(
|
|
202
217
|
state: ReactiveState<Item[], Item>,
|
|
203
218
|
length: Signal<number>,
|
|
204
|
-
|
|
219
|
+
first?: unknown,
|
|
220
|
+
second?: boolean,
|
|
205
221
|
): number | Item | Item[] | undefined {
|
|
206
|
-
if (
|
|
222
|
+
if (first === PROPERTY_LENGTH) {
|
|
207
223
|
return length.peek();
|
|
208
224
|
}
|
|
209
225
|
|
|
210
|
-
|
|
226
|
+
let value: Item | Item[] | undefined;
|
|
227
|
+
|
|
228
|
+
if (typeof first === 'number') {
|
|
229
|
+
value = state.value.at(first);
|
|
230
|
+
} else {
|
|
231
|
+
value = state.value;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (!(first === true || second === true)) {
|
|
235
|
+
return value;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (Array.isArray(value)) {
|
|
239
|
+
return value.slice();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (isPlainObject(value)) {
|
|
243
|
+
return {...value};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return value;
|
|
211
247
|
}
|
|
212
248
|
|
|
213
249
|
function setArray<Item>(state: ReactiveState<Item[], Item>, value: Item[] | undefined): void {
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {isPlainObject} from '@oscarpalmer/atoms/is';
|
|
2
|
+
import type {GenericCallback} from '@oscarpalmer/atoms/models';
|
|
3
|
+
import {NAME_MORA, NAME_READONLY} from '../constants';
|
|
4
|
+
import {getSimpleValue} from '../helpers/value';
|
|
5
|
+
import type {
|
|
6
|
+
ReactiveState,
|
|
7
|
+
ReadonlyFrozenSignal,
|
|
8
|
+
ReadonlyInstances,
|
|
9
|
+
ReadonlySignal,
|
|
10
|
+
ReadonlySignalValue,
|
|
11
|
+
} from '../models';
|
|
12
|
+
|
|
13
|
+
export function getReadonlyInstance<Value>(
|
|
14
|
+
state: ReactiveState<Value, never>,
|
|
15
|
+
instances: ReadonlyInstances<Value>,
|
|
16
|
+
handlers: Record<string, GenericCallback>,
|
|
17
|
+
frozen: boolean,
|
|
18
|
+
): ReadonlySignal<Value> {
|
|
19
|
+
const key = frozen ? 'frozen' : 'original';
|
|
20
|
+
|
|
21
|
+
instances[key] ??= getReadonlySignal(state, handlers, frozen) as never;
|
|
22
|
+
|
|
23
|
+
return instances[key] as ReadonlySignal<Value>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getReadonlySignal<Value>(
|
|
27
|
+
state: ReactiveState<Value, never>,
|
|
28
|
+
handlers: Record<string, GenericCallback>,
|
|
29
|
+
frozen: boolean,
|
|
30
|
+
): ReadonlySignal<Value> | ReadonlyFrozenSignal<Value> {
|
|
31
|
+
const instance = {
|
|
32
|
+
...handlers,
|
|
33
|
+
get: () => getReadonlyValue(state, false, frozen),
|
|
34
|
+
peek: () => getReadonlyValue(state, true, frozen),
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
Object.defineProperties(instance, {
|
|
38
|
+
[NAME_MORA]: {
|
|
39
|
+
enumerable: false,
|
|
40
|
+
value: NAME_READONLY,
|
|
41
|
+
},
|
|
42
|
+
frozen: {
|
|
43
|
+
enumerable: true,
|
|
44
|
+
value: frozen,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return Object.freeze(instance) as ReadonlySignal<Value>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getReadonlyValue<Value>(
|
|
52
|
+
state: ReactiveState<Value, never>,
|
|
53
|
+
peek: boolean,
|
|
54
|
+
frozen: boolean,
|
|
55
|
+
): Value | ReadonlySignalValue<Value> {
|
|
56
|
+
let value = peek ? state.value : getSimpleValue(state);
|
|
57
|
+
|
|
58
|
+
if (!frozen) {
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (Array.isArray(state.value)) {
|
|
63
|
+
value = [...state.value] as Value;
|
|
64
|
+
} else if (isPlainObject(state.value)) {
|
|
65
|
+
value = {...state.value} as Value;
|
|
66
|
+
} else {
|
|
67
|
+
value = state.value;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return Object.freeze(value);
|
|
71
|
+
}
|
package/src/value/signal.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import {NAME_MORA, NAME_SIGNAL} from '../constants';
|
|
2
2
|
import {emitValue, getSimpleValue, handleSimpleValue} from '../helpers/value';
|
|
3
|
-
import type {ReactiveOptions, ReactiveState, Signal} from '../models';
|
|
3
|
+
import type {ReactiveOptions, ReactiveState, ReadonlyInstances, Signal} from '../models';
|
|
4
4
|
import {subscribe} from '../subscription';
|
|
5
5
|
import {reactive} from './reactive';
|
|
6
|
+
import {getReadonlyInstance} from './readonly';
|
|
6
7
|
|
|
7
8
|
function setAndEmit<Value>(state: ReactiveState<Value, Value>, value: Value): void {
|
|
8
9
|
if (!state.equal(state.value, value)) {
|
|
@@ -51,8 +52,20 @@ export function signal<Value>(
|
|
|
51
52
|
): Signal<Value> {
|
|
52
53
|
const [rx, state] = reactive<Value>(undefined as unknown as Value, options);
|
|
53
54
|
|
|
54
|
-
const
|
|
55
|
+
const readonlies: ReadonlyInstances<Value> = {};
|
|
56
|
+
|
|
57
|
+
const handlers = {
|
|
55
58
|
...rx,
|
|
59
|
+
subscribe: (callback: (value: Value) => void) => subscribe(state, callback),
|
|
60
|
+
unsubscribe: (callback: (value: Value) => void) => {
|
|
61
|
+
state.subscriptions.delete(callback);
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const instance = {
|
|
66
|
+
...handlers,
|
|
67
|
+
asReadonly: (frozen?: unknown) =>
|
|
68
|
+
getReadonlyInstance(state, readonlies, handlers, frozen === true),
|
|
56
69
|
get: () => getSimpleValue(state),
|
|
57
70
|
peek: () => state.value,
|
|
58
71
|
set: (value: Value | (() => Value | Promise<Value>) | Promise<Value>) => {
|
|
@@ -61,10 +74,6 @@ export function signal<Value>(
|
|
|
61
74
|
update: (callback: (value: Value) => Value) => {
|
|
62
75
|
handleSimpleValue(state, callback(state.value), setAndEmit);
|
|
63
76
|
},
|
|
64
|
-
subscribe: (callback: (value: Value) => void) => subscribe(state, callback),
|
|
65
|
-
unsubscribe: (callback: (value: Value) => void) => {
|
|
66
|
-
state.subscriptions.delete(callback);
|
|
67
|
-
},
|
|
68
77
|
};
|
|
69
78
|
|
|
70
79
|
Object.defineProperty(instance, NAME_MORA, {
|
package/src/value/store.ts
CHANGED
|
@@ -15,10 +15,12 @@ import type {
|
|
|
15
15
|
ReactiveOptions,
|
|
16
16
|
ReactiveState,
|
|
17
17
|
ReactiveStore,
|
|
18
|
+
ReadonlyInstances,
|
|
18
19
|
Unsubscribe,
|
|
19
20
|
} from '../models';
|
|
20
21
|
import {noop, subscribe, unsubscribe} from '../subscription';
|
|
21
22
|
import {reactive} from './reactive';
|
|
23
|
+
import {getReadonlyInstance} from './readonly';
|
|
22
24
|
|
|
23
25
|
function isStoreObject<Value extends PlainObject>(value: unknown): value is Value | undefined {
|
|
24
26
|
return value == null || isPlainObject(value);
|
|
@@ -56,6 +58,34 @@ function setObject(state: ReactiveState<PlainObject, never>, value: PlainObject
|
|
|
56
58
|
stopBatch();
|
|
57
59
|
}
|
|
58
60
|
|
|
61
|
+
function peekStoreValue<Value extends PlainObject>(
|
|
62
|
+
state: ReactiveState<Value, Value>,
|
|
63
|
+
first?: unknown,
|
|
64
|
+
second?: boolean,
|
|
65
|
+
): unknown {
|
|
66
|
+
let value: unknown;
|
|
67
|
+
|
|
68
|
+
if (isKey(first)) {
|
|
69
|
+
value = state.value[first];
|
|
70
|
+
} else {
|
|
71
|
+
value = state.value;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!(first === true || second === true)) {
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (Array.isArray(value)) {
|
|
79
|
+
return value.slice();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (isPlainObject(value)) {
|
|
83
|
+
return {...value};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
88
|
+
|
|
59
89
|
function setProperty<Value extends PlainObject>(
|
|
60
90
|
state: ReactiveState<Value, Value>,
|
|
61
91
|
key: unknown,
|
|
@@ -107,6 +137,9 @@ export function store<Value extends PlainObject>(
|
|
|
107
137
|
const [rx, state] = reactive<Value>(undefined as never, options);
|
|
108
138
|
|
|
109
139
|
const keyed = new Map<Key, [Computed<unknown>, ComputedEffect]>();
|
|
140
|
+
const readonlies: ReadonlyInstances<Value> = {};
|
|
141
|
+
|
|
142
|
+
let instance: Record<string, GenericCallback> = {};
|
|
110
143
|
|
|
111
144
|
state.value = new Proxy({} as Value, {
|
|
112
145
|
set: (target: Value, property: PropertyKey, value: unknown) =>
|
|
@@ -119,16 +152,43 @@ export function store<Value extends PlainObject>(
|
|
|
119
152
|
}),
|
|
120
153
|
});
|
|
121
154
|
|
|
122
|
-
const
|
|
155
|
+
const handlers = {
|
|
123
156
|
...rx,
|
|
124
157
|
get: (value?: unknown): unknown =>
|
|
125
158
|
isKey(value)
|
|
126
|
-
? getReactiveValueInProxy(instance
|
|
159
|
+
? getReactiveValueInProxy(instance as ReactiveStore<Value>, keyed, value, false).get()
|
|
127
160
|
: getSimpleValue(state),
|
|
161
|
+
peek: (first?: unknown, second?: boolean): unknown => peekStoreValue(state, first, second),
|
|
162
|
+
subscribe: (first: Key | GenericCallback, second?: GenericCallback): Unsubscribe => {
|
|
163
|
+
if (isKey(first) && typeof second === 'function') {
|
|
164
|
+
return getReactiveValueInProxy(
|
|
165
|
+
instance as ReactiveStore<Value>,
|
|
166
|
+
keyed,
|
|
167
|
+
first,
|
|
168
|
+
false,
|
|
169
|
+
).subscribe(second);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return typeof first === 'function' ? subscribe(state, first) : noop;
|
|
173
|
+
},
|
|
174
|
+
unsubscribe: (first: Key | GenericCallback, second?: GenericCallback) => {
|
|
175
|
+
if (isKey(first) && typeof second === 'function') {
|
|
176
|
+
getReactiveValueInProxy(instance as ReactiveStore<Value>, keyed, first, false)?.unsubscribe(
|
|
177
|
+
second,
|
|
178
|
+
);
|
|
179
|
+
} else if (typeof first === 'function') {
|
|
180
|
+
unsubscribe(state, first);
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
instance = {
|
|
186
|
+
...handlers,
|
|
187
|
+
asReadonly: (frozen?: unknown) =>
|
|
188
|
+
getReadonlyInstance(state, readonlies, handlers, frozen === true),
|
|
128
189
|
notify(): void {
|
|
129
190
|
emityProxyValues(state, keyed);
|
|
130
191
|
},
|
|
131
|
-
peek: (value?: unknown): unknown => (isKey(value) ? state.value[value] : {...state.value}),
|
|
132
192
|
set: (first?: unknown, second?: unknown) => {
|
|
133
193
|
setProxyValue<Value>(
|
|
134
194
|
false,
|
|
@@ -141,20 +201,6 @@ export function store<Value extends PlainObject>(
|
|
|
141
201
|
second,
|
|
142
202
|
);
|
|
143
203
|
},
|
|
144
|
-
subscribe: (first: Key | GenericCallback, second?: GenericCallback): Unsubscribe => {
|
|
145
|
-
if (isKey(first) && typeof second === 'function') {
|
|
146
|
-
return getReactiveValueInProxy(instance, keyed, first, false).subscribe(second);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return typeof first === 'function' ? subscribe(state, first) : noop;
|
|
150
|
-
},
|
|
151
|
-
unsubscribe: (first: Key | GenericCallback, second?: GenericCallback) => {
|
|
152
|
-
if (isKey(first) && typeof second === 'function') {
|
|
153
|
-
getReactiveValueInProxy(instance, keyed, first, false)?.unsubscribe(second);
|
|
154
|
-
} else if (typeof first === 'function') {
|
|
155
|
-
unsubscribe(state, first);
|
|
156
|
-
}
|
|
157
|
-
},
|
|
158
204
|
update: (callback: (value: Value) => Value) => {
|
|
159
205
|
const updated = callback(state.value);
|
|
160
206
|
|