@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 +12 -0
- package/README.md +46 -39
- package/SECURITY.md +50 -15
- package/_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/number/index.cjs.js +4 -0
- package/_dependencies/@hyperfrontend/immutable-api-utils/built-in-copy/number/index.esm.js +3 -2
- package/bundle/index.iife.js +4 -289
- package/bundle/index.iife.min.js +1 -1
- package/bundle/index.umd.js +4 -289
- package/bundle/index.umd.min.js +1 -1
- package/index.cjs.js +4 -4
- package/index.d.ts +1 -1
- package/index.esm.js +5 -5
- package/package.json +1 -1
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
|
-
|
|
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 paused
|
|
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
|
|
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
|
|
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" →
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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.
|
package/bundle/index.iife.js
CHANGED
|
@@ -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
|
}
|
package/bundle/index.iife.min.js
CHANGED
|
@@ -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,
|
|
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}({});
|
package/bundle/index.umd.js
CHANGED
|
@@ -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
|
}
|
package/bundle/index.umd.min.js
CHANGED
|
@@ -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),
|
|
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
|
-
|
|
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
|
-
|
|
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;
|