@oscarpalmer/mora 0.29.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.
Files changed (51) hide show
  1. package/dist/batch.mjs +15 -6
  2. package/dist/constants.d.mts +2 -2
  3. package/dist/constants.mjs +7 -4
  4. package/dist/effect.d.mts +5 -18
  5. package/dist/effect.mjs +23 -19
  6. package/dist/helpers/is.d.mts +17 -16
  7. package/dist/helpers/is.mjs +20 -11
  8. package/dist/helpers/proxy.d.mts +5 -9
  9. package/dist/helpers/proxy.mjs +4 -4
  10. package/dist/helpers/value.d.mts +3 -3
  11. package/dist/helpers/value.mjs +22 -5
  12. package/dist/index.d.mts +8 -9
  13. package/dist/index.mjs +2 -2
  14. package/dist/models.d.mts +423 -19
  15. package/dist/mora.full.mjs +532 -424
  16. package/dist/subscription.d.mts +1 -9
  17. package/dist/subscription.mjs +14 -15
  18. package/dist/value/array.d.mts +5 -133
  19. package/dist/value/array.mjs +108 -138
  20. package/dist/value/computed.d.mts +4 -12
  21. package/dist/value/computed.mjs +54 -50
  22. package/dist/value/reactive.d.mts +3 -49
  23. package/dist/value/reactive.mjs +10 -54
  24. package/dist/value/readonly.d.mts +7 -0
  25. package/dist/value/readonly.mjs +37 -0
  26. package/dist/value/signal.d.mts +5 -21
  27. package/dist/value/signal.mjs +33 -49
  28. package/dist/value/store.d.mts +9 -92
  29. package/dist/value/store.mjs +58 -49
  30. package/package.json +7 -8
  31. package/src/batch.ts +22 -9
  32. package/src/constants.ts +12 -5
  33. package/src/effect.ts +35 -40
  34. package/src/helpers/is.ts +36 -20
  35. package/src/helpers/proxy.ts +23 -18
  36. package/src/helpers/value.ts +39 -5
  37. package/src/index.ts +23 -8
  38. package/src/models.ts +520 -17
  39. package/src/subscription.ts +20 -20
  40. package/src/value/array.ts +220 -266
  41. package/src/value/computed.ts +80 -74
  42. package/src/value/reactive.ts +16 -77
  43. package/src/value/readonly.ts +71 -0
  44. package/src/value/signal.ts +43 -71
  45. package/src/value/store.ts +130 -177
  46. package/types/constants.d.ts +1 -1
  47. package/types/index.d.ts +1 -1
  48. package/types/value/array.d.ts +1 -1
  49. package/types/value/computed.d.ts +3 -0
  50. package/types/value/signal.d.ts +1 -1
  51. package/types/value/store.d.ts +1 -1
@@ -2,15 +2,16 @@
2
2
  const ACTIVE = {};
3
3
  const BATCH = {
4
4
  depth: 0,
5
+ flushing: false,
5
6
  handlers: /* @__PURE__ */ new Set()
6
7
  };
7
- const METHODS_AFFECTING_LENGTH = new Set([
8
+ const METHODS_AFFECTING_LENGTH = /* @__PURE__ */ new Set([
8
9
  "pop",
9
10
  "push",
10
11
  "shift",
11
12
  "unshift"
12
13
  ]);
13
- const METHODS_UPDATE = new Set([
14
+ const METHODS_UPDATE = /* @__PURE__ */ new Set([
14
15
  ...METHODS_AFFECTING_LENGTH,
15
16
  "copyWithin",
16
17
  "fill",
@@ -22,52 +23,86 @@ const NAME_ARRAY = "array";
22
23
  const NAME_COMPUTED = "computed";
23
24
  const NAME_EFFECT = "effect";
24
25
  const NAME_MORA = "$mora";
26
+ const NAME_READONLY = "readonly";
25
27
  const NAME_SIGNAL = "signal";
26
28
  const NAME_STORE = "store";
27
- const NAME_ALL = new Set([
29
+ const NAME_ALL = /* @__PURE__ */ new Set([
28
30
  NAME_ARRAY,
29
31
  NAME_COMPUTED,
32
+ NAME_READONLY,
30
33
  NAME_SIGNAL,
31
34
  NAME_STORE
32
35
  ]);
33
36
  //#endregion
34
37
  //#region src/effect.ts
35
- var Effect = class {
36
- constructor(callback) {
37
- Object.defineProperty(this, NAME_MORA, { value: NAME_EFFECT });
38
- this.state = { callback };
39
- runEffect(this);
40
- }
41
- };
42
- function runEffect(effect) {
38
+ /**
39
+ * Create an effect
40
+ *
41
+ * @param callback Callback for handling signal effects
42
+ * @returns Effect
43
+ */
44
+ function effect(callback) {
45
+ if (typeof callback !== "function") throw new TypeError("Effect callback must be a function");
46
+ return getEffect(callback)[0];
47
+ }
48
+ function getEffect(callback) {
49
+ const state = { callback };
50
+ const instance = Object.freeze({ $mora: NAME_EFFECT });
51
+ runEffect(state);
52
+ return [instance, state];
53
+ }
54
+ function internalEffect(callback) {
55
+ return getEffect(callback)[1];
56
+ }
57
+ function runEffect(state) {
43
58
  const previousEffect = ACTIVE.effect;
44
- ACTIVE.effect = effect;
59
+ ACTIVE.effect = state;
45
60
  try {
46
- effect.state.callback();
61
+ state.callback();
47
62
  } finally {
48
63
  ACTIVE.effect = previousEffect;
49
64
  }
50
65
  }
66
+ //#endregion
67
+ //#region src/batch.ts
68
+ function flushHandlers() {
69
+ if (BATCH.flushing) return;
70
+ BATCH.flushing = true;
71
+ try {
72
+ while (BATCH.depth === 0 && BATCH.handlers.size > 0) {
73
+ const handlers = [...BATCH.handlers];
74
+ const { length } = handlers;
75
+ BATCH.handlers.clear();
76
+ for (let index = 0; index < length; index += 1) {
77
+ const handler = handlers[index];
78
+ if (typeof handler.destroy === "function") handler.callback(handler.state.value);
79
+ else runEffect(handler);
80
+ }
81
+ }
82
+ } finally {
83
+ BATCH.flushing = false;
84
+ }
85
+ }
51
86
  /**
52
- * Create an effect
53
- * @param callback Callback for handling signal effects
54
- * @returns Effect
87
+ * Start batching effects
88
+ *
89
+ * _(Use {@link stopBatch} to flush and run batched effects)_
55
90
  */
56
- function effect(callback) {
57
- return typeof callback === "function" ? new Effect(callback) : void 0;
91
+ function startBatch() {
92
+ BATCH.depth += 1;
58
93
  }
59
- //#endregion
60
- //#region src/helpers/is.ts
61
94
  /**
62
- * Is the value a reactive array?
63
- * @param value Value to check
64
- * @returns True if value is a {@link ReactiveArray}
95
+ * Stop batching effects and flush _(run)_ them
65
96
  */
66
- function isArray(value) {
67
- return isMora(value, NAME_ARRAY);
97
+ function stopBatch() {
98
+ if (BATCH.depth > 0) BATCH.depth -= 1;
99
+ flushHandlers();
68
100
  }
101
+ //#endregion
102
+ //#region src/helpers/is.ts
69
103
  /**
70
104
  * Is the value a computed signal?
105
+ *
71
106
  * @param value Value to check
72
107
  * @returns True if value is a {@link Computed}
73
108
  */
@@ -76,6 +111,7 @@ function isComputed(value) {
76
111
  }
77
112
  /**
78
113
  * Is the value an effect?
114
+ *
79
115
  * @param value Value to check
80
116
  * @returns True if value is an {@link Effect}
81
117
  */
@@ -87,6 +123,7 @@ function isMora(value, name) {
87
123
  }
88
124
  /**
89
125
  * Is the value reactive?
126
+ *
90
127
  * @param value Value to check
91
128
  * @returns True if value is a {@link Reactive}
92
129
  */
@@ -95,196 +132,143 @@ function isReactive(value) {
95
132
  }
96
133
  /**
97
134
  * Is the value a signal?
135
+ *
98
136
  * @param value Value to check
99
137
  * @returns True if value is a {@link Signal}
100
138
  */
101
139
  function isSignal(value) {
102
140
  return isMora(value, NAME_SIGNAL);
103
141
  }
104
- //#endregion
105
- //#region src/batch.ts
106
- function flushHandlers() {
107
- while (BATCH.depth === 0 && BATCH.handlers.size > 0) {
108
- const handlers = [...BATCH.handlers];
109
- BATCH.handlers.clear();
110
- for (const handler of handlers) if (isEffect(handler)) runEffect(handler);
111
- else handler.callback(handler.state.value);
112
- }
113
- }
114
142
  /**
115
- * Start batching effects
143
+ * Is the value a reactive array?
116
144
  *
117
- * _(Use {@link stopBatch} to flush and run batched effects)_
145
+ * @param value Value to check
146
+ * @returns True if value is a {@link ReactiveArray}
118
147
  */
119
- function startBatch() {
120
- BATCH.depth += 1;
148
+ function isReactiveArray(value) {
149
+ return isMora(value, NAME_ARRAY);
121
150
  }
122
151
  /**
123
- * Stop batching effects and flush _(run)_ them
152
+ * Is the value a reactive store?
153
+ *
154
+ * @param value Value to check
155
+ * @returns True if value is a {@link Store}
124
156
  */
125
- function stopBatch() {
126
- if (BATCH.depth > 0) BATCH.depth -= 1;
127
- flushHandlers();
157
+ function isReactiveStore(value) {
158
+ return isMora(value, NAME_STORE);
159
+ }
160
+ function isReadonlySignal(value) {
161
+ return isMora(value, NAME_READONLY);
128
162
  }
129
163
  //#endregion
130
- //#region src/subscription.ts
131
- var Subscription = class {
132
- callback;
133
- state;
134
- constructor(state, callback) {
135
- this.state = state;
136
- this.callback = callback;
137
- callback(state.value);
138
- }
139
- destroy() {
140
- this.callback = noop;
141
- this.state = void 0;
142
- }
143
- };
144
- function noop() {}
145
- function subscribe(state, callback) {
146
- if (typeof callback !== "function" || state.subscriptions.has(callback)) return noop;
147
- state.subscriptions.set(callback, new Subscription(state, callback));
148
- return () => {
149
- unsubscribe(state, callback);
164
+ //#region node_modules/@oscarpalmer/atoms/dist/internal/array/callbacks.mjs
165
+ function getArrayCallback(value) {
166
+ switch (typeof value) {
167
+ case "function": return value;
168
+ case "number":
169
+ case "string": return typeof value === "string" && value.includes(".") ? void 0 : (obj) => obj[value];
170
+ }
171
+ }
172
+ function getArrayCallbacks(bool, key, value) {
173
+ if (typeof bool === "function") return { bool };
174
+ return {
175
+ keyed: getArrayCallback(key),
176
+ value: getArrayCallback(value)
150
177
  };
151
178
  }
152
- function unsubscribe(state, callback) {
153
- state.subscriptions.get(callback)?.destroy();
154
- state.subscriptions.delete(callback);
155
- }
156
179
  //#endregion
157
- //#region src/value/reactive.ts
158
- var Reactive = class {
159
- /**
160
- * Internal state of the reactive value
161
- *
162
- * @internal
163
- */
164
- state = {
165
- computeds: /* @__PURE__ */ new Set(),
166
- effects: /* @__PURE__ */ new Set(),
167
- equal: Object.is,
168
- subscriptions: /* @__PURE__ */ new Map(),
169
- value: void 0
170
- };
171
- constructor(name, value, options) {
172
- this.state.value = value;
173
- if (typeof options === "object" && typeof options.equal === "function") this.state.equal = options.equal;
174
- Object.defineProperty(this, NAME_MORA, { value: name });
175
- }
176
- /**
177
- * Get the value _(without reactivity)_
178
- * @return Current value
179
- */
180
- peek() {
181
- return this.state.value;
182
- }
183
- /**
184
- * Subscribe to changes
185
- * @param callback Callback for changes
186
- * @return Unsubscribe callback
187
- */
188
- subscribe(callback) {
189
- return subscribe(this.state, callback);
190
- }
191
- /**
192
- * JSON representation of the value
193
- * @return JSON value
194
- */
195
- toJSON() {
196
- return this.get();
197
- }
198
- /**
199
- * String representation of the value
200
- * @return Value as string
201
- */
202
- toString() {
203
- return String(this.get());
204
- }
205
- /**
206
- * Unsubscribe from changes
207
- * @param callback Callback to unsubscribe
208
- */
209
- unsubscribe(callback) {
210
- unsubscribe(this.state, callback);
180
+ //#region node_modules/@oscarpalmer/atoms/dist/internal/array/find.mjs
181
+ function findValue(type, array, parameters, reversed) {
182
+ const findIndex = type === FIND_VALUE_INDEX;
183
+ if (!Array.isArray(array) || array.length === 0) return findIndex ? -1 : void 0;
184
+ const { bool, key, value } = getFindParameters(parameters);
185
+ const callbacks = getArrayCallbacks(bool, key);
186
+ if (callbacks?.bool == null && callbacks?.keyed == null) {
187
+ if (findIndex) return reversed ? array.lastIndexOf(value) : array.indexOf(value);
188
+ return reversed ? array.findLast((item) => Object.is(item, value)) : array.find((item) => Object.is(item, value));
189
+ }
190
+ if (callbacks.bool != null) {
191
+ const index = reversed ? array.findLastIndex(callbacks.bool) : array.findIndex(callbacks.bool);
192
+ return findIndex ? index : array[index];
193
+ }
194
+ return findValueInArray(array, callbacks.keyed, value, findIndex, reversed);
195
+ }
196
+ function findValueInArray(array, callback, value, findIndex, reversed) {
197
+ const { length } = array;
198
+ for (let index = 0; index < length; index += 1) {
199
+ const item = reversed ? array.at(-(index + 1)) : array[index];
200
+ if (Object.is(callback?.(item, index, array), value)) return findIndex ? index : item;
211
201
  }
212
- };
213
- //#endregion
214
- //#region src/value/computed.ts
215
- var Computed = class extends Reactive {
216
- effect = {
217
- dirty: true,
218
- instance: void 0
202
+ return findIndex ? -1 : void 0;
203
+ }
204
+ function findValues(type, array, parameters, mapper) {
205
+ const result = {
206
+ matched: [],
207
+ notMatched: []
219
208
  };
220
- constructor(callback, options) {
221
- super(NAME_COMPUTED, void 0, options);
222
- this.effect.instance = effect(() => {
223
- if (this.effect.dirty) {
224
- setValue$1(this, this.state, callback);
225
- this.effect.dirty = false;
226
- }
227
- });
209
+ if (!Array.isArray(array) || array.length === 0) return result;
210
+ const { length } = array;
211
+ const { bool, key, value } = getFindParameters(parameters);
212
+ const callbacks = getArrayCallbacks(bool, key);
213
+ if (type === "unique" && callbacks?.keyed == null && length >= UNIQUE_THRESHOLD) {
214
+ result.matched = [...new Set(array)];
215
+ return result;
228
216
  }
229
- /**
230
- * @inheritdoc
231
- */
232
- get() {
233
- if (ACTIVE.computed != null && this !== ACTIVE.computed) this.state.computeds.add(ACTIVE.computed);
234
- if (ACTIVE.effect != null && ACTIVE.effect !== this.effect.instance) this.state.effects.add(ACTIVE.effect);
235
- if (this.effect.dirty && BATCH.depth === 0) runEffect(this.effect.instance);
236
- return this.state.value;
217
+ const mapCallback = getArrayCallback(mapper);
218
+ if (callbacks?.bool != null || type === "all" && key == null) {
219
+ const callback = callbacks?.bool ?? ((item) => Object.is(item, value));
220
+ for (let index = 0; index < length; index += 1) {
221
+ const item = array[index];
222
+ if (callback(item, index, array)) result.matched.push(mapCallback?.(item, index, array) ?? item);
223
+ else result.notMatched.push(item);
224
+ }
225
+ return result;
237
226
  }
238
- };
239
- /**
240
- * Create a computed value
241
- * @param callback Callback to compute the value
242
- * @param options Reactivity options
243
- * @returns Computed value
244
- */
245
- function computed(callback, options) {
246
- return new Computed(callback, options);
227
+ const keys = /* @__PURE__ */ new Set();
228
+ for (let index = 0; index < length; index += 1) {
229
+ const item = array[index];
230
+ const keyed = callbacks?.keyed?.(item, index, array) ?? item;
231
+ if (type === "all" && Object.is(keyed, value) || type === "unique" && !keys.has(keyed)) {
232
+ keys.add(keyed);
233
+ result.matched.push(mapCallback?.(item, index, array) ?? item);
234
+ } else result.notMatched.push(item);
235
+ }
236
+ return result;
237
+ }
238
+ function getFindParameters(original) {
239
+ const { length } = original;
240
+ return {
241
+ bool: length === 1 && typeof original[0] === "function" ? original[0] : void 0,
242
+ key: length === 2 ? original[0] : void 0,
243
+ value: length === 1 && typeof original[0] !== "function" ? original[0] : original[1]
244
+ };
247
245
  }
248
- function setAndEmit$1(state, value) {
249
- if (state.equal(state.value, value)) return;
250
- state.value = value;
251
- for (const computed of state.computeds) computed.effect.dirty = true;
252
- for (const effect of state.effects) BATCH.handlers.add(effect);
253
- for (const [, subscription] of state.subscriptions) subscription.callback(value);
254
- flushHandlers();
246
+ const FIND_VALUE_INDEX = "index";
247
+ const FIND_VALUE_ITEM = "item";
248
+ const UNIQUE_THRESHOLD = 100;
249
+ //#endregion
250
+ //#region node_modules/@oscarpalmer/atoms/dist/internal/array/index-of.mjs
251
+ function indexOf(array, ...parameters) {
252
+ return findValue(FIND_VALUE_INDEX, array, parameters, false);
255
253
  }
256
- function setValue$1(instance, state, callback) {
257
- const previousComputed = ACTIVE.computed;
258
- ACTIVE.computed = instance;
259
- try {
260
- const value = callback();
261
- if (value instanceof Promise) {
262
- state.promise = value;
263
- value.then((resolvedValue) => {
264
- if (state.promise === value) {
265
- state.promise = void 0;
266
- setAndEmit$1(state, resolvedValue);
267
- }
268
- }).catch(() => {
269
- if (state.promise === value) state.promise = void 0;
270
- });
271
- } else setAndEmit$1(state, value);
272
- } finally {
273
- ACTIVE.computed = previousComputed;
274
- }
254
+ indexOf.last = lastIndexOf;
255
+ function lastIndexOf(array, ...parameters) {
256
+ return findValue(FIND_VALUE_INDEX, array, parameters, true);
275
257
  }
276
258
  //#endregion
277
259
  //#region node_modules/@oscarpalmer/atoms/dist/internal/is.mjs
278
260
  /**
279
- * Is the value a key?
261
+ * Is the value a _Key_?
262
+ *
280
263
  * @param value Value to check
281
- * @returns `true` if the value is a `Key` _(`number` or `string`)_, otherwise `false`
264
+ * @returns `true` if the value is a _Key_ _(`number` or `string`)_, otherwise `false`
282
265
  */
283
266
  function isKey(value) {
284
267
  return typeof value === "number" || typeof value === "string";
285
268
  }
286
269
  /**
287
270
  * Is the value a plain object?
271
+ *
288
272
  * @param value Value to check
289
273
  * @returns `true` if the value is a plain object, otherwise `false`
290
274
  */
@@ -295,26 +279,38 @@ function isPlainObject(value) {
295
279
  return prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null;
296
280
  }
297
281
  //#endregion
298
- //#region node_modules/@oscarpalmer/atoms/dist/math.mjs
299
- /**
300
- * Round a number
301
- * @param value Number to round
302
- * @param decimals Number of decimal places to round to _(defaults to `0`)_
303
- * @returns Rounded number, or `NaN` if the value if unable to be rounded
304
- */
305
- function round(value, decimals) {
306
- return roundNumber(Math.round, value, decimals);
282
+ //#region node_modules/@oscarpalmer/atoms/dist/array/find.mjs
283
+ function find(array, ...parameters) {
284
+ return findValue(FIND_VALUE_ITEM, array, parameters, false);
307
285
  }
308
- function roundNumber(callback, value, decimals) {
309
- if (typeof value !== "number") return NaN;
310
- if (typeof decimals !== "number" || decimals < 1) return callback(value);
311
- const mod = 10 ** decimals;
312
- return callback((value + Number.EPSILON) * mod) / mod;
286
+ find.last = findLast;
287
+ function findLast(array, ...parameters) {
288
+ return findValue(FIND_VALUE_ITEM, array, parameters, true);
289
+ }
290
+ //#endregion
291
+ //#region node_modules/@oscarpalmer/atoms/dist/array/select.mjs
292
+ function select(array, ...parameters) {
293
+ return findValues("all", array, parameters, parameters.pop()).matched;
313
294
  }
314
295
  //#endregion
296
+ //#region node_modules/@oscarpalmer/atoms/dist/array/filter.mjs
297
+ function exclude(array, ...parameters) {
298
+ return findValues("all", array, parameters).notMatched;
299
+ }
300
+ function filter(array, ...parameters) {
301
+ return findValues("all", array, parameters).matched;
302
+ }
303
+ filter.remove = exclude;
304
+ //#endregion
305
+ //#region node_modules/@oscarpalmer/atoms/dist/internal/function/misc.mjs
306
+ /**
307
+ * A function that does nothing, which can be useful, I guess…
308
+ */
309
+ function noop$1() {}
310
+ //#endregion
315
311
  //#region src/helpers/value.ts
316
312
  function emitValue(state) {
317
- for (const computed of state.computeds) computed.effect.dirty = true;
313
+ for (const computed of state.computeds) computed.dirty = true;
318
314
  for (const effect of state.effects) BATCH.handlers.add(effect);
319
315
  for (const [, subscription] of state.subscriptions) BATCH.handlers.add(subscription);
320
316
  if (BATCH.depth === 0) flushHandlers();
@@ -324,7 +320,7 @@ function equalArrays(state, first, second) {
324
320
  if (length !== second.length) return false;
325
321
  let offset = 0;
326
322
  if (length >= 100) {
327
- offset = round(length / 10);
323
+ offset = Math.round(length / 10);
328
324
  offset = offset > 25 ? 25 : offset;
329
325
  for (let index = 0; index < offset; index += 1) if (!state.equal(first[index], second[index])) return false;
330
326
  }
@@ -332,26 +328,150 @@ function equalArrays(state, first, second) {
332
328
  for (let index = offset; index < length; index += 1) if (!state.equal(first[index], second[index])) return false;
333
329
  return true;
334
330
  }
335
- function getValue(state) {
331
+ function getSimpleValue(state) {
336
332
  if (ACTIVE.computed != null) state.computeds.add(ACTIVE.computed);
337
333
  if (ACTIVE.effect != null) state.effects.add(ACTIVE.effect);
338
334
  return state.value;
339
335
  }
336
+ function handleSimpleValue(state, origin, setValue, onAfter) {
337
+ try {
338
+ let actual = typeof origin === "function" ? origin() : origin;
339
+ if (actual instanceof Promise) {
340
+ state.promise = actual;
341
+ actual.then((value) => {
342
+ if (actual === state.promise) {
343
+ state.promise = void 0;
344
+ setValue(state, value);
345
+ }
346
+ }).catch(() => {
347
+ if (actual === state.promise) state.promise = void 0;
348
+ });
349
+ } else setValue(state, actual);
350
+ } catch {} finally {
351
+ onAfter?.();
352
+ }
353
+ }
354
+ //#endregion
355
+ //#region src/subscription.ts
356
+ function noop() {}
357
+ function subscribe(state, callback) {
358
+ if (typeof callback !== "function" || state.subscriptions.has(callback)) return noop;
359
+ state.subscriptions.set(callback, subscription(state, callback));
360
+ return () => {
361
+ unsubscribe(state, callback);
362
+ };
363
+ }
364
+ function subscription(state, callback) {
365
+ const instance = {
366
+ callback,
367
+ state,
368
+ destroy: () => {
369
+ instance.callback = noop;
370
+ instance.state = void 0;
371
+ }
372
+ };
373
+ callback(state.value);
374
+ return instance;
375
+ }
376
+ function unsubscribe(state, callback) {
377
+ state.subscriptions.get(callback)?.destroy();
378
+ state.subscriptions.delete(callback);
379
+ }
380
+ //#endregion
381
+ //#region src/value/reactive.ts
382
+ function reactive(value, options) {
383
+ const state = {
384
+ computeds: /* @__PURE__ */ new Set(),
385
+ effects: /* @__PURE__ */ new Set(),
386
+ equal: typeof options === "object" && typeof options?.equal === "function" ? options.equal : Object.is,
387
+ subscriptions: /* @__PURE__ */ new Map(),
388
+ value
389
+ };
390
+ return [{
391
+ toJSON: () => state.value,
392
+ toString: () => String(state.value)
393
+ }, state];
394
+ }
395
+ //#endregion
396
+ //#region src/value/computed.ts
397
+ /**
398
+ * Create a computed value
399
+ *
400
+ * @param callback Callback to compute the value
401
+ * @param options Reactivity options
402
+ * @returns Computed value
403
+ */
404
+ function computed(callback, options) {
405
+ return getComputed(callback, options)[0];
406
+ }
407
+ function getComputed(callback, options) {
408
+ const [rx, state] = reactive(void 0, options);
409
+ let fx;
410
+ const instance = {
411
+ ...rx,
412
+ get: () => getValue(state, fx),
413
+ peek: () => state.value,
414
+ subscribe: (callback) => subscribe(state, callback),
415
+ unsubscribe: (callback) => {
416
+ state.subscriptions.delete(callback);
417
+ }
418
+ };
419
+ Object.defineProperty(instance, NAME_MORA, {
420
+ enumerable: false,
421
+ value: NAME_COMPUTED
422
+ });
423
+ fx = getComputedEffect(state, callback);
424
+ return [Object.freeze(instance), fx];
425
+ }
426
+ function getComputedEffect(state, callback) {
427
+ const fx = {
428
+ dirty: true,
429
+ instance: void 0
430
+ };
431
+ fx.instance = internalEffect(() => {
432
+ if (fx.dirty) {
433
+ const previousComputed = ACTIVE.computed;
434
+ ACTIVE.computed = fx;
435
+ handleSimpleValue(state, callback, setAndEmit$1, () => {
436
+ ACTIVE.computed = previousComputed;
437
+ });
438
+ fx.dirty = false;
439
+ }
440
+ });
441
+ return fx;
442
+ }
443
+ function getValue(state, fx) {
444
+ if (ACTIVE.computed != null && fx !== ACTIVE.computed) state.computeds.add(ACTIVE.computed);
445
+ if (ACTIVE.effect != null && ACTIVE.effect !== fx.instance) state.effects.add(ACTIVE.effect);
446
+ if (fx.dirty && BATCH.depth === 0) runEffect(fx.instance);
447
+ return state.value;
448
+ }
449
+ function internalComputed(callback, options) {
450
+ return getComputed(callback, options);
451
+ }
452
+ function setAndEmit$1(state, value) {
453
+ if (state.equal(state.value, value)) return;
454
+ state.value = value;
455
+ for (const computed of state.computeds) computed.dirty = true;
456
+ for (const effect of state.effects) BATCH.handlers.add(effect);
457
+ for (const [, subscription] of state.subscriptions) subscription.callback(value);
458
+ flushHandlers();
459
+ }
340
460
  //#endregion
341
461
  //#region src/helpers/proxy.ts
342
462
  function emityProxyValues(state, mapped) {
343
463
  const values = [...mapped.values()];
344
464
  const { length } = values;
345
- for (let index = 0; index < length; index += 1) values[index].effect.dirty = true;
465
+ for (let index = 0; index < length; index += 1) values[index][1].dirty = true;
346
466
  emitValue(state);
347
467
  }
348
468
  function getReactiveValueInProxy(reactive, mapped, key, isArray) {
349
469
  let item = mapped.get(key);
350
470
  if (item == null) {
351
- item = computed(() => isArray ? reactive.get().at(key) : reactive.get()[key]);
471
+ item = internalComputed(() => isArray ? reactive.get().at(key) : reactive.get()[key]);
352
472
  mapped.set(key, item);
353
473
  }
354
- return item;
474
+ return item[0];
355
475
  }
356
476
  function setProxyValue(array, state, isObject, isProperty, setObject, setProperty, first, second) {
357
477
  if (array && first === "length") {
@@ -406,190 +526,153 @@ function setValueInProxy(parameters) {
406
526
  return true;
407
527
  }
408
528
  //#endregion
529
+ //#region src/value/readonly.ts
530
+ function getReadonlyInstance(state, instances, handlers, frozen) {
531
+ const key = frozen ? "frozen" : "original";
532
+ instances[key] ??= getReadonlySignal(state, handlers, frozen);
533
+ return instances[key];
534
+ }
535
+ function getReadonlySignal(state, handlers, frozen) {
536
+ const instance = {
537
+ ...handlers,
538
+ get: () => getReadonlyValue(state, false, frozen),
539
+ peek: () => getReadonlyValue(state, true, frozen)
540
+ };
541
+ Object.defineProperties(instance, {
542
+ [NAME_MORA]: {
543
+ enumerable: false,
544
+ value: NAME_READONLY
545
+ },
546
+ frozen: {
547
+ enumerable: true,
548
+ value: frozen
549
+ }
550
+ });
551
+ return Object.freeze(instance);
552
+ }
553
+ function getReadonlyValue(state, peek, frozen) {
554
+ let value = peek ? state.value : getSimpleValue(state);
555
+ if (!frozen) return value;
556
+ if (Array.isArray(state.value)) value = [...state.value];
557
+ else if (isPlainObject(state.value)) value = { ...state.value };
558
+ else value = state.value;
559
+ return Object.freeze(value);
560
+ }
561
+ //#endregion
409
562
  //#region src/value/signal.ts
410
- var Signal = class extends Reactive {
411
- constructor(value, options) {
412
- super(NAME_SIGNAL, value, options);
413
- }
414
- /**
415
- * @inheritdoc
416
- */
417
- get() {
418
- return getValue(this.state);
419
- }
420
- /**
421
- * Set the value
422
- * @param value New value
423
- */
424
- set(value) {
425
- setValue(this.state, value);
426
- }
427
- /**
428
- * Update the value _(based on the current value)_
429
- * @param callback Callback to update the value
430
- */
431
- update(callback) {
432
- this.set(callback(this.state.value));
433
- }
434
- };
435
563
  function setAndEmit(state, value) {
436
564
  if (!state.equal(state.value, value)) {
437
565
  state.value = value;
438
566
  emitValue(state);
439
567
  }
440
568
  }
441
- function setValue(state, value) {
442
- try {
443
- let actual = value;
444
- if (typeof value === "function") actual = value();
445
- if (actual instanceof Promise) {
446
- state.promise = actual;
447
- actual.then((value) => {
448
- if (actual === state.promise) {
449
- state.promise = void 0;
450
- setAndEmit(state, value);
451
- }
452
- }).catch(() => {
453
- if (actual === state.promise) state.promise = void 0;
454
- });
455
- } else setAndEmit(state, actual);
456
- } catch {}
457
- }
458
569
  function signal(value, options) {
459
- const instance = new Signal(void 0, options);
460
- instance.set(value);
461
- return instance;
570
+ const [rx, state] = reactive(void 0, options);
571
+ const readonlies = {};
572
+ const handlers = {
573
+ ...rx,
574
+ subscribe: (callback) => subscribe(state, callback),
575
+ unsubscribe: (callback) => {
576
+ state.subscriptions.delete(callback);
577
+ }
578
+ };
579
+ const instance = {
580
+ ...handlers,
581
+ asReadonly: (frozen) => getReadonlyInstance(state, readonlies, handlers, frozen === true),
582
+ get: () => getSimpleValue(state),
583
+ peek: () => state.value,
584
+ set: (value) => {
585
+ handleSimpleValue(state, value, setAndEmit);
586
+ },
587
+ update: (callback) => {
588
+ handleSimpleValue(state, callback(state.value), setAndEmit);
589
+ }
590
+ };
591
+ Object.defineProperty(instance, NAME_MORA, {
592
+ enumerable: false,
593
+ value: NAME_SIGNAL
594
+ });
595
+ handleSimpleValue(state, value, setAndEmit);
596
+ return Object.freeze(instance);
462
597
  }
463
598
  //#endregion
464
599
  //#region src/value/array.ts
465
- var ReactiveArray = class extends Reactive {
466
- #indiced = /* @__PURE__ */ new Map();
467
- #size = signal(0);
468
- /**
469
- * The length of the array
470
- */
471
- get length() {
472
- return this.#size.get();
473
- }
474
- /**
475
- * Set the length of the array
476
- */
477
- set length(value) {
478
- if (typeof value === "number" && value >= 0 && value !== this.state.value.length) this.get().length = value;
479
- }
480
- constructor(value, options) {
481
- super(NAME_ARRAY, new Proxy(value, {
482
- get: (target, property) => METHODS_UPDATE.has(property) ? updateArray(property, target, this.state, this.#size) : Reflect.get(target, property),
483
- set: (target, property, value) => setValueInProxy({
484
- property,
485
- target,
486
- value,
487
- isArray: true,
488
- state: this.state,
489
- length: this.#size
490
- })
491
- }), options);
492
- this.#size.set(value.length);
493
- }
494
- /**
495
- * Clear the array
496
- */
497
- clear() {
498
- this.length = 0;
499
- }
500
- /**
501
- * Create a computed, filtered array
502
- * @param callback Callback to evaluate each item
503
- * @return Computed array of filtered items
504
- */
505
- filter(callback) {
506
- return computed(() => this.get().filter(callback));
507
- }
508
- get(first) {
509
- if (typeof first === "number") return getReactiveValueInProxy(this, this.#indiced, first, true).get();
510
- return first === "length" ? this.length : getValue(this.state);
511
- }
512
- /**
513
- * Create a computed, mapped array
514
- * @param callback Callback to transform each item
515
- * @return Computed array of mapped items
516
- */
517
- map(callback) {
518
- return computed(() => this.get().map(callback));
519
- }
520
- /**
521
- * Notify dependents of changes
522
- *
523
- * _This bypasses equality checks and will immediately notify dependents.
524
- * Use this only if you're modifying nested data that would be ignored by equality checks._
525
- */
526
- notify() {
527
- emityProxyValues(this.state, this.#indiced);
528
- }
529
- peek(value) {
530
- if (value === "length") return this.#size.peek();
531
- return typeof value === "number" ? this.state.value.at(value) : this.state.value.slice();
532
- }
533
- /**
534
- * Remove and return the last item of the array
535
- * @returns Removed item, or `undefined` if the array is empty
536
- */
537
- pop() {
538
- return this.state.value.pop();
539
- }
540
- /**
541
- * Add items to the end of the array
542
- * @param items Items to add
543
- * @returns New array length
544
- */
545
- push(...items) {
546
- return this.state.value.push(...items);
547
- }
548
- set(first, second) {
549
- setProxyValue(true, this.state, isArrayValue, isArrayIndex, setArray, setAtIndex, first, second);
550
- }
551
- /**
552
- * Remove and return the first item of the array
553
- * @returns Removed item, or `undefined` if the array is empty
554
- */
555
- shift() {
556
- return this.state.value.shift();
557
- }
558
- /**
559
- * Remove and return items from the array _(and optionally add new items)_
560
- * @param from Index to start removing items from
561
- * @param to Index to stop removing items at _(defaults to the end of the array)_
562
- * @param items Optional items to add
563
- * @returns Removed items
564
- */
565
- splice(from, to, ...items) {
566
- return this.state.value.splice(from, to ?? this.state.value.length, ...items);
567
- }
568
- subscribe(first, second) {
569
- if (typeof first === "number" && typeof second === "function") return getReactiveValueInProxy(this, this.#indiced, first, true).subscribe(second);
570
- return typeof first === "function" ? subscribe(this.state, first) : noop;
571
- }
572
- /**
573
- * Add items to the beginning of the array
574
- * @param items Items to add
575
- * @returns New array length
576
- */
577
- unshift(...items) {
578
- return this.state.value.unshift(...items);
579
- }
580
- /**
581
- * Update the value _(based on the current value)_
582
- * @param callback Callback to update the value
583
- */
584
- update(callback) {
585
- const updated = callback(this.state.value);
586
- if (updated == null || Array.isArray(updated)) this.set(updated);
587
- }
588
- };
589
600
  function array(value, options) {
590
- const instance = new ReactiveArray([], options);
591
- instance.set(value);
592
- return instance;
601
+ const [rx, state] = reactive([], options);
602
+ const indiced = /* @__PURE__ */ new Map();
603
+ const length = signal(0);
604
+ const readonlies = {};
605
+ let instance = {};
606
+ state.value = new Proxy([], {
607
+ get: (target, property) => METHODS_UPDATE.has(property) ? updateArray(property, target, state, length) : Reflect.get(target, property),
608
+ set: (target, property, value) => setValueInProxy({
609
+ length,
610
+ property,
611
+ state,
612
+ target,
613
+ value,
614
+ isArray: true
615
+ })
616
+ });
617
+ function get(value) {
618
+ return getArrayValue(instance, indiced, state, length, value);
619
+ }
620
+ function set(first, second) {
621
+ setProxyValue(true, state, isArrayValue, isArrayIndex, setArray, setAtIndex, first, second);
622
+ }
623
+ const handlers = {
624
+ ...rx,
625
+ get: (value) => get(value),
626
+ peek: (first, second) => peekArrayValue(state, length, first, second),
627
+ subscribe: (first, second) => {
628
+ if (typeof first === "number" && typeof second === "function") return getReactiveValueInProxy(instance, indiced, first, true).subscribe(second);
629
+ return typeof first === "function" ? subscribe(state, first) : noop$1;
630
+ },
631
+ unsubscribe: (first, second) => {
632
+ if (typeof first === "number" && typeof second === "function") getReactiveValueInProxy(instance, indiced, first, true)?.unsubscribe(second);
633
+ else if (typeof first === "function") unsubscribe(state, first);
634
+ }
635
+ };
636
+ instance = {
637
+ ...handlers,
638
+ asReadonly: (frozen) => getReadonlyInstance(state, readonlies, handlers, frozen === true),
639
+ at: (index) => get(index),
640
+ clear: () => {
641
+ state.value.length = 0;
642
+ },
643
+ filter: (callback) => computed(() => filter(get(), callback)),
644
+ map: (callback) => computed(() => get().map(callback)),
645
+ notify: () => {
646
+ emityProxyValues(state, indiced);
647
+ },
648
+ pop: () => state.value.pop(),
649
+ push: (...items) => state.value.push(...items),
650
+ select: (filter, map) => computed(() => select(get(), filter, map)),
651
+ set: (first, second) => {
652
+ set(first, second);
653
+ },
654
+ shift: () => state.value.shift(),
655
+ splice: (from, to, ...items) => state.value.splice(from, to ?? state.value.length, ...items),
656
+ unshift: (...items) => state.value.unshift(...items),
657
+ update: (callback) => updateArrayValue(instance, state, callback)
658
+ };
659
+ Object.defineProperties(instance, {
660
+ [NAME_MORA]: {
661
+ enumerable: false,
662
+ value: NAME_ARRAY
663
+ },
664
+ length: {
665
+ enumerable: true,
666
+ get: () => length.get(),
667
+ set: (value) => setArrayLength(state, value)
668
+ }
669
+ });
670
+ set(value);
671
+ return Object.freeze(instance);
672
+ }
673
+ function getArrayValue(instance, indiced, state, length, first) {
674
+ if (typeof first === "number") return getReactiveValueInProxy(instance, indiced, first, true).get();
675
+ return first === "length" ? length.get() : getSimpleValue(state);
593
676
  }
594
677
  function isArrayIndex(value) {
595
678
  return typeof value === "number";
@@ -597,6 +680,26 @@ function isArrayIndex(value) {
597
680
  function isArrayValue(value) {
598
681
  return value == null || Array.isArray(value);
599
682
  }
683
+ function peekArrayValue(state, length, first, second) {
684
+ if (first === "length") return length.peek();
685
+ let value;
686
+ if (typeof first === "number") value = state.value.at(first);
687
+ else value = state.value;
688
+ if (!(first === true || second === true)) return value;
689
+ if (Array.isArray(value)) return value.slice();
690
+ if (isPlainObject(value)) return { ...value };
691
+ return value;
692
+ }
693
+ function setArray(state, value) {
694
+ state.value.splice(0, state.value.length, ...value ?? []);
695
+ }
696
+ function setArrayLength(state, value) {
697
+ if (typeof value === "number" && value >= 0 && value !== state.value.length) state.value.length = value;
698
+ }
699
+ function setAtIndex(state, index, value) {
700
+ const actual = index < 0 ? state.value.length + index : index;
701
+ if (actual > -1) state.value[actual] = value;
702
+ }
600
703
  function updateArray(type, array, state, length) {
601
704
  const affectsLength = METHODS_AFFECTING_LENGTH.has(type);
602
705
  const previousArray = affectsLength ? [] : array.slice();
@@ -610,57 +713,12 @@ function updateArray(type, array, state, length) {
610
713
  return result;
611
714
  };
612
715
  }
613
- function setArray(state, value) {
614
- state.value.splice(0, state.value.length, ...value ?? []);
615
- }
616
- function setAtIndex(state, index, value) {
617
- const actual = index < 0 ? state.value.length + index : index;
618
- if (actual > -1) state.value[actual] = value;
716
+ function updateArrayValue(instance, state, callback) {
717
+ const updated = callback(state.value);
718
+ if (updated == null || Array.isArray(updated)) instance.set(updated);
619
719
  }
620
720
  //#endregion
621
721
  //#region src/value/store.ts
622
- var Store = class extends Reactive {
623
- #keyed = /* @__PURE__ */ new Map();
624
- constructor(value, options) {
625
- super(NAME_STORE, new Proxy(value, { set: (target, property, value) => setValueInProxy({
626
- target,
627
- property,
628
- value,
629
- isArray: false,
630
- state: this.state
631
- }) }), options);
632
- }
633
- get(key) {
634
- return isKey(key) ? getReactiveValueInProxy(this, this.#keyed, key, false).get() : getValue(this.state);
635
- }
636
- /**
637
- * Notify dependents of changes
638
- *
639
- * _This bypasses equality checks and will immediately notify dependents.
640
- * Use this only if you're modifying nested data that would be ignored by equality checks._
641
- */
642
- notify() {
643
- emityProxyValues(this.state, this.#keyed);
644
- }
645
- peek(key) {
646
- return isKey(key) ? this.state.value[key] : { ...this.state.value };
647
- }
648
- set(first, second) {
649
- setProxyValue(false, this.state, isStoreObject, isKey, setObject, setProperty, first, second);
650
- }
651
- subscribe(first, second) {
652
- if (isKey(first) && typeof second === "function") return getReactiveValueInProxy(this, this.#keyed, first, false).subscribe(second);
653
- return typeof first === "function" ? subscribe(this.state, first) : noop;
654
- }
655
- /**
656
- * Update the value _(based on the current value)_
657
- * @param callback Callback to update the value
658
- */
659
- update(callback) {
660
- const updated = callback(this.state.value);
661
- if (updated == null || isPlainObject(updated)) setObject(this.state, updated);
662
- }
663
- };
664
722
  function isStoreObject(value) {
665
723
  return value == null || isPlainObject(value);
666
724
  }
@@ -682,13 +740,63 @@ function setObject(state, value) {
682
740
  }
683
741
  stopBatch();
684
742
  }
743
+ function peekStoreValue(state, first, second) {
744
+ let value;
745
+ if (isKey(first)) value = state.value[first];
746
+ else value = state.value;
747
+ if (!(first === true || second === true)) return value;
748
+ if (Array.isArray(value)) return value.slice();
749
+ if (isPlainObject(value)) return { ...value };
750
+ return value;
751
+ }
685
752
  function setProperty(state, key, value) {
686
753
  state.value[key] = value;
687
754
  }
688
755
  function store(value, options) {
689
- const instance = new Store({}, options);
756
+ const [rx, state] = reactive(void 0, options);
757
+ const keyed = /* @__PURE__ */ new Map();
758
+ const readonlies = {};
759
+ let instance = {};
760
+ state.value = new Proxy({}, { set: (target, property, value) => setValueInProxy({
761
+ target,
762
+ property,
763
+ state,
764
+ value,
765
+ isArray: false
766
+ }) });
767
+ const handlers = {
768
+ ...rx,
769
+ get: (value) => isKey(value) ? getReactiveValueInProxy(instance, keyed, value, false).get() : getSimpleValue(state),
770
+ peek: (first, second) => peekStoreValue(state, first, second),
771
+ subscribe: (first, second) => {
772
+ if (isKey(first) && typeof second === "function") return getReactiveValueInProxy(instance, keyed, first, false).subscribe(second);
773
+ return typeof first === "function" ? subscribe(state, first) : noop;
774
+ },
775
+ unsubscribe: (first, second) => {
776
+ if (isKey(first) && typeof second === "function") getReactiveValueInProxy(instance, keyed, first, false)?.unsubscribe(second);
777
+ else if (typeof first === "function") unsubscribe(state, first);
778
+ }
779
+ };
780
+ instance = {
781
+ ...handlers,
782
+ asReadonly: (frozen) => getReadonlyInstance(state, readonlies, handlers, frozen === true),
783
+ notify() {
784
+ emityProxyValues(state, keyed);
785
+ },
786
+ set: (first, second) => {
787
+ setProxyValue(false, state, isStoreObject, isKey, setObject, setProperty, first, second);
788
+ },
789
+ update: (callback) => {
790
+ const updated = callback(state.value);
791
+ if (updated == null || isPlainObject(updated)) setObject(state, updated);
792
+ }
793
+ };
794
+ Object.defineProperty(instance, NAME_MORA, {
795
+ enumerable: false,
796
+ value: NAME_STORE
797
+ });
690
798
  instance.set(value);
691
- return instance;
799
+ return Object.freeze(instance);
692
800
  }
693
801
  //#endregion
694
- export { array, computed, effect, isArray, isComputed, isEffect, isReactive, isSignal, signal, startBatch, stopBatch, store };
802
+ export { array, computed, effect, isComputed, isEffect, isReactive, isReactiveArray, isReactiveStore, isReadonlySignal, isSignal, signal, startBatch, stopBatch, store };