@oscarpalmer/atoms 0.71.0 → 0.72.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.
@@ -0,0 +1,203 @@
1
+ import {clamp} from './number';
2
+
3
+ /**
4
+ * A Map with a maximum size
5
+ * - Maximum size defaults to _2^20_; any provided size will be clamped at _2^24_
6
+ * - Behaviour is similar to a _LRU_-cache, where the least recently used entries are removed
7
+ */
8
+ export class SizedMap<Key = unknown, Value = unknown> extends Map<Key, Value> {
9
+ private readonly maximumSize: number;
10
+
11
+ /**
12
+ * Is the Map full?
13
+ */
14
+ get full() {
15
+ return this.size >= this.maximumSize;
16
+ }
17
+
18
+ /**
19
+ * The maximum size of the Map
20
+ */
21
+ get maximum() {
22
+ return this.maximumSize;
23
+ }
24
+
25
+ /**
26
+ * Creates a new Map with entries and a maximum size _(2^20)_
27
+ */
28
+ constructor(entries: Array<[Key, Value]>);
29
+
30
+ /**
31
+ * Creates a new Map with a maximum size _(but clamped at 2^24)_
32
+ */
33
+ constructor(maximum: number);
34
+
35
+ /**
36
+ * Creates a new Map with _(optional)_ entries and a maximum size _(defaults to 2^20; clamped at 2^24)_
37
+ */
38
+ constructor(entries?: Array<[Key, Value]>, maximum?: number);
39
+
40
+ constructor(entries?: Array<[Key, Value]> | number, maximum?: number) {
41
+ const maximumSize = getMaximum(
42
+ typeof entries === 'number'
43
+ ? entries
44
+ : typeof maximum === 'number'
45
+ ? maximum
46
+ : undefined,
47
+ );
48
+
49
+ super(Array.isArray(entries) ? entries.slice(0, maximumSize) : undefined);
50
+
51
+ this.maximumSize = maximumSize;
52
+
53
+ if (Array.isArray(entries) && entries.length > maximumSize) {
54
+ for (let index = 0; index < maximumSize; index += 1) {
55
+ this.set(...entries[entries.length - maximumSize + index]);
56
+ }
57
+ }
58
+ }
59
+
60
+ /**
61
+ * @inheritdoc
62
+ */
63
+ get(key: Key): Value | undefined {
64
+ const value = super.get(key);
65
+
66
+ if (value === undefined && !this.has(key)) {
67
+ return;
68
+ }
69
+
70
+ this.set(key, value as Value);
71
+
72
+ return value;
73
+ }
74
+
75
+ /**
76
+ * @inheritdoc
77
+ */
78
+ set(key: Key, value: Value): this {
79
+ if (this.has(key)) {
80
+ this.delete(key);
81
+ } else if (this.size >= this.maximumSize) {
82
+ this.delete(this.keys().next().value as Key);
83
+ }
84
+
85
+ return super.set(key, value);
86
+ }
87
+ }
88
+
89
+ /**
90
+ * A Set with a maximum size
91
+ * - Maximum size defaults to _2^20_; any provided size will be clamped at _2^24_
92
+ * - Behaviour is similar to a _LRU_-cache, where the oldest values are removed
93
+ */
94
+ export class SizedSet<Value = unknown> extends Set<Value> {
95
+ private readonly maximumSize: number;
96
+
97
+ /**
98
+ * Is the Set full?
99
+ */
100
+ get full() {
101
+ return this.size >= this.maximumSize;
102
+ }
103
+
104
+ /**
105
+ * The maximum size of the Set
106
+ */
107
+ get maximum() {
108
+ return this.maximumSize;
109
+ }
110
+
111
+ /**
112
+ * Creates a new Set with values and a maximum size _(2^20)_
113
+ */
114
+ constructor(values: Value[]);
115
+
116
+ /**
117
+ * Creates a new Set with a maximum size _(but clamped at 2^24)_
118
+ */
119
+ constructor(maximum: number);
120
+
121
+ /**
122
+ * Creates a new Set with _(optional)_ values and a maximum size _(defaults to 2^20; clamped at 2^24)_
123
+ */
124
+ constructor(values?: Value[], maximum?: number);
125
+
126
+ constructor(values?: Value[] | number, maximum?: number) {
127
+ const maximumSize = getMaximum(
128
+ typeof values === 'number'
129
+ ? values
130
+ : typeof maximum === 'number'
131
+ ? maximum
132
+ : undefined,
133
+ );
134
+
135
+ super(
136
+ Array.isArray(values) && values.length <= maximumSize
137
+ ? values
138
+ : undefined,
139
+ );
140
+
141
+ this.maximumSize = maximumSize;
142
+
143
+ if (Array.isArray(values) && values.length > maximumSize) {
144
+ for (let index = 0; index < maximumSize; index += 1) {
145
+ this.add(values[values.length - maximumSize + index]);
146
+ }
147
+ }
148
+ }
149
+
150
+ /**
151
+ * @inheritdoc
152
+ */
153
+ add(value: Value): this {
154
+ if (this.has(value)) {
155
+ this.delete(value);
156
+ } else if (this.size >= this.maximumSize) {
157
+ this.delete(this.values().next().value as Value);
158
+ }
159
+
160
+ return super.add(value);
161
+ }
162
+
163
+ /**
164
+ * Get a value from an index in the Set, if it exists
165
+ * - Negative indices are counted from the end
166
+ * - Optionally move the value to the end with `update`
167
+ */
168
+ at(index: number, update?: boolean): Value | undefined {
169
+ const value = [...this.values()][index < 0 ? this.size + index : index];
170
+
171
+ if ((update ?? false) && this.has(value)) {
172
+ this.delete(value);
173
+ this.add(value);
174
+ }
175
+
176
+ return value;
177
+ }
178
+
179
+ /**
180
+ * Get a value from the Set, if it exists _(and move it to the end)_
181
+ */
182
+ get(value: Value, update?: boolean): Value | undefined {
183
+ if (this.has(value)) {
184
+ if (update ?? false) {
185
+ this.delete(value);
186
+ this.add(value);
187
+ }
188
+
189
+ return value;
190
+ }
191
+ }
192
+ }
193
+
194
+ function getMaximum(first?: unknown, second?: unknown): number {
195
+ const actual =
196
+ (typeof first === 'number'
197
+ ? first
198
+ : typeof second === 'number'
199
+ ? second
200
+ : undefined) ?? 2 ** 20;
201
+
202
+ return clamp(actual, 1, 2 ** 24);
203
+ }
@@ -85,4 +85,3 @@ export function words(value: string): string[] {
85
85
 
86
86
  export * from './case';
87
87
  export * from './template';
88
-
@@ -1,5 +1,6 @@
1
1
  import type {PlainObject} from '../models';
2
2
  import {getValue} from '../value';
3
+ import {getString} from './index';
3
4
 
4
5
  type Options = {
5
6
  /**
@@ -35,7 +36,7 @@ export function template(
35
36
  return '';
36
37
  }
37
38
 
38
- values[key] = String(value);
39
+ values[key] = getString(value);
39
40
 
40
41
  return values[key];
41
42
  });
@@ -0,0 +1,85 @@
1
+ import {max} from '../math';
2
+ import {getNumber} from '../number';
3
+ import {words, getString, join} from '../string';
4
+
5
+ /**
6
+ * Compare two values _(for sorting purposes)_
7
+ */
8
+ export function compare(first: unknown, second: unknown): number {
9
+ const firstParts = getParts(first);
10
+ const secondParts = getParts(second);
11
+ const length = max([firstParts.length, secondParts.length]);
12
+ const lastIndex = length - 1;
13
+
14
+ for (let index = 0; index < length; index += 1) {
15
+ const firstPart = firstParts[index];
16
+ const secondPart = secondParts[index];
17
+
18
+ if (firstPart === secondPart) {
19
+ if (index === lastIndex) {
20
+ return 0;
21
+ }
22
+
23
+ continue;
24
+ }
25
+
26
+ if (
27
+ firstPart == null ||
28
+ (typeof firstPart === 'string' && firstPart.length === 0)
29
+ ) {
30
+ return -1;
31
+ }
32
+
33
+ if (
34
+ secondPart == null ||
35
+ (typeof secondPart === 'string' && secondPart.length === 0)
36
+ ) {
37
+ return 1;
38
+ }
39
+
40
+ const firstNumber = getNumber(firstPart);
41
+ const secondNumber = getNumber(secondPart);
42
+
43
+ const firstIsNaN = Number.isNaN(firstNumber);
44
+ const secondIsNaN = Number.isNaN(secondNumber);
45
+
46
+ if (firstIsNaN || secondIsNaN) {
47
+ if (firstIsNaN && secondIsNaN) {
48
+ return getString(firstPart).localeCompare(getString(secondPart));
49
+ }
50
+
51
+ if (firstIsNaN) {
52
+ return 1;
53
+ }
54
+
55
+ if (secondIsNaN) {
56
+ return -1;
57
+ }
58
+ }
59
+
60
+ if (firstNumber === secondNumber) {
61
+ if (index === lastIndex) {
62
+ // Same value on last part? let's not fall through to string comparison
63
+ return 0;
64
+ }
65
+
66
+ continue;
67
+ }
68
+
69
+ return firstNumber - secondNumber;
70
+ }
71
+
72
+ return join(firstParts).localeCompare(join(secondParts));
73
+ }
74
+
75
+ function getParts(value: unknown): unknown[] {
76
+ if (value == null) {
77
+ return [''];
78
+ }
79
+
80
+ if (Array.isArray(value)) {
81
+ return value;
82
+ }
83
+
84
+ return typeof value === 'object' ? [value] : words(getString(value));
85
+ }
@@ -4,22 +4,23 @@ import type {PlainObject} from '../models';
4
4
  * Creates a new object with only the specified keys
5
5
  */
6
6
  export function partial<Value extends PlainObject, Key extends keyof Value>(
7
- value: Value,
8
- keys: Key[],
9
- ): Pick<Value, Key> {
10
- const result = {} as Pick<Value, Key>;
11
- const {length} = keys;
7
+ value: Value,
8
+ keys: Key[],
9
+ ): Pick<Value, Key> {
10
+ const result = {} as Pick<Value, Key>;
11
+ const {length} = keys;
12
12
 
13
- for (let index = 0; index < length; index += 1) {
14
- const key = keys[index];
13
+ for (let index = 0; index < length; index += 1) {
14
+ const key = keys[index];
15
15
 
16
- result[key] = value[key];
17
- }
18
-
19
- return result;
16
+ result[key] = value[key];
20
17
  }
21
18
 
19
+ return result;
20
+ }
21
+
22
22
  export * from './clone';
23
+ export * from './compare';
23
24
  export * from './diff';
24
25
  export * from './equal';
25
26
  export * from './get';
@@ -27,4 +28,3 @@ export * from './merge';
27
28
  export * from './set';
28
29
  export * from './smush';
29
30
  export * from './unsmush';
30
-
@@ -38,7 +38,6 @@ export function setValue<Data extends PlainObject>(
38
38
  const {length} = parts;
39
39
  const lastIndex = length - 1;
40
40
 
41
- let previous: PlainObject | undefined;
42
41
  let target: PlainObject =
43
42
  typeof data === 'object' && data !== null ? data : {};
44
43
 
@@ -59,7 +58,6 @@ export function setValue<Data extends PlainObject>(
59
58
  target[part] = next;
60
59
  }
61
60
 
62
- previous = target;
63
61
  target = next as PlainObject;
64
62
  }
65
63
 
@@ -67,6 +67,7 @@ declare class Subscription<Value> {
67
67
  * Is the subscription closed?
68
68
  */
69
69
  get closed(): boolean;
70
+ destroy(): void;
70
71
  /**
71
72
  * Unsubscribes from the observable
72
73
  */
@@ -16,6 +16,10 @@ declare class Memoised<Callback extends GenericCallback> {
16
16
  * Deletes a result from the cache
17
17
  */
18
18
  delete(key: Parameters<Callback>[0]): boolean;
19
+ /**
20
+ * Destroys the instance, clearing its cache and removing its callback
21
+ */
22
+ destroy(): void;
19
23
  /**
20
24
  * Retrieves the result from the cache if it exists, or `undefined` otherwise
21
25
  */