@oscarpalmer/atoms 0.191.0 → 0.193.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 (47) hide show
  1. package/dist/beacon.d.mts +6 -44
  2. package/dist/beacon.mjs +20 -67
  3. package/dist/color/constants.d.mts +82 -41
  4. package/dist/color/constants.mjs +102 -63
  5. package/dist/color/instance.mjs +36 -13
  6. package/dist/color/misc/alpha.mjs +9 -10
  7. package/dist/color/misc/format.mjs +4 -4
  8. package/dist/color/misc/get.mjs +18 -18
  9. package/dist/color/misc/is.mjs +22 -21
  10. package/dist/color/misc/state.d.mts +6 -5
  11. package/dist/color/misc/state.mjs +31 -26
  12. package/dist/color/models.d.mts +78 -1
  13. package/dist/color/space/hex.mjs +12 -12
  14. package/dist/color/space/hsl.mjs +16 -16
  15. package/dist/color/space/hwb.mjs +10 -10
  16. package/dist/color/space/rgb.mjs +16 -16
  17. package/dist/herald.d.mts +2 -3
  18. package/dist/index.d.mts +141 -67
  19. package/dist/index.mjs +552 -391
  20. package/dist/internal/subscription.d.mts +52 -0
  21. package/dist/internal/subscription.mjs +127 -0
  22. package/dist/models.d.mts +5 -1
  23. package/dist/queue.d.mts +25 -23
  24. package/dist/queue.mjs +27 -28
  25. package/package.json +1 -1
  26. package/src/beacon.ts +32 -117
  27. package/src/color/constants.ts +115 -78
  28. package/src/color/instance.ts +65 -12
  29. package/src/color/misc/alpha.ts +13 -21
  30. package/src/color/misc/format.ts +4 -4
  31. package/src/color/misc/get.ts +23 -40
  32. package/src/color/misc/is.ts +29 -39
  33. package/src/color/misc/state.ts +77 -41
  34. package/src/color/models.ts +94 -1
  35. package/src/color/space/hex.ts +18 -23
  36. package/src/color/space/hsl.ts +16 -24
  37. package/src/color/space/hwb.ts +10 -10
  38. package/src/color/space/rgb.ts +19 -16
  39. package/src/herald.ts +1 -3
  40. package/src/index.ts +1 -0
  41. package/src/internal/subscription.ts +264 -0
  42. package/src/models.ts +5 -0
  43. package/src/queue.ts +92 -92
  44. package/src/string/fuzzy.ts +1 -1
  45. package/src/value/freeze.ts +2 -2
  46. package/src/value/shake.ts +1 -1
  47. package/src/value/transform.ts +1 -1
@@ -1,16 +1,11 @@
1
1
  import {join} from '../../internal/string';
2
2
  import {
3
- ALPHA_FULL_HEX_LONG,
4
- ALPHA_FULL_HEX_SHORT,
5
- EXPRESSION_HEX_LONG,
6
- EXPRESSION_PREFIX,
7
- HEX_BLACK,
8
- LENGTH_LONG,
9
- LENGTH_SHORT,
10
- MAX_HEX,
11
- TYPE_HSL,
12
- TYPE_HWB,
13
- TYPE_RGB,
3
+ COLOR_ALPHA,
4
+ COLOR_DEFAULTS,
5
+ COLOR_EXPRESSION,
6
+ COLOR_LENGTHS,
7
+ COLOR_MAX,
8
+ COLOR_TYPE,
14
9
  } from '../constants';
15
10
  import {getHexValue, getPercentage} from '../misc/get';
16
11
  import {isHexColor} from '../misc/is';
@@ -30,7 +25,7 @@ import {convertRgbToHsla, convertRgbToHwba} from './rgb';
30
25
 
31
26
  function convertHexToRgba(value: string): RGBAColor {
32
27
  const normalized = getNormalizedHex(value, true);
33
- const pairs = EXPRESSION_HEX_LONG.exec(normalized) as RegExpExecArray;
28
+ const pairs = COLOR_EXPRESSION.hexLong.exec(normalized) as RegExpExecArray;
34
29
  const values: number[] = [];
35
30
 
36
31
  const {length} = pairs;
@@ -40,7 +35,7 @@ function convertHexToRgba(value: string): RGBAColor {
40
35
  }
41
36
 
42
37
  return {
43
- alpha: getPercentage((values[3] / MAX_HEX) * 100),
38
+ alpha: getPercentage((values[3] / COLOR_MAX.hex) * 100),
44
39
  blue: getHexValue(values[2]),
45
40
  green: getHexValue(values[1]),
46
41
  red: getHexValue(values[0]),
@@ -52,15 +47,15 @@ export function getColorFromHex<Type extends ColorType>(
52
47
  type: Type,
53
48
  ): NonNullable<ColorState[Type]> {
54
49
  switch (type) {
55
- case TYPE_HSL:
50
+ case COLOR_TYPE.hsl:
56
51
  state.hsl ??= hexToHsl(state.hex!);
57
52
  break;
58
53
 
59
- case TYPE_HWB:
54
+ case COLOR_TYPE.hwb:
60
55
  state.hwb ??= hexToHwb(state.hex!);
61
56
  break;
62
57
 
63
- case TYPE_RGB:
58
+ case COLOR_TYPE.rgb:
64
59
  state.rgb ??= hexToRgb(state.hex!);
65
60
  break;
66
61
  }
@@ -81,21 +76,21 @@ export function getNormalizedHex(value: unknown, alpha?: boolean): string {
81
76
  const includeAlpha = alpha ?? false;
82
77
 
83
78
  if (!isHexColor(value)) {
84
- return `${HEX_BLACK}${includeAlpha ? ALPHA_FULL_HEX_LONG : ''}`;
79
+ return `${COLOR_DEFAULTS.hexBlack}${includeAlpha ? COLOR_ALPHA.fullHexLong : ''}`;
85
80
  }
86
81
 
87
- const normalized = value.replace(EXPRESSION_PREFIX, '');
82
+ const normalized = value.replace(COLOR_EXPRESSION.prefix, '');
88
83
 
89
- if (normalized.length < LENGTH_LONG) {
90
- const hex = normalized.slice(0, LENGTH_SHORT);
91
- const a = includeAlpha ? (normalized[LENGTH_SHORT] ?? ALPHA_FULL_HEX_SHORT) : '';
84
+ if (normalized.length < COLOR_LENGTHS.hexLong) {
85
+ const hex = normalized.slice(0, COLOR_LENGTHS.hexShort);
86
+ const a = includeAlpha ? (normalized[COLOR_LENGTHS.hexShort] ?? COLOR_ALPHA.fullHexShort) : '';
92
87
 
93
88
  return join(`${hex}${a}`.split('').map(character => character.repeat(2)));
94
89
  }
95
90
 
96
- const hex = normalized.slice(0, LENGTH_LONG);
91
+ const hex = normalized.slice(0, COLOR_LENGTHS.hexLong);
97
92
 
98
- const a = includeAlpha ? normalized.slice(LENGTH_LONG) || ALPHA_FULL_HEX_LONG : '';
93
+ const a = includeAlpha ? normalized.slice(COLOR_LENGTHS.hexLong) || COLOR_ALPHA.fullHexLong : '';
99
94
 
100
95
  return `${hex}${a}`;
101
96
  }
@@ -1,12 +1,4 @@
1
- import {
2
- DEFAULT_HSL,
3
- MAX_DEGREE,
4
- MAX_HEX,
5
- MAX_PERCENT,
6
- TYPE_HEX,
7
- TYPE_HWB,
8
- TYPE_RGB,
9
- } from '../constants';
1
+ import {COLOR_DEFAULTS, COLOR_MAX, COLOR_TYPE} from '../constants';
10
2
  import {getAlphaValue} from '../misc/alpha';
11
3
  import {getDegrees, getHexValue, getPercentage} from '../misc/get';
12
4
  import {isHslLike} from '../misc/is';
@@ -25,12 +17,12 @@ import {convertRgbToHex} from './rgb';
25
17
  // #region Functions
26
18
 
27
19
  function convertHslToHwba(input: unknown): HWBAColor {
28
- const hsl = isHslLike(input) ? getHslValue(input) : {...DEFAULT_HSL};
20
+ const hsl = isHslLike(input) ? getHslValue(input) : {...COLOR_DEFAULTS.hsl};
29
21
 
30
22
  let {hue, lightness, saturation} = hsl;
31
23
 
32
- lightness /= MAX_PERCENT;
33
- saturation /= MAX_PERCENT;
24
+ lightness /= COLOR_MAX.percent;
25
+ saturation /= COLOR_MAX.percent;
34
26
 
35
27
  const value = lightness + saturation * Math.min(lightness, 1 - lightness);
36
28
 
@@ -45,21 +37,21 @@ function convertHslToHwba(input: unknown): HWBAColor {
45
37
 
46
38
  return {
47
39
  hue,
48
- whiteness: whiteness * MAX_PERCENT,
49
- blackness: blackness * MAX_PERCENT,
50
- alpha: getAlphaValue((input as HSLAColor)?.alpha ?? MAX_PERCENT),
40
+ whiteness: whiteness * COLOR_MAX.percent,
41
+ blackness: blackness * COLOR_MAX.percent,
42
+ alpha: getAlphaValue((input as HSLAColor)?.alpha ?? COLOR_MAX.percent),
51
43
  };
52
44
  }
53
45
 
54
46
  function convertHslToRgba(input: unknown): RGBAColor {
55
- const hsl = isHslLike(input) ? getHslValue(input) : {...DEFAULT_HSL};
47
+ const hsl = isHslLike(input) ? getHslValue(input) : {...COLOR_DEFAULTS.hsl};
56
48
 
57
- const hue = hsl.hue % MAX_DEGREE;
58
- const saturation = hsl.saturation / MAX_PERCENT;
59
- const lightness = hsl.lightness / MAX_PERCENT;
49
+ const hue = hsl.hue % COLOR_MAX.degree;
50
+ const saturation = hsl.saturation / COLOR_MAX.percent;
51
+ const lightness = hsl.lightness / COLOR_MAX.percent;
60
52
 
61
53
  return {
62
- alpha: getAlphaValue((input as HSLAColor)?.alpha ?? MAX_PERCENT),
54
+ alpha: getAlphaValue((input as HSLAColor)?.alpha ?? COLOR_MAX.percent),
63
55
  blue: getHexValue(getHexyValue(hue, lightness, saturation, 4)),
64
56
  green: getHexValue(getHexyValue(hue, lightness, saturation, 8)),
65
57
  red: getHexValue(getHexyValue(hue, lightness, saturation, 0)),
@@ -71,15 +63,15 @@ export function getColorFromHsl<Type extends ColorType>(
71
63
  type: Type,
72
64
  ): NonNullable<ColorState[Type]> {
73
65
  switch (type) {
74
- case TYPE_HEX:
66
+ case COLOR_TYPE.hex:
75
67
  state.hex = hslToHex(state.hsl!);
76
68
  break;
77
69
 
78
- case TYPE_HWB:
70
+ case COLOR_TYPE.hwb:
79
71
  state.hwb = hslToHwb(state.hsl!);
80
72
  break;
81
73
 
82
- case TYPE_RGB:
74
+ case COLOR_TYPE.rgb:
83
75
  state.rgb = hslToRgb(state.hsl!);
84
76
  break;
85
77
  }
@@ -91,7 +83,7 @@ function getHexyValue(hue: number, lightness: number, saturation: number, value:
91
83
  const part = (value + hue / 30) % 12;
92
84
  const mod = saturation * Math.min(lightness, 1 - lightness);
93
85
 
94
- return (lightness - mod * Math.max(-1, Math.min(part - 3, 9 - part, 1))) * MAX_HEX;
86
+ return (lightness - mod * Math.max(-1, Math.min(part - 3, 9 - part, 1))) * COLOR_MAX.hex;
95
87
  }
96
88
 
97
89
  export function getHslValue(value: Record<keyof HSLColor, unknown>): HSLColor {
@@ -1,4 +1,4 @@
1
- import {DEFAULT_HWB, MAX_PERCENT, TYPE_HEX, TYPE_HSL, TYPE_RGB} from '../constants';
1
+ import {COLOR_DEFAULTS, COLOR_MAX, COLOR_TYPE} from '../constants';
2
2
  import {getAlphaValue} from '../misc/alpha';
3
3
  import {getDegrees, getPercentage} from '../misc/get';
4
4
  import {isHwbLike} from '../misc/is';
@@ -18,10 +18,10 @@ import {rgbToHex} from './rgb';
18
18
  // #region Functions
19
19
 
20
20
  export function convertHwbToHsl(input: HWBAColor | HWBColor): HSLAColor {
21
- let {blackness, hue, whiteness} = isHwbLike(input) ? getHwbValue(input) : DEFAULT_HWB;
21
+ let {blackness, hue, whiteness} = isHwbLike(input) ? getHwbValue(input) : COLOR_DEFAULTS.hwb;
22
22
 
23
- blackness /= MAX_PERCENT;
24
- whiteness /= MAX_PERCENT;
23
+ blackness /= COLOR_MAX.percent;
24
+ whiteness /= COLOR_MAX.percent;
25
25
 
26
26
  if (blackness + whiteness > 1) {
27
27
  const total = blackness + whiteness;
@@ -36,9 +36,9 @@ export function convertHwbToHsl(input: HWBAColor | HWBColor): HSLAColor {
36
36
 
37
37
  return {
38
38
  hue,
39
- saturation: saturation * MAX_PERCENT,
40
- lightness: lightness * MAX_PERCENT,
41
- alpha: getAlphaValue((input as HWBAColor)?.alpha ?? MAX_PERCENT),
39
+ saturation: saturation * COLOR_MAX.percent,
40
+ lightness: lightness * COLOR_MAX.percent,
41
+ alpha: getAlphaValue((input as HWBAColor)?.alpha ?? COLOR_MAX.percent),
42
42
  };
43
43
  }
44
44
 
@@ -47,15 +47,15 @@ export function getColorFromHwb<Type extends ColorType>(
47
47
  type: Type,
48
48
  ): NonNullable<ColorState[Type]> {
49
49
  switch (type) {
50
- case TYPE_HEX:
50
+ case COLOR_TYPE.hex:
51
51
  state.hex = hwbToHex(state.hwb!);
52
52
  break;
53
53
 
54
- case TYPE_HSL:
54
+ case COLOR_TYPE.hsl:
55
55
  state.hsl = hwbToHsl(state.hwb!);
56
56
  break;
57
57
 
58
- case TYPE_RGB:
58
+ case COLOR_TYPE.rgb:
59
59
  state.rgb = hwbToRgb(state.hwb!);
60
60
  break;
61
61
  }
@@ -1,5 +1,5 @@
1
1
  import {join} from '../../internal/string';
2
- import {DEFAULT_RGB, MAX_HEX, MAX_PERCENT, TYPE_HEX, TYPE_HSL, TYPE_HWB} from '../constants';
2
+ import {COLOR_DEFAULTS, COLOR_MAX, COLOR_TYPE} from '../constants';
3
3
  import {getAlpha, getAlphaValue} from '../misc/alpha';
4
4
  import {getHexValue} from '../misc/get';
5
5
  import {isRgbLike} from '../misc/is';
@@ -48,7 +48,7 @@ export function convertRgbToHex(rgb: RGBAColor | RGBColor, alpha: boolean): stri
48
48
  }
49
49
 
50
50
  export function convertRgbToHsla(value: unknown): HSLAColor {
51
- const rgb = isRgbLike(value) ? getRgbValue(value) : {...DEFAULT_RGB};
51
+ const rgb = isRgbLike(value) ? getRgbValue(value) : {...COLOR_DEFAULTS.rgb};
52
52
  const values = getRgbValues(rgb);
53
53
 
54
54
  const {delta, max, min} = values;
@@ -68,14 +68,14 @@ export function convertRgbToHsla(value: unknown): HSLAColor {
68
68
 
69
69
  return {
70
70
  hue,
71
- saturation: saturation * MAX_PERCENT,
72
- lightness: lightness * MAX_PERCENT,
73
- alpha: getAlphaValue((value as RGBAColor)?.alpha ?? MAX_PERCENT),
71
+ saturation: saturation * COLOR_MAX.percent,
72
+ lightness: lightness * COLOR_MAX.percent,
73
+ alpha: getAlphaValue((value as RGBAColor)?.alpha ?? COLOR_MAX.percent),
74
74
  };
75
75
  }
76
76
 
77
77
  export function convertRgbToHwba(value: unknown): HWBAColor {
78
- const rgb = isRgbLike(value) ? getRgbValue(value) : {...DEFAULT_RGB};
78
+ const rgb = isRgbLike(value) ? getRgbValue(value) : {...COLOR_DEFAULTS.rgb};
79
79
  const values = getRgbValues(rgb);
80
80
 
81
81
  const {delta, max, min} = values;
@@ -90,9 +90,9 @@ export function convertRgbToHwba(value: unknown): HWBAColor {
90
90
 
91
91
  return {
92
92
  hue,
93
- whiteness: min * MAX_PERCENT,
94
- blackness: (1 - max) * MAX_PERCENT,
95
- alpha: getAlphaValue((value as RGBAColor)?.alpha ?? MAX_PERCENT),
93
+ whiteness: min * COLOR_MAX.percent,
94
+ blackness: (1 - max) * COLOR_MAX.percent,
95
+ alpha: getAlphaValue((value as RGBAColor)?.alpha ?? COLOR_MAX.percent),
96
96
  };
97
97
  }
98
98
 
@@ -101,15 +101,15 @@ export function getColorFromRgb<Type extends ColorType>(
101
101
  type: Type,
102
102
  ): NonNullable<ColorState[Type]> {
103
103
  switch (type) {
104
- case TYPE_HEX:
104
+ case COLOR_TYPE.hex:
105
105
  state.hex = rgbToHex(state.rgb!);
106
106
  break;
107
107
 
108
- case TYPE_HSL:
108
+ case COLOR_TYPE.hsl:
109
109
  state.hsl = rgbToHsl(state.rgb!);
110
110
  break;
111
111
 
112
- case TYPE_HWB:
112
+ case COLOR_TYPE.hwb:
113
113
  state.hwb = rgbToHwb(state.rgb!);
114
114
  break;
115
115
  }
@@ -141,9 +141,9 @@ export function getRgbValue(value: Record<keyof RGBColor, unknown>): RGBColor {
141
141
  }
142
142
 
143
143
  function getRgbValues(rgb: RGBColor): RgbValues {
144
- const blue = rgb.blue / MAX_HEX;
145
- const green = rgb.green / MAX_HEX;
146
- const red = rgb.red / MAX_HEX;
144
+ const blue = rgb.blue / COLOR_MAX.hex;
145
+ const green = rgb.green / COLOR_MAX.hex;
146
+ const red = rgb.red / COLOR_MAX.hex;
147
147
 
148
148
  const max = Math.max(blue, green, red);
149
149
  const min = Math.min(blue, green, red);
@@ -163,7 +163,10 @@ function getRgbValues(rgb: RGBColor): RgbValues {
163
163
  * @returns Hex color string
164
164
  */
165
165
  export function rgbToHex(rgb: RGBAColor | RGBColor, alpha?: boolean): string {
166
- return convertRgbToHex(isRgbLike(rgb) ? getRgbValue(rgb) : {...DEFAULT_RGB}, alpha ?? false);
166
+ return convertRgbToHex(
167
+ isRgbLike(rgb) ? getRgbValue(rgb) : {...COLOR_DEFAULTS.rgb},
168
+ alpha ?? false,
169
+ );
167
170
  }
168
171
 
169
172
  /**
package/src/herald.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import {noop} from './internal/function/misc';
2
- import type {GenericCallback} from './models';
2
+ import type {GenericCallback, Unsubscriber} from './models';
3
3
 
4
4
  // #region Types
5
5
 
@@ -57,8 +57,6 @@ export type Herald<Map extends Record<string, GenericCallback>> = {
57
57
  unsubscribe<Event extends keyof Map>(event: Event, callback?: Map[Event]): void;
58
58
  };
59
59
 
60
- export type Unsubscriber = () => void;
61
-
62
60
  // #endregion
63
61
 
64
62
  // #region Functions
package/src/index.ts CHANGED
@@ -21,6 +21,7 @@ export * from './herald';
21
21
 
22
22
  export * from './internal/function/misc';
23
23
  export * from './internal/string';
24
+ export {isSubscription, type Subscription} from './internal/subscription';
24
25
  export * from './internal/value/compare';
25
26
  export * from './internal/value/equal';
26
27
  export * from './internal/value/get';
@@ -0,0 +1,264 @@
1
+ import type {Key} from '../models';
2
+ import {isPlainObject} from './is';
3
+
4
+ // #region Types
5
+
6
+ export type Subscription = {
7
+ /**
8
+ * Is the subscription active?
9
+ */
10
+ get active(): boolean;
11
+
12
+ /**
13
+ * Unsubscribe from changes
14
+ */
15
+ unsubscribe(): void;
16
+ };
17
+
18
+ export type SubscriptionParameters = {
19
+ isActive?: () => boolean;
20
+ key?: Key;
21
+ property: SubscriptionProperty;
22
+ subscriptions: Subscriptions;
23
+ value: unknown;
24
+ };
25
+
26
+ export type SubscriptionProperty = {
27
+ key: string;
28
+ value: unknown;
29
+ };
30
+
31
+ type SubscriptionState = {
32
+ active: boolean;
33
+ parameters: SubscriptionParameters;
34
+ };
35
+
36
+ export type Subscriptions<Value = unknown> = {
37
+ items: SubscriptionsItems;
38
+ keys?: Set<Key>;
39
+ values: SubscriptionsValues<Value>;
40
+ };
41
+
42
+ type SubscriptionsItems = {
43
+ any: Set<Subscription>;
44
+ keyed?: Map<Key, Set<Subscription>>;
45
+ };
46
+
47
+ type SubscriptionsValues<Value = unknown> = {
48
+ from: SubscriptionsValuesItem<Value, Subscription>;
49
+ to: SubscriptionsValuesItem<Subscription, Value>;
50
+ };
51
+
52
+ type SubscriptionsValuesItem<MapKey, MapValue> = {
53
+ any: Map<MapKey, MapValue>;
54
+ keyed?: Map<Key, Map<MapKey, MapValue>>;
55
+ };
56
+
57
+ // #endregion
58
+
59
+ // #region Functions
60
+
61
+ function addSubscription(subscription: Subscription, state: SubscriptionState): void {
62
+ const {key, subscriptions, value} = state.parameters;
63
+ const {items, values} = subscriptions;
64
+
65
+ if (key == null) {
66
+ items.any.add(subscription);
67
+ values.from.any.set(value, subscription);
68
+ values.to.any.set(subscription, value);
69
+
70
+ return;
71
+ }
72
+
73
+ if (items.keyed == null || values.from.keyed == null || values.to.keyed == null) {
74
+ // TODO: fix tests or document
75
+ // istanbul ignore next
76
+ return;
77
+ }
78
+
79
+ let keyedItems = items.keyed.get(key);
80
+
81
+ if (keyedItems == null) {
82
+ keyedItems = new Set();
83
+
84
+ items.keyed.set(key, keyedItems);
85
+ }
86
+
87
+ keyedItems.add(subscription);
88
+
89
+ let keyedFrom = values.from.keyed.get(key);
90
+
91
+ if (keyedFrom == null) {
92
+ keyedFrom = new Map();
93
+
94
+ values.from.keyed.set(key, keyedFrom);
95
+ }
96
+
97
+ keyedFrom.set(value, subscription);
98
+
99
+ let keyedTo = values.to.keyed.get(key);
100
+
101
+ if (keyedTo == null) {
102
+ keyedTo = new Map();
103
+
104
+ values.to.keyed.set(key, keyedTo);
105
+ }
106
+
107
+ keyedTo.set(subscription, value);
108
+ }
109
+
110
+ export function clearSubscriptions(store: Subscriptions): void {
111
+ for (const susbcription of store.values.to.any.keys()) {
112
+ susbcription.unsubscribe();
113
+ }
114
+
115
+ if (store.values.to.keyed != null) {
116
+ for (const values of store.values.to.keyed.values()) {
117
+ for (const susbcription of values.keys()) {
118
+ susbcription.unsubscribe();
119
+ }
120
+ }
121
+ }
122
+ }
123
+
124
+ function getExistingSubscription(state: SubscriptionState): Subscription | undefined {
125
+ const {parameters} = state;
126
+ const {values} = parameters.subscriptions;
127
+
128
+ return parameters.key == null
129
+ ? values.from.any.get(parameters.value)
130
+ : values.from.keyed?.get(parameters.key)?.get(parameters.value);
131
+ }
132
+
133
+ export function getSubscription(parameters: SubscriptionParameters): Subscription {
134
+ const state: SubscriptionState = {
135
+ parameters,
136
+ active: true,
137
+ };
138
+
139
+ const existing = getExistingSubscription(state);
140
+
141
+ if (existing != null) {
142
+ return existing;
143
+ }
144
+
145
+ if (
146
+ parameters.subscriptions.keys != null &&
147
+ parameters.key != null &&
148
+ !parameters.subscriptions.keys.has(parameters.key)
149
+ ) {
150
+ throw new Error();
151
+ }
152
+
153
+ const instance = {
154
+ unsubscribe: () => {
155
+ removeSubscription(instance as Subscription, state);
156
+ },
157
+ };
158
+
159
+ Object.defineProperties(instance, {
160
+ [PROPERTY]: {
161
+ enumerable: false,
162
+ value: true,
163
+ },
164
+ [parameters.property.key]: {
165
+ enumerable: false,
166
+ value: parameters.property.value,
167
+ },
168
+ active: {
169
+ enumerable: true,
170
+ get: () => (parameters.isActive?.() ?? true) && state.active,
171
+ },
172
+ });
173
+
174
+ addSubscription(instance as Subscription, state);
175
+
176
+ return Object.freeze(instance) as Subscription;
177
+ }
178
+
179
+ export function getSubscriptions<Value = unknown>(keys?: Set<Key>): Subscriptions<Value> {
180
+ return {
181
+ keys,
182
+ items: {
183
+ any: new Set(),
184
+ keyed: keys == null ? undefined : new Map(),
185
+ },
186
+ values: {
187
+ from: {
188
+ any: new Map(),
189
+ keyed: keys == null ? undefined : new Map(),
190
+ },
191
+ to: {
192
+ any: new Map(),
193
+ keyed: keys == null ? undefined : new Map(),
194
+ },
195
+ },
196
+ };
197
+ }
198
+
199
+ /**
200
+ * Is the value a subscription?
201
+ *
202
+ * @param value Value to check
203
+ * @returns `true` if the value is a subscription, otherwise `false`
204
+ */
205
+ export function isSubscription(value: unknown): value is Subscription {
206
+ return isPlainObject(value) && PROPERTY in value && value[PROPERTY] === true;
207
+ }
208
+
209
+ function removeFromStore(
210
+ items: Set<Subscription>,
211
+ values: SubscriptionsValues,
212
+ subscription: Subscription,
213
+ value: unknown,
214
+ key?: Key,
215
+ ): void {
216
+ items.delete(subscription);
217
+
218
+ if (key == null) {
219
+ values.from.any.delete(value);
220
+ values.to.any.delete(subscription);
221
+ } else {
222
+ values.from.keyed?.get(key)?.delete(value);
223
+ values.to.keyed?.get(key)?.delete(subscription);
224
+ }
225
+ }
226
+
227
+ function removeSubscription(subscription: Subscription, state: SubscriptionState): void {
228
+ if (!state.active) {
229
+ return;
230
+ }
231
+
232
+ state.active = false;
233
+
234
+ const {key, subscriptions, value} = state.parameters;
235
+ const {items, values} = subscriptions;
236
+
237
+ if (key == null) {
238
+ removeFromStore(items.any, values, subscription, value);
239
+
240
+ return;
241
+ }
242
+
243
+ const keyed = items.keyed?.get(key);
244
+
245
+ if (items.keyed == null || keyed == null) {
246
+ // TODO, fix tests or document
247
+ // istanbul ignore next
248
+ return;
249
+ }
250
+
251
+ removeFromStore(keyed, values, subscription, value, key);
252
+
253
+ if (keyed.size === 0) {
254
+ items.keyed.delete(key);
255
+ }
256
+ }
257
+
258
+ // #endregion
259
+
260
+ // #region Variables
261
+
262
+ const PROPERTY = '$subscription';
263
+
264
+ // #endregion
package/src/models.ts CHANGED
@@ -278,4 +278,9 @@ export type UnionToIntersection<Union> = (
278
278
  ? Intersection & Union
279
279
  : never;
280
280
 
281
+ /**
282
+ * A function that unsubscribes from a subscription
283
+ */
284
+ export type Unsubscriber = () => void;
285
+
281
286
  // #endregion