@buenos-nachos/time-sync 0.4.0 → 0.4.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @buenos-nachos/time-sync
2
2
 
3
+ ## 0.4.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 5f37f1a: refactored class to remove private setSnapshost method
8
+
3
9
  ## 0.4.0
4
10
 
5
11
  ### Breaking Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buenos-nachos/time-sync",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "author": "Michael Smith <hello@nachos.dev> (https://www.nachos.dev)",
package/src/TimeSync.ts CHANGED
@@ -230,6 +230,23 @@ interface TimeSyncApi {
230
230
  actually want a completely empty function body. */
231
231
  function noOp(..._: readonly unknown[]): void {}
232
232
 
233
+ /**
234
+ * This function is just a convenience for us to sidestep some problems around
235
+ * TypeScript's LSP and Object.freeze. Because Object.freeze can accept any
236
+ * arbitrary type, it basically acts as a "type boundary" between the left and
237
+ * right sides of any snapshot assignments.
238
+ *
239
+ * That means that if you rename a property a a value that is passed to
240
+ * Object.freeze, the LSP can't auto-rename it, and you potentially get missing
241
+ * properties. This is a bit hokey, but because the function is defined strictly
242
+ * in terms of concrete snapshots, any value passed to this function won't have
243
+ * to worry about mismatches.
244
+ */
245
+ function freezeSnapshot(snap: Snapshot): Snapshot {
246
+ Object.freeze(snap.config);
247
+ return Object.freeze(snap);
248
+ }
249
+
233
250
  const defaultMinimumRefreshIntervalMs = 200;
234
251
 
235
252
  /**
@@ -363,43 +380,17 @@ export class TimeSync implements TimeSyncApi {
363
380
  this.#latestSnapshot = Object.freeze(initialSnapshot);
364
381
  }
365
382
 
366
- #setSnapshot(update: Partial<Snapshot>): boolean {
367
- const { date, subscriberCount, config } = this.#latestSnapshot;
368
- if (config.freezeUpdates) {
369
- return false;
370
- }
371
-
372
- // Avoiding both direct property assignment or spread syntax because
373
- // Object.freeze causes weird TypeScript LSP issues around assignability
374
- // where trying to rename a property. If you rename a property on a
375
- // type, it WON'T rename the runtime properties. Object.freeze
376
- // introduces an extra type boundary that break the linking
377
- const updated: Snapshot = {
378
- // Always reject any new configs because trying to remove them at
379
- // the type level isn't worth it for an internal implementation
380
- // detail
381
- config,
382
- date: update.date ?? date,
383
- subscriberCount: update.subscriberCount ?? subscriberCount,
384
- };
385
-
386
- this.#latestSnapshot = Object.freeze(updated);
387
- return true;
388
- }
389
-
390
- #notifyAllSubscriptions(): void {
383
+ #processSubscriptionUpdate(): void {
391
384
  // It's more important that we copy the date object into a separate
392
385
  // variable here than normal, because need make sure the `this` context
393
386
  // can't magically change between updates and cause subscribers to
394
- // receive different values (e.g., one of the subscribers calls the
395
- // invalidate method)
387
+ // receive different values
396
388
  const { date, config } = this.#latestSnapshot;
397
389
 
398
- // We still need to let the logic go through if the current fastest
399
- // interval is Infinity, so that we can support letting any arbitrary
400
- // consumer invalidate the date immediately
401
390
  const subscriptionsPaused =
402
- config.freezeUpdates || this.#subscriptions.size === 0;
391
+ config.freezeUpdates ||
392
+ this.#subscriptions.size === 0 ||
393
+ this.#fastestRefreshInterval === Number.POSITIVE_INFINITY;
403
394
  if (subscriptionsPaused) {
404
395
  return;
405
396
  }
@@ -465,18 +456,24 @@ export class TimeSync implements TimeSyncApi {
465
456
  * is one of them.
466
457
  */
467
458
  readonly #onTick = (): void => {
468
- // Defensive step to make sure that an invalid tick wasn't started
469
- const { config } = this.#latestSnapshot;
459
+ const { config, date } = this.#latestSnapshot;
470
460
  if (config.freezeUpdates) {
461
+ // Defensive step to make sure that an invalid tick wasn't started
471
462
  clearInterval(this.#intervalId);
472
463
  this.#intervalId = undefined;
473
464
  return;
474
465
  }
475
466
 
476
- const wasChanged = this.#setSnapshot({ date: new ReadonlyDate() });
477
- if (wasChanged) {
478
- this.#notifyAllSubscriptions();
467
+ const newDate = new ReadonlyDate();
468
+ if (newDate.getTime() === date.getTime()) {
469
+ return;
479
470
  }
471
+
472
+ this.#latestSnapshot = freezeSnapshot({
473
+ ...this.#latestSnapshot,
474
+ date: newDate,
475
+ });
476
+ this.#processSubscriptionUpdate();
480
477
  };
481
478
 
482
479
  #onFastestIntervalChange(): void {
@@ -497,10 +494,15 @@ export class TimeSync implements TimeSyncApi {
497
494
  clearInterval(this.#intervalId);
498
495
 
499
496
  if (timeBeforeNextUpdate <= 0) {
500
- const wasChanged = this.#setSnapshot({ date: new ReadonlyDate() });
501
- if (wasChanged) {
502
- this.#notifyAllSubscriptions();
497
+ const newDate = new ReadonlyDate();
498
+ if (newDate.getTime() !== date.getTime()) {
499
+ this.#latestSnapshot = freezeSnapshot({
500
+ ...this.#latestSnapshot,
501
+ date: newDate,
502
+ });
503
+ this.#processSubscriptionUpdate();
503
504
  }
505
+
504
506
  this.#intervalId = setInterval(this.#onTick, fastest);
505
507
  return;
506
508
  }
@@ -616,7 +618,8 @@ export class TimeSync implements TimeSyncApi {
616
618
  subsOnSetup.set(onUpdate, filtered);
617
619
  }
618
620
 
619
- void this.#setSnapshot({
621
+ this.#latestSnapshot = freezeSnapshot({
622
+ ...this.#latestSnapshot,
620
623
  subscriberCount: Math.max(0, this.#latestSnapshot.subscriberCount - 1),
621
624
  });
622
625
 
@@ -639,7 +642,8 @@ export class TimeSync implements TimeSyncApi {
639
642
  (e1, e2) => e1.targetRefreshIntervalMs - e2.targetRefreshIntervalMs,
640
643
  );
641
644
 
642
- void this.#setSnapshot({
645
+ this.#latestSnapshot = freezeSnapshot({
646
+ ...this.#latestSnapshot,
643
647
  subscriberCount: this.#latestSnapshot.subscriberCount + 1,
644
648
  });
645
649
 
@@ -664,6 +668,9 @@ export class TimeSync implements TimeSyncApi {
664
668
  // We swap the map out so that the unsubscribe callbacks can detect
665
669
  // whether their functionality is still relevant
666
670
  this.#subscriptions = new Map();
667
- void this.#setSnapshot({ subscriberCount: 0 });
671
+ this.#latestSnapshot = freezeSnapshot({
672
+ ...this.#latestSnapshot,
673
+ subscriberCount: 0,
674
+ });
668
675
  }
669
676
  }