@oimdb/core 3.1.0 → 3.2.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.
- package/dist/index.cjs +77 -35
- package/dist/index.d.cts +29 -4
- package/dist/index.d.ts +29 -4
- package/dist/index.js +77 -35
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -537,7 +537,6 @@ var OIMCarrierKeyedEmitter = class {
|
|
|
537
537
|
this.isFlushEnqueued = false;
|
|
538
538
|
this.onFlush = () => {
|
|
539
539
|
this.isFlushEnqueued = false;
|
|
540
|
-
this.dequeueFlush = void 0;
|
|
541
540
|
if (this.dirtyCarriers.length === 0) return;
|
|
542
541
|
const flushing = this.dirtyCarriers;
|
|
543
542
|
this.dirtyCarriers = [];
|
|
@@ -647,9 +646,9 @@ var OIMCarrierKeyedEmitter = class {
|
|
|
647
646
|
};
|
|
648
647
|
}
|
|
649
648
|
destroy() {
|
|
650
|
-
if (this.
|
|
651
|
-
this.
|
|
652
|
-
this.
|
|
649
|
+
if (this.isFlushEnqueued) {
|
|
650
|
+
this.queue.cancelTask(this.onFlush);
|
|
651
|
+
this.isFlushEnqueued = false;
|
|
653
652
|
}
|
|
654
653
|
this.subscribedCarriers.forEach((carrier) => {
|
|
655
654
|
carrier.subscribers?.clear();
|
|
@@ -672,7 +671,7 @@ var OIMCarrierKeyedEmitter = class {
|
|
|
672
671
|
scheduleFlush() {
|
|
673
672
|
if (this.isFlushEnqueued) return;
|
|
674
673
|
this.isFlushEnqueued = true;
|
|
675
|
-
this.
|
|
674
|
+
this.queue.enqueueTask(this.onFlush);
|
|
676
675
|
}
|
|
677
676
|
notify(carrier) {
|
|
678
677
|
const subscribers = carrier.subscribers;
|
|
@@ -1098,7 +1097,6 @@ var OIMReactiveCollection = class extends OIMCollection {
|
|
|
1098
1097
|
this.runAnyUpdate = () => {
|
|
1099
1098
|
if (!this.isAnyUpdateScheduled) return;
|
|
1100
1099
|
this.isAnyUpdateScheduled = false;
|
|
1101
|
-
this.dequeueAnyUpdate = void 0;
|
|
1102
1100
|
const pks = this.isAnyUpdateClearPending ? [] : Array.from(this.pendingAnyUpdatePks);
|
|
1103
1101
|
this.pendingAnyUpdatePks.clear();
|
|
1104
1102
|
this.isAnyUpdateClearPending = false;
|
|
@@ -1238,13 +1236,13 @@ var OIMReactiveCollection = class extends OIMCollection {
|
|
|
1238
1236
|
ensureAnyUpdateEnqueued() {
|
|
1239
1237
|
if (this.isAnyUpdateScheduled) return;
|
|
1240
1238
|
this.isAnyUpdateScheduled = true;
|
|
1241
|
-
this.
|
|
1239
|
+
this.queue.enqueueTask(this.runAnyUpdate);
|
|
1242
1240
|
}
|
|
1243
1241
|
destroy() {
|
|
1244
1242
|
this.updateEmitter.destroy();
|
|
1245
|
-
if (this.
|
|
1246
|
-
this.
|
|
1247
|
-
this.
|
|
1243
|
+
if (this.isAnyUpdateScheduled) {
|
|
1244
|
+
this.queue.cancelTask(this.runAnyUpdate);
|
|
1245
|
+
this.isAnyUpdateScheduled = false;
|
|
1248
1246
|
}
|
|
1249
1247
|
this.anyUpdateHandlers.clear();
|
|
1250
1248
|
this.pendingAnyUpdatePks.clear();
|
|
@@ -1386,7 +1384,6 @@ var OIMUpdateEventEmitter = class {
|
|
|
1386
1384
|
// Stable handler reference to avoid allocating a new closure on every HAS_CHANGES.
|
|
1387
1385
|
this.onFlush = () => {
|
|
1388
1386
|
this.isFlushEnqueued = false;
|
|
1389
|
-
this.dequeueFlush = void 0;
|
|
1390
1387
|
if (this.keyHandlers.size === 0) {
|
|
1391
1388
|
this.updatedKeys.clear();
|
|
1392
1389
|
return;
|
|
@@ -1439,7 +1436,7 @@ var OIMUpdateEventEmitter = class {
|
|
|
1439
1436
|
}
|
|
1440
1437
|
if (this.isFlushEnqueued) return;
|
|
1441
1438
|
this.isFlushEnqueued = true;
|
|
1442
|
-
this.
|
|
1439
|
+
this.queue.enqueueTask(this.onFlush);
|
|
1443
1440
|
}
|
|
1444
1441
|
markUpdatedKey(key) {
|
|
1445
1442
|
if (this.deliveryMode === "queue" && this.queue.isInFlush) {
|
|
@@ -1461,7 +1458,7 @@ var OIMUpdateEventEmitter = class {
|
|
|
1461
1458
|
}
|
|
1462
1459
|
if (this.isFlushEnqueued) return;
|
|
1463
1460
|
this.isFlushEnqueued = true;
|
|
1464
|
-
this.
|
|
1461
|
+
this.queue.enqueueTask(this.onFlush);
|
|
1465
1462
|
}
|
|
1466
1463
|
markAllUpdated() {
|
|
1467
1464
|
if (this.deliveryMode === "queue" && this.queue.isInFlush) {
|
|
@@ -1483,7 +1480,7 @@ var OIMUpdateEventEmitter = class {
|
|
|
1483
1480
|
}
|
|
1484
1481
|
if (this.isFlushEnqueued) return;
|
|
1485
1482
|
this.isFlushEnqueued = true;
|
|
1486
|
-
this.
|
|
1483
|
+
this.queue.enqueueTask(this.onFlush);
|
|
1487
1484
|
}
|
|
1488
1485
|
deliverImmediate() {
|
|
1489
1486
|
this.runSinglePass();
|
|
@@ -1531,9 +1528,9 @@ var OIMUpdateEventEmitter = class {
|
|
|
1531
1528
|
}
|
|
1532
1529
|
}
|
|
1533
1530
|
destroy() {
|
|
1534
|
-
if (this.
|
|
1535
|
-
this.
|
|
1536
|
-
this.
|
|
1531
|
+
if (this.isFlushEnqueued) {
|
|
1532
|
+
this.queue.cancelTask(this.onFlush);
|
|
1533
|
+
this.isFlushEnqueued = false;
|
|
1537
1534
|
}
|
|
1538
1535
|
if (this.unsubscribeAfterFlushDelivery) {
|
|
1539
1536
|
this.unsubscribeAfterFlushDelivery();
|
|
@@ -1591,9 +1588,8 @@ var OIMUpdateEventEmitter = class {
|
|
|
1591
1588
|
}
|
|
1592
1589
|
if (this.keyHandlers.size === 0) {
|
|
1593
1590
|
this.updatedKeys.clear();
|
|
1594
|
-
if (this.
|
|
1595
|
-
this.
|
|
1596
|
-
this.dequeueFlush = void 0;
|
|
1591
|
+
if (this.isFlushEnqueued) {
|
|
1592
|
+
this.queue.cancelTask(this.onFlush);
|
|
1597
1593
|
}
|
|
1598
1594
|
this.isFlushEnqueued = false;
|
|
1599
1595
|
}
|
|
@@ -2551,17 +2547,15 @@ var OIMObject = class {
|
|
|
2551
2547
|
}
|
|
2552
2548
|
setProperty(key, value) {
|
|
2553
2549
|
this.store.setProperty(key, value);
|
|
2554
|
-
this.
|
|
2550
|
+
this.onUpdatedKey(key);
|
|
2555
2551
|
}
|
|
2556
2552
|
merge(draft) {
|
|
2557
2553
|
this.store.merge(draft);
|
|
2558
|
-
this.
|
|
2559
|
-
keys: Object.keys(draft)
|
|
2560
|
-
});
|
|
2554
|
+
this.onUpdatedKeys(Object.keys(draft));
|
|
2561
2555
|
}
|
|
2562
2556
|
delete(key) {
|
|
2563
2557
|
this.store.delete(key);
|
|
2564
|
-
this.
|
|
2558
|
+
this.onUpdatedKey(key);
|
|
2565
2559
|
}
|
|
2566
2560
|
get(key) {
|
|
2567
2561
|
return this.store.get(key);
|
|
@@ -2571,7 +2565,24 @@ var OIMObject = class {
|
|
|
2571
2565
|
}
|
|
2572
2566
|
clear() {
|
|
2573
2567
|
this.store.clear();
|
|
2574
|
-
this.
|
|
2568
|
+
this.onUpdatedKeys([]);
|
|
2569
|
+
}
|
|
2570
|
+
/**
|
|
2571
|
+
* Write fan-out hooks. Split single-key vs multi-key so a reactive subclass
|
|
2572
|
+
* can mark its keyed emitter on the hot single-property path without
|
|
2573
|
+
* allocating a `{ keys: [key] }` payload. The base only fires the general
|
|
2574
|
+
* UPDATE event, and only when something is actually subscribed to it — so an
|
|
2575
|
+
* unobserved write skips the payload allocation entirely.
|
|
2576
|
+
*/
|
|
2577
|
+
onUpdatedKey(key) {
|
|
2578
|
+
if (this.emitter.hasHandlers(0 /* UPDATE */)) {
|
|
2579
|
+
this.emitter.emit(0 /* UPDATE */, { keys: [key] });
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
onUpdatedKeys(keys) {
|
|
2583
|
+
if (this.emitter.hasHandlers(0 /* UPDATE */)) {
|
|
2584
|
+
this.emitter.emit(0 /* UPDATE */, { keys });
|
|
2585
|
+
}
|
|
2575
2586
|
}
|
|
2576
2587
|
count() {
|
|
2577
2588
|
return this.store.count();
|
|
@@ -2595,10 +2606,20 @@ var OIMObject = class {
|
|
|
2595
2606
|
var OIMReactiveObject = class extends OIMObject {
|
|
2596
2607
|
constructor(queue, opts) {
|
|
2597
2608
|
super(opts);
|
|
2598
|
-
this.updateEmitter = new
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2609
|
+
this.updateEmitter = new OIMCarrierKeyedEmitter(queue, new OIMKeyedCarrierResolver());
|
|
2610
|
+
}
|
|
2611
|
+
/**
|
|
2612
|
+
* Mark the written key(s) on the keyed emitter directly — no general-event
|
|
2613
|
+
* bridge, no `{ keys }` payload on the single-key path. `super` still fires
|
|
2614
|
+
* the general UPDATE event for any low-level `emitter` subscribers.
|
|
2615
|
+
*/
|
|
2616
|
+
onUpdatedKey(key) {
|
|
2617
|
+
this.updateEmitter.markUpdatedKey(key);
|
|
2618
|
+
super.onUpdatedKey(key);
|
|
2619
|
+
}
|
|
2620
|
+
onUpdatedKeys(keys) {
|
|
2621
|
+
this.updateEmitter.markUpdatedKeys(keys);
|
|
2622
|
+
super.onUpdatedKeys(keys);
|
|
2602
2623
|
}
|
|
2603
2624
|
subscribeOnKey(key, handler) {
|
|
2604
2625
|
return this.updateEmitter.subscribeOnKey(key, handler);
|
|
@@ -2702,6 +2723,9 @@ var OIMEventQueue = class {
|
|
|
2702
2723
|
constructor(options = {}) {
|
|
2703
2724
|
this.emitter = new OIMEventEmitter();
|
|
2704
2725
|
this.tasks = /* @__PURE__ */ new Set();
|
|
2726
|
+
// Reused buffer swapped in during flush so the pending set is never copied
|
|
2727
|
+
// (no per-flush array snapshot) and new enqueues land in a fresh set.
|
|
2728
|
+
this.tasksSpare = /* @__PURE__ */ new Set();
|
|
2705
2729
|
this.isFlushing = false;
|
|
2706
2730
|
this.scheduler = options.scheduler;
|
|
2707
2731
|
if (this.scheduler) {
|
|
@@ -2730,6 +2754,22 @@ var OIMEventQueue = class {
|
|
|
2730
2754
|
this.tasks.delete(task);
|
|
2731
2755
|
};
|
|
2732
2756
|
}
|
|
2757
|
+
/**
|
|
2758
|
+
* Allocation-free enqueue for a **stable** function reference (e.g. a bound
|
|
2759
|
+
* `onFlush` method). The function is stored directly — no wrapper, no
|
|
2760
|
+
* returned closure — and cancelled via {@link cancelTask}. Enqueuing the
|
|
2761
|
+
* same reference twice is idempotent (Set-deduped); callers that may run a
|
|
2762
|
+
* task more than once per flush should use {@link enqueue} instead.
|
|
2763
|
+
*/
|
|
2764
|
+
enqueueTask(fn) {
|
|
2765
|
+
this.tasks.add(fn);
|
|
2766
|
+
this.ensureScheduled();
|
|
2767
|
+
}
|
|
2768
|
+
/** Cancel a task scheduled via {@link enqueueTask}; safe to call mid-flush. */
|
|
2769
|
+
cancelTask(fn) {
|
|
2770
|
+
this.tasks.delete(fn);
|
|
2771
|
+
this.flushing?.delete(fn);
|
|
2772
|
+
}
|
|
2733
2773
|
get isInFlush() {
|
|
2734
2774
|
return this.isFlushing;
|
|
2735
2775
|
}
|
|
@@ -2749,11 +2789,13 @@ var OIMEventQueue = class {
|
|
|
2749
2789
|
this.scheduler?.cancel();
|
|
2750
2790
|
this.isFlushing = true;
|
|
2751
2791
|
this.emitter.emit(1 /* BEFORE_FLUSH */, void 0);
|
|
2752
|
-
const
|
|
2753
|
-
this.tasks.
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2792
|
+
const flushing = this.tasks;
|
|
2793
|
+
this.tasks = this.tasksSpare;
|
|
2794
|
+
this.flushing = flushing;
|
|
2795
|
+
for (const task of flushing) task();
|
|
2796
|
+
flushing.clear();
|
|
2797
|
+
this.tasksSpare = flushing;
|
|
2798
|
+
this.flushing = void 0;
|
|
2757
2799
|
this.isFlushing = false;
|
|
2758
2800
|
this.emitter.emit(0 /* AFTER_FLUSH */, void 0);
|
|
2759
2801
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -247,6 +247,8 @@ declare enum EOIMEventQueueEventType {
|
|
|
247
247
|
declare class OIMEventQueue {
|
|
248
248
|
readonly emitter: OIMEventEmitter<Record<EOIMEventQueueEventType, void>>;
|
|
249
249
|
protected tasks: Set<() => void>;
|
|
250
|
+
protected tasksSpare: Set<() => void>;
|
|
251
|
+
protected flushing?: Set<() => void>;
|
|
250
252
|
protected readonly scheduler?: OIMEventQueueScheduler;
|
|
251
253
|
protected flushBound?: () => void;
|
|
252
254
|
protected isFlushing: boolean;
|
|
@@ -256,6 +258,16 @@ declare class OIMEventQueue {
|
|
|
256
258
|
* Returns a dequeue function that can be called to cancel the task before it runs.
|
|
257
259
|
*/
|
|
258
260
|
enqueue(fn: () => void): () => void;
|
|
261
|
+
/**
|
|
262
|
+
* Allocation-free enqueue for a **stable** function reference (e.g. a bound
|
|
263
|
+
* `onFlush` method). The function is stored directly — no wrapper, no
|
|
264
|
+
* returned closure — and cancelled via {@link cancelTask}. Enqueuing the
|
|
265
|
+
* same reference twice is idempotent (Set-deduped); callers that may run a
|
|
266
|
+
* task more than once per flush should use {@link enqueue} instead.
|
|
267
|
+
*/
|
|
268
|
+
enqueueTask(fn: () => void): void;
|
|
269
|
+
/** Cancel a task scheduled via {@link enqueueTask}; safe to call mid-flush. */
|
|
270
|
+
cancelTask(fn: () => void): void;
|
|
259
271
|
get isInFlush(): boolean;
|
|
260
272
|
/**
|
|
261
273
|
* Dequeue (cancel) a previously enqueued task by its dequeue function.
|
|
@@ -454,7 +466,6 @@ declare class OIMCarrierKeyedEmitter<TKey extends TOIMPk, TCarrier extends IOIMS
|
|
|
454
466
|
private readonly subscribedCarriers;
|
|
455
467
|
private dirtyCarriers;
|
|
456
468
|
private isFlushEnqueued;
|
|
457
|
-
private dequeueFlush?;
|
|
458
469
|
constructor(queue: OIMEventQueue, resolver: IOIMCarrierResolver<TKey, TCarrier>);
|
|
459
470
|
subscribeOnKey(key: TKey, handler: TOIMEventHandler<void>): () => void;
|
|
460
471
|
subscribeOnKeys(keys: readonly TKey[], handler: TOIMEventHandler<void>): () => void;
|
|
@@ -489,7 +500,6 @@ declare class OIMReactiveCollection<TEntity extends object, TPk extends TOIMPk>
|
|
|
489
500
|
private pendingAnyUpdatePks;
|
|
490
501
|
private isAnyUpdateClearPending;
|
|
491
502
|
private isAnyUpdateScheduled;
|
|
492
|
-
private dequeueAnyUpdate?;
|
|
493
503
|
constructor(queue: OIMEventQueue, opts?: TOIMCollectionOptions<TEntity, TPk>);
|
|
494
504
|
subscribeOnKey(pk: TPk, handler: TOIMEventHandler<void>): () => void;
|
|
495
505
|
subscribeOnKeys(pks: readonly TPk[], handler: TOIMEventHandler<void>): () => void;
|
|
@@ -661,7 +671,6 @@ declare class OIMUpdateEventEmitter<TKey extends TOIMPk> {
|
|
|
661
671
|
protected queue: OIMEventQueue;
|
|
662
672
|
private readonly deliveryMode;
|
|
663
673
|
private isFlushEnqueued;
|
|
664
|
-
private dequeueFlush?;
|
|
665
674
|
private updatedKeys;
|
|
666
675
|
private isAfterFlushDeliveryScheduled;
|
|
667
676
|
private unsubscribeAfterFlushDelivery?;
|
|
@@ -999,6 +1008,15 @@ declare class OIMObject<TKey extends string, TValue> {
|
|
|
999
1008
|
get(key: TKey): TValue | undefined;
|
|
1000
1009
|
getAll(): Record<TKey, TValue>;
|
|
1001
1010
|
clear(): void;
|
|
1011
|
+
/**
|
|
1012
|
+
* Write fan-out hooks. Split single-key vs multi-key so a reactive subclass
|
|
1013
|
+
* can mark its keyed emitter on the hot single-property path without
|
|
1014
|
+
* allocating a `{ keys: [key] }` payload. The base only fires the general
|
|
1015
|
+
* UPDATE event, and only when something is actually subscribed to it — so an
|
|
1016
|
+
* unobserved write skips the payload allocation entirely.
|
|
1017
|
+
*/
|
|
1018
|
+
protected onUpdatedKey(key: TKey): void;
|
|
1019
|
+
protected onUpdatedKeys(keys: TKey[]): void;
|
|
1002
1020
|
count(): number;
|
|
1003
1021
|
keys(): TKey[];
|
|
1004
1022
|
values(): TValue[];
|
|
@@ -1007,8 +1025,15 @@ declare class OIMObject<TKey extends string, TValue> {
|
|
|
1007
1025
|
}
|
|
1008
1026
|
|
|
1009
1027
|
declare class OIMReactiveObject<TKey extends string, TValue> extends OIMObject<TKey, TValue> implements IOIMKeyedSubscription<TKey> {
|
|
1010
|
-
protected readonly updateEmitter:
|
|
1028
|
+
protected readonly updateEmitter: IOIMKeyedUpdateEmitter<TKey>;
|
|
1011
1029
|
constructor(queue: OIMEventQueue, opts?: TOIMObjectOptions<TKey, TValue>);
|
|
1030
|
+
/**
|
|
1031
|
+
* Mark the written key(s) on the keyed emitter directly — no general-event
|
|
1032
|
+
* bridge, no `{ keys }` payload on the single-key path. `super` still fires
|
|
1033
|
+
* the general UPDATE event for any low-level `emitter` subscribers.
|
|
1034
|
+
*/
|
|
1035
|
+
protected onUpdatedKey(key: TKey): void;
|
|
1036
|
+
protected onUpdatedKeys(keys: TKey[]): void;
|
|
1012
1037
|
subscribeOnKey(key: TKey, handler: TOIMEventHandler<void>): () => void;
|
|
1013
1038
|
subscribeOnKeys(keys: readonly TKey[], handler: TOIMEventHandler<void>): () => void;
|
|
1014
1039
|
unsubscribeFromKey(key: TKey, handler: TOIMEventHandler<void>): void;
|
package/dist/index.d.ts
CHANGED
|
@@ -247,6 +247,8 @@ declare enum EOIMEventQueueEventType {
|
|
|
247
247
|
declare class OIMEventQueue {
|
|
248
248
|
readonly emitter: OIMEventEmitter<Record<EOIMEventQueueEventType, void>>;
|
|
249
249
|
protected tasks: Set<() => void>;
|
|
250
|
+
protected tasksSpare: Set<() => void>;
|
|
251
|
+
protected flushing?: Set<() => void>;
|
|
250
252
|
protected readonly scheduler?: OIMEventQueueScheduler;
|
|
251
253
|
protected flushBound?: () => void;
|
|
252
254
|
protected isFlushing: boolean;
|
|
@@ -256,6 +258,16 @@ declare class OIMEventQueue {
|
|
|
256
258
|
* Returns a dequeue function that can be called to cancel the task before it runs.
|
|
257
259
|
*/
|
|
258
260
|
enqueue(fn: () => void): () => void;
|
|
261
|
+
/**
|
|
262
|
+
* Allocation-free enqueue for a **stable** function reference (e.g. a bound
|
|
263
|
+
* `onFlush` method). The function is stored directly — no wrapper, no
|
|
264
|
+
* returned closure — and cancelled via {@link cancelTask}. Enqueuing the
|
|
265
|
+
* same reference twice is idempotent (Set-deduped); callers that may run a
|
|
266
|
+
* task more than once per flush should use {@link enqueue} instead.
|
|
267
|
+
*/
|
|
268
|
+
enqueueTask(fn: () => void): void;
|
|
269
|
+
/** Cancel a task scheduled via {@link enqueueTask}; safe to call mid-flush. */
|
|
270
|
+
cancelTask(fn: () => void): void;
|
|
259
271
|
get isInFlush(): boolean;
|
|
260
272
|
/**
|
|
261
273
|
* Dequeue (cancel) a previously enqueued task by its dequeue function.
|
|
@@ -454,7 +466,6 @@ declare class OIMCarrierKeyedEmitter<TKey extends TOIMPk, TCarrier extends IOIMS
|
|
|
454
466
|
private readonly subscribedCarriers;
|
|
455
467
|
private dirtyCarriers;
|
|
456
468
|
private isFlushEnqueued;
|
|
457
|
-
private dequeueFlush?;
|
|
458
469
|
constructor(queue: OIMEventQueue, resolver: IOIMCarrierResolver<TKey, TCarrier>);
|
|
459
470
|
subscribeOnKey(key: TKey, handler: TOIMEventHandler<void>): () => void;
|
|
460
471
|
subscribeOnKeys(keys: readonly TKey[], handler: TOIMEventHandler<void>): () => void;
|
|
@@ -489,7 +500,6 @@ declare class OIMReactiveCollection<TEntity extends object, TPk extends TOIMPk>
|
|
|
489
500
|
private pendingAnyUpdatePks;
|
|
490
501
|
private isAnyUpdateClearPending;
|
|
491
502
|
private isAnyUpdateScheduled;
|
|
492
|
-
private dequeueAnyUpdate?;
|
|
493
503
|
constructor(queue: OIMEventQueue, opts?: TOIMCollectionOptions<TEntity, TPk>);
|
|
494
504
|
subscribeOnKey(pk: TPk, handler: TOIMEventHandler<void>): () => void;
|
|
495
505
|
subscribeOnKeys(pks: readonly TPk[], handler: TOIMEventHandler<void>): () => void;
|
|
@@ -661,7 +671,6 @@ declare class OIMUpdateEventEmitter<TKey extends TOIMPk> {
|
|
|
661
671
|
protected queue: OIMEventQueue;
|
|
662
672
|
private readonly deliveryMode;
|
|
663
673
|
private isFlushEnqueued;
|
|
664
|
-
private dequeueFlush?;
|
|
665
674
|
private updatedKeys;
|
|
666
675
|
private isAfterFlushDeliveryScheduled;
|
|
667
676
|
private unsubscribeAfterFlushDelivery?;
|
|
@@ -999,6 +1008,15 @@ declare class OIMObject<TKey extends string, TValue> {
|
|
|
999
1008
|
get(key: TKey): TValue | undefined;
|
|
1000
1009
|
getAll(): Record<TKey, TValue>;
|
|
1001
1010
|
clear(): void;
|
|
1011
|
+
/**
|
|
1012
|
+
* Write fan-out hooks. Split single-key vs multi-key so a reactive subclass
|
|
1013
|
+
* can mark its keyed emitter on the hot single-property path without
|
|
1014
|
+
* allocating a `{ keys: [key] }` payload. The base only fires the general
|
|
1015
|
+
* UPDATE event, and only when something is actually subscribed to it — so an
|
|
1016
|
+
* unobserved write skips the payload allocation entirely.
|
|
1017
|
+
*/
|
|
1018
|
+
protected onUpdatedKey(key: TKey): void;
|
|
1019
|
+
protected onUpdatedKeys(keys: TKey[]): void;
|
|
1002
1020
|
count(): number;
|
|
1003
1021
|
keys(): TKey[];
|
|
1004
1022
|
values(): TValue[];
|
|
@@ -1007,8 +1025,15 @@ declare class OIMObject<TKey extends string, TValue> {
|
|
|
1007
1025
|
}
|
|
1008
1026
|
|
|
1009
1027
|
declare class OIMReactiveObject<TKey extends string, TValue> extends OIMObject<TKey, TValue> implements IOIMKeyedSubscription<TKey> {
|
|
1010
|
-
protected readonly updateEmitter:
|
|
1028
|
+
protected readonly updateEmitter: IOIMKeyedUpdateEmitter<TKey>;
|
|
1011
1029
|
constructor(queue: OIMEventQueue, opts?: TOIMObjectOptions<TKey, TValue>);
|
|
1030
|
+
/**
|
|
1031
|
+
* Mark the written key(s) on the keyed emitter directly — no general-event
|
|
1032
|
+
* bridge, no `{ keys }` payload on the single-key path. `super` still fires
|
|
1033
|
+
* the general UPDATE event for any low-level `emitter` subscribers.
|
|
1034
|
+
*/
|
|
1035
|
+
protected onUpdatedKey(key: TKey): void;
|
|
1036
|
+
protected onUpdatedKeys(keys: TKey[]): void;
|
|
1012
1037
|
subscribeOnKey(key: TKey, handler: TOIMEventHandler<void>): () => void;
|
|
1013
1038
|
subscribeOnKeys(keys: readonly TKey[], handler: TOIMEventHandler<void>): () => void;
|
|
1014
1039
|
unsubscribeFromKey(key: TKey, handler: TOIMEventHandler<void>): void;
|
package/dist/index.js
CHANGED
|
@@ -435,7 +435,6 @@ var OIMCarrierKeyedEmitter = class {
|
|
|
435
435
|
this.isFlushEnqueued = false;
|
|
436
436
|
this.onFlush = () => {
|
|
437
437
|
this.isFlushEnqueued = false;
|
|
438
|
-
this.dequeueFlush = void 0;
|
|
439
438
|
if (this.dirtyCarriers.length === 0) return;
|
|
440
439
|
const flushing = this.dirtyCarriers;
|
|
441
440
|
this.dirtyCarriers = [];
|
|
@@ -545,9 +544,9 @@ var OIMCarrierKeyedEmitter = class {
|
|
|
545
544
|
};
|
|
546
545
|
}
|
|
547
546
|
destroy() {
|
|
548
|
-
if (this.
|
|
549
|
-
this.
|
|
550
|
-
this.
|
|
547
|
+
if (this.isFlushEnqueued) {
|
|
548
|
+
this.queue.cancelTask(this.onFlush);
|
|
549
|
+
this.isFlushEnqueued = false;
|
|
551
550
|
}
|
|
552
551
|
this.subscribedCarriers.forEach((carrier) => {
|
|
553
552
|
carrier.subscribers?.clear();
|
|
@@ -570,7 +569,7 @@ var OIMCarrierKeyedEmitter = class {
|
|
|
570
569
|
scheduleFlush() {
|
|
571
570
|
if (this.isFlushEnqueued) return;
|
|
572
571
|
this.isFlushEnqueued = true;
|
|
573
|
-
this.
|
|
572
|
+
this.queue.enqueueTask(this.onFlush);
|
|
574
573
|
}
|
|
575
574
|
notify(carrier) {
|
|
576
575
|
const subscribers = carrier.subscribers;
|
|
@@ -996,7 +995,6 @@ var OIMReactiveCollection = class extends OIMCollection {
|
|
|
996
995
|
this.runAnyUpdate = () => {
|
|
997
996
|
if (!this.isAnyUpdateScheduled) return;
|
|
998
997
|
this.isAnyUpdateScheduled = false;
|
|
999
|
-
this.dequeueAnyUpdate = void 0;
|
|
1000
998
|
const pks = this.isAnyUpdateClearPending ? [] : Array.from(this.pendingAnyUpdatePks);
|
|
1001
999
|
this.pendingAnyUpdatePks.clear();
|
|
1002
1000
|
this.isAnyUpdateClearPending = false;
|
|
@@ -1136,13 +1134,13 @@ var OIMReactiveCollection = class extends OIMCollection {
|
|
|
1136
1134
|
ensureAnyUpdateEnqueued() {
|
|
1137
1135
|
if (this.isAnyUpdateScheduled) return;
|
|
1138
1136
|
this.isAnyUpdateScheduled = true;
|
|
1139
|
-
this.
|
|
1137
|
+
this.queue.enqueueTask(this.runAnyUpdate);
|
|
1140
1138
|
}
|
|
1141
1139
|
destroy() {
|
|
1142
1140
|
this.updateEmitter.destroy();
|
|
1143
|
-
if (this.
|
|
1144
|
-
this.
|
|
1145
|
-
this.
|
|
1141
|
+
if (this.isAnyUpdateScheduled) {
|
|
1142
|
+
this.queue.cancelTask(this.runAnyUpdate);
|
|
1143
|
+
this.isAnyUpdateScheduled = false;
|
|
1146
1144
|
}
|
|
1147
1145
|
this.anyUpdateHandlers.clear();
|
|
1148
1146
|
this.pendingAnyUpdatePks.clear();
|
|
@@ -1284,7 +1282,6 @@ var OIMUpdateEventEmitter = class {
|
|
|
1284
1282
|
// Stable handler reference to avoid allocating a new closure on every HAS_CHANGES.
|
|
1285
1283
|
this.onFlush = () => {
|
|
1286
1284
|
this.isFlushEnqueued = false;
|
|
1287
|
-
this.dequeueFlush = void 0;
|
|
1288
1285
|
if (this.keyHandlers.size === 0) {
|
|
1289
1286
|
this.updatedKeys.clear();
|
|
1290
1287
|
return;
|
|
@@ -1337,7 +1334,7 @@ var OIMUpdateEventEmitter = class {
|
|
|
1337
1334
|
}
|
|
1338
1335
|
if (this.isFlushEnqueued) return;
|
|
1339
1336
|
this.isFlushEnqueued = true;
|
|
1340
|
-
this.
|
|
1337
|
+
this.queue.enqueueTask(this.onFlush);
|
|
1341
1338
|
}
|
|
1342
1339
|
markUpdatedKey(key) {
|
|
1343
1340
|
if (this.deliveryMode === "queue" && this.queue.isInFlush) {
|
|
@@ -1359,7 +1356,7 @@ var OIMUpdateEventEmitter = class {
|
|
|
1359
1356
|
}
|
|
1360
1357
|
if (this.isFlushEnqueued) return;
|
|
1361
1358
|
this.isFlushEnqueued = true;
|
|
1362
|
-
this.
|
|
1359
|
+
this.queue.enqueueTask(this.onFlush);
|
|
1363
1360
|
}
|
|
1364
1361
|
markAllUpdated() {
|
|
1365
1362
|
if (this.deliveryMode === "queue" && this.queue.isInFlush) {
|
|
@@ -1381,7 +1378,7 @@ var OIMUpdateEventEmitter = class {
|
|
|
1381
1378
|
}
|
|
1382
1379
|
if (this.isFlushEnqueued) return;
|
|
1383
1380
|
this.isFlushEnqueued = true;
|
|
1384
|
-
this.
|
|
1381
|
+
this.queue.enqueueTask(this.onFlush);
|
|
1385
1382
|
}
|
|
1386
1383
|
deliverImmediate() {
|
|
1387
1384
|
this.runSinglePass();
|
|
@@ -1429,9 +1426,9 @@ var OIMUpdateEventEmitter = class {
|
|
|
1429
1426
|
}
|
|
1430
1427
|
}
|
|
1431
1428
|
destroy() {
|
|
1432
|
-
if (this.
|
|
1433
|
-
this.
|
|
1434
|
-
this.
|
|
1429
|
+
if (this.isFlushEnqueued) {
|
|
1430
|
+
this.queue.cancelTask(this.onFlush);
|
|
1431
|
+
this.isFlushEnqueued = false;
|
|
1435
1432
|
}
|
|
1436
1433
|
if (this.unsubscribeAfterFlushDelivery) {
|
|
1437
1434
|
this.unsubscribeAfterFlushDelivery();
|
|
@@ -1489,9 +1486,8 @@ var OIMUpdateEventEmitter = class {
|
|
|
1489
1486
|
}
|
|
1490
1487
|
if (this.keyHandlers.size === 0) {
|
|
1491
1488
|
this.updatedKeys.clear();
|
|
1492
|
-
if (this.
|
|
1493
|
-
this.
|
|
1494
|
-
this.dequeueFlush = void 0;
|
|
1489
|
+
if (this.isFlushEnqueued) {
|
|
1490
|
+
this.queue.cancelTask(this.onFlush);
|
|
1495
1491
|
}
|
|
1496
1492
|
this.isFlushEnqueued = false;
|
|
1497
1493
|
}
|
|
@@ -2449,17 +2445,15 @@ var OIMObject = class {
|
|
|
2449
2445
|
}
|
|
2450
2446
|
setProperty(key, value) {
|
|
2451
2447
|
this.store.setProperty(key, value);
|
|
2452
|
-
this.
|
|
2448
|
+
this.onUpdatedKey(key);
|
|
2453
2449
|
}
|
|
2454
2450
|
merge(draft) {
|
|
2455
2451
|
this.store.merge(draft);
|
|
2456
|
-
this.
|
|
2457
|
-
keys: Object.keys(draft)
|
|
2458
|
-
});
|
|
2452
|
+
this.onUpdatedKeys(Object.keys(draft));
|
|
2459
2453
|
}
|
|
2460
2454
|
delete(key) {
|
|
2461
2455
|
this.store.delete(key);
|
|
2462
|
-
this.
|
|
2456
|
+
this.onUpdatedKey(key);
|
|
2463
2457
|
}
|
|
2464
2458
|
get(key) {
|
|
2465
2459
|
return this.store.get(key);
|
|
@@ -2469,7 +2463,24 @@ var OIMObject = class {
|
|
|
2469
2463
|
}
|
|
2470
2464
|
clear() {
|
|
2471
2465
|
this.store.clear();
|
|
2472
|
-
this.
|
|
2466
|
+
this.onUpdatedKeys([]);
|
|
2467
|
+
}
|
|
2468
|
+
/**
|
|
2469
|
+
* Write fan-out hooks. Split single-key vs multi-key so a reactive subclass
|
|
2470
|
+
* can mark its keyed emitter on the hot single-property path without
|
|
2471
|
+
* allocating a `{ keys: [key] }` payload. The base only fires the general
|
|
2472
|
+
* UPDATE event, and only when something is actually subscribed to it — so an
|
|
2473
|
+
* unobserved write skips the payload allocation entirely.
|
|
2474
|
+
*/
|
|
2475
|
+
onUpdatedKey(key) {
|
|
2476
|
+
if (this.emitter.hasHandlers(0 /* UPDATE */)) {
|
|
2477
|
+
this.emitter.emit(0 /* UPDATE */, { keys: [key] });
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
onUpdatedKeys(keys) {
|
|
2481
|
+
if (this.emitter.hasHandlers(0 /* UPDATE */)) {
|
|
2482
|
+
this.emitter.emit(0 /* UPDATE */, { keys });
|
|
2483
|
+
}
|
|
2473
2484
|
}
|
|
2474
2485
|
count() {
|
|
2475
2486
|
return this.store.count();
|
|
@@ -2493,10 +2504,20 @@ var OIMObject = class {
|
|
|
2493
2504
|
var OIMReactiveObject = class extends OIMObject {
|
|
2494
2505
|
constructor(queue, opts) {
|
|
2495
2506
|
super(opts);
|
|
2496
|
-
this.updateEmitter = new
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2507
|
+
this.updateEmitter = new OIMCarrierKeyedEmitter(queue, new OIMKeyedCarrierResolver());
|
|
2508
|
+
}
|
|
2509
|
+
/**
|
|
2510
|
+
* Mark the written key(s) on the keyed emitter directly — no general-event
|
|
2511
|
+
* bridge, no `{ keys }` payload on the single-key path. `super` still fires
|
|
2512
|
+
* the general UPDATE event for any low-level `emitter` subscribers.
|
|
2513
|
+
*/
|
|
2514
|
+
onUpdatedKey(key) {
|
|
2515
|
+
this.updateEmitter.markUpdatedKey(key);
|
|
2516
|
+
super.onUpdatedKey(key);
|
|
2517
|
+
}
|
|
2518
|
+
onUpdatedKeys(keys) {
|
|
2519
|
+
this.updateEmitter.markUpdatedKeys(keys);
|
|
2520
|
+
super.onUpdatedKeys(keys);
|
|
2500
2521
|
}
|
|
2501
2522
|
subscribeOnKey(key, handler) {
|
|
2502
2523
|
return this.updateEmitter.subscribeOnKey(key, handler);
|
|
@@ -2600,6 +2621,9 @@ var OIMEventQueue = class {
|
|
|
2600
2621
|
constructor(options = {}) {
|
|
2601
2622
|
this.emitter = new OIMEventEmitter();
|
|
2602
2623
|
this.tasks = /* @__PURE__ */ new Set();
|
|
2624
|
+
// Reused buffer swapped in during flush so the pending set is never copied
|
|
2625
|
+
// (no per-flush array snapshot) and new enqueues land in a fresh set.
|
|
2626
|
+
this.tasksSpare = /* @__PURE__ */ new Set();
|
|
2603
2627
|
this.isFlushing = false;
|
|
2604
2628
|
this.scheduler = options.scheduler;
|
|
2605
2629
|
if (this.scheduler) {
|
|
@@ -2628,6 +2652,22 @@ var OIMEventQueue = class {
|
|
|
2628
2652
|
this.tasks.delete(task);
|
|
2629
2653
|
};
|
|
2630
2654
|
}
|
|
2655
|
+
/**
|
|
2656
|
+
* Allocation-free enqueue for a **stable** function reference (e.g. a bound
|
|
2657
|
+
* `onFlush` method). The function is stored directly — no wrapper, no
|
|
2658
|
+
* returned closure — and cancelled via {@link cancelTask}. Enqueuing the
|
|
2659
|
+
* same reference twice is idempotent (Set-deduped); callers that may run a
|
|
2660
|
+
* task more than once per flush should use {@link enqueue} instead.
|
|
2661
|
+
*/
|
|
2662
|
+
enqueueTask(fn) {
|
|
2663
|
+
this.tasks.add(fn);
|
|
2664
|
+
this.ensureScheduled();
|
|
2665
|
+
}
|
|
2666
|
+
/** Cancel a task scheduled via {@link enqueueTask}; safe to call mid-flush. */
|
|
2667
|
+
cancelTask(fn) {
|
|
2668
|
+
this.tasks.delete(fn);
|
|
2669
|
+
this.flushing?.delete(fn);
|
|
2670
|
+
}
|
|
2631
2671
|
get isInFlush() {
|
|
2632
2672
|
return this.isFlushing;
|
|
2633
2673
|
}
|
|
@@ -2647,11 +2687,13 @@ var OIMEventQueue = class {
|
|
|
2647
2687
|
this.scheduler?.cancel();
|
|
2648
2688
|
this.isFlushing = true;
|
|
2649
2689
|
this.emitter.emit(1 /* BEFORE_FLUSH */, void 0);
|
|
2650
|
-
const
|
|
2651
|
-
this.tasks.
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2690
|
+
const flushing = this.tasks;
|
|
2691
|
+
this.tasks = this.tasksSpare;
|
|
2692
|
+
this.flushing = flushing;
|
|
2693
|
+
for (const task of flushing) task();
|
|
2694
|
+
flushing.clear();
|
|
2695
|
+
this.tasksSpare = flushing;
|
|
2696
|
+
this.flushing = void 0;
|
|
2655
2697
|
this.isFlushing = false;
|
|
2656
2698
|
this.emitter.emit(0 /* AFTER_FLUSH */, void 0);
|
|
2657
2699
|
}
|