@oscarpalmer/timer 0.22.0 → 0.23.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/when.js CHANGED
@@ -4,10 +4,24 @@ function noop() {
4
4
 
5
5
  // src/constants.ts
6
6
  var activeTimers = new Set;
7
+ var beginTypes = new Set(["continue", "start"]);
8
+ var endTypes = new Set(["pause", "stop"]);
9
+ var endOrRestartTypes = new Set([
10
+ "pause",
11
+ "restart",
12
+ "stop"
13
+ ]);
7
14
  var hiddenTimers = new Set;
8
15
  var milliseconds = 1000 / 60;
9
16
 
10
17
  // src/functions.ts
18
+ function destroyWhen(state) {
19
+ state.timer?.destroy();
20
+ state.promise = undefined;
21
+ state.rejecter = undefined;
22
+ state.resolver = undefined;
23
+ state.timer = undefined;
24
+ }
11
25
  function getOptions(options, isRepeated) {
12
26
  return {
13
27
  afterCallback: options.afterCallback,
@@ -21,12 +35,12 @@ function getValueOrDefault(value, defaultValue, minimum) {
21
35
  return typeof value === "number" && value > (minimum ?? 0) ? value : defaultValue;
22
36
  }
23
37
  function work(type, timer, state, options) {
24
- if (["continue", "start"].includes(type) && state.active || ["pause", "stop"].includes(type) && !state.active) {
38
+ if (state.destroyed && type !== "stop" || beginTypes.has(type) && state.active || endTypes.has(type) && !state.active) {
25
39
  return timer;
26
40
  }
27
41
  const { count, interval, timeout } = options;
28
42
  const { isRepeated, minimum } = state;
29
- if (["pause", "restart", "stop"].includes(type)) {
43
+ if (endOrRestartTypes.has(type)) {
30
44
  const isStop = type === "stop";
31
45
  activeTimers.delete(timer);
32
46
  cancelAnimationFrame(state.frame);
@@ -95,6 +109,14 @@ function work(type, timer, state, options) {
95
109
  return timer;
96
110
  }
97
111
 
112
+ // src/models.ts
113
+ class TimerTrace extends Error {
114
+ constructor() {
115
+ super();
116
+ this.name = "TimerTrace";
117
+ }
118
+ }
119
+
98
120
  // src/timer.ts
99
121
  function timer(type, callback, partial, start) {
100
122
  const isRepeated = type === "repeat";
@@ -103,6 +125,7 @@ function timer(type, callback, partial, start) {
103
125
  callback,
104
126
  isRepeated,
105
127
  active: false,
128
+ destroyed: false,
106
129
  minimum: options.interval - options.interval % milliseconds / 2,
107
130
  paused: false,
108
131
  trace: new TimerTrace
@@ -123,6 +146,9 @@ class Timer extends BasicTimer {
123
146
  get active() {
124
147
  return this.state.active;
125
148
  }
149
+ get destroyed() {
150
+ return this.state.destroyed;
151
+ }
126
152
  get paused() {
127
153
  return this.state.paused;
128
154
  }
@@ -136,6 +162,16 @@ class Timer extends BasicTimer {
136
162
  continue() {
137
163
  return work("continue", this, this.state, this.options);
138
164
  }
165
+ destroy() {
166
+ if (!this.state.destroyed) {
167
+ this.state.destroyed = true;
168
+ this.stop();
169
+ this.options.afterCallback = undefined;
170
+ this.options.errorCallback = undefined;
171
+ this.state.callback = undefined;
172
+ this.state.trace = undefined;
173
+ }
174
+ }
139
175
  pause() {
140
176
  return work("pause", this, this.state, this.options);
141
177
  }
@@ -150,72 +186,82 @@ class Timer extends BasicTimer {
150
186
  }
151
187
  }
152
188
 
153
- class TimerTrace extends Error {
154
- constructor() {
155
- super();
156
- this.name = "TimerTrace";
157
- }
158
- }
159
-
160
189
  // src/when.ts
161
190
  function when(condition, options) {
162
- const repeated = timer("repeat", () => {
163
- if (condition()) {
164
- repeated.stop();
165
- state.resolver?.();
166
- }
167
- }, {
168
- afterCallback() {
169
- if (!repeated.paused) {
170
- if (condition()) {
171
- state.resolver?.();
172
- } else {
173
- state.rejecter?.();
174
- }
191
+ const state = {
192
+ started: false,
193
+ timer: timer("repeat", () => {
194
+ if (condition()) {
195
+ state.timer.stop();
175
196
  }
176
- },
177
- errorCallback() {
178
- state.rejecter?.();
179
- },
180
- count: options?.count,
181
- interval: options?.interval,
182
- timeout: options?.timeout
183
- }, false);
184
- const state = {};
185
- state.promise = new Promise((resolve, reject) => {
197
+ }, {
198
+ afterCallback() {
199
+ if (!state.timer.paused) {
200
+ if (condition()) {
201
+ state.resolver?.();
202
+ } else {
203
+ state.rejecter?.();
204
+ }
205
+ destroyWhen(state);
206
+ }
207
+ },
208
+ errorCallback() {
209
+ state.rejecter?.();
210
+ destroyWhen(state);
211
+ },
212
+ count: options?.count,
213
+ interval: options?.interval,
214
+ timeout: options?.timeout
215
+ }, false)
216
+ };
217
+ const promise = new Promise((resolve, reject) => {
186
218
  state.resolver = resolve;
187
219
  state.rejecter = reject;
188
220
  });
189
- state.timer = repeated;
221
+ state.promise = promise;
190
222
  return new When(state);
191
223
  }
192
224
 
193
225
  class When extends BasicTimer {
194
226
  get active() {
195
- return this.state.timer.active;
227
+ return this.state.timer?.active ?? false;
228
+ }
229
+ get destroyed() {
230
+ return this.state.timer == null;
196
231
  }
197
232
  get paused() {
198
- return this.state.timer.paused;
233
+ return this.state.timer?.paused ?? false;
234
+ }
235
+ get trace() {
236
+ return this.state.timer?.trace;
199
237
  }
200
238
  constructor(state) {
201
239
  super("when", state);
202
240
  }
203
241
  continue() {
204
- this.state.timer.continue();
242
+ this.state.timer?.continue();
205
243
  return this;
206
244
  }
245
+ destroy() {
246
+ if (this.state.timer != null) {
247
+ this.state.timer.destroy();
248
+ }
249
+ }
207
250
  pause() {
208
- this.state.timer.pause();
251
+ this.state.timer?.pause();
209
252
  return this;
210
253
  }
211
254
  stop() {
212
- if (this.state.timer.active) {
213
- this.state.timer.stop();
214
- this.state.rejecter?.();
215
- }
255
+ this.state.timer?.stop();
216
256
  return this;
217
257
  }
218
258
  then(resolve, reject) {
259
+ if (this.state.timer == null || this.state.started) {
260
+ return new Promise(() => {
261
+ (reject ?? noop)();
262
+ });
263
+ }
264
+ this.state.started = true;
219
265
  this.state.timer.start();
220
266
  return this.state.promise.then(resolve ?? noop, reject ?? noop);
221
267
  }
package/dist/when.mjs CHANGED
@@ -1,64 +1,82 @@
1
1
  // src/when.ts
2
2
  import {noop} from "@oscarpalmer/atoms/function";
3
+ import {destroyWhen} from "./functions";
3
4
  import {BasicTimer, timer as timer2} from "./timer";
4
5
  function when(condition, options) {
5
- const repeated = timer2("repeat", () => {
6
- if (condition()) {
7
- repeated.stop();
8
- state.resolver?.();
9
- }
10
- }, {
11
- afterCallback() {
12
- if (!repeated.paused) {
13
- if (condition()) {
14
- state.resolver?.();
15
- } else {
16
- state.rejecter?.();
17
- }
6
+ const state = {
7
+ started: false,
8
+ timer: timer2("repeat", () => {
9
+ if (condition()) {
10
+ state.timer.stop();
18
11
  }
19
- },
20
- errorCallback() {
21
- state.rejecter?.();
22
- },
23
- count: options?.count,
24
- interval: options?.interval,
25
- timeout: options?.timeout
26
- }, false);
27
- const state = {};
28
- state.promise = new Promise((resolve, reject) => {
12
+ }, {
13
+ afterCallback() {
14
+ if (!state.timer.paused) {
15
+ if (condition()) {
16
+ state.resolver?.();
17
+ } else {
18
+ state.rejecter?.();
19
+ }
20
+ destroyWhen(state);
21
+ }
22
+ },
23
+ errorCallback() {
24
+ state.rejecter?.();
25
+ destroyWhen(state);
26
+ },
27
+ count: options?.count,
28
+ interval: options?.interval,
29
+ timeout: options?.timeout
30
+ }, false)
31
+ };
32
+ const promise = new Promise((resolve, reject) => {
29
33
  state.resolver = resolve;
30
34
  state.rejecter = reject;
31
35
  });
32
- state.timer = repeated;
36
+ state.promise = promise;
33
37
  return new When(state);
34
38
  }
35
39
 
36
40
  class When extends BasicTimer {
37
41
  get active() {
38
- return this.state.timer.active;
42
+ return this.state.timer?.active ?? false;
43
+ }
44
+ get destroyed() {
45
+ return this.state.timer == null;
39
46
  }
40
47
  get paused() {
41
- return this.state.timer.paused;
48
+ return this.state.timer?.paused ?? false;
49
+ }
50
+ get trace() {
51
+ return this.state.timer?.trace;
42
52
  }
43
53
  constructor(state) {
44
54
  super("when", state);
45
55
  }
46
56
  continue() {
47
- this.state.timer.continue();
57
+ this.state.timer?.continue();
48
58
  return this;
49
59
  }
60
+ destroy() {
61
+ if (this.state.timer != null) {
62
+ this.state.timer.destroy();
63
+ }
64
+ }
50
65
  pause() {
51
- this.state.timer.pause();
66
+ this.state.timer?.pause();
52
67
  return this;
53
68
  }
54
69
  stop() {
55
- if (this.state.timer.active) {
56
- this.state.timer.stop();
57
- this.state.rejecter?.();
58
- }
70
+ this.state.timer?.stop();
59
71
  return this;
60
72
  }
61
73
  then(resolve, reject) {
74
+ if (this.state.timer == null || this.state.started) {
75
+ return new Promise(() => {
76
+ (reject ?? noop)();
77
+ });
78
+ }
79
+ this.state.started = true;
62
80
  this.state.timer.start();
63
81
  return this.state.promise.then(resolve ?? noop, reject ?? noop);
64
82
  }
package/package.json CHANGED
@@ -8,19 +8,19 @@
8
8
  },
9
9
  "description": "A better solution for timeout- and interval-based timers.",
10
10
  "devDependencies": {
11
- "@biomejs/biome": "^1.8.3",
12
- "@happy-dom/global-registrator": "^14.12.3",
13
- "@types/bun": "^1.1.6",
14
- "bun": "^1.1.21",
11
+ "@biomejs/biome": "^1.9.0",
12
+ "@happy-dom/global-registrator": "^15.7.4",
13
+ "@types/bun": "^1.1.9",
14
+ "bun": "^1.1.27",
15
15
  "dts-bundle-generator": "9.5.1",
16
- "typescript": "^5.5.4"
16
+ "typescript": "^5.6.2"
17
17
  },
18
18
  "exports": {
19
19
  ".": {
20
20
  "bun": "./src/index.ts",
21
21
  "import": {
22
22
  "types": "./types/index.d.ts",
23
- "default": "./dist/timer.mjs"
23
+ "default": "./dist/index.mjs"
24
24
  },
25
25
  "require": {
26
26
  "types": "./types/index.d.cts",
@@ -46,9 +46,9 @@
46
46
  "types": "bun run types:cjs && bun run types:esm",
47
47
  "types:cjs": "bunx dts-bundle-generator --out-file ./types/index.d.cts --external-inlines '@oscarpalmer/atoms' --no-check --silent ./src/index.ts",
48
48
  "types:esm": "bunx tsc -p ./tsconfig.json",
49
- "watch": "bun build ./src/index.ts --outfile ./dist/timer.js --watch"
49
+ "watch": "bun build ./src/index.ts --outfile ./dist/index.js --watch"
50
50
  },
51
51
  "type": "module",
52
52
  "types": "types/index.d.cts",
53
- "version": "0.22.0"
53
+ "version": "0.23.0"
54
54
  }
package/src/constants.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type {WorkType} from './models';
1
2
  import type {Timer} from './timer';
2
3
 
3
4
  /**
@@ -5,6 +6,25 @@ import type {Timer} from './timer';
5
6
  */
6
7
  export const activeTimers = new Set<Timer>();
7
8
 
9
+ /**
10
+ * A set of types that allow work to begin
11
+ */
12
+ export const beginTypes = new Set<WorkType>(['continue', 'start']);
13
+
14
+ /**
15
+ * A set of types that allow work to end
16
+ */
17
+ export const endTypes = new Set<WorkType>(['pause', 'stop']);
18
+
19
+ /**
20
+ * A set of types that allow work to end or restart
21
+ */
22
+ export const endOrRestartTypes = new Set<WorkType>([
23
+ 'pause',
24
+ 'restart',
25
+ 'stop',
26
+ ]);
27
+
8
28
  /**
9
29
  * A set of timers that were paused due to the document being hidden
10
30
  */
package/src/functions.ts CHANGED
@@ -1,7 +1,22 @@
1
- import {activeTimers, milliseconds} from './constants';
2
- import type {TimerOptions, TimerState, WorkType} from './models';
1
+ import {
2
+ activeTimers,
3
+ beginTypes,
4
+ endOrRestartTypes,
5
+ endTypes,
6
+ milliseconds,
7
+ } from './constants';
8
+ import type {TimerOptions, TimerState, WhenState, WorkType} from './models';
3
9
  import type {Timer} from './timer';
4
10
 
11
+ export function destroyWhen(state: WhenState): void {
12
+ state.timer?.destroy();
13
+
14
+ state.promise = undefined as never;
15
+ state.rejecter = undefined;
16
+ state.resolver = undefined;
17
+ state.timer = undefined as never;
18
+ }
19
+
5
20
  export function getOptions(
6
21
  options: Partial<TimerOptions>,
7
22
  isRepeated: boolean,
@@ -38,8 +53,9 @@ export function work(
38
53
  options: TimerOptions,
39
54
  ): Timer {
40
55
  if (
41
- (['continue', 'start'].includes(type) && state.active) ||
42
- (['pause', 'stop'].includes(type) && !state.active)
56
+ (state.destroyed && type !== 'stop') ||
57
+ (beginTypes.has(type) && state.active) ||
58
+ (endTypes.has(type) && !state.active)
43
59
  ) {
44
60
  return timer;
45
61
  }
@@ -47,7 +63,7 @@ export function work(
47
63
  const {count, interval, timeout} = options;
48
64
  const {isRepeated, minimum} = state;
49
65
 
50
- if (['pause', 'restart', 'stop'].includes(type)) {
66
+ if (endOrRestartTypes.has(type)) {
51
67
  const isStop = type === 'stop';
52
68
 
53
69
  activeTimers.delete(timer);
package/src/index.ts CHANGED
@@ -8,11 +8,22 @@ import {wait} from './timer';
8
8
  */
9
9
  export function delay(time: number, timeout?: number): Promise<void> {
10
10
  return new Promise((resolve, reject) => {
11
- wait(resolve ?? noop, {
12
- timeout,
13
- errorCallback: reject ?? noop,
14
- interval: time,
15
- });
11
+ const delayed = wait(
12
+ () => {
13
+ delayed.destroy();
14
+
15
+ (resolve ?? noop)();
16
+ },
17
+ {
18
+ timeout,
19
+ errorCallback: () => {
20
+ delayed.destroy();
21
+
22
+ (reject ?? noop)();
23
+ },
24
+ interval: time,
25
+ },
26
+ );
16
27
  });
17
28
  }
18
29
 
@@ -34,4 +45,3 @@ document.addEventListener('visibilitychange', () => {
34
45
  export {isRepeated, isTimer, isWaited, isWhen} from './is';
35
46
  export {repeat, wait, type Timer} from './timer';
36
47
  export {when, type When} from './when';
37
-
package/src/models.ts CHANGED
@@ -52,6 +52,7 @@ export type TimerOptions = {} & RepeatOptions;
52
52
  export type TimerState = {
53
53
  active: boolean;
54
54
  callback: AnyCallback;
55
+ destroyed: boolean;
55
56
  count?: number;
56
57
  elapsed?: number;
57
58
  frame?: number;
@@ -59,9 +60,17 @@ export type TimerState = {
59
60
  isRepeated: boolean;
60
61
  minimum: number;
61
62
  paused: boolean;
62
- trace: unknown;
63
+ trace: TimerTrace;
63
64
  };
64
65
 
66
+ export class TimerTrace extends Error {
67
+ constructor() {
68
+ super();
69
+
70
+ this.name = 'TimerTrace';
71
+ }
72
+ }
73
+
65
74
  export type WaitOptions = {} & BaseOptions & OptionsWithError;
66
75
 
67
76
  export type WhenOptions = {} & OptionsWithCount;
@@ -70,6 +79,7 @@ export type WhenState = {
70
79
  promise: Promise<void>;
71
80
  rejecter?: () => void;
72
81
  resolver?: () => void;
82
+ started: boolean;
73
83
  timer: Timer;
74
84
  };
75
85
 
package/src/timer.ts CHANGED
@@ -1,12 +1,13 @@
1
1
  import {milliseconds} from './constants';
2
2
  import {getOptions, work} from './functions';
3
- import type {
4
- AnyCallback,
5
- IndexedCallback,
6
- RepeatOptions,
7
- TimerOptions,
8
- TimerState,
9
- WaitOptions,
3
+ import {
4
+ TimerTrace,
5
+ type AnyCallback,
6
+ type IndexedCallback,
7
+ type RepeatOptions,
8
+ type TimerOptions,
9
+ type TimerState,
10
+ type WaitOptions,
10
11
  } from './models';
11
12
 
12
13
  export abstract class BasicTimer<State> {
@@ -23,10 +24,20 @@ export abstract class BasicTimer<State> {
23
24
  */
24
25
  abstract readonly active: boolean;
25
26
 
27
+ /**
28
+ * Is the timer destroyed?
29
+ */
30
+ abstract readonly destroyed: boolean;
31
+
26
32
  /**
27
33
  * Is the timer paused?
28
34
  */
29
35
  abstract readonly paused: boolean;
36
+
37
+ /**
38
+ * Gets the traced location of the timer
39
+ */
40
+ abstract readonly trace: TimerTrace | undefined;
30
41
  }
31
42
 
32
43
  /**
@@ -39,13 +50,14 @@ export class Timer extends BasicTimer<TimerState> {
39
50
  return this.state.active;
40
51
  }
41
52
 
53
+ get destroyed() {
54
+ return this.state.destroyed;
55
+ }
56
+
42
57
  get paused() {
43
58
  return this.state.paused;
44
59
  }
45
60
 
46
- /**
47
- * Gets the traced location of the timer
48
- */
49
61
  get trace() {
50
62
  return globalThis._oscarpalmer_timer_debug ? this.state.trace : undefined;
51
63
  }
@@ -67,6 +79,23 @@ export class Timer extends BasicTimer<TimerState> {
67
79
  return work('continue', this, this.state, this.options);
68
80
  }
69
81
 
82
+ /**
83
+ * Destroys the timer _(after stopping it, if it was running)_
84
+ */
85
+ destroy(): void {
86
+ if (!this.state.destroyed) {
87
+ this.state.destroyed = true;
88
+
89
+ this.stop();
90
+
91
+ this.options.afterCallback = undefined;
92
+ this.options.errorCallback = undefined;
93
+
94
+ this.state.callback = undefined as never;
95
+ this.state.trace = undefined as never;
96
+ }
97
+ }
98
+
70
99
  /**
71
100
  * Pauses the timer _(if it was running)_
72
101
  */
@@ -96,14 +125,6 @@ export class Timer extends BasicTimer<TimerState> {
96
125
  }
97
126
  }
98
127
 
99
- class TimerTrace extends Error {
100
- constructor() {
101
- super();
102
-
103
- this.name = 'TimerTrace';
104
- }
105
- }
106
-
107
128
  /**
108
129
  * Creates a timer which:
109
130
  * - calls a callback after a certain amount of time...
@@ -135,6 +156,7 @@ export function timer(
135
156
  callback,
136
157
  isRepeated,
137
158
  active: false,
159
+ destroyed: false,
138
160
  minimum: options.interval - (options.interval % milliseconds) / 2,
139
161
  paused: false,
140
162
  trace: new TimerTrace(),