@hyperfrontend/time-utils 0.0.5 → 1.0.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
@@ -2,6 +2,18 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [1.0.1](https://github.com/AndrewRedican/hyperfrontend/compare/106ce4517092cdfa9f222b73d938d272c333d69b...adf0a4f77dece2be855e7ab88185a4fe84e7b16b) - 2026-09-14
6
+
7
+ ### Bug Fixes
8
+
9
+ - reject a non-finite base time window instead of returning an invalid date
10
+
11
+ ## [1.0.0](https://github.com/AndrewRedican/hyperfrontend/compare/5f116abb8ba6355dfb283fa03b7481e5eb029480...4b34883aead4c721f021465b19fa5bdd49514d37) - 2026-08-31
12
+
13
+ ### Features
14
+
15
+ - Marked stable. No API changes since 0.0.5.
16
+
5
17
  ## [0.0.5](https://github.com/AndrewRedican/hyperfrontend/compare/c8db08be8b183addd26caf81fdd17fb3693f296f...466c0388c4cd516b9c704214140b4df1004098e6) - 2026-06-23
6
18
 
7
19
  ### Other
package/README.md CHANGED
@@ -1,4 +1,8 @@
1
- # @hyperfrontend/time-utils
1
+ <p align="center">
2
+ <a href="https://www.hyperfrontend.dev/docs/libraries/utils/time/">
3
+ <img width="640" height="180" src="https://www.hyperfrontend.dev/media/banner-time-utils/banner.gif" alt="@hyperfrontend/time-utils">
4
+ </a>
5
+ </p>
2
6
 
3
7
  <p align="center">
4
8
  <a href="https://github.com/AndrewRedican/hyperfrontend/actions/workflows/ci-lib-time-utils.yml">
@@ -33,38 +37,41 @@
33
37
  <img src="https://img.shields.io/badge/tree%20shakeable-%E2%9C%93-success?style=flat-square" alt="Tree Shakeable">
34
38
  </p>
35
39
 
40
+ <p align="center">
41
+ <a href="https://www.hyperfrontend.dev/docs/libraries/utils/time/">
42
+ <img width="640" height="360" src="https://www.hyperfrontend.dev/media/time-utils-countdown/hero.gif" alt="Two stacked 30-second countdown bars draining in parallel: the setTimeout bar runs red straight to zero, while the createTimer bar holds green at 21.0s through a pause and then continues down from there">
43
+ </a>
44
+ </p>
45
+
36
46
  Functional time utilities for async operations, intervals, and time normalization.
37
47
 
38
48
  • 👉 See [**documentation**](https://www.hyperfrontend.dev/docs/libraries/utils/time/)
49
+ • 👉 See [**guides & tutorials**](https://www.hyperfrontend.dev/docs/guides/?package=%40hyperfrontend%2Ftime-utils)
39
50
 
40
51
  ## What is @hyperfrontend/time-utils?
41
52
 
42
- `@hyperfrontend/time-utils` provides composable, testable utilities for working with time-based operations in JavaScript. The library focuses on enhancing the control and flexibility of standard timing APIs (`setTimeout`, `setInterval`) while adding specialized utilities for async workflows and time window calculations.
53
+ [`@hyperfrontend/time-utils`](https://www.hyperfrontend.dev/docs/libraries/utils/time/) provides composable, testable utilities for working with time-based operations in JavaScript. The library focuses on enhancing the control and flexibility of standard timing APIs (`setTimeout`, `setInterval`) while adding specialized utilities for async workflows and time window calculations.
43
54
 
44
55
  Unlike the native timing APIs which offer limited lifecycle control, this library wraps them in functional interfaces that support pausing, resuming, resetting, and subscription management. All utilities return immutable objects with frozen APIs, preventing accidental mutation while maintaining predictable behavior.
45
56
 
46
57
  ### Key Features
47
58
 
48
- - **Controllable timers** - Pause, resume, and reset `setTimeout` operations with tracked remaining time
49
- - **Multi-subscriber clocks** - Observable interval loops supporting multiple callbacks with unified start/stop control
50
- - **Promise-based delays** - Async/await compatible `sleep()` utility for sequential code flows
51
- - **Time window normalization** - Bucket timestamps into fixed intervals (e.g., 5-minute windows for aggregation)
59
+ - **[Controllable timers](https://www.hyperfrontend.dev/docs/libraries/utils/time/#api-createTimer)** - Pause, resume, and reset `setTimeout` operations with tracked remaining time
60
+ - **[Multi-subscriber clocks](https://www.hyperfrontend.dev/docs/libraries/utils/time/#api-createClock)** - Observable interval loops supporting multiple callbacks with unified start/stop control
61
+ - **[Promise-based delays](https://www.hyperfrontend.dev/docs/libraries/utils/time/#api-sleep)** - Async/await compatible `sleep()` utility for sequential code flows
62
+ - **[Time window normalization](https://www.hyperfrontend.dev/docs/libraries/utils/time/#api-normalizeToBaseTimeWindow)** - Bucket timestamps into fixed intervals (e.g., 5-minute windows for aggregation)
52
63
  - **Functional cleanup** - All repeating operations return cleanup functions for straightforward teardown
53
64
  - **Immutable APIs** - All returned objects are frozen, preventing accidental state modifications
54
65
  - **Zero dependencies** - Self-contained timing utilities with no external dependencies
55
66
  - **TypeScript native** - Full type definitions with comprehensive JSDoc documentation
56
67
 
57
- ### Architecture Highlights
58
-
59
- All timing abstractions maintain internal state privately while exposing frozen API objects, following the revealing module pattern. Timer implementations track elapsed time explicitly to enable pause/resume functionality, while clock implementations manage subscriber arrays with simple filter-based unsubscription. The library avoids classes and prototypes in favor of factory functions that return object literals.
60
-
61
68
  ## Why Use @hyperfrontend/time-utils?
62
69
 
63
70
  ### 1. Pause/Resume Capabilities Native APIs Lack
64
71
 
65
- JavaScript's `setTimeout` and `setInterval` cannot be pausedonce started, they either complete or get cancelled. This creates problems for features like user-initiated pauses in games, animations during background tabs, or request throttling. `createTimer()` tracks elapsed time internally, enabling pause/resume without restarting from the beginning or losing progress.
72
+ JavaScript's `setTimeout` and `setInterval` cannot be paused: once started, they either complete or get cancelled. This creates problems for features like user-initiated pauses in games, animations during background tabs, or request throttling. `createTimer()` tracks elapsed time internally, enabling pause/resume without restarting from the beginning or losing progress.
66
73
 
67
- **Example:** A countdown timer in a game needs to pause when the user switches tabs. With `setTimeout`, you'd need to calculate remaining time manually and create a new timeout. With `createTimer`, just call `timer.pause()`.
74
+ **Example:** A countdown timer in a game needs to pause when the user switches tabs. With `setTimeout`, you'd need to calculate remaining time manually and create a new timeout. With [`createTimer`](https://www.hyperfrontend.dev/docs/libraries/utils/time/#api-createTimer), just call `timer.pause()`.
68
75
 
69
76
  ### 2. Multi-Subscriber Interval Management
70
77
 
@@ -161,7 +168,8 @@ async function retryWithDelay(fn, attempts = 3) {
161
168
  import { normalizeToBaseTimeWindow } from '@hyperfrontend/time-utils'
162
169
 
163
170
  // Group metrics into 5-minute windows
164
- const events = [new Date('2024-01-17T10:03:45Z'), new Date('2024-01-17T10:07:22Z'), new Date('2024-01-17T10:12:03Z')]
171
+ const times = ['10:03:45', '10:07:22', '10:12:03']
172
+ const events = times.map((time) => new Date(`2024-01-17T${time}Z`))
165
173
 
166
174
  const buckets = new Map()
167
175
  events.forEach((timestamp) => {
@@ -170,8 +178,9 @@ events.forEach((timestamp) => {
170
178
  buckets.set(key, (buckets.get(key) || 0) + 1)
171
179
  })
172
180
 
173
- // Results:
174
- // "2024-01-17T10:00:00Z" → 2 events
181
+ // Results: 10:07:22 floors to 10:05, not to 10:00, so the three events land in three separate buckets
182
+ // "2024-01-17T10:00:00Z" → 1 event
183
+ // "2024-01-17T10:05:00Z" → 1 event
175
184
  // "2024-01-17T10:10:00Z" → 1 event
176
185
  ```
177
186
 
@@ -191,34 +200,32 @@ cleanup()
191
200
 
192
201
  ## API Overview
193
202
 
194
- **Timing Abstractions:**
195
-
196
- - **`createTimer(callback, delay)`** - Creates a pauseable, resumable timer (enhanced setTimeout)
197
- - `timer.pause()` - Pauses timer, preserving remaining time
198
- - `timer.resume()` - Resumes timer from remaining time
199
- - `timer.reset(newDelay?)` - Restarts timer with optional new duration
203
+ Five functions, one gap they all fill: `setTimeout` and `setInterval` schedule work but give you nothing to steer it with afterwards. Two of the five are where you
204
+ start. [`createTimer`](https://www.hyperfrontend.dev/docs/libraries/utils/time/#api-createTimer) is a timeout you can interrupt: pausing banks whatever was
205
+ left of the delay instead of discarding it, so resuming runs out that remainder rather than serving the full delay again, and `reset(newDelay?)` is the one call
206
+ that does start over. [`createClock`](https://www.hyperfrontend.dev/docs/libraries/utils/time/#api-createClock) solves the opposite problem, fanning a single
207
+ interval out to any number of subscribers, so ten widgets on a one-second cadence share one tick and one `Date` rather than drifting apart on ten intervals of
208
+ their own.
200
209
 
201
- - **`createClock(interval?)`** - Creates a multi-subscriber interval loop (default: 1000ms)
202
- - `clock.start()` - Begins interval loop
203
- - `clock.stop()` - Stops interval loop
204
- - `clock.subscribe(callback)` - Adds callback to subscriber list
205
- - `clock.unsubscribe(callback)` - Removes callback from subscribers
206
- - `clock.interval` - Read-only interval duration
210
+ Both hand back a frozen object rather than a numeric handle you are expected to hold onto and clear. A [`Timer`](https://www.hyperfrontend.dev/docs/libraries/utils/time/#api-Timer)
211
+ is [`pause`](https://www.hyperfrontend.dev/docs/libraries/utils/time/#api-Timer-prop-pause), [`resume`](https://www.hyperfrontend.dev/docs/libraries/utils/time/#api-Timer-prop-resume) and [`reset`](https://www.hyperfrontend.dev/docs/libraries/utils/time/#api-Timer-prop-reset); a [`Clock`](https://www.hyperfrontend.dev/docs/libraries/utils/time/#api-Clock) is [`start`](https://www.hyperfrontend.dev/docs/libraries/utils/time/#api-Clock-prop-start), [`stop`](https://www.hyperfrontend.dev/docs/libraries/utils/time/#api-Clock-prop-stop), [`subscribe`](https://www.hyperfrontend.dev/docs/libraries/utils/time/#api-Clock-prop-subscribe), [`unsubscribe`](https://www.hyperfrontend.dev/docs/libraries/utils/time/#api-Clock-prop-unsubscribe)
212
+ and a read-only [`interval`](https://www.hyperfrontend.dev/docs/libraries/utils/time/#api-Clock-prop-interval). One detail worth knowing before your first call: a timer is created idle, so nothing is scheduled until you `resume()` it once.
207
213
 
208
- **Utility Functions:**
214
+ The remaining three are single-purpose and take no object at all. [`sleep`](https://www.hyperfrontend.dev/docs/libraries/utils/time/#api-sleep) is a delay you can
215
+ `await` in sequence. [`setIntervalCallback`](https://www.hyperfrontend.dev/docs/libraries/utils/time/#api-setIntervalCallback) is a repeating interval for the case
216
+ where teardown is all you want back, returning the cleanup function directly. And
217
+ [`normalizeToBaseTimeWindow`](https://www.hyperfrontend.dev/docs/libraries/utils/time/#api-normalizeToBaseTimeWindow) floors a `Date` to a window boundary and
218
+ returns a new `Date`, which is how independent callers derive the same bucket key from clocks that never agreed to the millisecond.
209
219
 
210
- - **`sleep(milliseconds)`** - Returns promise that resolves after delay (async/await compatible)
211
- - **`setIntervalCallback(callback, interval)`** - Simple setInterval wrapper returning cleanup function
212
- - **`normalizeToBaseTimeWindow(time, baseTimeWindow)`** - Rounds timestamp down to nearest time window boundary (window in minutes)
220
+ Every signature, option and return type is in the [API reference](https://www.hyperfrontend.dev/docs/libraries/utils/time/#api-reference).
213
221
 
214
222
  ## Compatibility
215
223
 
216
- | Platform | Support |
217
- | ----------------------------- | :-----: |
218
- | Browser | ✅ |
219
- | Node.js | ✅ |
220
- | Web Workers | ✅ |
221
- | Deno, Bun, Cloudflare Workers | ✅ |
224
+ <p align="center">
225
+ <a href="https://www.hyperfrontend.dev/docs/libraries/utils/time/#compatibility">
226
+ <img width="640" height="150" src="https://www.hyperfrontend.dev/media/runtimes-time-utils/runtimes.png" alt="Runs in Node.js 18 or later, evergreen browsers and web workers">
227
+ </a>
228
+ </p>
222
229
 
223
230
  ### Output Formats
224
231
 
@@ -243,11 +250,11 @@ cleanup()
243
250
  </script>
244
251
  ```
245
252
 
246
- **Global variable:** `HyperfrontendTimeUtils`
253
+ **Global variable:** [`HyperfrontendTimeUtils`](https://www.hyperfrontend.dev/docs/libraries/utils/time/)
247
254
 
248
255
  ### Dependencies
249
256
 
250
- None zero external dependencies.
257
+ None: zero external dependencies.
251
258
 
252
259
  ## Part of hyperfrontend
253
260
 
package/SECURITY.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Security Policy
2
2
 
3
+ ## The Security Model
4
+
5
+ Before reporting or reviewing, read the model this project is built on:
6
+ **[Security Model](https://www.hyperfrontend.dev/docs/core-concepts/security)**. It is the canonical
7
+ statement of what hyperfrontend defends against and what it does not, and it is the reference every
8
+ other security claim in these docs points back to. In short:
9
+
10
+ - **The named adversary is co-resident script**: an analytics snippet, tag manager, compromised
11
+ dependency, or unknown page that embeds a feature URL. A host that deliberately installs a feature
12
+ is trusting it, the way it trusts any dependency; the controls exist to bound a trusted
13
+ feature's bad day, not to treat its authors as hostile.
14
+ - **Origin checks authenticate rooms, not speakers.** Once arbitrary script runs inside a page, no
15
+ message check distinguishes it from the application. Threats inside a page need Content Security
16
+ Policy, Trusted Types, dependency provenance, and server-side authorisation. That is a different
17
+ treatment, deliberately outside this model.
18
+ - **Three parties carry the security of an integration.** The browser enforces document isolation
19
+ and the frame's capability attributes; the protocol enforces the relationship (pinned
20
+ counterparts, gated handshake, validated payloads, versioned contracts, an optional encrypted
21
+ envelope); and **you** decide authorisation: `frame-ancestors`, backend checks, HTTPS, the
22
+ envelope you choose, and the containment posture you set.
23
+
24
+ A vulnerability report is most useful when it names which of those three the issue defeats.
25
+
3
26
  ## Reporting a Vulnerability
4
27
 
5
28
  We take the security of hyperfrontend seriously. If you discover a security vulnerability, please help us protect our users by following responsible disclosure practices.
@@ -58,14 +81,30 @@ Thank you for helping keep hyperfrontend and its users safe!
58
81
 
59
82
  ## Security Best Practices
60
83
 
61
- When using hyperfrontend in your applications:
62
-
63
- 1. **Keep Dependencies Updated**: Regularly update to the latest version to receive security patches
64
- 2. **Content Security Policy**: Implement appropriate CSP headers when embedding features
65
- 3. **Input Validation**: Validate and sanitize all data passed between features
66
- 4. **Origin Verification**: Always verify the origin of messages in cross-frame communication
67
- 5. **Authentication**: Implement proper authentication and authorization for sensitive features
68
- 6. **HTTPS**: Always serve hyperfrontend features over HTTPS in production
84
+ These are the decisions the SDK cannot make for you. Everything the protocol already enforces —
85
+ origin pinning, window binding, instance identity, the gated handshake, payload validation on both
86
+ ends is on by default and is not something you should be re-implementing by hand.
87
+
88
+ 1. **Restrict who may embed the feature.** Send
89
+ `Content-Security-Policy: frame-ancestors <hosts>` on the response that serves the feature
90
+ document. Origin pinning keeps a conversation consistent; only `frame-ancestors` decides whether
91
+ a page was ever allowed to frame you.
92
+ 2. **Authorise on the server.** A message that crossed the boundary is not an authorised operation.
93
+ Protected work needs credentials the feature's own backend validates.
94
+ 3. **Choose the envelope deliberately.** `v2` with a pre-shared key is the confidentiality control;
95
+ `v1` is time-window obfuscation and buys deterrence only. Provision and rotate the `v2` key
96
+ yourself; a key is never baked into a built artifact. A handshake that cannot agree on an
97
+ encrypted transport falls back to plaintext; where that would be unacceptable, drive
98
+ `@hyperfrontend/nexus` directly and set `security.mode: 'fail-closed'` on the channel so the
99
+ connection is denied instead.
100
+ 4. **Declare schemas and a contract version.** Actions without a schema pass unvalidated, and a side
101
+ without a version always passes the compatibility gate. Both are how drift is caught early.
102
+ 5. **Grant capability narrowly.** Delegate only the Permissions-Policy features the integration
103
+ needs, and price a `sandbox` posture against what the product actually requires.
104
+ 6. **Serve everything over HTTPS**, host and feature alike.
105
+ 7. **Keep dependencies updated** on both sides of the boundary, and pair that with the page-integrity
106
+ controls this model deliberately leaves to you: Content Security Policy, Trusted Types,
107
+ Subresource Integrity, and dependency provenance.
69
108
 
70
109
  ## Security Updates
71
110
 
@@ -73,10 +112,6 @@ Security updates will be released as patch versions and documented in the [CHANG
73
112
 
74
113
  ## Supported Versions
75
114
 
76
- We currently provide security updates for:
77
-
78
- | Version | Supported |
79
- | ------- | ------------------ |
80
- | 0.0.x | :white_check_mark: |
81
-
82
- As the project matures, we will update this table to reflect our long-term support policy.
115
+ Security updates are provided for the latest published version of each `@hyperfrontend/*` package.
116
+ Older releases receive no backports. A long-term support policy will replace this section once the
117
+ packages settle on a stable release cadence.
@@ -1,4 +1,8 @@
1
1
  'use strict';
2
+
3
+ const _Number = globalThis.Number;
2
4
  const _isNaN = globalThis.isNaN;
5
+ const isFinite = _Number.isFinite;
3
6
  const globalIsNaN = _isNaN;
4
7
  exports.globalIsNaN = globalIsNaN;
8
+ exports.isFinite = isFinite;
@@ -1,5 +1,6 @@
1
-
1
+ const _Number = globalThis.Number;
2
2
  const _isNaN = globalThis.isNaN;
3
+ const isFinite = _Number.isFinite;
3
4
  const globalIsNaN = _isNaN;
4
5
 
5
- export { globalIsNaN };
6
+ export { globalIsNaN, isFinite };
@@ -1,158 +1,29 @@
1
1
  var HyperfrontendTimeUtils = (function (exports) {
2
2
  'use strict';
3
3
 
4
- /**
5
- * Safe copies of Date built-in via factory function and static methods.
6
- *
7
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/date
8
- */
9
- /* eslint-disable jsdoc/require-param */
10
4
  const _Date = globalThis.Date;
11
5
  const _Reflect$2 = globalThis.Reflect;
12
- /**
13
- * (Safe copy) Creates a new Date using the captured Date constructor.
14
- * Use this instead of `new Date()`. Accepts all standard Date constructor signatures.
15
- *
16
- * @returns A new Date instance.
17
- *
18
- * @example Creating Date instances
19
- * ```typescript
20
- * const now = createDate()
21
- * const fromTimestamp = createDate(1704067200000)
22
- * const fromString = createDate('2024-01-01T00:00:00Z')
23
- * const fromParts = createDate(2024, 0, 1, 12, 30, 0) // Jan 1, 2024 12:30:00
24
- * ```
25
- */
26
6
  function createDate(...args) {
27
7
  return _Reflect$2.construct(_Date, args);
28
8
  }
29
- /**
30
- * (Safe copy) Returns the number of milliseconds elapsed since January 1, 1970 00:00:00 UTC.
31
- *
32
- * @example
33
- * ```typescript
34
- * const timestamp = dateNow()
35
- * // => 1704067200000 (example timestamp)
36
- * ```
37
- */
38
9
  const dateNow = _Date.now;
39
10
 
40
- /**
41
- * Safe copies of Object built-in methods.
42
- *
43
- * These references are captured at module initialization time to protect against
44
- * prototype pollution attacks. Import only what you need for tree-shaking.
45
- *
46
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/object
47
- */
48
11
  const _Object = globalThis.Object;
49
- /**
50
- * (Safe copy) Prevents modification of existing property attributes and values,
51
- * and prevents the addition of new properties.
52
- */
53
12
  const freeze = _Object.freeze;
54
13
 
55
- /**
56
- * Safe copies of Timer/Scheduling built-in functions.
57
- *
58
- * These references are captured at module initialization time to protect against
59
- * prototype pollution attacks. Import only what you need for tree-shaking.
60
- *
61
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/timers
62
- */
63
14
  const _setTimeout = globalThis.setTimeout;
64
15
  const _setInterval = globalThis.setInterval;
65
16
  const _clearTimeout = globalThis.clearTimeout;
66
17
  const _clearInterval = globalThis.clearInterval;
67
- /**
68
- * (Safe copy) Sets a timer which executes a function once the timer expires.
69
- *
70
- * @param callback - Function to call when the timer elapses.
71
- * @param delay - Time in milliseconds before executing.
72
- * @param args - Additional arguments to pass to the callback.
73
- * @returns A numeric ID for the timer.
74
- *
75
- * @example Setting a timeout
76
- * ```typescript
77
- * const timerId = setTimeout(() => console.log('Executed'), 1000)
78
- * // Pass arguments to callback
79
- * setTimeout((name, count) => console.log(name, count), 500, 'items', 5)
80
- * ```
81
- */
82
18
  const setTimeout = (callback, delay, ...args) => _setTimeout(callback, delay, ...args);
83
- /**
84
- * (Safe copy) Repeatedly calls a function with a fixed time delay between each call.
85
- *
86
- * @param callback - Function to call at each interval.
87
- * @param delay - Time in milliseconds between calls.
88
- * @param args - Additional arguments to pass to the callback.
89
- * @returns A numeric ID for the interval.
90
- *
91
- * @example Setting an interval
92
- * ```typescript
93
- * let count = 0
94
- * const intervalId = setInterval(() => {
95
- * count++
96
- * if (count >= 5) clearInterval(intervalId)
97
- * }, 1000)
98
- * ```
99
- */
100
19
  const setInterval = (callback, delay, ...args) => _setInterval(callback, delay, ...args);
101
- /**
102
- * (Safe copy) Cancels a timeout previously established by setTimeout.
103
- *
104
- * @param id - The identifier of the timeout to cancel.
105
- *
106
- * @example Canceling a timeout
107
- * ```typescript
108
- * const timerId = setTimeout(() => console.log('Never runs'), 5000)
109
- * clearTimeout(timerId)
110
- * ```
111
- */
112
20
  const clearTimeout = (id) => {
113
21
  _clearTimeout(id);
114
22
  };
115
- /**
116
- * (Safe copy) Cancels a timed, repeating action previously established by setInterval.
117
- *
118
- * @param id - The identifier of the interval to cancel.
119
- *
120
- * @example Canceling an interval
121
- * ```typescript
122
- * const intervalId = setInterval(() => console.log('tick'), 1000)
123
- * // Stop after some condition
124
- * clearInterval(intervalId)
125
- * ```
126
- */
127
23
  const clearInterval = (id) => {
128
24
  _clearInterval(id);
129
25
  };
130
26
 
131
- /**
132
- * Creates an interval loop that invokes one or more subscribed callback functions
133
- * at the specified internal (in milliseconds).
134
- *
135
- * Allows you to start or stop the interval loop, much like a stop watch.
136
- * Allows you to unsubscribe callback functions.
137
- *
138
- * @param interval - Time in milliseconds between each callback invocation (default: 1000ms)
139
- * @returns A Clock instance with start, stop, subscribe, and unsubscribe methods
140
- *
141
- * @example Creating interval clock
142
- * ```typescript
143
- * const clock = createClock(1000)
144
- *
145
- * const updateDisplay = (currentTime: Date) => {
146
- * console.log(currentTime.toISOString())
147
- * }
148
- *
149
- * clock.subscribe(updateDisplay)
150
- * clock.start()
151
- *
152
- * // Later: stop receiving updates
153
- * clock.stop()
154
- * ```
155
- */
156
27
  function createClock(interval = 1000) {
157
28
  let clockId = null;
158
29
  let subscribers = [];
@@ -179,32 +50,6 @@ var HyperfrontendTimeUtils = (function (exports) {
179
50
  return freeze({ start, stop, subscribe, unsubscribe, interval });
180
51
  }
181
52
 
182
- /**
183
- * Invokes callback function after the designated time has passed, much like a timer.
184
- * Allows you to pause, resume, or reset the progress of time tracked.
185
- *
186
- * @param callback - The function to invoke after the delay
187
- * @param delay - Time in milliseconds to wait until callback is invoked
188
- * @returns A Timer instance with pause, resume, and reset methods
189
- *
190
- * @example Creating pausable timer
191
- * ```typescript
192
- * const timer = createTimer(() => {
193
- * console.log('Session expired')
194
- * }, 30_000)
195
- *
196
- * timer.resume() // Start the 30-second countdown
197
- *
198
- * // User activity detected - pause the timer
199
- * timer.pause()
200
- *
201
- * // User idle again - resume from where we left off
202
- * timer.resume()
203
- *
204
- * // Reset to full 30 seconds on explicit action
205
- * timer.reset()
206
- * ```
207
- */
208
53
  function createTimer(callback, delay) {
209
54
  let timerId = null;
210
55
  let start = null;
@@ -213,7 +58,6 @@ var HyperfrontendTimeUtils = (function (exports) {
213
58
  if (timerId !== null) {
214
59
  clearTimeout(timerId);
215
60
  const now = dateNow();
216
- /* istanbul ignore else - start is always set when timerId is not null */
217
61
  if (start !== null) {
218
62
  remaining -= now - start;
219
63
  }
@@ -237,85 +81,24 @@ var HyperfrontendTimeUtils = (function (exports) {
237
81
  return freeze({ pause, resume, reset });
238
82
  }
239
83
 
240
- /**
241
- * Safe copies of Error built-ins via factory functions.
242
- *
243
- * Since constructors cannot be safely captured via Object.assign, this module
244
- * provides factory functions that use Reflect.construct internally.
245
- *
246
- * These references are captured at module initialization time to protect against
247
- * prototype pollution attacks. Import only what you need for tree-shaking.
248
- *
249
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/error
250
- */
251
84
  const _Error = globalThis.Error;
252
85
  const _Reflect$1 = globalThis.Reflect;
253
- /**
254
- * (Safe copy) Creates a new Error using the captured Error constructor.
255
- * Use this instead of `new Error()`.
256
- *
257
- * @param message - Optional error message.
258
- * @param options - Optional error options.
259
- * @returns A new Error instance.
260
- *
261
- * @example Creating Error instances
262
- * ```typescript
263
- * const error = createError('Operation failed')
264
- * // With cause for error chaining
265
- * const wrapped = createError('Request failed', { cause: originalError })
266
- * ```
267
- */
268
86
  const createError = (message, options) => _Reflect$1.construct(_Error, [message, options]);
269
87
 
270
- /**
271
- * Safe copies of Math built-in methods.
272
- *
273
- * These references are captured at module initialization time to protect against
274
- * prototype pollution attacks. Import only what you need for tree-shaking.
275
- *
276
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/math
277
- */
278
88
  const _Math = globalThis.Math;
279
- /**
280
- * (Safe copy) Returns the largest integer less than or equal to a number.
281
- */
282
89
  const floor = _Math.floor;
283
90
 
284
- /**
285
- * Safe copies of Number built-in methods and constants.
286
- *
287
- * These references are captured at module initialization time to protect against
288
- * prototype pollution attacks. Import only what you need for tree-shaking.
289
- *
290
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/number
291
- */
91
+ const _Number = globalThis.Number;
292
92
  const _isNaN = globalThis.isNaN;
293
- /**
294
- * (Safe copy) Global isNaN function (coerces to number first, less strict than Number.isNaN).
295
- */
93
+ const isFinite = _Number.isFinite;
296
94
  const globalIsNaN = _isNaN;
297
95
 
298
- /**
299
- * Normalizes a given time to the nearest base time window.
300
- *
301
- * @param time - The Date object to normalize to the nearest time window
302
- * @param baseTimeWindow - The size of the time window in minutes for normalization
303
- * @returns A new Date object normalized to the start of the time window
304
- *
305
- * @example Normalizing to 15-minute buckets
306
- * ```typescript
307
- * // Round timestamps to 15-minute intervals for analytics bucketing
308
- * const eventTime = new Date('2024-03-15T14:23:45Z')
309
- * const bucketTime = normalizeToBaseTimeWindow(eventTime, 15)
310
- * // => 2024-03-15T14:15:00.000Z
311
- * ```
312
- */
313
96
  function normalizeToBaseTimeWindow(time, baseTimeWindow) {
314
97
  if (!time || !(time instanceof Date) || globalIsNaN(time.getTime())) {
315
98
  throw createError('Invalid time input');
316
99
  }
317
- if (baseTimeWindow <= 0) {
318
- throw createError('Base time window must be positive');
100
+ if (!isFinite(baseTimeWindow) || baseTimeWindow <= 0) {
101
+ throw createError('Base time window must be a positive finite number');
319
102
  }
320
103
  const timeInMs = time.getTime();
321
104
  const windowInMs = baseTimeWindow * 60 * 1000;
@@ -323,90 +106,22 @@ var HyperfrontendTimeUtils = (function (exports) {
323
106
  return createDate(normalizedTimeInMs);
324
107
  }
325
108
 
326
- /**
327
- * Creates a repeating interval that invokes a callback function at regular intervals.
328
- *
329
- * @param callback - The function to invoke at each interval
330
- * @param interval - Time in milliseconds between each callback invocation
331
- * @returns A cleanup function that stops the interval when called
332
- *
333
- * @example Setting interval with cleanup
334
- * ```typescript
335
- * const stopPolling = setIntervalCallback(() => {
336
- * fetchLatestData()
337
- * }, 5000)
338
- *
339
- * // Later: clean up when component unmounts
340
- * stopPolling()
341
- * ```
342
- */
343
109
  function setIntervalCallback(callback, interval) {
344
110
  const timerId = setInterval(callback, interval);
345
111
  return () => clearInterval(timerId);
346
112
  }
347
113
 
348
- /**
349
- * Safe Promise factory and bound static methods.
350
- *
351
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/promise
352
- */
353
- /* eslint-disable workspace/lib-require-jsdoc-example */
354
114
  const _Promise = globalThis.Promise;
355
115
  const _Reflect = globalThis.Reflect;
356
- /**
357
- * (Safe copy) Creates a new Promise using the captured Promise constructor.
358
- * Use this instead of `new Promise()`.
359
- *
360
- * @param executor - The executor function.
361
- * @returns A new Promise instance.
362
- */
363
116
  const createPromise = (executor) => _Reflect.construct(_Promise, [executor]);
364
- /**
365
- * (Safe copy) Returns a Promise that resolves with the given value.
366
- */
367
117
  _Promise.resolve.bind(_Promise);
368
- /**
369
- * (Safe copy) Returns a Promise that rejects with the given reason.
370
- */
371
118
  _Promise.reject.bind(_Promise);
372
- /**
373
- * (Safe copy) Returns a Promise that resolves when all promises resolve.
374
- */
375
119
  _Promise.all.bind(_Promise);
376
- /**
377
- * (Safe copy) Returns a Promise that resolves/rejects with the first settled promise.
378
- */
379
120
  _Promise.race.bind(_Promise);
380
- /**
381
- * (Safe copy) Returns a Promise that resolves when all promises settle.
382
- */
383
121
  _Promise.allSettled.bind(_Promise);
384
- /**
385
- * (Safe copy) Returns a Promise that resolves with the first fulfilled promise.
386
- */
387
122
  _Promise.any.bind(_Promise);
388
- /**
389
- * (Safe copy) Creates a Promise along with its resolve and reject functions.
390
- * Note: Available only in ES2024+ environments.
391
- */
392
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
393
123
  _Promise.withResolvers?.bind(_Promise);
394
124
 
395
- /**
396
- * Pauses execution for a specified duration.
397
- *
398
- * @param milliseconds - The duration to sleep in milliseconds
399
- * @returns A promise that resolves after the specified duration
400
- *
401
- * @example Implementing retry backoff
402
- * ```typescript
403
- * async function retryWithBackoff(attempt: number) {
404
- * const backoffMs = Math.min(1000 * 2 ** attempt, 30_000)
405
- * await sleep(backoffMs)
406
- * return fetchResource()
407
- * }
408
- * ```
409
- */
410
125
  function sleep(milliseconds) {
411
126
  return createPromise((resolve) => setTimeout(resolve, milliseconds));
412
127
  }
@@ -1 +1 @@
1
- var HyperfrontendTimeUtils=function(e){"use strict";const l=globalThis.Date,t=globalThis.Reflect;function n(...e){return t.construct(l,e)}const i=l.now,o=globalThis.Object.freeze,s=globalThis.setTimeout,r=globalThis.setInterval,a=globalThis.clearTimeout,c=globalThis.clearInterval,u=(e,l,...t)=>s(e,l,...t),b=(e,l,...t)=>r(e,l,...t),T=e=>{c(e)};const f=globalThis.Error,h=globalThis.Reflect,g=(e,l)=>h.construct(f,[e,l]),m=globalThis.Math.floor,d=globalThis.isNaN;const v=globalThis.Promise,p=globalThis.Reflect;return v.resolve.bind(v),v.reject.bind(v),v.all.bind(v),v.race.bind(v),v.allSettled.bind(v),v.any.bind(v),v.withResolvers?.bind(v),e.createClock=function(e=1e3){let l=null,t=[];return o({start:()=>{null===l&&(l=b(()=>{const e=n();t.forEach(l=>l(e))},e))},stop:()=>{null!==l&&(T(l),l=null)},subscribe:e=>{t.push(e)},unsubscribe:e=>{t=t.filter(l=>l!==e)},interval:e})},e.createTimer=function(e,l){let t=null,n=null,s=l;const r=()=>{if(null!==t){a(t);const e=i();null!==n&&(s-=e-n),t=null}},c=()=>{null===t&&(n=i(),t=u(()=>{e(),t=null},s))};return o({pause:r,resume:c,reset:(e=l)=>{r(),s=e,c()}})},e.normalizeToBaseTimeWindow=function(e,l){if(!e||!(e instanceof Date)||d(e.getTime()))throw g("Invalid time input");if(l<=0)throw g("Base time window must be positive");const t=e.getTime(),i=60*l*1e3;return n(m(t/i)*i)},e.setIntervalCallback=function(e,l){const t=b(e,l);return()=>T(t)},e.sleep=function(e){return l=l=>u(l,e),p.construct(v,[l]);var l},e}({});
1
+ var HyperfrontendTimeUtils=function(e){"use strict";const l=globalThis.Date,t=globalThis.Reflect;function n(...e){return t.construct(l,e)}const i=l.now,o=globalThis.Object.freeze,s=globalThis.setTimeout,r=globalThis.setInterval,a=globalThis.clearTimeout,u=globalThis.clearInterval,c=(e,l,...t)=>s(e,l,...t),b=(e,l,...t)=>r(e,l,...t),T=e=>{u(e)};const f=globalThis.Error,h=globalThis.Reflect,g=(e,l)=>h.construct(f,[e,l]),m=globalThis.Math.floor,d=globalThis.Number,v=globalThis.isNaN,p=d.isFinite,w=v;const I=globalThis.Promise,R=globalThis.Reflect;return I.resolve.bind(I),I.reject.bind(I),I.all.bind(I),I.race.bind(I),I.allSettled.bind(I),I.any.bind(I),I.withResolvers?.bind(I),e.createClock=function(e=1e3){let l=null,t=[];return o({start:()=>{null===l&&(l=b(()=>{const e=n();t.forEach(l=>l(e))},e))},stop:()=>{null!==l&&(T(l),l=null)},subscribe:e=>{t.push(e)},unsubscribe:e=>{t=t.filter(l=>l!==e)},interval:e})},e.createTimer=function(e,l){let t=null,n=null,s=l;const r=()=>{if(null!==t){a(t);const e=i();null!==n&&(s-=e-n),t=null}},u=()=>{null===t&&(n=i(),t=c(()=>{e(),t=null},s))};return o({pause:r,resume:u,reset:(e=l)=>{r(),s=e,u()}})},e.normalizeToBaseTimeWindow=function(e,l){if(!e||!(e instanceof Date)||w(e.getTime()))throw g("Invalid time input");if(!p(l)||l<=0)throw g("Base time window must be a positive finite number");const t=e.getTime(),i=60*l*1e3;return n(m(t/i)*i)},e.setIntervalCallback=function(e,l){const t=b(e,l);return()=>T(t)},e.sleep=function(e){return l=l=>c(l,e),R.construct(I,[l]);var l},e}({});
@@ -4,158 +4,29 @@
4
4
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.HyperfrontendTimeUtils = {}));
5
5
  })(this, (function (exports) { 'use strict';
6
6
 
7
- /**
8
- * Safe copies of Date built-in via factory function and static methods.
9
- *
10
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/date
11
- */
12
- /* eslint-disable jsdoc/require-param */
13
7
  const _Date = globalThis.Date;
14
8
  const _Reflect$2 = globalThis.Reflect;
15
- /**
16
- * (Safe copy) Creates a new Date using the captured Date constructor.
17
- * Use this instead of `new Date()`. Accepts all standard Date constructor signatures.
18
- *
19
- * @returns A new Date instance.
20
- *
21
- * @example Creating Date instances
22
- * ```typescript
23
- * const now = createDate()
24
- * const fromTimestamp = createDate(1704067200000)
25
- * const fromString = createDate('2024-01-01T00:00:00Z')
26
- * const fromParts = createDate(2024, 0, 1, 12, 30, 0) // Jan 1, 2024 12:30:00
27
- * ```
28
- */
29
9
  function createDate(...args) {
30
10
  return _Reflect$2.construct(_Date, args);
31
11
  }
32
- /**
33
- * (Safe copy) Returns the number of milliseconds elapsed since January 1, 1970 00:00:00 UTC.
34
- *
35
- * @example
36
- * ```typescript
37
- * const timestamp = dateNow()
38
- * // => 1704067200000 (example timestamp)
39
- * ```
40
- */
41
12
  const dateNow = _Date.now;
42
13
 
43
- /**
44
- * Safe copies of Object built-in methods.
45
- *
46
- * These references are captured at module initialization time to protect against
47
- * prototype pollution attacks. Import only what you need for tree-shaking.
48
- *
49
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/object
50
- */
51
14
  const _Object = globalThis.Object;
52
- /**
53
- * (Safe copy) Prevents modification of existing property attributes and values,
54
- * and prevents the addition of new properties.
55
- */
56
15
  const freeze = _Object.freeze;
57
16
 
58
- /**
59
- * Safe copies of Timer/Scheduling built-in functions.
60
- *
61
- * These references are captured at module initialization time to protect against
62
- * prototype pollution attacks. Import only what you need for tree-shaking.
63
- *
64
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/timers
65
- */
66
17
  const _setTimeout = globalThis.setTimeout;
67
18
  const _setInterval = globalThis.setInterval;
68
19
  const _clearTimeout = globalThis.clearTimeout;
69
20
  const _clearInterval = globalThis.clearInterval;
70
- /**
71
- * (Safe copy) Sets a timer which executes a function once the timer expires.
72
- *
73
- * @param callback - Function to call when the timer elapses.
74
- * @param delay - Time in milliseconds before executing.
75
- * @param args - Additional arguments to pass to the callback.
76
- * @returns A numeric ID for the timer.
77
- *
78
- * @example Setting a timeout
79
- * ```typescript
80
- * const timerId = setTimeout(() => console.log('Executed'), 1000)
81
- * // Pass arguments to callback
82
- * setTimeout((name, count) => console.log(name, count), 500, 'items', 5)
83
- * ```
84
- */
85
21
  const setTimeout = (callback, delay, ...args) => _setTimeout(callback, delay, ...args);
86
- /**
87
- * (Safe copy) Repeatedly calls a function with a fixed time delay between each call.
88
- *
89
- * @param callback - Function to call at each interval.
90
- * @param delay - Time in milliseconds between calls.
91
- * @param args - Additional arguments to pass to the callback.
92
- * @returns A numeric ID for the interval.
93
- *
94
- * @example Setting an interval
95
- * ```typescript
96
- * let count = 0
97
- * const intervalId = setInterval(() => {
98
- * count++
99
- * if (count >= 5) clearInterval(intervalId)
100
- * }, 1000)
101
- * ```
102
- */
103
22
  const setInterval = (callback, delay, ...args) => _setInterval(callback, delay, ...args);
104
- /**
105
- * (Safe copy) Cancels a timeout previously established by setTimeout.
106
- *
107
- * @param id - The identifier of the timeout to cancel.
108
- *
109
- * @example Canceling a timeout
110
- * ```typescript
111
- * const timerId = setTimeout(() => console.log('Never runs'), 5000)
112
- * clearTimeout(timerId)
113
- * ```
114
- */
115
23
  const clearTimeout = (id) => {
116
24
  _clearTimeout(id);
117
25
  };
118
- /**
119
- * (Safe copy) Cancels a timed, repeating action previously established by setInterval.
120
- *
121
- * @param id - The identifier of the interval to cancel.
122
- *
123
- * @example Canceling an interval
124
- * ```typescript
125
- * const intervalId = setInterval(() => console.log('tick'), 1000)
126
- * // Stop after some condition
127
- * clearInterval(intervalId)
128
- * ```
129
- */
130
26
  const clearInterval = (id) => {
131
27
  _clearInterval(id);
132
28
  };
133
29
 
134
- /**
135
- * Creates an interval loop that invokes one or more subscribed callback functions
136
- * at the specified internal (in milliseconds).
137
- *
138
- * Allows you to start or stop the interval loop, much like a stop watch.
139
- * Allows you to unsubscribe callback functions.
140
- *
141
- * @param interval - Time in milliseconds between each callback invocation (default: 1000ms)
142
- * @returns A Clock instance with start, stop, subscribe, and unsubscribe methods
143
- *
144
- * @example Creating interval clock
145
- * ```typescript
146
- * const clock = createClock(1000)
147
- *
148
- * const updateDisplay = (currentTime: Date) => {
149
- * console.log(currentTime.toISOString())
150
- * }
151
- *
152
- * clock.subscribe(updateDisplay)
153
- * clock.start()
154
- *
155
- * // Later: stop receiving updates
156
- * clock.stop()
157
- * ```
158
- */
159
30
  function createClock(interval = 1000) {
160
31
  let clockId = null;
161
32
  let subscribers = [];
@@ -182,32 +53,6 @@
182
53
  return freeze({ start, stop, subscribe, unsubscribe, interval });
183
54
  }
184
55
 
185
- /**
186
- * Invokes callback function after the designated time has passed, much like a timer.
187
- * Allows you to pause, resume, or reset the progress of time tracked.
188
- *
189
- * @param callback - The function to invoke after the delay
190
- * @param delay - Time in milliseconds to wait until callback is invoked
191
- * @returns A Timer instance with pause, resume, and reset methods
192
- *
193
- * @example Creating pausable timer
194
- * ```typescript
195
- * const timer = createTimer(() => {
196
- * console.log('Session expired')
197
- * }, 30_000)
198
- *
199
- * timer.resume() // Start the 30-second countdown
200
- *
201
- * // User activity detected - pause the timer
202
- * timer.pause()
203
- *
204
- * // User idle again - resume from where we left off
205
- * timer.resume()
206
- *
207
- * // Reset to full 30 seconds on explicit action
208
- * timer.reset()
209
- * ```
210
- */
211
56
  function createTimer(callback, delay) {
212
57
  let timerId = null;
213
58
  let start = null;
@@ -216,7 +61,6 @@
216
61
  if (timerId !== null) {
217
62
  clearTimeout(timerId);
218
63
  const now = dateNow();
219
- /* istanbul ignore else - start is always set when timerId is not null */
220
64
  if (start !== null) {
221
65
  remaining -= now - start;
222
66
  }
@@ -240,85 +84,24 @@
240
84
  return freeze({ pause, resume, reset });
241
85
  }
242
86
 
243
- /**
244
- * Safe copies of Error built-ins via factory functions.
245
- *
246
- * Since constructors cannot be safely captured via Object.assign, this module
247
- * provides factory functions that use Reflect.construct internally.
248
- *
249
- * These references are captured at module initialization time to protect against
250
- * prototype pollution attacks. Import only what you need for tree-shaking.
251
- *
252
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/error
253
- */
254
87
  const _Error = globalThis.Error;
255
88
  const _Reflect$1 = globalThis.Reflect;
256
- /**
257
- * (Safe copy) Creates a new Error using the captured Error constructor.
258
- * Use this instead of `new Error()`.
259
- *
260
- * @param message - Optional error message.
261
- * @param options - Optional error options.
262
- * @returns A new Error instance.
263
- *
264
- * @example Creating Error instances
265
- * ```typescript
266
- * const error = createError('Operation failed')
267
- * // With cause for error chaining
268
- * const wrapped = createError('Request failed', { cause: originalError })
269
- * ```
270
- */
271
89
  const createError = (message, options) => _Reflect$1.construct(_Error, [message, options]);
272
90
 
273
- /**
274
- * Safe copies of Math built-in methods.
275
- *
276
- * These references are captured at module initialization time to protect against
277
- * prototype pollution attacks. Import only what you need for tree-shaking.
278
- *
279
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/math
280
- */
281
91
  const _Math = globalThis.Math;
282
- /**
283
- * (Safe copy) Returns the largest integer less than or equal to a number.
284
- */
285
92
  const floor = _Math.floor;
286
93
 
287
- /**
288
- * Safe copies of Number built-in methods and constants.
289
- *
290
- * These references are captured at module initialization time to protect against
291
- * prototype pollution attacks. Import only what you need for tree-shaking.
292
- *
293
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/number
294
- */
94
+ const _Number = globalThis.Number;
295
95
  const _isNaN = globalThis.isNaN;
296
- /**
297
- * (Safe copy) Global isNaN function (coerces to number first, less strict than Number.isNaN).
298
- */
96
+ const isFinite = _Number.isFinite;
299
97
  const globalIsNaN = _isNaN;
300
98
 
301
- /**
302
- * Normalizes a given time to the nearest base time window.
303
- *
304
- * @param time - The Date object to normalize to the nearest time window
305
- * @param baseTimeWindow - The size of the time window in minutes for normalization
306
- * @returns A new Date object normalized to the start of the time window
307
- *
308
- * @example Normalizing to 15-minute buckets
309
- * ```typescript
310
- * // Round timestamps to 15-minute intervals for analytics bucketing
311
- * const eventTime = new Date('2024-03-15T14:23:45Z')
312
- * const bucketTime = normalizeToBaseTimeWindow(eventTime, 15)
313
- * // => 2024-03-15T14:15:00.000Z
314
- * ```
315
- */
316
99
  function normalizeToBaseTimeWindow(time, baseTimeWindow) {
317
100
  if (!time || !(time instanceof Date) || globalIsNaN(time.getTime())) {
318
101
  throw createError('Invalid time input');
319
102
  }
320
- if (baseTimeWindow <= 0) {
321
- throw createError('Base time window must be positive');
103
+ if (!isFinite(baseTimeWindow) || baseTimeWindow <= 0) {
104
+ throw createError('Base time window must be a positive finite number');
322
105
  }
323
106
  const timeInMs = time.getTime();
324
107
  const windowInMs = baseTimeWindow * 60 * 1000;
@@ -326,90 +109,22 @@
326
109
  return createDate(normalizedTimeInMs);
327
110
  }
328
111
 
329
- /**
330
- * Creates a repeating interval that invokes a callback function at regular intervals.
331
- *
332
- * @param callback - The function to invoke at each interval
333
- * @param interval - Time in milliseconds between each callback invocation
334
- * @returns A cleanup function that stops the interval when called
335
- *
336
- * @example Setting interval with cleanup
337
- * ```typescript
338
- * const stopPolling = setIntervalCallback(() => {
339
- * fetchLatestData()
340
- * }, 5000)
341
- *
342
- * // Later: clean up when component unmounts
343
- * stopPolling()
344
- * ```
345
- */
346
112
  function setIntervalCallback(callback, interval) {
347
113
  const timerId = setInterval(callback, interval);
348
114
  return () => clearInterval(timerId);
349
115
  }
350
116
 
351
- /**
352
- * Safe Promise factory and bound static methods.
353
- *
354
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/promise
355
- */
356
- /* eslint-disable workspace/lib-require-jsdoc-example */
357
117
  const _Promise = globalThis.Promise;
358
118
  const _Reflect = globalThis.Reflect;
359
- /**
360
- * (Safe copy) Creates a new Promise using the captured Promise constructor.
361
- * Use this instead of `new Promise()`.
362
- *
363
- * @param executor - The executor function.
364
- * @returns A new Promise instance.
365
- */
366
119
  const createPromise = (executor) => _Reflect.construct(_Promise, [executor]);
367
- /**
368
- * (Safe copy) Returns a Promise that resolves with the given value.
369
- */
370
120
  _Promise.resolve.bind(_Promise);
371
- /**
372
- * (Safe copy) Returns a Promise that rejects with the given reason.
373
- */
374
121
  _Promise.reject.bind(_Promise);
375
- /**
376
- * (Safe copy) Returns a Promise that resolves when all promises resolve.
377
- */
378
122
  _Promise.all.bind(_Promise);
379
- /**
380
- * (Safe copy) Returns a Promise that resolves/rejects with the first settled promise.
381
- */
382
123
  _Promise.race.bind(_Promise);
383
- /**
384
- * (Safe copy) Returns a Promise that resolves when all promises settle.
385
- */
386
124
  _Promise.allSettled.bind(_Promise);
387
- /**
388
- * (Safe copy) Returns a Promise that resolves with the first fulfilled promise.
389
- */
390
125
  _Promise.any.bind(_Promise);
391
- /**
392
- * (Safe copy) Creates a Promise along with its resolve and reject functions.
393
- * Note: Available only in ES2024+ environments.
394
- */
395
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
396
126
  _Promise.withResolvers?.bind(_Promise);
397
127
 
398
- /**
399
- * Pauses execution for a specified duration.
400
- *
401
- * @param milliseconds - The duration to sleep in milliseconds
402
- * @returns A promise that resolves after the specified duration
403
- *
404
- * @example Implementing retry backoff
405
- * ```typescript
406
- * async function retryWithBackoff(attempt: number) {
407
- * const backoffMs = Math.min(1000 * 2 ** attempt, 30_000)
408
- * await sleep(backoffMs)
409
- * return fetchResource()
410
- * }
411
- * ```
412
- */
413
128
  function sleep(milliseconds) {
414
129
  return createPromise((resolve) => setTimeout(resolve, milliseconds));
415
130
  }
@@ -1 +1 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).HyperfrontendTimeUtils={})}(this,function(e){"use strict";const t=globalThis.Date,l=globalThis.Reflect;function n(...e){return l.construct(t,e)}const i=t.now,o=globalThis.Object.freeze,s=globalThis.setTimeout,r=globalThis.setInterval,a=globalThis.clearTimeout,u=globalThis.clearInterval,c=(e,t,...l)=>s(e,t,...l),f=(e,t,...l)=>r(e,t,...l),b=e=>{u(e)};const T=globalThis.Error,h=globalThis.Reflect,d=(e,t)=>h.construct(T,[e,t]),g=globalThis.Math.floor,m=globalThis.isNaN;const p=globalThis.Promise,v=globalThis.Reflect;p.resolve.bind(p),p.reject.bind(p),p.all.bind(p),p.race.bind(p),p.allSettled.bind(p),p.any.bind(p),p.withResolvers?.bind(p),e.createClock=function(e=1e3){let t=null,l=[];return o({start:()=>{null===t&&(t=f(()=>{const e=n();l.forEach(t=>t(e))},e))},stop:()=>{null!==t&&(b(t),t=null)},subscribe:e=>{l.push(e)},unsubscribe:e=>{l=l.filter(t=>t!==e)},interval:e})},e.createTimer=function(e,t){let l=null,n=null,s=t;const r=()=>{if(null!==l){a(l);const e=i();null!==n&&(s-=e-n),l=null}},u=()=>{null===l&&(n=i(),l=c(()=>{e(),l=null},s))};return o({pause:r,resume:u,reset:(e=t)=>{r(),s=e,u()}})},e.normalizeToBaseTimeWindow=function(e,t){if(!e||!(e instanceof Date)||m(e.getTime()))throw d("Invalid time input");if(t<=0)throw d("Base time window must be positive");const l=e.getTime(),i=60*t*1e3;return n(g(l/i)*i)},e.setIntervalCallback=function(e,t){const l=f(e,t);return()=>b(l)},e.sleep=function(e){return t=t=>c(t,e),v.construct(p,[t]);var t}});
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).HyperfrontendTimeUtils={})}(this,function(e){"use strict";const t=globalThis.Date,l=globalThis.Reflect;function n(...e){return l.construct(t,e)}const i=t.now,o=globalThis.Object.freeze,s=globalThis.setTimeout,r=globalThis.setInterval,a=globalThis.clearTimeout,u=globalThis.clearInterval,c=(e,t,...l)=>s(e,t,...l),b=(e,t,...l)=>r(e,t,...l),f=e=>{u(e)};const T=globalThis.Error,h=globalThis.Reflect,d=(e,t)=>h.construct(T,[e,t]),g=globalThis.Math.floor,m=globalThis.Number,p=globalThis.isNaN,v=m.isFinite,w=p;const y=globalThis.Promise,I=globalThis.Reflect;y.resolve.bind(y),y.reject.bind(y),y.all.bind(y),y.race.bind(y),y.allSettled.bind(y),y.any.bind(y),y.withResolvers?.bind(y),e.createClock=function(e=1e3){let t=null,l=[];return o({start:()=>{null===t&&(t=b(()=>{const e=n();l.forEach(t=>t(e))},e))},stop:()=>{null!==t&&(f(t),t=null)},subscribe:e=>{l.push(e)},unsubscribe:e=>{l=l.filter(t=>t!==e)},interval:e})},e.createTimer=function(e,t){let l=null,n=null,s=t;const r=()=>{if(null!==l){a(l);const e=i();null!==n&&(s-=e-n),l=null}},u=()=>{null===l&&(n=i(),l=c(()=>{e(),l=null},s))};return o({pause:r,resume:u,reset:(e=t)=>{r(),s=e,u()}})},e.normalizeToBaseTimeWindow=function(e,t){if(!e||!(e instanceof Date)||w(e.getTime()))throw d("Invalid time input");if(!v(t)||t<=0)throw d("Base time window must be a positive finite number");const l=e.getTime(),i=60*t*1e3;return n(g(l/i)*i)},e.setIntervalCallback=function(e,t){const l=b(e,t);return()=>f(l)},e.sleep=function(e){return t=t=>c(t,e),I.construct(y,[t]);var t}});
package/index.cjs.js CHANGED
@@ -93,7 +93,7 @@ function createTimer(callback, delay) {
93
93
  if (timerId !== null) {
94
94
  index_cjs_js$1.clearTimeout(timerId);
95
95
  const now = index_cjs_js$2.dateNow();
96
- /* istanbul ignore else - start is always set when timerId is not null */
96
+ // why: start is always set when timerId is not null, so the guard never falls through.
97
97
  if (start !== null) {
98
98
  remaining -= now - start;
99
99
  }
@@ -121,7 +121,7 @@ function createTimer(callback, delay) {
121
121
  * Normalizes a given time to the nearest base time window.
122
122
  *
123
123
  * @param time - The Date object to normalize to the nearest time window
124
- * @param baseTimeWindow - The size of the time window in minutes for normalization
124
+ * @param baseTimeWindow - The size of the time window in minutes for normalization; must be a positive finite number
125
125
  * @returns A new Date object normalized to the start of the time window
126
126
  *
127
127
  * @example Normalizing to 15-minute buckets
@@ -136,8 +136,8 @@ function normalizeToBaseTimeWindow(time, baseTimeWindow) {
136
136
  if (!time || !(time instanceof Date) || index_cjs_js$3.globalIsNaN(time.getTime())) {
137
137
  throw index_cjs_js$4.createError('Invalid time input');
138
138
  }
139
- if (baseTimeWindow <= 0) {
140
- throw index_cjs_js$4.createError('Base time window must be positive');
139
+ if (!index_cjs_js$3.isFinite(baseTimeWindow) || baseTimeWindow <= 0) {
140
+ throw index_cjs_js$4.createError('Base time window must be a positive finite number');
141
141
  }
142
142
  const timeInMs = time.getTime();
143
143
  const windowInMs = baseTimeWindow * 60 * 1000;
package/index.d.ts CHANGED
@@ -81,7 +81,7 @@ declare function createTimer(callback: () => void, delay: number): Timer;
81
81
  * Normalizes a given time to the nearest base time window.
82
82
  *
83
83
  * @param time - The Date object to normalize to the nearest time window
84
- * @param baseTimeWindow - The size of the time window in minutes for normalization
84
+ * @param baseTimeWindow - The size of the time window in minutes for normalization; must be a positive finite number
85
85
  * @returns A new Date object normalized to the start of the time window
86
86
  *
87
87
  * @example Normalizing to 15-minute buckets
package/index.esm.js CHANGED
@@ -3,7 +3,7 @@ import { freeze } from './_dependencies/@hyperfrontend/immutable-api-utils/built
3
3
  import { setInterval, clearInterval, setTimeout, clearTimeout } from './_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/timers/index.esm.js';
4
4
  import { createError } from './_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/error/index.esm.js';
5
5
  import { floor } from './_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/math/index.esm.js';
6
- import { globalIsNaN } from './_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/number/index.esm.js';
6
+ import { globalIsNaN, isFinite } from './_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/number/index.esm.js';
7
7
  import { createPromise } from './_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/promise/index.esm.js';
8
8
 
9
9
  /**
@@ -91,7 +91,7 @@ function createTimer(callback, delay) {
91
91
  if (timerId !== null) {
92
92
  clearTimeout(timerId);
93
93
  const now = dateNow();
94
- /* istanbul ignore else - start is always set when timerId is not null */
94
+ // why: start is always set when timerId is not null, so the guard never falls through.
95
95
  if (start !== null) {
96
96
  remaining -= now - start;
97
97
  }
@@ -119,7 +119,7 @@ function createTimer(callback, delay) {
119
119
  * Normalizes a given time to the nearest base time window.
120
120
  *
121
121
  * @param time - The Date object to normalize to the nearest time window
122
- * @param baseTimeWindow - The size of the time window in minutes for normalization
122
+ * @param baseTimeWindow - The size of the time window in minutes for normalization; must be a positive finite number
123
123
  * @returns A new Date object normalized to the start of the time window
124
124
  *
125
125
  * @example Normalizing to 15-minute buckets
@@ -134,8 +134,8 @@ function normalizeToBaseTimeWindow(time, baseTimeWindow) {
134
134
  if (!time || !(time instanceof Date) || globalIsNaN(time.getTime())) {
135
135
  throw createError('Invalid time input');
136
136
  }
137
- if (baseTimeWindow <= 0) {
138
- throw createError('Base time window must be positive');
137
+ if (!isFinite(baseTimeWindow) || baseTimeWindow <= 0) {
138
+ throw createError('Base time window must be a positive finite number');
139
139
  }
140
140
  const timeInMs = time.getTime();
141
141
  const windowInMs = baseTimeWindow * 60 * 1000;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperfrontend/time-utils",
3
- "version": "0.0.5",
3
+ "version": "1.0.1",
4
4
  "description": "Functional time utilities for async operations, intervals, and time normalization.",
5
5
  "license": "MIT",
6
6
  "sideEffects": false,