@buenos-nachos/time-sync 0.3.1 → 0.4.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/CHANGELOG.md +13 -1
- package/package.json +1 -1
- package/src/TimeSync.test.ts +0 -47
- package/src/TimeSync.ts +26 -43
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @buenos-nachos/time-sync
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
### Breaking Changes
|
|
6
|
+
|
|
7
|
+
- 663479e: Removed `isSubscribed` property from context and made all other context properties readonly.
|
|
8
|
+
|
|
9
|
+
## 0.3.2
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- b8fbaf8: cleanup up comments and types for exported class, methods, and types.
|
|
14
|
+
|
|
3
15
|
## 0.3.1
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -8,7 +20,7 @@
|
|
|
8
20
|
|
|
9
21
|
## 0.3.0
|
|
10
22
|
|
|
11
|
-
###
|
|
23
|
+
### Breaking Changes
|
|
12
24
|
|
|
13
25
|
- 122f6c1: Updated `SubscriptionContext.timeSync` type to be readonly and non-nullable, and renamed `SubscriptionContext.isLive` to `SubscriptionContext.isSubscribed`.
|
|
14
26
|
|
package/package.json
CHANGED
package/src/TimeSync.test.ts
CHANGED
|
@@ -131,7 +131,6 @@ describe(TimeSync, () => {
|
|
|
131
131
|
|
|
132
132
|
const expectedCtx: SubscriptionContext = {
|
|
133
133
|
unsubscribe,
|
|
134
|
-
isSubscribed: true,
|
|
135
134
|
timeSync: sync,
|
|
136
135
|
intervalLastFulfilledAt: dateAfter,
|
|
137
136
|
registeredAt: dateBefore,
|
|
@@ -563,28 +562,6 @@ describe(TimeSync, () => {
|
|
|
563
562
|
const dateAfter = sync.getStateSnapshot().date;
|
|
564
563
|
expect(dateAfter).not.toEqual(dateBefore);
|
|
565
564
|
});
|
|
566
|
-
|
|
567
|
-
it("Mutates the isLive context value to be false on unsubscribe", async ({
|
|
568
|
-
expect,
|
|
569
|
-
}) => {
|
|
570
|
-
const sync = new TimeSync();
|
|
571
|
-
|
|
572
|
-
let ejectedContext: SubscriptionContext | undefined;
|
|
573
|
-
const onUpdate = vi.fn((_: unknown, ctx: SubscriptionContext) => {
|
|
574
|
-
ejectedContext = ctx;
|
|
575
|
-
});
|
|
576
|
-
|
|
577
|
-
const unsub = sync.subscribe({
|
|
578
|
-
onUpdate,
|
|
579
|
-
targetRefreshIntervalMs: refreshRates.oneMinute,
|
|
580
|
-
});
|
|
581
|
-
|
|
582
|
-
await vi.advanceTimersByTimeAsync(refreshRates.oneMinute);
|
|
583
|
-
expect(ejectedContext?.isSubscribed).toBe(true);
|
|
584
|
-
|
|
585
|
-
unsub();
|
|
586
|
-
expect(ejectedContext?.isSubscribed).toBe(false);
|
|
587
|
-
});
|
|
588
565
|
});
|
|
589
566
|
|
|
590
567
|
describe("Subscriptions: context values", () => {
|
|
@@ -827,7 +804,6 @@ describe(TimeSync, () => {
|
|
|
827
804
|
|
|
828
805
|
await vi.advanceTimersByTimeAsync(refreshRates.oneSecond);
|
|
829
806
|
expect(ejectedContext).toEqual<SubscriptionContext>({
|
|
830
|
-
isSubscribed: true,
|
|
831
807
|
intervalLastFulfilledAt: null,
|
|
832
808
|
registeredAt: snapBefore,
|
|
833
809
|
targetRefreshIntervalMs: refreshRates.oneHour,
|
|
@@ -840,7 +816,6 @@ describe(TimeSync, () => {
|
|
|
840
816
|
|
|
841
817
|
const snapAfter = sync.getStateSnapshot().date;
|
|
842
818
|
expect(ejectedContext).toEqual<SubscriptionContext>({
|
|
843
|
-
isSubscribed: true,
|
|
844
819
|
intervalLastFulfilledAt: snapAfter,
|
|
845
820
|
registeredAt: snapBefore,
|
|
846
821
|
targetRefreshIntervalMs: refreshRates.oneHour,
|
|
@@ -1086,28 +1061,6 @@ describe(TimeSync, () => {
|
|
|
1086
1061
|
await vi.advanceTimersByTimeAsync(refreshRates.oneMinute);
|
|
1087
1062
|
expect(sharedOnUpdate).not.toHaveBeenCalled();
|
|
1088
1063
|
});
|
|
1089
|
-
|
|
1090
|
-
it("Mutates the isLive context value to be false", async ({ expect }) => {
|
|
1091
|
-
const sync = new TimeSync();
|
|
1092
|
-
|
|
1093
|
-
let ejectedContext: SubscriptionContext | undefined;
|
|
1094
|
-
const onUpdate = vi.fn((_: unknown, ctx: SubscriptionContext) => {
|
|
1095
|
-
if (ejectedContext === undefined) {
|
|
1096
|
-
ejectedContext = ctx;
|
|
1097
|
-
}
|
|
1098
|
-
});
|
|
1099
|
-
|
|
1100
|
-
void sync.subscribe({
|
|
1101
|
-
onUpdate,
|
|
1102
|
-
targetRefreshIntervalMs: refreshRates.oneMinute,
|
|
1103
|
-
});
|
|
1104
|
-
|
|
1105
|
-
await vi.advanceTimersByTimeAsync(refreshRates.oneMinute);
|
|
1106
|
-
expect(ejectedContext?.isSubscribed).toBe(true);
|
|
1107
|
-
|
|
1108
|
-
sync.clearAll();
|
|
1109
|
-
expect(ejectedContext?.isSubscribed).toBe(false);
|
|
1110
|
-
});
|
|
1111
1064
|
});
|
|
1112
1065
|
|
|
1113
1066
|
/**
|
package/src/TimeSync.ts
CHANGED
|
@@ -30,7 +30,8 @@ export const refreshRates = Object.freeze({
|
|
|
30
30
|
export interface Configuration {
|
|
31
31
|
/**
|
|
32
32
|
* Indicates whether the TimeSync instance should be frozen for Snapshot
|
|
33
|
-
* tests.
|
|
33
|
+
* tests. Highly encouraged that you use this together with
|
|
34
|
+
* `initialDate`.
|
|
34
35
|
*
|
|
35
36
|
* Defaults to false.
|
|
36
37
|
*/
|
|
@@ -44,7 +45,7 @@ export interface Configuration {
|
|
|
44
45
|
* subscription, this minimum will be used instead.
|
|
45
46
|
*
|
|
46
47
|
* It is highly recommended that you only modify this value if you have a
|
|
47
|
-
* good reason. Updating this value to be too low
|
|
48
|
+
* good reason. Updating this value to be too low can make the event loop
|
|
48
49
|
* get really hot and really tank performance elsewhere in an application.
|
|
49
50
|
*
|
|
50
51
|
* Defaults to 200ms.
|
|
@@ -55,9 +56,11 @@ export interface Configuration {
|
|
|
55
56
|
* Indicates whether the same `onUpdate` callback (by reference) should be
|
|
56
57
|
* called multiple time if registered by multiple systems.
|
|
57
58
|
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
59
|
+
* If this value is flipped to false, each onUpdate callback will receive
|
|
60
|
+
* the subscription context for the FIRST subscriber that registered the
|
|
61
|
+
* onUpdate callback.
|
|
62
|
+
*
|
|
63
|
+
* Defaults to true.
|
|
61
64
|
*/
|
|
62
65
|
readonly allowDuplicateOnUpdateCalls: boolean;
|
|
63
66
|
}
|
|
@@ -66,16 +69,6 @@ export interface Configuration {
|
|
|
66
69
|
* The set of options that can be used to instantiate a TimeSync.
|
|
67
70
|
*/
|
|
68
71
|
export interface InitOptions extends Configuration {
|
|
69
|
-
/**
|
|
70
|
-
* Indicates whether the TimeSync instance should be frozen for snapshot
|
|
71
|
-
* tests. Highly encouraged that you use this together with
|
|
72
|
-
* `initialDate`.
|
|
73
|
-
*
|
|
74
|
-
* Defaults to false.
|
|
75
|
-
*/
|
|
76
|
-
// Duplicated property to override the LSP comment
|
|
77
|
-
readonly freezeUpdates: boolean;
|
|
78
|
-
|
|
79
72
|
/**
|
|
80
73
|
* The Date object to use when initializing TimeSync to make the
|
|
81
74
|
* constructor more pure and deterministic.
|
|
@@ -142,9 +135,8 @@ export interface Snapshot {
|
|
|
142
135
|
* TimeSync.
|
|
143
136
|
*
|
|
144
137
|
* For performance reasons, this object has ZERO readonly guarantees enforced at
|
|
145
|
-
* runtime.
|
|
146
|
-
*
|
|
147
|
-
* state. Proceed with caution.
|
|
138
|
+
* runtime. All properties are defined as readonly at the type level, but an
|
|
139
|
+
* accidental mutation can still slip through.
|
|
148
140
|
*/
|
|
149
141
|
export interface SubscriptionContext {
|
|
150
142
|
/**
|
|
@@ -169,12 +161,6 @@ export interface SubscriptionContext {
|
|
|
169
161
|
*/
|
|
170
162
|
readonly timeSync: TimeSync;
|
|
171
163
|
|
|
172
|
-
/**
|
|
173
|
-
* Indicates whether the subscription is still live. Will be mutated to be
|
|
174
|
-
* false when a subscription is
|
|
175
|
-
*/
|
|
176
|
-
isSubscribed: boolean;
|
|
177
|
-
|
|
178
164
|
/**
|
|
179
165
|
* Indicates when the last time the subscription had its explicit interval
|
|
180
166
|
* "satisfied".
|
|
@@ -183,7 +169,7 @@ export interface SubscriptionContext {
|
|
|
183
169
|
* the active interval is set to fire every second, you may need to know
|
|
184
170
|
* which update actually happened five minutes later.
|
|
185
171
|
*/
|
|
186
|
-
intervalLastFulfilledAt: ReadonlyDate | null;
|
|
172
|
+
readonly intervalLastFulfilledAt: ReadonlyDate | null;
|
|
187
173
|
}
|
|
188
174
|
|
|
189
175
|
/**
|
|
@@ -203,8 +189,16 @@ interface TimeSyncApi {
|
|
|
203
189
|
* intervals. Depending on how TimeSync is instantiated, it may choose to
|
|
204
190
|
* de-duplicate these function calls on each round of updates.
|
|
205
191
|
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
192
|
+
* If a value of Number.POSITIVE_INFINITY is used, the subscription will be
|
|
193
|
+
* considered "idle". Idle subscriptions cannot trigger updates on their
|
|
194
|
+
* own, but can stay in the loop as otherupdates get dispatched from via
|
|
195
|
+
* other subscriptions.
|
|
196
|
+
*
|
|
197
|
+
* Consider using the refreshRates object from this package for a set of
|
|
198
|
+
* commonly-used intervals.
|
|
199
|
+
*
|
|
200
|
+
* @throws {RangeError} If the provided interval is neither a positive
|
|
201
|
+
* integer nor positive infinity.
|
|
208
202
|
* @returns An unsubscribe callback. Calling the callback more than once
|
|
209
203
|
* results in a no-op.
|
|
210
204
|
*/
|
|
@@ -438,7 +432,7 @@ export class TimeSync implements TimeSyncApi {
|
|
|
438
432
|
// first context in a sub array gets removed by unsubscribing, we
|
|
439
433
|
// want what was the the second element to still be up to date
|
|
440
434
|
let shouldCallOnUpdate = true;
|
|
441
|
-
for (const
|
|
435
|
+
for (const ctx of subs as readonly Writeable<SubscriptionContext>[]) {
|
|
442
436
|
// We're not doing anything more sophisticated here because
|
|
443
437
|
// we're assuming that any systems that can clear out the
|
|
444
438
|
// subscriptions will handle cleaning up each context, too
|
|
@@ -447,17 +441,15 @@ export class TimeSync implements TimeSyncApi {
|
|
|
447
441
|
break outer;
|
|
448
442
|
}
|
|
449
443
|
|
|
450
|
-
const comparisonDate =
|
|
451
|
-
context.intervalLastFulfilledAt ?? context.registeredAt;
|
|
444
|
+
const comparisonDate = ctx.intervalLastFulfilledAt ?? ctx.registeredAt;
|
|
452
445
|
const isIntervalMatch =
|
|
453
|
-
dateTime - comparisonDate.getTime() >=
|
|
454
|
-
context.targetRefreshIntervalMs;
|
|
446
|
+
dateTime - comparisonDate.getTime() >= ctx.targetRefreshIntervalMs;
|
|
455
447
|
if (isIntervalMatch) {
|
|
456
|
-
|
|
448
|
+
ctx.intervalLastFulfilledAt = date;
|
|
457
449
|
}
|
|
458
450
|
|
|
459
451
|
if (shouldCallOnUpdate) {
|
|
460
|
-
onUpdate(date,
|
|
452
|
+
onUpdate(date, ctx);
|
|
461
453
|
shouldCallOnUpdate = config.allowDuplicateOnUpdateCalls;
|
|
462
454
|
}
|
|
463
455
|
}
|
|
@@ -585,7 +577,6 @@ export class TimeSync implements TimeSyncApi {
|
|
|
585
577
|
// Have to define this as a writeable to avoid a chicken-and-the-egg
|
|
586
578
|
// problem for the unsubscribe callback
|
|
587
579
|
const context: Writeable<SubscriptionContext> = {
|
|
588
|
-
isSubscribed: true,
|
|
589
580
|
timeSync: this,
|
|
590
581
|
unsubscribe: noOp,
|
|
591
582
|
registeredAt: new ReadonlyDate(),
|
|
@@ -603,7 +594,6 @@ export class TimeSync implements TimeSyncApi {
|
|
|
603
594
|
const subsOnSetup = this.#subscriptions;
|
|
604
595
|
const unsubscribe = (): void => {
|
|
605
596
|
if (!subscribed || this.#subscriptions !== subsOnSetup) {
|
|
606
|
-
context.isSubscribed = false;
|
|
607
597
|
subscribed = false;
|
|
608
598
|
return;
|
|
609
599
|
}
|
|
@@ -630,7 +620,6 @@ export class TimeSync implements TimeSyncApi {
|
|
|
630
620
|
subscriberCount: Math.max(0, this.#latestSnapshot.subscriberCount - 1),
|
|
631
621
|
});
|
|
632
622
|
|
|
633
|
-
context.isSubscribed = false;
|
|
634
623
|
subscribed = false;
|
|
635
624
|
};
|
|
636
625
|
context.unsubscribe = unsubscribe;
|
|
@@ -670,12 +659,6 @@ export class TimeSync implements TimeSyncApi {
|
|
|
670
659
|
// As long as we clean things the internal state, it's safe not to
|
|
671
660
|
// bother calling each unsubscribe callback. Not calling them one by
|
|
672
661
|
// one actually has much better time complexity
|
|
673
|
-
for (const subArray of this.#subscriptions.values()) {
|
|
674
|
-
for (const ctx of subArray) {
|
|
675
|
-
ctx.isSubscribed = false;
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
|
|
679
662
|
this.#subscriptions.clear();
|
|
680
663
|
|
|
681
664
|
// We swap the map out so that the unsubscribe callbacks can detect
|