@oscarpalmer/mora 0.26.0 → 0.28.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.
Files changed (46) hide show
  1. package/dist/batch.d.mts +14 -0
  2. package/dist/{batch.js → batch.mjs} +13 -3
  3. package/dist/constants.d.mts +20 -0
  4. package/dist/{constants.js → constants.mjs} +2 -0
  5. package/dist/effect.d.mts +17 -0
  6. package/dist/{effect.js → effect.mjs} +11 -4
  7. package/dist/helpers/is.d.mts +47 -0
  8. package/dist/helpers/is.mjs +55 -0
  9. package/dist/helpers/proxy.d.mts +15 -0
  10. package/dist/helpers/proxy.mjs +72 -0
  11. package/dist/helpers/value.d.mts +8 -0
  12. package/dist/helpers/{value.js → value.mjs} +4 -2
  13. package/dist/index.d.mts +10 -0
  14. package/dist/index.mjs +8 -0
  15. package/dist/models.d.mts +62 -0
  16. package/dist/models.mjs +1 -0
  17. package/dist/{mora.full.js → mora.full.mjs} +206 -106
  18. package/dist/subscription.d.mts +15 -0
  19. package/dist/{subscription.js → subscription.mjs} +2 -0
  20. package/dist/value/array.d.mts +156 -0
  21. package/dist/value/array.mjs +165 -0
  22. package/dist/value/computed.d.mts +21 -0
  23. package/dist/value/computed.mjs +68 -0
  24. package/dist/value/reactive.d.mts +40 -0
  25. package/dist/value/{reactive.js → reactive.mjs} +25 -2
  26. package/dist/value/signal.d.mts +44 -0
  27. package/dist/value/signal.mjs +59 -0
  28. package/dist/value/store.d.mts +112 -0
  29. package/dist/value/store.mjs +81 -0
  30. package/package.json +41 -35
  31. package/src/constants.ts +1 -6
  32. package/src/helpers/is.ts +1 -1
  33. package/src/helpers/proxy.ts +64 -17
  34. package/src/models.ts +3 -1
  35. package/src/value/array.ts +79 -23
  36. package/src/value/computed.ts +65 -30
  37. package/src/value/signal.ts +77 -9
  38. package/src/value/store.ts +101 -12
  39. package/dist/helpers/is.js +0 -23
  40. package/dist/helpers/proxy.js +0 -48
  41. package/dist/index.js +0 -8
  42. package/dist/models.js +0 -0
  43. package/dist/value/array.js +0 -100
  44. package/dist/value/computed.js +0 -36
  45. package/dist/value/signal.js +0 -24
  46. package/dist/value/store.js +0 -43
@@ -0,0 +1,112 @@
1
+ import { Reactive } from "./reactive.mjs";
2
+ import { ReactiveOptions, Unsubscribe } from "../models.mjs";
3
+ import { Key, PlainObject } from "@oscarpalmer/atoms/models";
4
+
5
+ //#region src/value/store.d.ts
6
+ declare class Store<Value extends PlainObject> extends Reactive<Value, Value> {
7
+ #private;
8
+ constructor(value: Value, options?: ReactiveOptions<Value>);
9
+ /**
10
+ * @inheritdoc
11
+ */
12
+ get(): Value;
13
+ /**
14
+ * Get a value by key
15
+ * @param key Key of the value to get
16
+ * @returns Value for the specified key, or `undefined` if it doesn't exist
17
+ */
18
+ get<Key extends keyof Value>(key: Key): Value[Key];
19
+ /**
20
+ * Get a value by key
21
+ * @param key Key of the value to get
22
+ * @returns Value for the specified key, or `undefined` if it doesn't exist
23
+ */
24
+ get(key: Key): unknown;
25
+ /**
26
+ * Notify dependents of changes
27
+ *
28
+ * _This bypasses equality checks and will immediately notify dependents.
29
+ * Use this only if you're modifying nested data that would be ignored by equality checks._
30
+ */
31
+ notify(): void;
32
+ /**
33
+ * Get the value _(without reactivity)_
34
+ * @returns The current value
35
+ */
36
+ peek(): Value;
37
+ /**
38
+ * Get a value by key _(without reactivity)_
39
+ * @param key Key of the value to get
40
+ * @returns Value for the specified key, or `undefined` if it doesn't exist
41
+ */
42
+ peek<Key extends keyof Value>(key: Key): Value[Key];
43
+ /**
44
+ * Get a value by key _(without reactivity)_
45
+ * @param key Key of the value to get
46
+ * @returns Value for the specified key, or `undefined` if it doesn't exist
47
+ */
48
+ peek(key: Key): unknown;
49
+ /**
50
+ * Set the value
51
+ * @param value New value _(defaults to an empty object)_
52
+ */
53
+ set(value?: Value | (() => null | undefined | Value | Promise<null | undefined | Value>) | Promise<null | undefined | Value>): void;
54
+ /**
55
+ * Set a value by key
56
+ * @param key Key of the value to set
57
+ * @param value New value
58
+ */
59
+ set<Key extends keyof Value>(key: Key, value: Value[Key] | (() => Value[Key] | Promise<Value[Key]>) | Promise<Value[Key]>): void;
60
+ /**
61
+ * Set a value by key
62
+ * @param key Key of the value to set
63
+ * @param value New value
64
+ */
65
+ set(key: Key, value: unknown): void;
66
+ /**
67
+ * @inheritdoc
68
+ */
69
+ subscribe(callback: (value: Value) => void): Unsubscribe;
70
+ /**
71
+ * Subscribe to changes for a specific key
72
+ * @param key Key of the value to subscribe to
73
+ * @param callback Callback for changes
74
+ * @returns Unsubscribe callback
75
+ */
76
+ subscribe<Key extends keyof Value>(key: Key, callback: (value: Value[Key] | undefined) => void): Unsubscribe;
77
+ /**
78
+ * Subscribe to changes for a specific key
79
+ * @param key Key of the value to subscribe to
80
+ * @param callback Callback for changes
81
+ * @returns Unsubscribe callback
82
+ */
83
+ subscribe(key: Key, callback: (value: unknown) => void): Unsubscribe;
84
+ /**
85
+ * Update the value _(based on the current value)_
86
+ * @param callback Callback to update the value
87
+ */
88
+ update(callback: (value: Value) => Value): void;
89
+ }
90
+ /**
91
+ * Create a reactive store from a function result
92
+ * @param value Initial object value
93
+ * @param options Reactivity options
94
+ * @returns Reactive store
95
+ */
96
+ declare function store<Value extends PlainObject>(value: () => Value | Promise<Value>, options?: ReactiveOptions<Value>): Store<Value>;
97
+ /**
98
+ * Create a reactive store from a promise
99
+ * @param value Initial object value
100
+ * @param options Reactivity options
101
+ * @returns Reactive store
102
+ */
103
+ declare function store<Value extends PlainObject>(value: Promise<Value>, options?: ReactiveOptions<Value>): Store<Value>;
104
+ /**
105
+ * Create a reactive store
106
+ * @param value Initial object value
107
+ * @param options Reactivity options
108
+ * @returns Reactive store
109
+ */
110
+ declare function store<Value extends PlainObject>(value: Value, options?: ReactiveOptions<Value>): Store<Value>;
111
+ //#endregion
112
+ export { Store, store };
@@ -0,0 +1,81 @@
1
+ import { NAME_STORE } from "../constants.mjs";
2
+ import { startBatch, stopBatch } from "../batch.mjs";
3
+ import { noop, subscribe } from "../subscription.mjs";
4
+ import { Reactive } from "./reactive.mjs";
5
+ import { getValue } from "../helpers/value.mjs";
6
+ import { emityProxyValues, getReactiveValueInProxy, setProxyValue, setValueInProxy } from "../helpers/proxy.mjs";
7
+ import { isKey, isPlainObject } from "@oscarpalmer/atoms/is";
8
+ //#region src/value/store.ts
9
+ var Store = class extends Reactive {
10
+ #keyed = /* @__PURE__ */ new Map();
11
+ constructor(value, options) {
12
+ super(NAME_STORE, new Proxy(value, { set: (target, property, value) => setValueInProxy({
13
+ target,
14
+ property,
15
+ value,
16
+ isArray: false,
17
+ state: this.state
18
+ }) }), options);
19
+ }
20
+ get(key) {
21
+ return isKey(key) ? getReactiveValueInProxy(this, this.#keyed, key, false).get() : getValue(this.state);
22
+ }
23
+ /**
24
+ * Notify dependents of changes
25
+ *
26
+ * _This bypasses equality checks and will immediately notify dependents.
27
+ * Use this only if you're modifying nested data that would be ignored by equality checks._
28
+ */
29
+ notify() {
30
+ emityProxyValues(this.state, this.#keyed);
31
+ }
32
+ peek(key) {
33
+ return isKey(key) ? this.state.value[key] : { ...this.state.value };
34
+ }
35
+ set(first, second) {
36
+ setProxyValue(false, this.state, isStoreObject, isKey, setObject, setProperty, first, second);
37
+ }
38
+ subscribe(first, second) {
39
+ if (isKey(first) && typeof second === "function") return getReactiveValueInProxy(this, this.#keyed, first, false).subscribe(second);
40
+ return typeof first === "function" ? subscribe(this.state, first) : noop;
41
+ }
42
+ /**
43
+ * Update the value _(based on the current value)_
44
+ * @param callback Callback to update the value
45
+ */
46
+ update(callback) {
47
+ const updated = callback(this.state.value);
48
+ if (updated == null || isPlainObject(updated)) setObject(this.state, updated);
49
+ }
50
+ };
51
+ function isStoreObject(value) {
52
+ return value == null || isPlainObject(value);
53
+ }
54
+ function setObject(state, value) {
55
+ startBatch();
56
+ const actual = value ?? {};
57
+ const proxy = state.value;
58
+ const proxyKeys = Object.keys(proxy);
59
+ const actualKeys = Object.keys(actual);
60
+ let { length } = proxyKeys;
61
+ for (let index = 0; index < length; index += 1) {
62
+ const key = proxyKeys[index];
63
+ proxy[key] = actualKeys.includes(key) ? actual[key] : void 0;
64
+ }
65
+ length = actualKeys.length;
66
+ for (let index = 0; index < length; index += 1) {
67
+ const key = actualKeys[index];
68
+ if (!proxyKeys.includes(key)) proxy[key] = actual[key];
69
+ }
70
+ stopBatch();
71
+ }
72
+ function setProperty(state, key, value) {
73
+ state.value[key] = value;
74
+ }
75
+ function store(value, options) {
76
+ const instance = new Store({}, options);
77
+ instance.set(value);
78
+ return instance;
79
+ }
80
+ //#endregion
81
+ export { Store, store };
package/package.json CHANGED
@@ -1,49 +1,55 @@
1
1
  {
2
+ "name": "@oscarpalmer/mora",
3
+ "version": "0.28.0",
4
+ "description": "Signals and stuff…",
5
+ "keywords": [
6
+ "reactive",
7
+ "signal",
8
+ "signals"
9
+ ],
10
+ "license": "MIT",
2
11
  "author": {
3
12
  "name": "Oscar Palmér",
4
13
  "url": "https://oscarpalmer.se"
5
14
  },
6
- "dependencies": {
7
- "@oscarpalmer/atoms": "^0.124"
8
- },
9
- "description": "Signals and stuff…",
10
- "devDependencies": {
11
- "@types/node": "^25",
12
- "@vitest/coverage-istanbul": "^4",
13
- "jsdom": "^27.3",
14
- "oxfmt": "^0.21",
15
- "oxlint": "^1.36",
16
- "rolldown": "1.0.0-beta.58",
17
- "tslib": "^2.8",
18
- "typescript": "^5.9",
19
- "vite": "8.0.0-beta.5",
20
- "vitest": "^4"
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/oscarpalmer/mora.git"
21
18
  },
19
+ "files": [
20
+ "dist",
21
+ "src",
22
+ "types"
23
+ ],
24
+ "type": "module",
25
+ "module": "./dist/index.mjs",
26
+ "types": "./dist/index.d.mts",
22
27
  "exports": {
23
28
  "./package.json": "./package.json",
24
29
  ".": {
25
- "types": "./types/index.d.ts",
26
- "default": "./dist/index.js"
30
+ "types": "./dist/index.d.mts",
31
+ "default": "./dist/index.mjs"
27
32
  }
28
33
  },
29
- "files": ["dist", "src", "types"],
30
- "keywords": ["signal", "signals", "reactive"],
31
- "license": "MIT",
32
- "module": "./dist/index.js",
33
- "name": "@oscarpalmer/mora",
34
- "repository": {
35
- "type": "git",
36
- "url": "git+https://github.com/oscarpalmer/mora.git"
37
- },
38
34
  "scripts": {
39
- "build": "npm run clean && npx vite build && npm run rolldown:build && npx tsc",
40
- "clean": "rm -rf ./dist && rm -rf ./types && rm -f ./tsconfig.tsbuildinfo",
41
- "rolldown:build": "npx rolldown -c",
42
- "rolldown:watch": "npx rolldown -c --watch",
43
- "test": "npx vitest --coverage",
44
- "watch": "npx vite build --watch"
35
+ "build": "vpx vp run tsdown:build && vpx vp pack",
36
+ "tsdown:build": "vpx tsdown -c ./tsdown.config.ts",
37
+ "tsdown:watch": "vpx tsdown -c ./tsdown.config.ts --watch",
38
+ "test": "vpx vp test run --coverage",
39
+ "test:leak": "vpx vp test run --detect-async-leaks --coverage"
45
40
  },
46
- "type": "module",
47
- "types": "./types/index.d.ts",
48
- "version": "0.26.0"
41
+ "dependencies": {
42
+ "@oscarpalmer/atoms": "^0.172"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "^25.5",
46
+ "@vitest/coverage-istanbul": "^4.1",
47
+ "jsdom": "^29",
48
+ "tsdown": "^0.21",
49
+ "typescript": "^5.9",
50
+ "vite": "npm:@voidzero-dev/vite-plus-core@latest",
51
+ "vite-plus": "latest",
52
+ "vitest": "npm:@voidzero-dev/vite-plus-test@latest"
53
+ },
54
+ "packageManager": "npm@11.11.1"
49
55
  }
package/src/constants.ts CHANGED
@@ -15,12 +15,7 @@ export const BATCH: Batch = {
15
15
  handlers: new Set<Effect | Subscription>(),
16
16
  };
17
17
 
18
- export const METHODS_AFFECTING_LENGTH = new Set<string>([
19
- 'pop',
20
- 'push',
21
- 'shift',
22
- 'unshift',
23
- ]);
18
+ export const METHODS_AFFECTING_LENGTH = new Set<string>(['pop', 'push', 'shift', 'unshift']);
24
19
 
25
20
  export const METHODS_UPDATE = new Set<string>([
26
21
  ...METHODS_AFFECTING_LENGTH,
package/src/helpers/is.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import type {PlainObject} from '@oscarpalmer/atoms/models';
2
2
  import {
3
+ NAME_ALL,
3
4
  NAME_ARRAY,
4
5
  NAME_COMPUTED,
5
6
  NAME_EFFECT,
6
7
  NAME_MORA,
7
8
  NAME_SIGNAL,
8
9
  NAME_STORE,
9
- NAME_ALL,
10
10
  } from '../constants';
11
11
  import type {Effect} from '../effect';
12
12
  import type {ReactiveArray} from '../value/array';
@@ -1,5 +1,4 @@
1
1
  import type {ArrayOrPlainObject, Key, PlainObject} from '@oscarpalmer/atoms/models';
2
- import {startBatch, stopBatch} from '../batch';
3
2
  import {PROPERTY_LENGTH} from '../constants';
4
3
  import type {InternalComputed, ReactiveState, SetValueInProxyParameters} from '../models';
5
4
  import type {ReactiveArray} from '../value/array';
@@ -66,33 +65,81 @@ export function getReactiveValueInProxy(
66
65
  return item;
67
66
  }
68
67
 
69
- export function setProxyValue(proxy: ArrayOrPlainObject, value: ArrayOrPlainObject): void {
70
- startBatch();
68
+ export function setProxyValue<Value, Item = Value>(
69
+ array: boolean,
70
+ state: ReactiveState<Value, Item>,
71
+ isObject: (value: unknown) => value is Value | undefined,
72
+ isProperty: (value: unknown) => boolean,
73
+ setObject: (state: ReactiveState<Value, Item>, value: Value | undefined) => void,
74
+ setProperty: (state: ReactiveState<Value, Item>, property: unknown, value: Item) => void,
75
+ first?: unknown,
76
+ second?: unknown,
77
+ ): void {
78
+ if (array && first === PROPERTY_LENGTH) {
79
+ (state.value as unknown[]).length = second as number;
71
80
 
72
- const proxyKeys = Object.keys(proxy);
73
- const valueKeys = Object.keys(value);
81
+ return;
82
+ }
74
83
 
75
- let {length} = proxyKeys;
84
+ if (isObject(first)) {
85
+ setObject(state, first);
76
86
 
77
- for (let index = 0; index < length; index += 1) {
78
- const key = proxyKeys[index];
79
-
80
- (proxy as PlainObject)[key] = valueKeys.includes(key) ? (value as PlainObject)[key] : undefined;
87
+ return;
81
88
  }
82
89
 
83
- length = valueKeys.length;
90
+ const property = isProperty(first);
84
91
 
85
- for (let index = 0; index < length; index += 1) {
86
- const key = valueKeys[index];
92
+ if (array && property && Number.isNaN(first)) {
93
+ return;
94
+ }
87
95
 
88
- if (!proxyKeys.includes(key)) {
89
- const keyedValue = (value as PlainObject)[key];
96
+ let actual = property ? second : first;
90
97
 
91
- (proxy as PlainObject)[key] = keyedValue;
98
+ if (typeof actual === 'function') {
99
+ try {
100
+ actual = (actual as Function)();
101
+ } catch {
102
+ return;
92
103
  }
93
104
  }
94
105
 
95
- stopBatch();
106
+ if (actual instanceof Promise) {
107
+ if (property) {
108
+ state.promises ??= new Map();
109
+
110
+ state.promises.set(first as Key, actual as Promise<never>);
111
+ } else {
112
+ state.promise = actual as Promise<never>;
113
+ }
114
+
115
+ void actual
116
+ .then(value => {
117
+ if (property && state.promises!.get(first as Key) === actual) {
118
+ state.promises!.delete(first as Key);
119
+
120
+ setProperty(state, first, value);
121
+
122
+ return;
123
+ }
124
+
125
+ if (!property && isObject(value) && state.promise === actual) {
126
+ state.promise = undefined;
127
+
128
+ setObject(state, value);
129
+ }
130
+ })
131
+ .catch(() => {
132
+ if (property && state.promises!.get(first as Key) === actual) {
133
+ state.promises!.delete(first as Key);
134
+ } else if (!property && state.promise === actual) {
135
+ state.promise = undefined;
136
+ }
137
+ });
138
+ } else if (property) {
139
+ setProperty(state, first, actual as Item);
140
+ } else if (isObject(actual)) {
141
+ setObject(state, actual);
142
+ }
96
143
  }
97
144
 
98
145
  export function setValueInProxy<Value extends ArrayOrPlainObject, Equal>(
package/src/models.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type {GenericCallback} from '@oscarpalmer/atoms/models';
1
+ import type {GenericCallback, Key} from '@oscarpalmer/atoms/models';
2
2
  import type {Effect} from './effect';
3
3
  import type {Subscription} from './subscription';
4
4
  import type {Computed} from './value/computed';
@@ -47,6 +47,8 @@ export type ReactiveState<Value, Equal> = {
47
47
  computeds: Set<Computed<unknown>>;
48
48
  effects: Set<Effect>;
49
49
  equal: (first: Equal, second: Equal) => boolean;
50
+ promise?: Promise<Value>;
51
+ promises?: Map<Key, Promise<never>>;
50
52
  subscriptions: Map<GenericCallback, Subscription>;
51
53
  value: Value;
52
54
  };
@@ -1,6 +1,11 @@
1
1
  import type {GenericCallback} from '@oscarpalmer/atoms/models';
2
2
  import {METHODS_AFFECTING_LENGTH, METHODS_UPDATE, NAME_ARRAY, PROPERTY_LENGTH} from '../constants';
3
- import {emityProxyValues, getReactiveValueInProxy, setValueInProxy} from '../helpers/proxy';
3
+ import {
4
+ emityProxyValues,
5
+ getReactiveValueInProxy,
6
+ setProxyValue,
7
+ setValueInProxy,
8
+ } from '../helpers/proxy';
4
9
  import {emitValue, equalArrays, getValue} from '../helpers/value';
5
10
  import type {ReactiveOptions, ReactiveState, Unsubscribe} from '../models';
6
11
  import {noop, subscribe} from '../subscription';
@@ -85,7 +90,7 @@ export class ReactiveArray<Item> extends Reactive<Item[], Item> {
85
90
  * Get the length of the array
86
91
  * @returns Length of the array
87
92
  */
88
- get(property: 'length'): number;
93
+ get(property: typeof PROPERTY_LENGTH): number;
89
94
 
90
95
  get(first?: unknown): unknown {
91
96
  if (typeof first === 'number') {
@@ -130,14 +135,14 @@ export class ReactiveArray<Item> extends Reactive<Item[], Item> {
130
135
  * Get the length of the array _(without reactivity)_
131
136
  * @returns Length of the array
132
137
  */
133
- override peek(property: 'length'): number;
138
+ override peek(property: typeof PROPERTY_LENGTH): number;
134
139
 
135
140
  override peek(value?: unknown): unknown {
136
- if (value === 'length') {
141
+ if (value === PROPERTY_LENGTH) {
137
142
  return this.#size.peek();
138
143
  }
139
144
 
140
- return typeof value === 'number' ? this.state.value.at(value) : [...this.state.value];
145
+ return typeof value === 'number' ? this.state.value.at(value) : this.state.value.slice();
141
146
  }
142
147
 
143
148
  /**
@@ -161,29 +166,37 @@ export class ReactiveArray<Item> extends Reactive<Item[], Item> {
161
166
  * Set the value
162
167
  * @param value New array of items _(defaults to an empty array)_
163
168
  */
164
- set(value?: Item[]): void;
169
+ set(
170
+ value?:
171
+ | Item[]
172
+ | (() => null | undefined | Item[] | Promise<null | undefined | Item[]>)
173
+ | Promise<null | undefined | Item[]>,
174
+ ): void;
165
175
 
166
176
  /**
167
177
  * Set the value at an index
168
178
  * @param index Index of item to set __(if negative, starts from the end)_
169
179
  * @param value New item
170
180
  */
171
- set(index: number, value: Item): void;
181
+ set(index: number, value: Item | (() => Item | Promise<Item>) | Promise<Item>): void;
172
182
 
173
183
  /**
174
184
  * Set the length of the array
175
185
  * @param value New array length
176
186
  */
177
- set(property: 'length', value: number): void;
178
-
179
- set(first?: number | 'length' | Item[], second?: number | Item): void {
180
- if (first == null || Array.isArray(first)) {
181
- this.state.value.splice(0, this.state.value.length, ...(first ?? []));
182
- } else if (first === 'length') {
183
- this.length = second as number;
184
- } else if (typeof first === 'number' && !Number.isNaN(first)) {
185
- setAtIndex(this.state.value, first, second as Item);
186
- }
187
+ set(property: typeof PROPERTY_LENGTH, value: number): void;
188
+
189
+ set(first?: unknown, second?: unknown): void {
190
+ setProxyValue<Item[], Item>(
191
+ true,
192
+ this.state,
193
+ isArrayValue,
194
+ isArrayIndex,
195
+ setArray,
196
+ setAtIndex,
197
+ first,
198
+ second,
199
+ );
187
200
  }
188
201
 
189
202
  /**
@@ -248,14 +261,53 @@ export class ReactiveArray<Item> extends Reactive<Item[], Item> {
248
261
  }
249
262
  }
250
263
 
264
+ /**
265
+ * Create a reactive array from a function result
266
+ * @param value Initial array of items
267
+ * @param options Reactivity options
268
+ * @returns Reactive array
269
+ */
270
+ export function array<Item>(
271
+ value: () => Item[] | Promise<Item[]>,
272
+ options?: ReactiveOptions<Item>,
273
+ ): ReactiveArray<Item>;
274
+
275
+ /**
276
+ * Create a reactive array from a promise
277
+ * @param value Initial array of items
278
+ * @param options Reactivity options
279
+ * @returns Reactive array
280
+ */
281
+ export function array<Item>(
282
+ value: Promise<Item[]>,
283
+ options?: ReactiveOptions<Item>,
284
+ ): ReactiveArray<Item>;
285
+
251
286
  /**
252
287
  * Create a reactive array
253
288
  * @param value Initial array of items
254
289
  * @param options Reactivity options
255
290
  * @returns Reactive array
256
291
  */
257
- export function array<Item>(value: Item[], options?: ReactiveOptions<Item>): ReactiveArray<Item> {
258
- return new ReactiveArray(Array.isArray(value) ? value : [], options);
292
+ export function array<Item>(value: Item[], options?: ReactiveOptions<Item>): ReactiveArray<Item>;
293
+
294
+ export function array<Item>(
295
+ value: Item[] | (() => Item[] | Promise<Item[]>) | Promise<Item[]>,
296
+ options?: ReactiveOptions<Item>,
297
+ ): ReactiveArray<Item> {
298
+ const instance = new ReactiveArray([], options);
299
+
300
+ instance.set(value);
301
+
302
+ return instance;
303
+ }
304
+
305
+ function isArrayIndex(value: unknown): boolean {
306
+ return typeof value === 'number';
307
+ }
308
+
309
+ function isArrayValue<Item>(value: unknown): value is Item[] | undefined {
310
+ return value == null || Array.isArray(value);
259
311
  }
260
312
 
261
313
  function updateArray<Item>(
@@ -265,7 +317,7 @@ function updateArray<Item>(
265
317
  length: Signal<number>,
266
318
  ): unknown {
267
319
  const affectsLength = METHODS_AFFECTING_LENGTH.has(type);
268
- const previousArray = affectsLength ? [] : [...array];
320
+ const previousArray = affectsLength ? [] : array.slice();
269
321
  const previousLength = array.length;
270
322
 
271
323
  return (...args: unknown[]): unknown => {
@@ -283,10 +335,14 @@ function updateArray<Item>(
283
335
  };
284
336
  }
285
337
 
286
- function setAtIndex<Item>(array: Item[], index: number, value: Item): void {
287
- const actual = index < 0 ? array.length + index : index;
338
+ function setArray<Item>(state: ReactiveState<Item[], Item>, value: Item[] | undefined): void {
339
+ state.value.splice(0, state.value.length, ...(value ?? []));
340
+ }
341
+
342
+ function setAtIndex<Item>(state: ReactiveState<Item[], Item>, index: unknown, value: Item): void {
343
+ const actual = (index as number) < 0 ? state.value.length + (index as number) : (index as number);
288
344
 
289
345
  if (actual > -1) {
290
- array[actual] = value;
346
+ state.value[actual] = value;
291
347
  }
292
348
  }