@just-io/utils 2.0.0 → 2.0.1
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.
- package/README.md +239 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,12 +1,245 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
@just-io/utils
|
|
2
|
+
==============
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
Lightweight TypeScript utility functions.
|
|
5
5
|
|
|
6
6
|
## Installation
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
```bash
|
|
9
|
+
npm install @just-io/utils
|
|
10
|
+
```
|
|
9
11
|
|
|
10
|
-
##
|
|
12
|
+
## Utilities
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
### debounce / debounceByKey
|
|
15
|
+
|
|
16
|
+
Debounce function calls with optional key-based grouping.
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { debounce, debounceByKey } from '@just-io/utils';
|
|
20
|
+
|
|
21
|
+
// Basic debounce - executes after delay with last arguments
|
|
22
|
+
function setFilterValue(values: string[]): void {
|
|
23
|
+
console.log('Filter:', values);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const debouncedSetFilterValue = debounce(setFilterValue, 1000);
|
|
27
|
+
|
|
28
|
+
debouncedSetFilterValue(['first']);
|
|
29
|
+
// wait 300ms...
|
|
30
|
+
debouncedSetFilterValue(['second']); // Only ['second'] executes after 1000ms
|
|
31
|
+
|
|
32
|
+
// Cancel pending execution
|
|
33
|
+
debouncedSetFilterValue.teardown();
|
|
34
|
+
|
|
35
|
+
// Key-based debounce - separate timers per extracted key
|
|
36
|
+
function notify(event: { type: string; at: number }): void {
|
|
37
|
+
console.log(event.type, event.at);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const debouncedNotify = debounceByKey(notify, (event) => event.type, 1000);
|
|
41
|
+
|
|
42
|
+
debouncedNotify({ type: 'change', at: 0 });
|
|
43
|
+
debouncedNotify({ type: 'update', at: 0 }); // Both execute - different keys
|
|
44
|
+
debouncedNotify({ type: 'change', at: 10 }); // Replaces first 'change', only at:10 executes
|
|
45
|
+
|
|
46
|
+
// Cancel by key or all
|
|
47
|
+
debouncedNotify.teardown({ type: 'change', at: 0 });
|
|
48
|
+
debouncedNotify.teardownAll();
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### memo / memoByKey
|
|
52
|
+
|
|
53
|
+
Memoize function calls - skip execution if arguments haven't changed.
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { memo, memoByKey } from '@just-io/utils';
|
|
57
|
+
|
|
58
|
+
// Basic memo - only calls when extracted values change
|
|
59
|
+
function notify(event: { type: string; at: number }): void {
|
|
60
|
+
console.log(event.type, event.at);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const memoNotify = memo(notify, (event) => [event.type, event.at]);
|
|
64
|
+
|
|
65
|
+
memoNotify({ type: 'change', at: 0 }); // Executes
|
|
66
|
+
memoNotify({ type: 'change', at: 0 }); // Skipped - same values
|
|
67
|
+
memoNotify({ type: 'change', at: 1 }); // Executes - 'at' changed
|
|
68
|
+
|
|
69
|
+
// Only trigger on changes (skip first call)
|
|
70
|
+
function setFilterValue(values: string[]): void {
|
|
71
|
+
console.log('Filters changed:', values);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const memoSetFilterValue = memo(setFilterValue, (values) => values, true);
|
|
75
|
+
|
|
76
|
+
memoSetFilterValue([]); // Skipped (initial call)
|
|
77
|
+
memoSetFilterValue(['first']); // Executes (values changed)
|
|
78
|
+
|
|
79
|
+
// Key-based memo - separate memo state per key
|
|
80
|
+
function notifyChannel(channel: string, event: { type: string; at: number }): void {
|
|
81
|
+
console.log(channel, event);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const memoNotifyChannel = memoByKey(
|
|
85
|
+
notifyChannel,
|
|
86
|
+
(channel) => channel,
|
|
87
|
+
(channel, event) => [event.type, event.at],
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
memoNotifyChannel('channel1', { type: 'change', at: 0 }); // Executes
|
|
91
|
+
memoNotifyChannel('channel2', { type: 'change', at: 0 }); // Executes (different key)
|
|
92
|
+
memoNotifyChannel('channel1', { type: 'change', at: 0 }); // Skipped
|
|
93
|
+
memoNotifyChannel('channel1', { type: 'change', at: 10 }); // Executes (values changed)
|
|
94
|
+
|
|
95
|
+
// Key-based with onlyChanges
|
|
96
|
+
function setFilter(filter: string, values: string[]): void {
|
|
97
|
+
console.log(filter, values);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const memoSetFilter = memoByKey(
|
|
101
|
+
setFilter,
|
|
102
|
+
(filter) => filter,
|
|
103
|
+
(filter, values) => values,
|
|
104
|
+
true,
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
memoSetFilter('filter1', []); // Skipped (initial)
|
|
108
|
+
memoSetFilter('filter1', ['1']); // Executes (changed)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### DeepMap
|
|
112
|
+
|
|
113
|
+
Map with array keys (nested key paths). Useful for hierarchical data.
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
import { DeepMap } from '@just-io/utils';
|
|
117
|
+
|
|
118
|
+
// Create empty or with initial entries
|
|
119
|
+
const deepMap = new DeepMap<string, string>();
|
|
120
|
+
const withEntries = new DeepMap<string, string>([
|
|
121
|
+
[['one'], 'str'],
|
|
122
|
+
[['one', 'two'], 'str'],
|
|
123
|
+
]);
|
|
124
|
+
|
|
125
|
+
// Copy from another DeepMap
|
|
126
|
+
const copy = new DeepMap(withEntries);
|
|
127
|
+
|
|
128
|
+
// Set and get with array keys
|
|
129
|
+
deepMap.set([], 'root'); // Empty key path
|
|
130
|
+
deepMap.set(['one'], 'first');
|
|
131
|
+
deepMap.set(['one', 'two'], 'nested');
|
|
132
|
+
|
|
133
|
+
deepMap.get(['one']); // 'first'
|
|
134
|
+
deepMap.get(['one', 'two']); // 'nested'
|
|
135
|
+
deepMap.get(['missing']); // undefined
|
|
136
|
+
|
|
137
|
+
// Check existence
|
|
138
|
+
deepMap.has(['one']); // true
|
|
139
|
+
deepMap.has(['missing']); // false
|
|
140
|
+
|
|
141
|
+
// Delete entries
|
|
142
|
+
deepMap.delete(['one', 'two']); // returns true
|
|
143
|
+
deepMap.delete(['missing']); // returns false
|
|
144
|
+
|
|
145
|
+
// Take: get and delete in one operation
|
|
146
|
+
const value = deepMap.take(['one']); // 'first', now deleted
|
|
147
|
+
deepMap.take(['missing']); // undefined
|
|
148
|
+
|
|
149
|
+
// Size and clear
|
|
150
|
+
deepMap.size; // number of entries
|
|
151
|
+
deepMap.clear(); // remove all
|
|
152
|
+
|
|
153
|
+
// Iteration
|
|
154
|
+
deepMap.forEach((value, key) => console.log(key, value));
|
|
155
|
+
|
|
156
|
+
for (const [key, value] of deepMap) {
|
|
157
|
+
console.log(key, value);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
Array.from(deepMap.entries()); // [[key, value], ...]
|
|
161
|
+
Array.from(deepMap.keys()); // [key, ...]
|
|
162
|
+
Array.from(deepMap.values()); // [value, ...]
|
|
163
|
+
|
|
164
|
+
// Clone subtree (non-destructive)
|
|
165
|
+
deepMap.set(['one'], 'str');
|
|
166
|
+
deepMap.set(['one', 'two'], 'str');
|
|
167
|
+
const cloned = deepMap.clone(['one']);
|
|
168
|
+
// cloned: [[], 'str'], [['two'], 'str']
|
|
169
|
+
|
|
170
|
+
// Extract subtree (removes from original)
|
|
171
|
+
const extracted = deepMap.extract(['one']);
|
|
172
|
+
// extracted has the subtree, deepMap no longer has it
|
|
173
|
+
|
|
174
|
+
// Append entries under a prefix
|
|
175
|
+
deepMap.append(['one'], [[['two'], 'str']]);
|
|
176
|
+
// Result: [['one', 'two'], 'str']
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### EventEmitter / Notifier
|
|
180
|
+
|
|
181
|
+
Type-safe event handling.
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
import { EventEmitter, EventTuple, Notifier } from '@just-io/utils';
|
|
185
|
+
|
|
186
|
+
// Notifier - single event type
|
|
187
|
+
type Event = [first: string, second: string];
|
|
188
|
+
|
|
189
|
+
const notifier = new Notifier<Event>();
|
|
190
|
+
|
|
191
|
+
const handler = (first: string, second: string) => console.log(first, second);
|
|
192
|
+
notifier.subscribe(handler);
|
|
193
|
+
notifier.notify('a', 'b'); // logs: a b
|
|
194
|
+
|
|
195
|
+
// One-time subscription
|
|
196
|
+
notifier.subscribe((a, b) => console.log('once:', a, b), { once: true });
|
|
197
|
+
notifier.notify('x', 'y'); // Both handlers called
|
|
198
|
+
notifier.notify('x', 'y'); // Only permanent handler
|
|
199
|
+
|
|
200
|
+
notifier.unsubscribe(handler); // Remove specific
|
|
201
|
+
notifier.unsubscribeAll(); // Remove all
|
|
202
|
+
|
|
203
|
+
// EventEmitter - multiple named events
|
|
204
|
+
type EventMap = {
|
|
205
|
+
one: [first: number];
|
|
206
|
+
two: [first: string, second: number];
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const emitter = new EventEmitter<EventMap>();
|
|
210
|
+
|
|
211
|
+
emitter.on('one', (first) => console.log('one:', first));
|
|
212
|
+
emitter.on('two', (first, second) => console.log('two:', first, second));
|
|
213
|
+
|
|
214
|
+
emitter.emit('one', 1);
|
|
215
|
+
emitter.emit('two', '1', 2);
|
|
216
|
+
|
|
217
|
+
// One-time listener
|
|
218
|
+
emitter.once('one', (first) => console.log('once:', first));
|
|
219
|
+
|
|
220
|
+
// Emit from tuple (useful for batching)
|
|
221
|
+
type ETuple = EventTuple<EventMap>;
|
|
222
|
+
const events: ETuple[] = [
|
|
223
|
+
['one', 1],
|
|
224
|
+
['two', '2', 1],
|
|
225
|
+
];
|
|
226
|
+
for (const event of events) {
|
|
227
|
+
emitter.emit(...event);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Unsubscribe
|
|
231
|
+
emitter.off('one', handler);
|
|
232
|
+
emitter.unsubscribeAll('one'); // Remove all 'one' handlers
|
|
233
|
+
emitter.unsubscribeAll(); // Remove all handlers
|
|
234
|
+
|
|
235
|
+
// Event store - batch events and emit later
|
|
236
|
+
const store = emitter.makeStore();
|
|
237
|
+
store.add('one', 1);
|
|
238
|
+
store.add('one', 2);
|
|
239
|
+
store.emit(); // Emits both events
|
|
240
|
+
store.emit(); // Does nothing (already emitted)
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## License
|
|
244
|
+
|
|
245
|
+
MIT
|