tiny-essentials 1.25.2 → 1.25.4
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/changelog/1/25/3.md +6 -0
- package/changelog/1/25/4.md +3 -0
- package/dist/v1/TinyArrayComparator.min.js +1 -0
- package/dist/v1/TinyEssentials.min.js +1 -1
- package/dist/v1/TinyIframeEvents.min.js +1 -1
- package/dist/v1/build/TinyArrayComparator.cjs +7 -0
- package/dist/v1/build/TinyArrayComparator.d.mts +3 -0
- package/dist/v1/build/TinyArrayComparator.mjs +2 -0
- package/dist/v1/index.cjs +2 -0
- package/dist/v1/index.d.mts +2 -1
- package/dist/v1/index.mjs +2 -1
- package/dist/v1/libs/TinyArrayComparator.cjs +130 -0
- package/dist/v1/libs/TinyArrayComparator.d.mts +79 -0
- package/dist/v1/libs/TinyArrayComparator.mjs +112 -0
- package/dist/v1/libs/TinyIframeEvents.cjs +266 -94
- package/dist/v1/libs/TinyIframeEvents.d.mts +119 -26
- package/dist/v1/libs/TinyIframeEvents.mjs +224 -86
- package/docs/v1/README.md +1 -0
- package/docs/v1/libs/TinyArrayComparator.md +101 -0
- package/docs/v1/libs/TinyIframeEvents.md +61 -32
- package/package.json +5 -1
|
@@ -9,18 +9,35 @@ const instances = new WeakMap();
|
|
|
9
9
|
* @param {MessageEvent<any>} event - Metadata about the message.
|
|
10
10
|
*/
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* @typedef {object} TinyIframeEventsConfig
|
|
13
|
+
* Configuration object for initializing TinyIframeEvents.
|
|
14
|
+
* @property {HTMLIFrameElement} [targetIframe] - The target iframe element to post messages to. Required if instantiated in the parent window.
|
|
15
|
+
* @property {string} [targetOrigin] - The target origin to restrict messages to. Defaults to `window.location.origin`.
|
|
16
|
+
* @property {string} [secretEventName] - Custom internal name used to validate standard routing messages.
|
|
17
|
+
* @property {string} [handshakeEventName] - Custom internal name used for the initial MessageChannel handshake.
|
|
18
|
+
* @property {string} [readyEventName] - Custom internal name used for the ready event trigger.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {object} IframeEventBase
|
|
22
|
+
* Internal message structure for routed communication.
|
|
23
|
+
* @property {boolean} [secretIndicator] - Dynamic key based on secretEventName to validate the message.
|
|
24
|
+
* @property {string} eventName - The name of the custom event route.
|
|
25
|
+
* @property {any} payload - The data being sent (can be any type).
|
|
26
|
+
* @property {'iframe' | 'parent'} direction - Indicates the sender: 'iframe' or 'parent'.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* A highly secure and flexible event routing system for structured communication
|
|
30
|
+
* between a parent window and its iframe using `MessageChannel`.
|
|
14
31
|
*
|
|
15
|
-
* This class abstracts the complexity of cross-origin
|
|
16
|
-
*
|
|
17
|
-
* - Send events with arbitrary payloads
|
|
18
|
-
* - Listen to specific event names
|
|
19
|
-
* - Filter events by origin and source
|
|
20
|
-
* - Work symmetrically from both sides with automatic direction handling
|
|
32
|
+
* This class abstracts the complexity of cross-origin communication by establishing
|
|
33
|
+
* a direct, un-interceptable port connection after a secure initial handshake.
|
|
21
34
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
35
|
+
* Features:
|
|
36
|
+
* - Secure direct pipeline via `MessageChannel`.
|
|
37
|
+
* - Customizable internal event names to avoid collisions.
|
|
38
|
+
* - Symmetrical usage for both parent and iframe.
|
|
39
|
+
* - Queue management for messages sent before the connection is established.
|
|
40
|
+
* - Auto-reconnects and resets ports if the target iframe is reloaded.
|
|
24
41
|
*/
|
|
25
42
|
class TinyIframeEvents {
|
|
26
43
|
#events = new TinyEvents();
|
|
@@ -188,29 +205,87 @@ class TinyIframeEvents {
|
|
|
188
205
|
///////////////////////////////////////////////////
|
|
189
206
|
/** @type {Window} */
|
|
190
207
|
#targetWindow;
|
|
208
|
+
get targetWindow() {
|
|
209
|
+
return this.#targetWindow;
|
|
210
|
+
}
|
|
211
|
+
/** @type {HTMLIFrameElement | undefined} */
|
|
212
|
+
#targetIframeElement;
|
|
213
|
+
get targetIframeElement() {
|
|
214
|
+
return this.#targetIframeElement;
|
|
215
|
+
}
|
|
191
216
|
/** @type {string} */
|
|
192
217
|
#targetOrigin;
|
|
193
|
-
|
|
218
|
+
get targetOrigin() {
|
|
219
|
+
return this.#targetOrigin;
|
|
220
|
+
}
|
|
221
|
+
/** @type {'iframe' | 'parent'} */
|
|
194
222
|
#selfType;
|
|
223
|
+
get selfType() {
|
|
224
|
+
return this.#selfType;
|
|
225
|
+
}
|
|
195
226
|
/** @type {boolean} */
|
|
196
227
|
#isDestroyed = false;
|
|
197
228
|
/** @type {boolean} */
|
|
198
229
|
#ready = false;
|
|
230
|
+
get ready() {
|
|
231
|
+
return this.#ready;
|
|
232
|
+
}
|
|
199
233
|
/**
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
* @
|
|
234
|
+
* Executes the provided callback when the secure MessageChannel connection is fully established.
|
|
235
|
+
* If the connection is already ready, the callback is executed immediately.
|
|
236
|
+
*
|
|
237
|
+
* @param {function(): void} handler - The callback function to execute.
|
|
204
238
|
*/
|
|
239
|
+
onReady(handler) {
|
|
240
|
+
if (this.#ready) {
|
|
241
|
+
handler();
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
this.#events.once(this.#readyEventName, handler);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/** @type {MessagePort | null} */
|
|
248
|
+
#port = null;
|
|
249
|
+
/** @type {IframeEventBase[]} */
|
|
250
|
+
#pendingQueue = [];
|
|
251
|
+
/** @type {string} */
|
|
252
|
+
#secretEventName;
|
|
253
|
+
/** @type {string} */
|
|
254
|
+
#handshakeEventName;
|
|
255
|
+
/** @type {string} */
|
|
256
|
+
#readyEventName;
|
|
257
|
+
/** @type {(() => void) | null} */
|
|
258
|
+
#boundSendPort = null;
|
|
205
259
|
/**
|
|
206
|
-
*
|
|
207
|
-
*
|
|
260
|
+
* Creates a new TinyIframeEvents instance to manage secure communication.
|
|
261
|
+
* Automatically establishes a MessageChannel connection between contexts.
|
|
262
|
+
*
|
|
263
|
+
* @param {TinyIframeEventsConfig} config - Configuration object.
|
|
208
264
|
*/
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
265
|
+
constructor({ targetIframe, targetOrigin = window.location.origin, secretEventName = '__tinyIframeEvent__', handshakeEventName = '__tinyIframeHandshake__', readyEventName = '__tinyIframeReady__', } = {}) {
|
|
266
|
+
if (targetIframe !== undefined &&
|
|
267
|
+
(!(targetIframe instanceof HTMLIFrameElement) || !targetIframe.contentWindow)) {
|
|
268
|
+
throw new TypeError(`[TinyIframeEvents] Invalid "targetIframe": expected HTMLIFrameElement, received ${typeof targetIframe}`);
|
|
269
|
+
}
|
|
270
|
+
if (typeof targetOrigin !== 'string') {
|
|
271
|
+
throw new TypeError(`[TinyIframeEvents] Invalid "targetOrigin": expected string, received ${typeof targetOrigin}`);
|
|
272
|
+
}
|
|
273
|
+
this.#targetIframeElement = targetIframe;
|
|
274
|
+
this.#targetWindow = targetIframe?.contentWindow ?? window.parent;
|
|
275
|
+
this.#targetOrigin = targetOrigin;
|
|
276
|
+
this.#selfType = !targetIframe ? 'iframe' : 'parent';
|
|
277
|
+
this.#secretEventName = secretEventName;
|
|
278
|
+
this.#handshakeEventName = handshakeEventName;
|
|
279
|
+
this.#readyEventName = readyEventName;
|
|
280
|
+
if (instances.has(this.#targetWindow))
|
|
281
|
+
throw new Error('Duplicate window reference.');
|
|
282
|
+
this._boundWindowMessage = this.#onWindowMessage.bind(this);
|
|
283
|
+
this._boundPortMessage = this.#onPortMessage.bind(this);
|
|
284
|
+
this.#initializeConnection();
|
|
285
|
+
instances.set(this.#targetWindow, this);
|
|
286
|
+
}
|
|
212
287
|
/**
|
|
213
|
-
* Gets the internal secret iframe event name.
|
|
288
|
+
* Gets the internal secret iframe event name used for validation.
|
|
214
289
|
* @returns {string}
|
|
215
290
|
*/
|
|
216
291
|
get secretEventName() {
|
|
@@ -227,71 +302,140 @@ class TinyIframeEvents {
|
|
|
227
302
|
this.#secretEventName = name;
|
|
228
303
|
}
|
|
229
304
|
/**
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
this.#
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
305
|
+
* Gets the internal handshake event name used for establishing the MessageChannel.
|
|
306
|
+
* @returns {string}
|
|
307
|
+
*/
|
|
308
|
+
get handshakeEventName() {
|
|
309
|
+
return this.#handshakeEventName;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Sets the internal handshake event name.
|
|
313
|
+
* @param {string} name
|
|
314
|
+
* @throws {TypeError} If the value is not a string.
|
|
315
|
+
*/
|
|
316
|
+
set handshakeEventName(name) {
|
|
317
|
+
if (typeof name !== 'string')
|
|
318
|
+
throw new TypeError('TinyIframeEvents: handshakeEventName must be a string.');
|
|
319
|
+
this.#handshakeEventName = name;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Gets the internal ready event name used when the connection is fully established.
|
|
323
|
+
* @returns {string}
|
|
324
|
+
*/
|
|
325
|
+
get readyEventName() {
|
|
326
|
+
return this.#readyEventName;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Sets the internal ready event name.
|
|
330
|
+
* @param {string} name
|
|
331
|
+
* @throws {TypeError} If the value is not a string.
|
|
332
|
+
*/
|
|
333
|
+
set readyEventName(name) {
|
|
334
|
+
if (typeof name !== 'string')
|
|
335
|
+
throw new TypeError('TinyIframeEvents: readyEventName must be a string.');
|
|
336
|
+
this.#readyEventName = name;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Initializes the correct connection strategy based on the current context (parent or iframe).
|
|
340
|
+
*/
|
|
341
|
+
#initializeConnection() {
|
|
342
|
+
if (this.#selfType === 'parent') {
|
|
343
|
+
this.#boundSendPort = () => {
|
|
344
|
+
if (this.#isDestroyed)
|
|
345
|
+
return;
|
|
346
|
+
// Closes any existing port to prevent memory leaks during iframe reloads
|
|
347
|
+
if (this.#port) {
|
|
348
|
+
this.#port.close();
|
|
349
|
+
}
|
|
350
|
+
// Creates a fresh channel for every attempt to prevent clone errors
|
|
351
|
+
const channel = new MessageChannel();
|
|
352
|
+
this.#port = channel.port1;
|
|
353
|
+
this.#port.onmessage = this._boundPortMessage;
|
|
354
|
+
// Resets ready state to require a new ACK
|
|
355
|
+
this.#ready = false;
|
|
356
|
+
this.#targetWindow.postMessage({ type: this.#handshakeEventName }, this.#targetOrigin, [
|
|
357
|
+
channel.port2,
|
|
358
|
+
]);
|
|
359
|
+
};
|
|
360
|
+
if (this.#targetIframeElement) {
|
|
361
|
+
this.#targetIframeElement.addEventListener('load', this.#boundSendPort);
|
|
362
|
+
this.#boundSendPort();
|
|
363
|
+
}
|
|
364
|
+
}
|
|
253
365
|
else {
|
|
254
|
-
|
|
255
|
-
this.#targetWindow.addEventListener('DOMContentLoaded', this._boundOnceMessage, false);
|
|
366
|
+
window.addEventListener('message', this._boundWindowMessage, false);
|
|
256
367
|
}
|
|
257
|
-
window.addEventListener('message', this._boundOnMessage, false);
|
|
258
|
-
instances.set(this.#targetWindow, this);
|
|
259
368
|
}
|
|
260
369
|
/**
|
|
261
|
-
*
|
|
370
|
+
* Internal handler for the initial window message event (used by iframe to receive the port).
|
|
371
|
+
*
|
|
372
|
+
* @param {MessageEvent<any>} event - The message event received via `postMessage`.
|
|
262
373
|
*/
|
|
263
|
-
#
|
|
264
|
-
|
|
374
|
+
#onWindowMessage(event) {
|
|
375
|
+
const data = event.data;
|
|
376
|
+
const ports = event.ports;
|
|
377
|
+
if (!isJsonObject(data) || data.type !== this.#handshakeEventName || ports.length === 0)
|
|
265
378
|
return;
|
|
266
|
-
this.#
|
|
267
|
-
this.#
|
|
379
|
+
this.#port = ports[0];
|
|
380
|
+
this.#port.onmessage = this._boundPortMessage;
|
|
381
|
+
window.removeEventListener('message', this._boundWindowMessage);
|
|
382
|
+
this.#port.postMessage({ type: `${this.#handshakeEventName}_ACK` });
|
|
383
|
+
this.#markReady();
|
|
268
384
|
}
|
|
269
385
|
/**
|
|
270
|
-
* Internal handler for
|
|
386
|
+
* Internal handler for messages received through the secure MessageChannel port.
|
|
271
387
|
*
|
|
272
|
-
* @param {MessageEvent<any>} event - The message event received via `
|
|
388
|
+
* @param {MessageEvent<any>} event - The message event received via `MessagePort`.
|
|
273
389
|
*/
|
|
274
|
-
#
|
|
275
|
-
|
|
276
|
-
|
|
390
|
+
#onPortMessage(event) {
|
|
391
|
+
/** @type {any} */
|
|
392
|
+
const data = event.data;
|
|
393
|
+
if (isJsonObject(data) && data.type === `${this.#handshakeEventName}_ACK`) {
|
|
394
|
+
this.#markReady();
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
277
397
|
if (!isJsonObject(data) || !data[this.#secretEventName])
|
|
278
398
|
return;
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
399
|
+
const eventName = data.eventName;
|
|
400
|
+
const payload = data.payload;
|
|
401
|
+
const direction = data.direction;
|
|
402
|
+
if (typeof eventName !== 'string' ||
|
|
403
|
+
(this.#selfType === 'iframe' && direction !== 'iframe') ||
|
|
404
|
+
(this.#selfType === 'parent' && direction !== 'parent')) {
|
|
282
405
|
return;
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
406
|
+
}
|
|
407
|
+
this.#events.emit(eventName, payload, event);
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Marks the communication as ready, flushes any queued messages,
|
|
411
|
+
* and triggers the internal ready event for any waiting listeners.
|
|
412
|
+
*/
|
|
413
|
+
#markReady() {
|
|
414
|
+
if (this.#ready)
|
|
286
415
|
return;
|
|
287
|
-
this.#
|
|
416
|
+
this.#ready = true;
|
|
417
|
+
this.#flushQueue();
|
|
418
|
+
this.#events.emit(this.#readyEventName);
|
|
288
419
|
}
|
|
289
420
|
/**
|
|
290
|
-
* Sends
|
|
421
|
+
* Sends all pending messages queued before the secure port was established.
|
|
422
|
+
*/
|
|
423
|
+
#flushQueue() {
|
|
424
|
+
while (this.#pendingQueue.length > 0) {
|
|
425
|
+
/** @type {IframeEventBase | undefined} */
|
|
426
|
+
const data = this.#pendingQueue.shift();
|
|
427
|
+
if (data && this.#port) {
|
|
428
|
+
this.#port.postMessage(data);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Sends an event to the target window through the secure MessageChannel.
|
|
291
434
|
*
|
|
292
435
|
* @param {string} eventName - A unique name identifying the event.
|
|
293
436
|
* @param {*} payload - The data to send with the event. Can be any serializable value.
|
|
294
|
-
* @throws {
|
|
437
|
+
* @throws {TypeError} If `eventName` is not a string.
|
|
438
|
+
* @throws {Error} If instance has been destroyed.
|
|
295
439
|
*/
|
|
296
440
|
emit(eventName, payload) {
|
|
297
441
|
if (typeof eventName !== 'string')
|
|
@@ -305,41 +449,35 @@ class TinyIframeEvents {
|
|
|
305
449
|
payload,
|
|
306
450
|
direction: this.#selfType === 'parent' ? 'iframe' : 'parent',
|
|
307
451
|
};
|
|
308
|
-
if (!this.#ready) {
|
|
452
|
+
if (!this.#ready || !this.#port) {
|
|
309
453
|
this.#pendingQueue.push(message);
|
|
310
454
|
return;
|
|
311
455
|
}
|
|
312
|
-
this.#
|
|
313
|
-
}
|
|
314
|
-
/**
|
|
315
|
-
* Sends all pending messages queued before handshake completion.
|
|
316
|
-
*
|
|
317
|
-
* @returns {void}
|
|
318
|
-
*/
|
|
319
|
-
#flushQueue() {
|
|
320
|
-
while (this.#pendingQueue.length) {
|
|
321
|
-
const data = this.#pendingQueue.shift();
|
|
322
|
-
if (data)
|
|
323
|
-
this.#targetWindow.postMessage(data, this.#targetOrigin);
|
|
324
|
-
}
|
|
456
|
+
this.#port.postMessage(message);
|
|
325
457
|
}
|
|
326
458
|
/**
|
|
327
459
|
* Checks if the communication instance has been destroyed.
|
|
328
460
|
*
|
|
329
|
-
* @returns {boolean}
|
|
461
|
+
* @returns {boolean} True if destroyed, false otherwise.
|
|
330
462
|
*/
|
|
331
463
|
isDestroyed() {
|
|
332
464
|
return this.#isDestroyed;
|
|
333
465
|
}
|
|
334
466
|
/**
|
|
335
|
-
* Unsubscribes all registered event listeners
|
|
467
|
+
* Unsubscribes all registered event listeners, closes the message port, and cleans up references.
|
|
336
468
|
* Call this when the instance is no longer needed to prevent memory leaks.
|
|
337
469
|
*/
|
|
338
470
|
destroy() {
|
|
339
471
|
this.#isDestroyed = true;
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
this.#
|
|
472
|
+
this.#ready = false;
|
|
473
|
+
window.removeEventListener('message', this._boundWindowMessage);
|
|
474
|
+
if (this.#targetIframeElement && this.#boundSendPort) {
|
|
475
|
+
this.#targetIframeElement.removeEventListener('load', this.#boundSendPort);
|
|
476
|
+
}
|
|
477
|
+
if (this.#port) {
|
|
478
|
+
this.#port.close();
|
|
479
|
+
this.#port = null;
|
|
480
|
+
}
|
|
343
481
|
this.#events.offAllTypes();
|
|
344
482
|
this.#pendingQueue = [];
|
|
345
483
|
instances.delete(this.#targetWindow);
|
package/docs/v1/README.md
CHANGED
|
@@ -62,6 +62,7 @@ This folder contains the core scripts we have worked on so far. Each file is a m
|
|
|
62
62
|
- 🎨 **[TinyColorValidator](./libs/TinyColorValidator.md)** — A comprehensive CSS color validation and parsing utility supporting HEX, HEXA, RGB, RGBA, HSL, HSLA, HWB, Lab, LCH, standard HTML color names, and special keywords, with automatic type detection and parsing.
|
|
63
63
|
* 📝 **[TinyTextDiffer](./libs/TinyTextDiffer.md)** — A granular text comparison utility using the LCS algorithm to detect additions, deletions, and unchanged segments between multiple history versions, returning a clean, parseable diff structure.
|
|
64
64
|
* 🕒 **[TinyAnalogClock](./libs/TinyAnalogClock.md)** — A lightweight analog clock engine for managing time-based rotations, supporting custom offsets, smooth transitions, and easy binding to CSS variables rendering.
|
|
65
|
+
* 🔍 **[TinyArrayComparator](./libs/TinyArrayComparator.md)** — A lightweight, highly optimized JavaScript utility class designed to compare two arrays and efficiently detect which items were **added** or **deleted**.
|
|
65
66
|
|
|
66
67
|
### 3. **`fileManager/`**
|
|
67
68
|
* 📁 **[Main](./fileManager/main.md)** — A Node.js file/directory utility module with support for JSON, backups, renaming, size analysis, and more.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# 🔍 TinyArrayComparator
|
|
2
|
+
|
|
3
|
+
A lightweight, highly optimized JavaScript utility class designed to compare two arrays and efficiently detect which items were **added** or **deleted**.
|
|
4
|
+
|
|
5
|
+
## ✨ Features
|
|
6
|
+
|
|
7
|
+
- **Fast Comparison:** Uses a Map and hash-based lookups for $O(N)$ performance.
|
|
8
|
+
- **Stateful Design:** Store your base array once and compare it multiple times.
|
|
9
|
+
- **Zero Dependencies:** Pure Vanilla JavaScript.
|
|
10
|
+
- **Deep Compatibility:** Safely hashes strings, numbers, arrays, and deep objects.
|
|
11
|
+
- **Clean Architecture:** Built as an ES6 Module with strict JSDoc typing and input validation.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 🚀 Quick Start
|
|
16
|
+
|
|
17
|
+
### 1. Import the Class
|
|
18
|
+
Since this project uses modern ES6 modules, simply import it into your working file.
|
|
19
|
+
|
|
20
|
+
```javascript
|
|
21
|
+
import TinyArrayComparator from './TinyArrayComparator.js';
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### 2. Prepare Your Arrays
|
|
25
|
+
Set up the initial state (`oldArray`) and the modified state (`newArray`) that you want to compare.
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
const oldList = [
|
|
29
|
+
{ id: 1, role: 'admin' },
|
|
30
|
+
{ id: 2, role: 'user' },
|
|
31
|
+
{ id: 3, role: 'moderator' }
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
const newList = [
|
|
35
|
+
{ id: 1, role: 'admin' }, // Unchanged
|
|
36
|
+
{ id: 3, role: 'moderator' }, // Unchanged
|
|
37
|
+
{ id: 4, role: 'guest' } // Added
|
|
38
|
+
// User ID 2 was deleted
|
|
39
|
+
];
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 3. Initialize and Compare
|
|
43
|
+
Instantiate the class passing the initial array. Then, call the `compare` method with your new array.
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
const comparator = new TinyArrayComparator(oldList);
|
|
47
|
+
const results = comparator.compare(newList);
|
|
48
|
+
|
|
49
|
+
console.log(results);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Output:**
|
|
53
|
+
```json
|
|
54
|
+
[
|
|
55
|
+
{
|
|
56
|
+
"item": { "id": 4, "role": "guest" },
|
|
57
|
+
"status": "added"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"item": { "id": 2, "role": "user" },
|
|
61
|
+
"status": "deleted"
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 🛠️ API Reference
|
|
69
|
+
|
|
70
|
+
### `constructor([oldArray])`
|
|
71
|
+
Creates a new comparator instance.
|
|
72
|
+
* **`oldArray`** `(Array<any>)` *(Optional)*: The initial state of the array to be stored. Throws a `TypeError` if the provided value is not an Array.
|
|
73
|
+
|
|
74
|
+
### `compare(newArray)`
|
|
75
|
+
Evaluates differences between the internally stored `oldArray` and the provided `newArray`. Throws a `TypeError` if the provided value is not an Array.
|
|
76
|
+
* **`newArray`** `(Array<any>)`: The new array containing current modifications.
|
|
77
|
+
* **Returns:** An array of objects. Each object contains the original `item` and a `status` string (either `'added'` or `'deleted'`).
|
|
78
|
+
|
|
79
|
+
### `get oldArray` / `set oldArray(value)`
|
|
80
|
+
Allows you to retrieve or update the base array stored inside the instance.
|
|
81
|
+
* **Throws:** `TypeError` if attempting to set a non-array value.
|
|
82
|
+
|
|
83
|
+
### `static generateHash(item)`
|
|
84
|
+
A static helper method if you ever need to generate a unique base36 hash string for an item outside the comparison flow.
|
|
85
|
+
* **`item`** `(any)`: The target object, string, or number to hash.
|
|
86
|
+
* **Returns:** A `string` representing the unique hash.
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
const myHash = TinyArrayComparator.generateHash({ pony: 'Pudding' });
|
|
90
|
+
console.log(myHash); // Returns a base36 string like "1b4x9z"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 💡 Best Practices
|
|
96
|
+
|
|
97
|
+
* **Order Doesn't Matter:** Because this utility relies on value-hashing, the index order of items inside the arrays does not affect the comparison. It strictly checks for the existence of the values.
|
|
98
|
+
* **Object References:** The returned array preserves the exact references to the items passed in the parameters, allowing you to seamlessly integrate the output back into your application logic.
|
|
99
|
+
* **Reusability:** You can easily update the base state by assigning a new array to `comparator.oldArray = [...]` without creating a new instance.
|
|
100
|
+
|
|
101
|
+
Happy coding! 💻✨
|