@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.
@@ -4,15 +4,19 @@ import { subscribe, unsubscribe } from "../subscription.mjs";
4
4
  import { reactive } from "./reactive.mjs";
5
5
  import { computed } from "./computed.mjs";
6
6
  import { emityProxyValues, getReactiveValueInProxy, setProxyValue, setValueInProxy } from "../helpers/proxy.mjs";
7
+ import { getReadonlyInstance } from "./readonly.mjs";
7
8
  import { signal } from "./signal.mjs";
8
9
  import { select } from "@oscarpalmer/atoms/array";
9
10
  import { filter } from "@oscarpalmer/atoms/array/filter";
10
11
  import { noop } from "@oscarpalmer/atoms/function";
12
+ import { isPlainObject } from "@oscarpalmer/atoms/is";
11
13
  //#region src/value/array.ts
12
14
  function array(value, options) {
13
15
  const [rx, state] = reactive([], options);
14
16
  const indiced = /* @__PURE__ */ new Map();
15
17
  const length = signal(0);
18
+ const readonlies = {};
19
+ let instance = {};
16
20
  state.value = new Proxy([], {
17
21
  get: (target, property) => METHODS_UPDATE.has(property) ? updateArray(property, target, state, length) : Reflect.get(target, property),
18
22
  set: (target, property, value) => setValueInProxy({
@@ -27,37 +31,43 @@ function array(value, options) {
27
31
  function get(value) {
28
32
  return getArrayValue(instance, indiced, state, length, value);
29
33
  }
30
- const instance = {
34
+ function set(first, second) {
35
+ setProxyValue(true, state, isArrayValue, isArrayIndex, setArray, setAtIndex, first, second);
36
+ }
37
+ const handlers = {
31
38
  ...rx,
32
- length: state.value.length,
39
+ get: (value) => get(value),
40
+ peek: (first, second) => peekArrayValue(state, length, first, second),
41
+ subscribe: (first, second) => {
42
+ if (typeof first === "number" && typeof second === "function") return getReactiveValueInProxy(instance, indiced, first, true).subscribe(second);
43
+ return typeof first === "function" ? subscribe(state, first) : noop;
44
+ },
45
+ unsubscribe: (first, second) => {
46
+ if (typeof first === "number" && typeof second === "function") getReactiveValueInProxy(instance, indiced, first, true)?.unsubscribe(second);
47
+ else if (typeof first === "function") unsubscribe(state, first);
48
+ }
49
+ };
50
+ instance = {
51
+ ...handlers,
52
+ asReadonly: (frozen) => getReadonlyInstance(state, readonlies, handlers, frozen === true),
33
53
  at: (index) => get(index),
34
54
  clear: () => {
35
55
  state.value.length = 0;
36
56
  },
37
57
  filter: (callback) => computed(() => filter(get(), callback)),
38
- get: (value) => get(value),
39
58
  map: (callback) => computed(() => get().map(callback)),
40
59
  notify: () => {
41
60
  emityProxyValues(state, indiced);
42
61
  },
43
- peek: (value) => peekArrayValue(state, length, value),
44
62
  pop: () => state.value.pop(),
45
63
  push: (...items) => state.value.push(...items),
46
64
  select: (filter, map) => computed(() => select(get(), filter, map)),
47
65
  set: (first, second) => {
48
- setProxyValue(true, state, isArrayValue, isArrayIndex, setArray, setAtIndex, first, second);
66
+ set(first, second);
49
67
  },
50
68
  shift: () => state.value.shift(),
51
69
  splice: (from, to, ...items) => state.value.splice(from, to ?? state.value.length, ...items),
52
- subscribe: (first, second) => {
53
- if (typeof first === "number" && typeof second === "function") return getReactiveValueInProxy(instance, indiced, first, true).subscribe(second);
54
- return typeof first === "function" ? subscribe(state, first) : noop;
55
- },
56
70
  unshift: (...items) => state.value.unshift(...items),
57
- unsubscribe: (first, second) => {
58
- if (typeof first === "number" && typeof second === "function") getReactiveValueInProxy(instance, indiced, first, true)?.unsubscribe(second);
59
- else if (typeof first === "function") unsubscribe(state, first);
60
- },
61
71
  update: (callback) => updateArrayValue(instance, state, callback)
62
72
  };
63
73
  Object.defineProperties(instance, {
@@ -71,7 +81,7 @@ function array(value, options) {
71
81
  set: (value) => setArrayLength(state, value)
72
82
  }
73
83
  });
74
- instance.set(value);
84
+ set(value);
75
85
  return Object.freeze(instance);
76
86
  }
77
87
  function getArrayValue(instance, indiced, state, length, first) {
@@ -84,9 +94,15 @@ function isArrayIndex(value) {
84
94
  function isArrayValue(value) {
85
95
  return value == null || Array.isArray(value);
86
96
  }
87
- function peekArrayValue(state, length, value) {
88
- if (value === "length") return length.peek();
89
- return typeof value === "number" ? state.value.at(value) : state.value.slice();
97
+ function peekArrayValue(state, length, first, second) {
98
+ if (first === "length") return length.peek();
99
+ let value;
100
+ if (typeof first === "number") value = state.value.at(first);
101
+ else value = state.value;
102
+ if (!(first === true || second === true)) return value;
103
+ if (Array.isArray(value)) return value.slice();
104
+ if (isPlainObject(value)) return { ...value };
105
+ return value;
90
106
  }
91
107
  function setArray(state, value) {
92
108
  state.value.splice(0, state.value.length, ...value ?? []);
@@ -0,0 +1,7 @@
1
+ import { ReactiveState, ReadonlyFrozenSignal, ReadonlyInstances, ReadonlySignal } from "../models.mjs";
2
+ import { GenericCallback } from "@oscarpalmer/atoms/models";
3
+ //#region src/value/readonly.d.ts
4
+ declare function getReadonlyInstance<Value>(state: ReactiveState<Value, never>, instances: ReadonlyInstances<Value>, handlers: Record<string, GenericCallback>, frozen: boolean): ReadonlySignal<Value>;
5
+ declare function getReadonlySignal<Value>(state: ReactiveState<Value, never>, handlers: Record<string, GenericCallback>, frozen: boolean): ReadonlySignal<Value> | ReadonlyFrozenSignal<Value>;
6
+ //#endregion
7
+ export { getReadonlyInstance, getReadonlySignal };
@@ -0,0 +1,37 @@
1
+ import { NAME_MORA, NAME_READONLY } from "../constants.mjs";
2
+ import { getSimpleValue } from "../helpers/value.mjs";
3
+ import { isPlainObject } from "@oscarpalmer/atoms/is";
4
+ //#region src/value/readonly.ts
5
+ function getReadonlyInstance(state, instances, handlers, frozen) {
6
+ const key = frozen ? "frozen" : "original";
7
+ instances[key] ??= getReadonlySignal(state, handlers, frozen);
8
+ return instances[key];
9
+ }
10
+ function getReadonlySignal(state, handlers, frozen) {
11
+ const instance = {
12
+ ...handlers,
13
+ get: () => getReadonlyValue(state, false, frozen),
14
+ peek: () => getReadonlyValue(state, true, frozen)
15
+ };
16
+ Object.defineProperties(instance, {
17
+ [NAME_MORA]: {
18
+ enumerable: false,
19
+ value: NAME_READONLY
20
+ },
21
+ frozen: {
22
+ enumerable: true,
23
+ value: frozen
24
+ }
25
+ });
26
+ return Object.freeze(instance);
27
+ }
28
+ function getReadonlyValue(state, peek, frozen) {
29
+ let value = peek ? state.value : getSimpleValue(state);
30
+ if (!frozen) return value;
31
+ if (Array.isArray(state.value)) value = [...state.value];
32
+ else if (isPlainObject(state.value)) value = { ...state.value };
33
+ else value = state.value;
34
+ return Object.freeze(value);
35
+ }
36
+ //#endregion
37
+ export { getReadonlyInstance, getReadonlySignal };
@@ -2,6 +2,7 @@ import { NAME_MORA, NAME_SIGNAL } from "../constants.mjs";
2
2
  import { emitValue, getSimpleValue, handleSimpleValue } from "../helpers/value.mjs";
3
3
  import { subscribe } from "../subscription.mjs";
4
4
  import { reactive } from "./reactive.mjs";
5
+ import { getReadonlyInstance } from "./readonly.mjs";
5
6
  //#region src/value/signal.ts
6
7
  function setAndEmit(state, value) {
7
8
  if (!state.equal(state.value, value)) {
@@ -11,8 +12,17 @@ function setAndEmit(state, value) {
11
12
  }
12
13
  function signal(value, options) {
13
14
  const [rx, state] = reactive(void 0, options);
14
- const instance = {
15
+ const readonlies = {};
16
+ const handlers = {
15
17
  ...rx,
18
+ subscribe: (callback) => subscribe(state, callback),
19
+ unsubscribe: (callback) => {
20
+ state.subscriptions.delete(callback);
21
+ }
22
+ };
23
+ const instance = {
24
+ ...handlers,
25
+ asReadonly: (frozen) => getReadonlyInstance(state, readonlies, handlers, frozen === true),
16
26
  get: () => getSimpleValue(state),
17
27
  peek: () => state.value,
18
28
  set: (value) => {
@@ -20,10 +30,6 @@ function signal(value, options) {
20
30
  },
21
31
  update: (callback) => {
22
32
  handleSimpleValue(state, callback(state.value), setAndEmit);
23
- },
24
- subscribe: (callback) => subscribe(state, callback),
25
- unsubscribe: (callback) => {
26
- state.subscriptions.delete(callback);
27
33
  }
28
34
  };
29
35
  Object.defineProperty(instance, NAME_MORA, {
@@ -4,6 +4,7 @@ import { getSimpleValue } from "../helpers/value.mjs";
4
4
  import { noop, subscribe, unsubscribe } from "../subscription.mjs";
5
5
  import { reactive } from "./reactive.mjs";
6
6
  import { emityProxyValues, getReactiveValueInProxy, setProxyValue, setValueInProxy } from "../helpers/proxy.mjs";
7
+ import { getReadonlyInstance } from "./readonly.mjs";
7
8
  import { isKey, isPlainObject } from "@oscarpalmer/atoms/is";
8
9
  //#region src/value/store.ts
9
10
  function isStoreObject(value) {
@@ -27,12 +28,23 @@ function setObject(state, value) {
27
28
  }
28
29
  stopBatch();
29
30
  }
31
+ function peekStoreValue(state, first, second) {
32
+ let value;
33
+ if (isKey(first)) value = state.value[first];
34
+ else value = state.value;
35
+ if (!(first === true || second === true)) return value;
36
+ if (Array.isArray(value)) return value.slice();
37
+ if (isPlainObject(value)) return { ...value };
38
+ return value;
39
+ }
30
40
  function setProperty(state, key, value) {
31
41
  state.value[key] = value;
32
42
  }
33
43
  function store(value, options) {
34
44
  const [rx, state] = reactive(void 0, options);
35
45
  const keyed = /* @__PURE__ */ new Map();
46
+ const readonlies = {};
47
+ let instance = {};
36
48
  state.value = new Proxy({}, { set: (target, property, value) => setValueInProxy({
37
49
  target,
38
50
  property,
@@ -40,16 +52,10 @@ function store(value, options) {
40
52
  value,
41
53
  isArray: false
42
54
  }) });
43
- const instance = {
55
+ const handlers = {
44
56
  ...rx,
45
57
  get: (value) => isKey(value) ? getReactiveValueInProxy(instance, keyed, value, false).get() : getSimpleValue(state),
46
- notify() {
47
- emityProxyValues(state, keyed);
48
- },
49
- peek: (value) => isKey(value) ? state.value[value] : { ...state.value },
50
- set: (first, second) => {
51
- setProxyValue(false, state, isStoreObject, isKey, setObject, setProperty, first, second);
52
- },
58
+ peek: (first, second) => peekStoreValue(state, first, second),
53
59
  subscribe: (first, second) => {
54
60
  if (isKey(first) && typeof second === "function") return getReactiveValueInProxy(instance, keyed, first, false).subscribe(second);
55
61
  return typeof first === "function" ? subscribe(state, first) : noop;
@@ -57,6 +63,16 @@ function store(value, options) {
57
63
  unsubscribe: (first, second) => {
58
64
  if (isKey(first) && typeof second === "function") getReactiveValueInProxy(instance, keyed, first, false)?.unsubscribe(second);
59
65
  else if (typeof first === "function") unsubscribe(state, first);
66
+ }
67
+ };
68
+ instance = {
69
+ ...handlers,
70
+ asReadonly: (frozen) => getReadonlyInstance(state, readonlies, handlers, frozen === true),
71
+ notify() {
72
+ emityProxyValues(state, keyed);
73
+ },
74
+ set: (first, second) => {
75
+ setProxyValue(false, state, isStoreObject, isKey, setObject, setProperty, first, second);
60
76
  },
61
77
  update: (callback) => {
62
78
  const updated = callback(state.value);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oscarpalmer/mora",
3
- "version": "0.30.0",
3
+ "version": "0.31.0",
4
4
  "description": "Signals and stuff…",
5
5
  "keywords": [
6
6
  "reactive",
package/src/constants.ts CHANGED
@@ -33,10 +33,18 @@ export const NAME_EFFECT = 'effect';
33
33
 
34
34
  export const NAME_MORA = '$mora';
35
35
 
36
+ export const NAME_READONLY = 'readonly';
37
+
36
38
  export const NAME_SIGNAL = 'signal';
37
39
 
38
40
  export const NAME_STORE = 'store';
39
41
 
40
- export const NAME_ALL = new Set([NAME_ARRAY, NAME_COMPUTED, NAME_SIGNAL, NAME_STORE]);
42
+ export const NAME_ALL = new Set([
43
+ NAME_ARRAY,
44
+ NAME_COMPUTED,
45
+ NAME_READONLY,
46
+ NAME_SIGNAL,
47
+ NAME_STORE,
48
+ ]);
41
49
 
42
50
  export const PROPERTY_LENGTH = 'length';
package/src/helpers/is.ts CHANGED
@@ -5,20 +5,19 @@ import {
5
5
  NAME_COMPUTED,
6
6
  NAME_EFFECT,
7
7
  NAME_MORA,
8
+ NAME_READONLY,
8
9
  NAME_SIGNAL,
9
10
  NAME_STORE,
10
11
  } from '../constants';
11
- import type {Computed, Effect, Reactive, ReactiveArray, ReactiveStore, Signal} from '../models';
12
-
13
- /**
14
- * Is the value a reactive array?
15
- *
16
- * @param value Value to check
17
- * @returns True if value is a {@link ReactiveArray}
18
- */
19
- export function isArray<Item>(value: unknown): value is ReactiveArray<Item> {
20
- return isMora<ReactiveArray<Item>>(value, NAME_ARRAY);
21
- }
12
+ import type {
13
+ Computed,
14
+ Effect,
15
+ Reactive,
16
+ ReactiveArray,
17
+ ReactiveStore,
18
+ ReadonlySignal,
19
+ Signal,
20
+ } from '../models';
22
21
 
23
22
  /**
24
23
  * Is the value a computed signal?
@@ -40,7 +39,7 @@ export function isEffect(value: unknown): value is Effect {
40
39
  return isMora<Effect>(value, NAME_EFFECT);
41
40
  }
42
41
 
43
- function isMora<T>(value: unknown, name: string | Set<string>): value is T {
42
+ function isMora<Instance>(value: unknown, name: string | Set<string>): value is Instance {
44
43
  return (
45
44
  typeof value === 'object' &&
46
45
  value != null &&
@@ -71,12 +70,28 @@ export function isSignal<Value>(value: unknown): value is Signal<Value> {
71
70
  return isMora<Signal<Value>>(value, NAME_SIGNAL);
72
71
  }
73
72
 
73
+ /**
74
+ * Is the value a reactive array?
75
+ *
76
+ * @param value Value to check
77
+ * @returns True if value is a {@link ReactiveArray}
78
+ */
79
+ export function isReactiveArray<Item>(value: unknown): value is ReactiveArray<Item> {
80
+ return isMora<ReactiveArray<Item>>(value, NAME_ARRAY);
81
+ }
82
+
74
83
  /**
75
84
  * Is the value a reactive store?
76
85
  *
77
86
  * @param value Value to check
78
87
  * @returns True if value is a {@link Store}
79
88
  */
80
- export function isStore<Value extends PlainObject>(value: unknown): value is ReactiveStore<Value> {
89
+ export function isReactiveStore<Value extends PlainObject>(
90
+ value: unknown,
91
+ ): value is ReactiveStore<Value> {
81
92
  return isMora<ReactiveStore<Value>>(value, NAME_STORE);
82
93
  }
94
+
95
+ export function isReadonlySignal<Value>(value: unknown): value is ReadonlySignal<Value> {
96
+ return isMora<ReadonlySignal<Value>>(value, NAME_READONLY);
97
+ }
package/src/index.ts CHANGED
@@ -1,7 +1,23 @@
1
1
  export {startBatch, stopBatch} from './batch';
2
2
  export {effect} from './effect';
3
- export {isArray, isComputed, isEffect, isReactive, isSignal} from './helpers/is';
4
- export type {Computed, Effect, ReactiveArray, ReactiveStore, Signal, Unsubscribe} from './models';
3
+ export {
4
+ isComputed,
5
+ isEffect,
6
+ isReactive,
7
+ isReactiveArray,
8
+ isReactiveStore,
9
+ isReadonlySignal,
10
+ isSignal,
11
+ } from './helpers/is';
12
+ export type {
13
+ Computed,
14
+ Effect,
15
+ ReactiveArray,
16
+ ReactiveStore,
17
+ ReadonlySignal,
18
+ Signal,
19
+ Unsubscribe,
20
+ } from './models';
5
21
  export {array} from './value/array';
6
22
  export {computed} from './value/computed';
7
23
  export {signal} from './value/signal';
package/src/models.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type {GenericCallback, Key} from '@oscarpalmer/atoms/models';
1
+ import type {GenericCallback, Key, PlainObject} from '@oscarpalmer/atoms/models';
2
2
  import type {PROPERTY_LENGTH} from './constants';
3
3
 
4
4
  export type Active = {
@@ -52,6 +52,24 @@ export type ReactiveArray<Item> = {
52
52
  */
53
53
  set length(value: number);
54
54
 
55
+ /**
56
+ * Get a readonly version of the reactive array
57
+ *
58
+ * @param frozen Freeze the array? _(defaults to `false`)_
59
+ * @returns Readonly reactive array
60
+ */
61
+ asReadonly(frozen: true): ReadonlyFrozenSignal<Item[]>;
62
+
63
+ /**
64
+ * Get a readonly version of the reactive array
65
+ *
66
+ * @param frozen Freeze the array? _(defaults to `false`)_
67
+ * @returns Readonly reactive array
68
+ */
69
+ asReadonly(
70
+ frozen?: boolean,
71
+ ): typeof frozen extends true ? ReadonlyFrozenSignal<Item[]> : ReadonlySignal<Item[]>;
72
+
55
73
  /**
56
74
  * Get the value at an index
57
75
  *
@@ -114,17 +132,19 @@ export type ReactiveArray<Item> = {
114
132
  /**
115
133
  * Get the array _(without reactivity)_
116
134
  *
135
+ * @param copy Copy the array? _(defaults to `false`)_
117
136
  * @returns Array of items
118
137
  */
119
- peek(): Item[];
138
+ peek(copy?: boolean): Item[];
120
139
 
121
140
  /**
122
141
  * Get the value at an index _(without reactivity)_
123
142
  *
124
143
  * @param index Index of item to get _(if negative, starts from the end)_
144
+ * @param copy If the item is an array or record, should it be copied? _(defaults to `false`)_
125
145
  * @returns Item at index, or `undefined` if it doesn't exist
126
146
  */
127
- peek(index: number): Item | undefined;
147
+ peek(index: number, copy?: boolean): Item | undefined;
128
148
 
129
149
  /**
130
150
  * Get the length of the array _(without reactivity)_
@@ -272,13 +292,31 @@ export type ReactiveState<Value, Equal> = {
272
292
  value: Value;
273
293
  };
274
294
 
275
- export type ReactiveStore<Value> = {
295
+ export type ReactiveStore<Store> = {
296
+ /**
297
+ * Get a readonly version of the reactive store
298
+ *
299
+ * @param frozen Freeze the store? _(defaults to `false`)_
300
+ * @returns Readonly reactive store
301
+ */
302
+ asReadonly(frozen: true): ReadonlyFrozenSignal<Store>;
303
+
304
+ /**
305
+ * Get a readonly version of the reactive store
306
+ *
307
+ * @param frozen Freeze the store? _(defaults to `false`)_
308
+ * @returns Readonly reactive store
309
+ */
310
+ asReadonly(
311
+ frozen?: boolean,
312
+ ): typeof frozen extends true ? ReadonlyFrozenSignal<Store> : ReadonlySignal<Store>;
313
+
276
314
  /**
277
315
  * Get the value
278
316
  *
279
317
  * @returns Current value
280
318
  */
281
- get(): Value;
319
+ get(): Store;
282
320
 
283
321
  /**
284
322
  * Get a value by key
@@ -286,7 +324,7 @@ export type ReactiveStore<Value> = {
286
324
  * @param key Key of the value to get
287
325
  * @returns Value for the specified key, or `undefined` if it doesn't exist
288
326
  */
289
- get<Key extends keyof Value>(key: Key): Value[Key];
327
+ get<Key extends keyof Store>(key: Key): Store[Key];
290
328
 
291
329
  /**
292
330
  * Get a value by key
@@ -307,25 +345,28 @@ export type ReactiveStore<Value> = {
307
345
  /**
308
346
  * Get the value _(without reactivity)_
309
347
  *
348
+ * @param copy Copy the store? _(defaults to `false`)_
310
349
  * @returns Current value
311
350
  */
312
- peek(): Value;
351
+ peek(copy?: boolean): Store;
313
352
 
314
353
  /**
315
354
  * Get a value by key _(without reactivity)_
316
355
  *
317
356
  * @param key Key of the value to get
357
+ * @param copy If the value is an array or record, should it be copied? _(defaults to `false`)_
318
358
  * @returns Value for the specified key, or `undefined` if it doesn't exist
319
359
  */
320
- peek<Key extends keyof Value>(key: Key): Value[Key];
360
+ peek<Key extends keyof Store>(key: Key, copy?: boolean): Store[Key];
321
361
 
322
362
  /**
323
363
  * Get a value by key _(without reactivity)_
324
364
  *
325
365
  * @param key Key of the value to get
366
+ * @param copy If the value is an array or record, should it be copied? _(defaults to `false`)_
326
367
  * @returns Value for the specified key, or `undefined` if it doesn't exist
327
368
  */
328
- peek(key: Key): unknown;
369
+ peek(key: Key, copy?: boolean): unknown;
329
370
 
330
371
  /**
331
372
  * Set the value
@@ -334,9 +375,9 @@ export type ReactiveStore<Value> = {
334
375
  */
335
376
  set(
336
377
  value?:
337
- | Value
338
- | (() => null | undefined | Value | Promise<null | undefined | Value>)
339
- | Promise<null | undefined | Value>,
378
+ | Store
379
+ | (() => null | undefined | Store | Promise<null | undefined | Store>)
380
+ | Promise<null | undefined | Store>,
340
381
  ): void;
341
382
 
342
383
  /**
@@ -345,9 +386,9 @@ export type ReactiveStore<Value> = {
345
386
  * @param key Key of the value to set
346
387
  * @param value New value
347
388
  */
348
- set<Key extends keyof Value>(
389
+ set<Key extends keyof Store>(
349
390
  key: Key,
350
- value: Value[Key] | (() => Value[Key] | Promise<Value[Key]>) | Promise<Value[Key]>,
391
+ value: Store[Key] | (() => Store[Key] | Promise<Store[Key]>) | Promise<Store[Key]>,
351
392
  ): void;
352
393
 
353
394
  /**
@@ -364,7 +405,7 @@ export type ReactiveStore<Value> = {
364
405
  * @param callback Callback for changes
365
406
  * @returns Unsubscribe callback
366
407
  */
367
- subscribe(callback: (value: Value) => void): Unsubscribe;
408
+ subscribe(callback: (value: Store) => void): Unsubscribe;
368
409
 
369
410
  /**
370
411
  * Subscribe to changes for a specific key
@@ -373,9 +414,9 @@ export type ReactiveStore<Value> = {
373
414
  * @param callback Callback for changes
374
415
  * @returns Unsubscribe callback
375
416
  */
376
- subscribe<Key extends keyof Value>(
417
+ subscribe<Key extends keyof Store>(
377
418
  key: Key,
378
- callback: (value: Value[Key] | undefined) => void,
419
+ callback: (value: Store[Key] | undefined) => void,
379
420
  ): Unsubscribe;
380
421
 
381
422
  /**
@@ -393,25 +434,54 @@ export type ReactiveStore<Value> = {
393
434
  * @param key Key of the value to unsubscribe from
394
435
  * @param callback Callback to unsubscribe
395
436
  */
396
- unsubscribe<Key extends keyof Value>(
437
+ unsubscribe<Key extends keyof Store>(
397
438
  key: Key,
398
- callback: (value: Value[Key] | undefined) => void,
439
+ callback: (value: Store[Key] | undefined) => void,
399
440
  ): void;
400
441
 
442
+ /**
443
+ * Unsubscribe from changes for a specific key
444
+ *
445
+ * @param key Key of the value to unsubscribe from
446
+ * @param callback Callback to unsubscribe
447
+ */
448
+ unsubscribe(key: Key, callback: (value: unknown | undefined) => void): void;
449
+
401
450
  /**
402
451
  * Unsubscribe from changes
403
452
  *
404
453
  * @param callback Callback to unsubscribe
405
454
  */
406
- unsubscribe(callback: (value: Value) => void): void;
455
+ unsubscribe(callback: (value: Store) => void): void;
407
456
 
408
457
  /**
409
458
  * Update the value _(based on the current value)_
410
459
  *
411
460
  * @param callback Callback to update the value
412
461
  */
413
- update(callback: (value: Value) => Value): void;
414
- } & Reactive<Value>;
462
+ update(callback: (value: Store) => Store): void;
463
+ } & Reactive<Store>;
464
+
465
+ export type ReadonlyInstances<Value> = {
466
+ frozen?: ReadonlyFrozenSignal<Value>;
467
+ original?: ReadonlySignal<Value>;
468
+ };
469
+
470
+ export type ReadonlyFrozenSignal<Value> = ReadonlySignal<ReadonlySignalValue<Value>>;
471
+
472
+ export type ReadonlySignal<Value> = {
473
+ /**
474
+ * Is the signal frozen?
475
+ */
476
+ get frozen(): boolean;
477
+ } & Reactive<Value> &
478
+ SimpleReactive<Value>;
479
+
480
+ export type ReadonlySignalValue<Value> = Value extends unknown[]
481
+ ? Readonly<Value>
482
+ : Value extends PlainObject
483
+ ? Readonly<Value>
484
+ : Value;
415
485
 
416
486
  export type SetValueInProxyParameters<Value, Equal> = {
417
487
  target: Value;
@@ -423,6 +493,24 @@ export type SetValueInProxyParameters<Value, Equal> = {
423
493
  };
424
494
 
425
495
  export type Signal<Value> = {
496
+ /**
497
+ * Get a readonly version of the signal
498
+ *
499
+ * @param frozen Freeze the signal? _(defaults to `false`)_
500
+ * @returns Readonly signal
501
+ */
502
+ asReadonly(frozen: true): ReadonlyFrozenSignal<Value>;
503
+
504
+ /**
505
+ * Get a readonly version of the signal
506
+ *
507
+ * @param frozen Freeze the signal? _(defaults to `false`)_
508
+ * @returns Readonly signal
509
+ */
510
+ asReadonly(
511
+ frozen?: boolean,
512
+ ): typeof frozen extends true ? ReadonlyFrozenSignal<Value> : ReadonlySignal<Value>;
513
+
426
514
  /**
427
515
  * Set the value
428
516
  *