@hyperfrontend/list-utils 0.0.5 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.1.0](https://github.com/AndrewRedican/hyperfrontend/compare/5f116abb8ba6355dfb283fa03b7481e5eb029480...cc16961a470190848f71a7cce41f7377bc4511a4) - 2026-08-25
6
+
7
+ ### Breaking Changes
8
+
9
+ - **BREAKING** type fifo pull as T | undefined, reword throws
10
+
11
+ ### Bug Fixes
12
+
13
+ - **BREAKING** type fifo pull as T | undefined, reword throws
14
+
5
15
  ## [0.0.5](https://github.com/AndrewRedican/hyperfrontend/compare/c8db08be8b183addd26caf81fdd17fb3693f296f...466c0388c4cd516b9c704214140b4df1004098e6) - 2026-06-23
6
16
 
7
17
  ### Other
package/README.md CHANGED
@@ -36,6 +36,7 @@
36
36
  Purpose-built collection utilities for queue management, filtering, and iteration patterns.
37
37
 
38
38
  • 👉 See [**documentation**](https://www.hyperfrontend.dev/docs/libraries/utils/list/)
39
+ • 👉 See [**guides & tutorials**](https://www.hyperfrontend.dev/docs/guides/?package=%40hyperfrontend%2Flist-utils)
39
40
 
40
41
  ## What is @hyperfrontend/list-utils?
41
42
 
@@ -60,11 +61,11 @@ Queue implementations return frozen objects to prevent external mutation while u
60
61
 
61
62
  ### Prevents Queue Bugs in Event-Driven Systems
62
63
 
63
- Native arrays don't enforce uniqueness, making it easy to accidentally register the same event handler, task, or subscription multiple times. FIFO/LIFO lists automatically reject duplicates based on reference equality, eliminating a common source of memory leaks and duplicate processing in event loops, job queues, and observer patterns.
64
+ Native arrays don't enforce uniqueness, making it easy to accidentally register the same event handler, task, or subscription multiple times. Both list kinds reject duplicates based on reference equality, eliminating a common source of memory leaks and duplicate processing in event loops, job queues, and observer patterns. A FIFO list throws on a duplicate push; a LIFO list ignores it.
64
65
 
65
66
  ### Simplifies Round-Robin and Cyclical Patterns
66
67
 
67
- The value picker solves the boilerplate problem of cycling through options (load balancer endpoints, color schemes, retry strategies). No manual index tracking or modulo math—just call `next()`. Particularly useful for testing scenarios where you need predictable value rotation.
68
+ The value picker solves the boilerplate problem of cycling through options (load balancer endpoints, color schemes, retry strategies). No manual index tracking or modulo math, just call `next()`. Particularly useful for testing scenarios where you need predictable value rotation.
68
69
 
69
70
  ### Type-Safe String Sanitization
70
71
 
@@ -89,7 +90,7 @@ import { createFifoList, createValuePicker, nonEmptyStrings, uniqueStrings } fro
89
90
  const taskQueue = createFifoList<{ id: string; execute: () => void }>()
90
91
  taskQueue.push({ id: 'task1', execute: () => console.log('Task 1') })
91
92
  taskQueue.push({ id: 'task2', execute: () => console.log('Task 2') })
92
- const nextTask = taskQueue.pull() // Gets task1 (first in)
93
+ const nextTask = taskQueue.pull() // Gets task1 (first in), or undefined once the queue is drained
93
94
 
94
95
  // Round-robin value picker
95
96
  const colorPicker = createValuePicker(['red', 'blue', 'green'])
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,86 +1,19 @@
1
1
  var HyperfrontendListUtils = (function (exports) {
2
2
  'use strict';
3
3
 
4
- /**
5
- * Safe copies of Array built-in static methods.
6
- *
7
- * These references are captured at module initialization time to protect against
8
- * prototype pollution attacks. Import only what you need for tree-shaking.
9
- *
10
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/array
11
- */
12
4
  const _Array = globalThis.Array;
13
- /**
14
- * (Safe copy) Determines whether the passed value is an Array.
15
- */
16
5
  const isArray = _Array.isArray;
17
- /**
18
- * (Safe copy) Creates an array from an array-like or iterable object.
19
- */
20
6
  const from = _Array.from;
21
7
 
22
- /**
23
- * Safe copies of Error built-ins via factory functions.
24
- *
25
- * Since constructors cannot be safely captured via Object.assign, this module
26
- * provides factory functions that use Reflect.construct internally.
27
- *
28
- * These references are captured at module initialization time to protect against
29
- * prototype pollution attacks. Import only what you need for tree-shaking.
30
- *
31
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/error
32
- */
33
8
  const _Error = globalThis.Error;
34
9
  const _Reflect$1 = globalThis.Reflect;
35
- /**
36
- * (Safe copy) Creates a new Error using the captured Error constructor.
37
- * Use this instead of `new Error()`.
38
- *
39
- * @param message - Optional error message.
40
- * @param options - Optional error options.
41
- * @returns A new Error instance.
42
- *
43
- * @example Creating Error instances
44
- * ```typescript
45
- * const error = createError('Operation failed')
46
- * // With cause for error chaining
47
- * const wrapped = createError('Request failed', { cause: originalError })
48
- * ```
49
- */
50
10
  const createError = (message, options) => _Reflect$1.construct(_Error, [message, options]);
51
11
 
52
- /**
53
- * Safe copies of Object built-in methods.
54
- *
55
- * These references are captured at module initialization time to protect against
56
- * prototype pollution attacks. Import only what you need for tree-shaking.
57
- *
58
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/object
59
- */
60
12
  const _Object = globalThis.Object;
61
- /**
62
- * (Safe copy) Prevents modification of existing property attributes and values,
63
- * and prevents the addition of new properties.
64
- */
65
13
  const freeze = _Object.freeze;
66
14
 
67
15
  const registeredClasses = [];
68
16
 
69
- /**
70
- * Returns the data type of the target.
71
- * Uses native `typeof` operator, however, makes distinction between `null`, `array`, and `object`.
72
- * Also, when classes are registered via `registerClass`, it checks if objects are instance of any known registered class.
73
- *
74
- * @param target - The target to get the data type of.
75
- * @returns The data type of the target.
76
- *
77
- * @example Determining data types
78
- * ```typescript
79
- * getType([1, 2]) // 'array'
80
- * getType({ a: 1 }) // 'object'
81
- * getType(null) // 'null'
82
- * ```
83
- */
84
17
  const getType = (target) => {
85
18
  if (target === null)
86
19
  return 'null';
@@ -96,48 +29,18 @@ var HyperfrontendListUtils = (function (exports) {
96
29
  return nativeDataType;
97
30
  };
98
31
 
99
- /**
100
- * Safe Set factory for protected set construction.
101
- *
102
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/set
103
- */
104
- /* eslint-disable workspace/lib-require-jsdoc-example */
105
32
  const _Set = globalThis.Set;
106
33
  const _Reflect = globalThis.Reflect;
107
- /**
108
- * (Safe copy) Creates a new Set using the captured Set constructor.
109
- * Use this instead of `new Set()`.
110
- *
111
- * @param iterable - Optional iterable of values.
112
- * @returns A new Set instance.
113
- */
114
34
  const createSet = (iterable) => _Reflect.construct(_Set, iterable ? [iterable] : []);
115
35
 
116
- /**
117
- * Creates a FIFO (First-In-First-Out) list.
118
- *
119
- * @template T The type of elements in the list, must be an object.
120
- * @returns {FifoList<T>} A FIFO list with methods to manipulate and query the list.
121
- * @example Creating and using FIFO queue
122
- * ```typescript
123
- * interface Task { id: number; name: string }
124
- * const queue = createFifoList<Task>()
125
- *
126
- * queue.push({ id: 1, name: 'first' })
127
- * queue.push({ id: 2, name: 'second' })
128
- *
129
- * queue.pull() // => { id: 1, name: 'first' }
130
- * queue.pull() // => { id: 2, name: 'second' }
131
- * ```
132
- */
133
36
  function createFifoList() {
134
37
  const list = createSet();
135
38
  const push = (item) => {
136
39
  if (getType(item) !== 'object') {
137
- throw createError('A fifo list only non-primitive values');
40
+ throw createError('A fifo list only supports non-primitive values');
138
41
  }
139
42
  if (list.has(item)) {
140
- throw createError('Cannot a item that already exists');
43
+ throw createError('Cannot push an item that already exists in the list');
141
44
  }
142
45
  list.add(item);
143
46
  };
@@ -169,23 +72,6 @@ var HyperfrontendListUtils = (function (exports) {
169
72
  return freeze(result);
170
73
  }
171
74
 
172
- /**
173
- * Creates a LIFO (Last-In-First-Out) list.
174
- *
175
- * @template T The type of elements in the list, must be an object.
176
- * @returns {LifoList<T>} A LIFO list with methods to manipulate and query the list.
177
- * @example Creating and using LIFO stack
178
- * ```typescript
179
- * interface HistoryEntry { url: string; timestamp: number }
180
- * const history = createLifoList<HistoryEntry>()
181
- *
182
- * history.push({ url: '/home', timestamp: 1 })
183
- * history.push({ url: '/about', timestamp: 2 })
184
- *
185
- * history.pull() // => { url: '/about', timestamp: 2 }
186
- * history.pull() // => { url: '/home', timestamp: 1 }
187
- * ```
188
- */
189
75
  function createLifoList() {
190
76
  const list = createSet();
191
77
  const push = (item) => {
@@ -226,21 +112,6 @@ var HyperfrontendListUtils = (function (exports) {
226
112
  return freeze(result);
227
113
  }
228
114
 
229
- /**
230
- * Creates an array of numbers within a specified range (inclusive).
231
- *
232
- * @param start - The starting number of the range
233
- * @param end - The ending number of the range (inclusive)
234
- * @returns An array containing all numbers from start to end
235
- * @example Generating number ranges
236
- * ```typescript
237
- * createRange(1, 5)
238
- * // => [1, 2, 3, 4, 5]
239
- *
240
- * createRange(3, 3)
241
- * // => [3]
242
- * ```
243
- */
244
115
  function createRange(start, end) {
245
116
  const range = [];
246
117
  for (let i = start; i <= end; i += 1) {
@@ -249,42 +120,16 @@ var HyperfrontendListUtils = (function (exports) {
249
120
  return range;
250
121
  }
251
122
 
252
- /**
253
- * Creates a value picker that cycles through a list of string values.
254
- *
255
- * @param values - The array of string values to cycle through
256
- * @returns A ValuePicker instance with current and next methods
257
- * @example Cycling through values
258
- * ```typescript
259
- * const colorPicker = createValuePicker(['red', 'green', 'blue'])
260
- *
261
- * colorPicker.current() // => 'red'
262
- * colorPicker.next() // => 'red' (first call initializes)
263
- * colorPicker.next() // => 'green'
264
- * colorPicker.next() // => 'blue'
265
- * colorPicker.next() // => 'red' (cycles back)
266
- * ```
267
- */
268
123
  function createValuePicker(values) {
269
124
  if (!values || !values.length) {
270
125
  throw createError('Expected values not to be an empty list.');
271
126
  }
272
127
  let index = -1;
273
- /**
274
- * Returns the current value without advancing the picker.
275
- *
276
- * @returns The current value in the list
277
- */
278
128
  function current() {
279
129
  if (index < 0)
280
130
  index = 0;
281
131
  return values[index];
282
132
  }
283
- /**
284
- * Advances to and returns the next value, cycling back to the start when reaching the end.
285
- *
286
- * @returns The next value in the list
287
- */
288
133
  function next() {
289
134
  if (index < 0)
290
135
  return current();
@@ -297,53 +142,15 @@ var HyperfrontendListUtils = (function (exports) {
297
142
  };
298
143
  }
299
144
 
300
- /* eslint-disable @typescript-eslint/no-explicit-any */
301
- /**
302
- * Retrieves the final key from a Map by converting keys to an array.
303
- *
304
- * @param map - The Map to extract the last key from
305
- * @returns The key that was inserted last, or undefined if map is empty
306
- * @example Getting last key from Map
307
- * ```typescript
308
- * const userRoles = new Map<string, string>()
309
- * userRoles.set('alice', 'admin')
310
- * userRoles.set('bob', 'editor')
311
- * userRoles.set('charlie', 'viewer')
312
- *
313
- * getLastKeyInMap(userRoles) // => 'charlie'
314
- * ```
315
- */
316
145
  const getLastKeyInMap = (map) => {
317
146
  const items = [...map.keys()];
318
147
  return items[items.length - 1];
319
148
  };
320
149
 
321
- /**
322
- * Filters out empty, null, undefined, and whitespace-only strings from an array.
323
- *
324
- * @param values - The array of strings to filter
325
- * @returns A new array containing only non-empty strings with content
326
- * @example Filtering empty strings
327
- * ```typescript
328
- * nonEmptyStrings(['hello', '', ' ', 'world', null as unknown as string])
329
- * // => ['hello', 'world']
330
- * ```
331
- */
332
150
  function nonEmptyStrings(values) {
333
151
  return values.filter((value) => ![undefined, null, ''].includes(value) && value.trim() !== '');
334
152
  }
335
153
 
336
- /**
337
- * Removes duplicate strings from an array, preserving insertion order.
338
- *
339
- * @param values - The array of strings to deduplicate
340
- * @returns A new array containing only unique strings
341
- * @example Deduplicating strings
342
- * ```typescript
343
- * uniqueStrings(['apple', 'banana', 'apple', 'cherry', 'banana'])
344
- * // => ['apple', 'banana', 'cherry']
345
- * ```
346
- */
347
154
  function uniqueStrings(values) {
348
155
  return from(createSet(values));
349
156
  }
@@ -1 +1 @@
1
- var HyperfrontendListUtils=function(e){"use strict";const t=globalThis.Array,r=t.isArray,n=t.from,o=globalThis.Error,i=globalThis.Reflect,l=(e,t)=>i.construct(o,[e,t]),s=globalThis.Object.freeze,a=[],u=e=>{if(null===e)return"null";const t=typeof e;if("object"===t){if(r(e))return"array";for(const t of a)if(e instanceof t)return t.name}return t},c=globalThis.Set,f=globalThis.Reflect,h=e=>f.construct(c,e?[e]:[]);return e.createFifoList=function(){const e=h();return s({push:t=>{if("object"!==u(t))throw l("A fifo list only non-primitive values");if(e.has(t))throw l("Cannot a item that already exists");e.add(t)},pull:()=>{const t=e.values().next().value;return e.delete(t),t},map:t=>n(e.values()).map(t),forEach:t=>{e.forEach(t)},remove:t=>e.delete(t),has:t=>e.has(t),size:()=>e.size,clear:()=>e.clear()})},e.createLifoList=function(){const e=h();return s({push:t=>{if("object"!==u(t))throw l("A lifo list only supports non-primitive values");e.add(t)},pull:()=>{const t=n(e).pop();return t&&e.delete(t),t},map:t=>n(e).map(t),forEach:t=>{e.forEach(t)},remove:t=>e.delete(t),has:t=>e.has(t),size:()=>e.size,clear:()=>{e.clear()}})},e.createRange=function(e,t){const r=[];for(let n=e;n<=t;n+=1)r.push(n);return r},e.createValuePicker=function(e){if(!e||!e.length)throw l("Expected values not to be an empty list.");let t=-1;function r(){return t<0&&(t=0),e[t]}return{current:r,next:function(){return t<0?r():(t=++t<e.length?t:0,e[t])}}},e.getLastKeyInMap=e=>{const t=[...e.keys()];return t[t.length-1]},e.nonEmptyStrings=function(e){return e.filter(e=>![void 0,null,""].includes(e)&&""!==e.trim())},e.uniqueStrings=function(e){return n(h(e))},e}({});
1
+ var HyperfrontendListUtils=function(e){"use strict";const t=globalThis.Array,r=t.isArray,n=t.from,o=globalThis.Error,i=globalThis.Reflect,s=(e,t)=>i.construct(o,[e,t]),l=globalThis.Object.freeze,a=[],u=e=>{if(null===e)return"null";const t=typeof e;if("object"===t){if(r(e))return"array";for(const t of a)if(e instanceof t)return t.name}return t},c=globalThis.Set,f=globalThis.Reflect,h=e=>f.construct(c,e?[e]:[]);return e.createFifoList=function(){const e=h();return l({push:t=>{if("object"!==u(t))throw s("A fifo list only supports non-primitive values");if(e.has(t))throw s("Cannot push an item that already exists in the list");e.add(t)},pull:()=>{const t=e.values().next().value;return e.delete(t),t},map:t=>n(e.values()).map(t),forEach:t=>{e.forEach(t)},remove:t=>e.delete(t),has:t=>e.has(t),size:()=>e.size,clear:()=>e.clear()})},e.createLifoList=function(){const e=h();return l({push:t=>{if("object"!==u(t))throw s("A lifo list only supports non-primitive values");e.add(t)},pull:()=>{const t=n(e).pop();return t&&e.delete(t),t},map:t=>n(e).map(t),forEach:t=>{e.forEach(t)},remove:t=>e.delete(t),has:t=>e.has(t),size:()=>e.size,clear:()=>{e.clear()}})},e.createRange=function(e,t){const r=[];for(let n=e;n<=t;n+=1)r.push(n);return r},e.createValuePicker=function(e){if(!e||!e.length)throw s("Expected values not to be an empty list.");let t=-1;function r(){return t<0&&(t=0),e[t]}return{current:r,next:function(){return t<0?r():(t=++t<e.length?t:0,e[t])}}},e.getLastKeyInMap=e=>{const t=[...e.keys()];return t[t.length-1]},e.nonEmptyStrings=function(e){return e.filter(e=>![void 0,null,""].includes(e)&&""!==e.trim())},e.uniqueStrings=function(e){return n(h(e))},e}({});
@@ -4,86 +4,19 @@
4
4
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.HyperfrontendListUtils = {}));
5
5
  })(this, (function (exports) { 'use strict';
6
6
 
7
- /**
8
- * Safe copies of Array built-in static methods.
9
- *
10
- * These references are captured at module initialization time to protect against
11
- * prototype pollution attacks. Import only what you need for tree-shaking.
12
- *
13
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/array
14
- */
15
7
  const _Array = globalThis.Array;
16
- /**
17
- * (Safe copy) Determines whether the passed value is an Array.
18
- */
19
8
  const isArray = _Array.isArray;
20
- /**
21
- * (Safe copy) Creates an array from an array-like or iterable object.
22
- */
23
9
  const from = _Array.from;
24
10
 
25
- /**
26
- * Safe copies of Error built-ins via factory functions.
27
- *
28
- * Since constructors cannot be safely captured via Object.assign, this module
29
- * provides factory functions that use Reflect.construct internally.
30
- *
31
- * These references are captured at module initialization time to protect against
32
- * prototype pollution attacks. Import only what you need for tree-shaking.
33
- *
34
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/error
35
- */
36
11
  const _Error = globalThis.Error;
37
12
  const _Reflect$1 = globalThis.Reflect;
38
- /**
39
- * (Safe copy) Creates a new Error using the captured Error constructor.
40
- * Use this instead of `new Error()`.
41
- *
42
- * @param message - Optional error message.
43
- * @param options - Optional error options.
44
- * @returns A new Error instance.
45
- *
46
- * @example Creating Error instances
47
- * ```typescript
48
- * const error = createError('Operation failed')
49
- * // With cause for error chaining
50
- * const wrapped = createError('Request failed', { cause: originalError })
51
- * ```
52
- */
53
13
  const createError = (message, options) => _Reflect$1.construct(_Error, [message, options]);
54
14
 
55
- /**
56
- * Safe copies of Object built-in methods.
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/object
62
- */
63
15
  const _Object = globalThis.Object;
64
- /**
65
- * (Safe copy) Prevents modification of existing property attributes and values,
66
- * and prevents the addition of new properties.
67
- */
68
16
  const freeze = _Object.freeze;
69
17
 
70
18
  const registeredClasses = [];
71
19
 
72
- /**
73
- * Returns the data type of the target.
74
- * Uses native `typeof` operator, however, makes distinction between `null`, `array`, and `object`.
75
- * Also, when classes are registered via `registerClass`, it checks if objects are instance of any known registered class.
76
- *
77
- * @param target - The target to get the data type of.
78
- * @returns The data type of the target.
79
- *
80
- * @example Determining data types
81
- * ```typescript
82
- * getType([1, 2]) // 'array'
83
- * getType({ a: 1 }) // 'object'
84
- * getType(null) // 'null'
85
- * ```
86
- */
87
20
  const getType = (target) => {
88
21
  if (target === null)
89
22
  return 'null';
@@ -99,48 +32,18 @@
99
32
  return nativeDataType;
100
33
  };
101
34
 
102
- /**
103
- * Safe Set factory for protected set construction.
104
- *
105
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/set
106
- */
107
- /* eslint-disable workspace/lib-require-jsdoc-example */
108
35
  const _Set = globalThis.Set;
109
36
  const _Reflect = globalThis.Reflect;
110
- /**
111
- * (Safe copy) Creates a new Set using the captured Set constructor.
112
- * Use this instead of `new Set()`.
113
- *
114
- * @param iterable - Optional iterable of values.
115
- * @returns A new Set instance.
116
- */
117
37
  const createSet = (iterable) => _Reflect.construct(_Set, iterable ? [iterable] : []);
118
38
 
119
- /**
120
- * Creates a FIFO (First-In-First-Out) list.
121
- *
122
- * @template T The type of elements in the list, must be an object.
123
- * @returns {FifoList<T>} A FIFO list with methods to manipulate and query the list.
124
- * @example Creating and using FIFO queue
125
- * ```typescript
126
- * interface Task { id: number; name: string }
127
- * const queue = createFifoList<Task>()
128
- *
129
- * queue.push({ id: 1, name: 'first' })
130
- * queue.push({ id: 2, name: 'second' })
131
- *
132
- * queue.pull() // => { id: 1, name: 'first' }
133
- * queue.pull() // => { id: 2, name: 'second' }
134
- * ```
135
- */
136
39
  function createFifoList() {
137
40
  const list = createSet();
138
41
  const push = (item) => {
139
42
  if (getType(item) !== 'object') {
140
- throw createError('A fifo list only non-primitive values');
43
+ throw createError('A fifo list only supports non-primitive values');
141
44
  }
142
45
  if (list.has(item)) {
143
- throw createError('Cannot a item that already exists');
46
+ throw createError('Cannot push an item that already exists in the list');
144
47
  }
145
48
  list.add(item);
146
49
  };
@@ -172,23 +75,6 @@
172
75
  return freeze(result);
173
76
  }
174
77
 
175
- /**
176
- * Creates a LIFO (Last-In-First-Out) list.
177
- *
178
- * @template T The type of elements in the list, must be an object.
179
- * @returns {LifoList<T>} A LIFO list with methods to manipulate and query the list.
180
- * @example Creating and using LIFO stack
181
- * ```typescript
182
- * interface HistoryEntry { url: string; timestamp: number }
183
- * const history = createLifoList<HistoryEntry>()
184
- *
185
- * history.push({ url: '/home', timestamp: 1 })
186
- * history.push({ url: '/about', timestamp: 2 })
187
- *
188
- * history.pull() // => { url: '/about', timestamp: 2 }
189
- * history.pull() // => { url: '/home', timestamp: 1 }
190
- * ```
191
- */
192
78
  function createLifoList() {
193
79
  const list = createSet();
194
80
  const push = (item) => {
@@ -229,21 +115,6 @@
229
115
  return freeze(result);
230
116
  }
231
117
 
232
- /**
233
- * Creates an array of numbers within a specified range (inclusive).
234
- *
235
- * @param start - The starting number of the range
236
- * @param end - The ending number of the range (inclusive)
237
- * @returns An array containing all numbers from start to end
238
- * @example Generating number ranges
239
- * ```typescript
240
- * createRange(1, 5)
241
- * // => [1, 2, 3, 4, 5]
242
- *
243
- * createRange(3, 3)
244
- * // => [3]
245
- * ```
246
- */
247
118
  function createRange(start, end) {
248
119
  const range = [];
249
120
  for (let i = start; i <= end; i += 1) {
@@ -252,42 +123,16 @@
252
123
  return range;
253
124
  }
254
125
 
255
- /**
256
- * Creates a value picker that cycles through a list of string values.
257
- *
258
- * @param values - The array of string values to cycle through
259
- * @returns A ValuePicker instance with current and next methods
260
- * @example Cycling through values
261
- * ```typescript
262
- * const colorPicker = createValuePicker(['red', 'green', 'blue'])
263
- *
264
- * colorPicker.current() // => 'red'
265
- * colorPicker.next() // => 'red' (first call initializes)
266
- * colorPicker.next() // => 'green'
267
- * colorPicker.next() // => 'blue'
268
- * colorPicker.next() // => 'red' (cycles back)
269
- * ```
270
- */
271
126
  function createValuePicker(values) {
272
127
  if (!values || !values.length) {
273
128
  throw createError('Expected values not to be an empty list.');
274
129
  }
275
130
  let index = -1;
276
- /**
277
- * Returns the current value without advancing the picker.
278
- *
279
- * @returns The current value in the list
280
- */
281
131
  function current() {
282
132
  if (index < 0)
283
133
  index = 0;
284
134
  return values[index];
285
135
  }
286
- /**
287
- * Advances to and returns the next value, cycling back to the start when reaching the end.
288
- *
289
- * @returns The next value in the list
290
- */
291
136
  function next() {
292
137
  if (index < 0)
293
138
  return current();
@@ -300,53 +145,15 @@
300
145
  };
301
146
  }
302
147
 
303
- /* eslint-disable @typescript-eslint/no-explicit-any */
304
- /**
305
- * Retrieves the final key from a Map by converting keys to an array.
306
- *
307
- * @param map - The Map to extract the last key from
308
- * @returns The key that was inserted last, or undefined if map is empty
309
- * @example Getting last key from Map
310
- * ```typescript
311
- * const userRoles = new Map<string, string>()
312
- * userRoles.set('alice', 'admin')
313
- * userRoles.set('bob', 'editor')
314
- * userRoles.set('charlie', 'viewer')
315
- *
316
- * getLastKeyInMap(userRoles) // => 'charlie'
317
- * ```
318
- */
319
148
  const getLastKeyInMap = (map) => {
320
149
  const items = [...map.keys()];
321
150
  return items[items.length - 1];
322
151
  };
323
152
 
324
- /**
325
- * Filters out empty, null, undefined, and whitespace-only strings from an array.
326
- *
327
- * @param values - The array of strings to filter
328
- * @returns A new array containing only non-empty strings with content
329
- * @example Filtering empty strings
330
- * ```typescript
331
- * nonEmptyStrings(['hello', '', ' ', 'world', null as unknown as string])
332
- * // => ['hello', 'world']
333
- * ```
334
- */
335
153
  function nonEmptyStrings(values) {
336
154
  return values.filter((value) => ![undefined, null, ''].includes(value) && value.trim() !== '');
337
155
  }
338
156
 
339
- /**
340
- * Removes duplicate strings from an array, preserving insertion order.
341
- *
342
- * @param values - The array of strings to deduplicate
343
- * @returns A new array containing only unique strings
344
- * @example Deduplicating strings
345
- * ```typescript
346
- * uniqueStrings(['apple', 'banana', 'apple', 'cherry', 'banana'])
347
- * // => ['apple', 'banana', 'cherry']
348
- * ```
349
- */
350
157
  function uniqueStrings(values) {
351
158
  return from(createSet(values));
352
159
  }
@@ -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).HyperfrontendListUtils={})}(this,function(e){"use strict";const t=globalThis.Array,n=t.isArray,r=t.from,o=globalThis.Error,i=globalThis.Reflect,s=(e,t)=>i.construct(o,[e,t]),l=globalThis.Object.freeze,a=[],u=e=>{if(null===e)return"null";const t=typeof e;if("object"===t){if(n(e))return"array";for(const t of a)if(e instanceof t)return t.name}return t},c=globalThis.Set,f=globalThis.Reflect,h=e=>f.construct(c,e?[e]:[]);e.createFifoList=function(){const e=h();return l({push:t=>{if("object"!==u(t))throw s("A fifo list only non-primitive values");if(e.has(t))throw s("Cannot a item that already exists");e.add(t)},pull:()=>{const t=e.values().next().value;return e.delete(t),t},map:t=>r(e.values()).map(t),forEach:t=>{e.forEach(t)},remove:t=>e.delete(t),has:t=>e.has(t),size:()=>e.size,clear:()=>e.clear()})},e.createLifoList=function(){const e=h();return l({push:t=>{if("object"!==u(t))throw s("A lifo list only supports non-primitive values");e.add(t)},pull:()=>{const t=r(e).pop();return t&&e.delete(t),t},map:t=>r(e).map(t),forEach:t=>{e.forEach(t)},remove:t=>e.delete(t),has:t=>e.has(t),size:()=>e.size,clear:()=>{e.clear()}})},e.createRange=function(e,t){const n=[];for(let r=e;r<=t;r+=1)n.push(r);return n},e.createValuePicker=function(e){if(!e||!e.length)throw s("Expected values not to be an empty list.");let t=-1;function n(){return t<0&&(t=0),e[t]}return{current:n,next:function(){return t<0?n():(t=++t<e.length?t:0,e[t])}}},e.getLastKeyInMap=e=>{const t=[...e.keys()];return t[t.length-1]},e.nonEmptyStrings=function(e){return e.filter(e=>![void 0,null,""].includes(e)&&""!==e.trim())},e.uniqueStrings=function(e){return r(h(e))}});
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).HyperfrontendListUtils={})}(this,function(e){"use strict";const t=globalThis.Array,n=t.isArray,r=t.from,o=globalThis.Error,i=globalThis.Reflect,s=(e,t)=>i.construct(o,[e,t]),l=globalThis.Object.freeze,a=[],u=e=>{if(null===e)return"null";const t=typeof e;if("object"===t){if(n(e))return"array";for(const t of a)if(e instanceof t)return t.name}return t},c=globalThis.Set,f=globalThis.Reflect,h=e=>f.construct(c,e?[e]:[]);e.createFifoList=function(){const e=h();return l({push:t=>{if("object"!==u(t))throw s("A fifo list only supports non-primitive values");if(e.has(t))throw s("Cannot push an item that already exists in the list");e.add(t)},pull:()=>{const t=e.values().next().value;return e.delete(t),t},map:t=>r(e.values()).map(t),forEach:t=>{e.forEach(t)},remove:t=>e.delete(t),has:t=>e.has(t),size:()=>e.size,clear:()=>e.clear()})},e.createLifoList=function(){const e=h();return l({push:t=>{if("object"!==u(t))throw s("A lifo list only supports non-primitive values");e.add(t)},pull:()=>{const t=r(e).pop();return t&&e.delete(t),t},map:t=>r(e).map(t),forEach:t=>{e.forEach(t)},remove:t=>e.delete(t),has:t=>e.has(t),size:()=>e.size,clear:()=>{e.clear()}})},e.createRange=function(e,t){const n=[];for(let r=e;r<=t;r+=1)n.push(r);return n},e.createValuePicker=function(e){if(!e||!e.length)throw s("Expected values not to be an empty list.");let t=-1;function n(){return t<0&&(t=0),e[t]}return{current:n,next:function(){return t<0?n():(t=++t<e.length?t:0,e[t])}}},e.getLastKeyInMap=e=>{const t=[...e.keys()];return t[t.length-1]},e.nonEmptyStrings=function(e){return e.filter(e=>![void 0,null,""].includes(e)&&""!==e.trim())},e.uniqueStrings=function(e){return r(h(e))}});
package/index.cjs.js CHANGED
@@ -21,16 +21,19 @@ const index_cjs_js = require('./_dependencies/@hyperfrontend/immutable-api-utils
21
21
  *
22
22
  * queue.pull() // => { id: 1, name: 'first' }
23
23
  * queue.pull() // => { id: 2, name: 'second' }
24
+ * queue.pull() // => undefined (the list is empty)
24
25
  * ```
26
+ * @throws {Error} When the pushed item is a primitive rather than an object.
27
+ * @throws {Error} When the pushed item is already in the list. Use has() to check first if a duplicate is expected.
25
28
  */
26
29
  function createFifoList() {
27
30
  const list = index_cjs_js.createSet();
28
31
  const push = (item) => {
29
32
  if (index_cjs_js$2.getType(item) !== 'object') {
30
- throw index_cjs_js$3.createError('A fifo list only non-primitive values');
33
+ throw index_cjs_js$3.createError('A fifo list only supports non-primitive values');
31
34
  }
32
35
  if (list.has(item)) {
33
- throw index_cjs_js$3.createError('Cannot a item that already exists');
36
+ throw index_cjs_js$3.createError('Cannot push an item that already exists in the list');
34
37
  }
35
38
  list.add(item);
36
39
  };
package/index.d.ts CHANGED
@@ -4,8 +4,8 @@
4
4
  interface FifoList<T extends object> {
5
5
  /** Adds an item to the end of the list */
6
6
  push(item: T): void;
7
- /** Removes and returns the first item in the list */
8
- pull(): T;
7
+ /** Removes and returns the first item in the list, or undefined when the list is empty */
8
+ pull(): T | undefined;
9
9
  /** Maps each item using the provided callback */
10
10
  map<U>(callback: (item: T) => U): U[];
11
11
  /** Iterates over each item in the list */
@@ -34,7 +34,10 @@ interface FifoList<T extends object> {
34
34
  *
35
35
  * queue.pull() // => { id: 1, name: 'first' }
36
36
  * queue.pull() // => { id: 2, name: 'second' }
37
+ * queue.pull() // => undefined (the list is empty)
37
38
  * ```
39
+ * @throws {Error} When the pushed item is a primitive rather than an object.
40
+ * @throws {Error} When the pushed item is already in the list. Use has() to check first if a duplicate is expected.
38
41
  */
39
42
  declare function createFifoList<T extends object>(): FifoList<T>;
40
43
 
package/index.esm.js CHANGED
@@ -19,16 +19,19 @@ import { createSet } from './_dependencies/@hyperfrontend/immutable-api-utils/bu
19
19
  *
20
20
  * queue.pull() // => { id: 1, name: 'first' }
21
21
  * queue.pull() // => { id: 2, name: 'second' }
22
+ * queue.pull() // => undefined (the list is empty)
22
23
  * ```
24
+ * @throws {Error} When the pushed item is a primitive rather than an object.
25
+ * @throws {Error} When the pushed item is already in the list. Use has() to check first if a duplicate is expected.
23
26
  */
24
27
  function createFifoList() {
25
28
  const list = createSet();
26
29
  const push = (item) => {
27
30
  if (getType(item) !== 'object') {
28
- throw createError('A fifo list only non-primitive values');
31
+ throw createError('A fifo list only supports non-primitive values');
29
32
  }
30
33
  if (list.has(item)) {
31
- throw createError('Cannot a item that already exists');
34
+ throw createError('Cannot push an item that already exists in the list');
32
35
  }
33
36
  list.add(item);
34
37
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperfrontend/list-utils",
3
- "version": "0.0.5",
3
+ "version": "0.1.0",
4
4
  "description": "Purpose-built collection utilities for queue management, filtering, and iteration patterns.",
5
5
  "license": "MIT",
6
6
  "sideEffects": false,