range-pie 2.4.0 → 3.0.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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 SkorpionG2000
3
+ Copyright (c) 2026 SkorpionG2000
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -2,16 +2,17 @@
2
2
 
3
3
  A TypeScript/JavaScript library that brings Python's range functionality to JavaScript, enhanced with familiar array methods. This lightweight utility provides a seamless way to work with numeric sequences while maintaining JavaScript's functional programming paradigm. Fully typed for TypeScript users while remaining compatible with JavaScript projects.
4
4
 
5
+ [![npm version](https://img.shields.io/npm/v/range-pie.svg)](https://www.npmjs.com/package/range-pie)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
+
5
8
  ## Table of Contents
6
9
 
7
10
  - [Installation](#installation)
8
11
  - [Basic Usage](#basic-usage)
9
-
10
12
  - [JavaScript](#javascript)
11
13
  - [TypeScript](#typescript)
12
14
 
13
15
  - [API Reference](#api-reference)
14
-
15
16
  - [Constructor Options](#constructor-options)
16
17
  - [Properties](#properties)
17
18
  - [at()](#at)
@@ -37,12 +38,10 @@ A TypeScript/JavaScript library that brings Python's range functionality to Java
37
38
  - [values()](#values)
38
39
 
39
40
  - [Advanced Usage](#advanced-usage)
40
-
41
41
  - [Iteration](#iteration)
42
42
  - [Proxy Access](#proxy-access)
43
43
 
44
44
  - [Examples](#examples)
45
-
46
45
  - [Methods chaining](#methods-chaining)
47
46
  - [Using as Array-like Object](#using-as-array-like-object)
48
47
  - [Example Files](#example-files)
@@ -62,9 +61,9 @@ npm install range-pie
62
61
 
63
62
  ```javascript
64
63
  // CommonJS - Both import styles are supported
65
- const { PyRange } = require('range-pie'); // Named import style
64
+ const { PyRange } = require("range-pie"); // Named import style
66
65
  // OR
67
- const PyRange = require('range-pie'); // Default import style
66
+ const PyRange = require("range-pie"); // Default import style
68
67
 
69
68
  // Create a range from 0 to 5
70
69
  const range = new PyRange(5);
@@ -87,20 +86,20 @@ console.log([...range4]); // [5, 4, 3]
87
86
 
88
87
  ```typescript
89
88
  // ES Modules - Both import styles are supported
90
- import { PyRange } from 'range-pie'; // Named import style
89
+ import { PyRange } from "range-pie"; // Named import style
91
90
  // OR
92
- import PyRange from 'range-pie'; // Default import style
91
+ import PyRange from "range-pie"; // Default import style
93
92
 
94
93
  // Create a range from 0 to 5
95
94
  const range = new PyRange(5);
96
95
  console.log([...range]); // [0, 1, 2, 3, 4]
97
96
 
98
97
  // Type-safe operations
99
- const doubledValues: number[] = range.map(x => x * 2);
98
+ const doubledValues: number[] = range.map((x) => x * 2);
100
99
  console.log(doubledValues); // [0, 2, 4, 6, 8]
101
100
 
102
101
  // Type inference works with generics
103
- const stringValues: string[] = range.map(x => `Value: ${x}`);
102
+ const stringValues: string[] = range.map((x) => `Value: ${x}`);
104
103
  console.log(stringValues); // ['Value: 0', 'Value: 1', 'Value: 2', 'Value: 3', 'Value: 4']
105
104
  ```
106
105
 
@@ -109,9 +108,9 @@ console.log(stringValues); // ['Value: 0', 'Value: 1', 'Value: 2', 'Value: 3', '
109
108
  ### Constructor Options
110
109
 
111
110
  ```javascript
112
- new PyRange(stop) // 0 to stop-1
113
- new PyRange(start, stop) // start to stop-1
114
- new PyRange(start, stop, step) // start to stop-1 with step
111
+ new PyRange(stop); // 0 to stop-1
112
+ new PyRange(start, stop); // start to stop-1
113
+ new PyRange(start, stop, step); // start to stop-1 with step
115
114
  ```
116
115
 
117
116
  - **PyRange(stop:number)**
@@ -148,21 +147,22 @@ console.log(PyRange(2, -10, -1));
148
147
 
149
148
  ```javascript
150
149
  const range = new PyRange(1, 10, 2);
151
- console.log(range.start); // 1
152
- console.log(range.stop); // 10
153
- console.log(range.step); // 2
150
+ console.log(range.start); // 1
151
+ console.log(range.stop); // 10
152
+ console.log(range.step); // 2
154
153
  console.log(range.length); // 5
155
154
  ```
156
155
 
157
156
  ### at()
158
157
 
159
- The 'at' method accepts a number as argument to gets the value at the specified index in a range. Generate a RangeError if the index is out of range.
158
+ The 'at' method accepts a number as argument to get the value at the specified index in a range. Returns `undefined` if the index is out of range, perfectly matching `Array.prototype.at`. Negative indices count backwards from the end of the range.
160
159
 
161
160
  ```javascript
162
- const range = new PyRange(1, 5); // [1, 2, 3, 4]
163
- console.log(range.at(0)); // 1
164
- console.log(range.at(2)); // 3
165
- console.log(range.at(-1)); // RangeError
161
+ const range = new PyRange(1, 5); // [1, 2, 3, 4]
162
+ console.log(range.at(0)); // 1
163
+ console.log(range.at(2)); // 3
164
+ console.log(range.at(-1)); // 4 (negative index wraps from the end)
165
+ console.log(range.at(10)); // undefined (out of bounds)
166
166
  ```
167
167
 
168
168
  ### toString()
@@ -188,8 +188,8 @@ console.log(range.toArray()); // [1, 2, 3]
188
188
  It works the same as [**`Array.prototype.map`**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)
189
189
 
190
190
  ```javascript
191
- const range = new PyRange(1, 4); // [1, 2, 3]
192
- console.log(range.map(x => x * 2)); // [2, 4, 6]
191
+ const range = new PyRange(1, 4); // [1, 2, 3]
192
+ console.log(range.map((x) => x * 2)); // [2, 4, 6]
193
193
  ```
194
194
 
195
195
  ### filter()
@@ -197,8 +197,8 @@ console.log(range.map(x => x * 2)); // [2, 4, 6]
197
197
  It works the same as [**`Array.prototype.filter`**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)
198
198
 
199
199
  ```javascript
200
- const range = new PyRange(1, 6); // [1, 2, 3, 4, 5]
201
- console.log(range.filter(x => x % 2 === 0)); // [2, 4]
200
+ const range = new PyRange(1, 6); // [1, 2, 3, 4, 5]
201
+ console.log(range.filter((x) => x % 2 === 0)); // [2, 4]
202
202
  ```
203
203
 
204
204
  ### reduce()
@@ -206,9 +206,9 @@ console.log(range.filter(x => x % 2 === 0)); // [2, 4]
206
206
  It works the same as [**`Array.prototype.reduce`**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce)
207
207
 
208
208
  ```javascript
209
- const range = new PyRange(1, 4); // [1, 2, 3]
209
+ const range = new PyRange(1, 4); // [1, 2, 3]
210
210
  const sum = range.reduce((acc, curr) => acc + curr, 0);
211
- console.log(sum); // 6
211
+ console.log(sum); // 6
212
212
  ```
213
213
 
214
214
  ### some()
@@ -216,9 +216,9 @@ console.log(sum); // 6
216
216
  It works the same as [**`Array.prototype.some`**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some)
217
217
 
218
218
  ```javascript
219
- const range = new PyRange(1, 5); // [1, 2, 3, 4]
220
- console.log(range.some(x => x > 3)); // true
221
- console.log(range.some(x => x < 0)); // false
219
+ const range = new PyRange(1, 5); // [1, 2, 3, 4]
220
+ console.log(range.some((x) => x > 3)); // true
221
+ console.log(range.some((x) => x < 0)); // false
222
222
  ```
223
223
 
224
224
  ### every()
@@ -226,9 +226,9 @@ console.log(range.some(x => x < 0)); // false
226
226
  It works the same as [**`Array.prototype.every`**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every)
227
227
 
228
228
  ```javascript
229
- const range = new PyRange(1, 5); // [1, 2, 3, 4]
230
- console.log(range.every(x => x > 0)); // true
231
- console.log(range.every(x => x > 2)); // false
229
+ const range = new PyRange(1, 5); // [1, 2, 3, 4]
230
+ console.log(range.every((x) => x > 0)); // true
231
+ console.log(range.every((x) => x > 2)); // false
232
232
  ```
233
233
 
234
234
  ### find()
@@ -236,9 +236,9 @@ console.log(range.every(x => x > 2)); // false
236
236
  It works the same as [**`Array.prototype.find`**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find)
237
237
 
238
238
  ```javascript
239
- const range = new PyRange(1, 5); // [1, 2, 3, 4]
240
- console.log(range.find(x => x > 2)); // 3
241
- console.log(range.find(x => x > 5)); // undefined
239
+ const range = new PyRange(1, 5); // [1, 2, 3, 4]
240
+ console.log(range.find((x) => x > 2)); // 3
241
+ console.log(range.find((x) => x > 5)); // undefined
242
242
  ```
243
243
 
244
244
  ### findIndex()
@@ -246,9 +246,9 @@ console.log(range.find(x => x > 5)); // undefined
246
246
  It works the same as [**`Array.prototype.findIndex`**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex)
247
247
 
248
248
  ```javascript
249
- const range = new PyRange(1, 5); // [1, 2, 3, 4]
250
- console.log(range.findIndex(x => x > 2)); // 2
251
- console.log(range.findIndex(x => x > 5)); // -1
249
+ const range = new PyRange(1, 5); // [1, 2, 3, 4]
250
+ console.log(range.findIndex((x) => x > 2)); // 2
251
+ console.log(range.findIndex((x) => x > 5)); // -1
252
252
  ```
253
253
 
254
254
  ### findLastIndex()
@@ -256,9 +256,9 @@ console.log(range.findIndex(x => x > 5)); // -1
256
256
  It works the same as [**`Array.prototype.findLastIndex`**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findLastIndex)
257
257
 
258
258
  ```javascript
259
- const range = new PyRange(1, 5); // [1, 2, 3, 4]
260
- console.log(range.findLastIndex(x => x > 2)); // 3
261
- console.log(range.findLastIndex(x => x > 5)); // -1
259
+ const range = new PyRange(1, 5); // [1, 2, 3, 4]
260
+ console.log(range.findLastIndex((x) => x > 2)); // 3
261
+ console.log(range.findLastIndex((x) => x > 5)); // -1
262
262
  ```
263
263
 
264
264
  ### forEach()
@@ -266,8 +266,8 @@ console.log(range.findLastIndex(x => x > 5)); // -1
266
266
  It works the same as [**`Array.prototype.forEach`**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach)
267
267
 
268
268
  ```javascript
269
- const range = new PyRange(1, 4); // [1, 2, 3]
270
- range.forEach(x => console.log(x));
269
+ const range = new PyRange(1, 4); // [1, 2, 3]
270
+ range.forEach((x) => console.log(x));
271
271
  // 1
272
272
  // 2
273
273
  // 3
@@ -278,9 +278,9 @@ range.forEach(x => console.log(x));
278
278
  It works the same as [**`Array.prototype.includes`**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes)
279
279
 
280
280
  ```javascript
281
- const range = new PyRange(1, 5); // [1, 2, 3, 4]
282
- console.log(range.includes(3)); // true
283
- console.log(range.includes(5)); // false
281
+ const range = new PyRange(1, 5); // [1, 2, 3, 4]
282
+ console.log(range.includes(3)); // true
283
+ console.log(range.includes(5)); // false
284
284
  ```
285
285
 
286
286
  ### indexOf()
@@ -288,9 +288,9 @@ console.log(range.includes(5)); // false
288
288
  It works the same as [**`Array.prototype.indexOf`**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
289
289
 
290
290
  ```javascript
291
- const range = new PyRange(1, 5); // [1, 2, 3, 4]
292
- console.log(range.indexOf(3)); // 2
293
- console.log(range.indexOf(5)); // -1
291
+ const range = new PyRange(1, 5); // [1, 2, 3, 4]
292
+ console.log(range.indexOf(3)); // 2
293
+ console.log(range.indexOf(5)); // -1
294
294
  ```
295
295
 
296
296
  ### lastIndexOf()
@@ -298,9 +298,9 @@ console.log(range.indexOf(5)); // -1
298
298
  It works the same as [**`Array.prototype.lastIndexOf`**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf)
299
299
 
300
300
  ```javascript
301
- const range = new PyRange(1, 5, 1); // [1, 2, 3, 4]
302
- console.log(range.lastIndexOf(3)); // 2
303
- console.log(range.lastIndexOf(5)); // -1
301
+ const range = new PyRange(1, 5, 1); // [1, 2, 3, 4]
302
+ console.log(range.lastIndexOf(3)); // 2
303
+ console.log(range.lastIndexOf(5)); // -1
304
304
  ```
305
305
 
306
306
  ### pop()
@@ -310,9 +310,9 @@ this method removes the last value from the range, shortens the range and
310
310
  returns that value.
311
311
 
312
312
  ```javascript
313
- const range = new PyRange(1, 5); // [1, 2, 3, 4]
314
- console.log(range.pop()); // 4
315
- console.log([...range]); // [1, 2, 3]
313
+ const range = new PyRange(1, 5); // [1, 2, 3, 4]
314
+ console.log(range.pop()); // 4
315
+ console.log([...range]); // [1, 2, 3]
316
316
  ```
317
317
 
318
318
  ### slice()
@@ -322,10 +322,10 @@ It returns a new PyRange instance containing elements from the specified indices
322
322
  The original range is not modified.
323
323
 
324
324
  ```javascript
325
- const range = new PyRange(0, 10); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
325
+ const range = new PyRange(0, 10); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
326
326
  const sliced = range.slice(2, 5);
327
327
  console.log([...sliced]); // [2, 3, 4]
328
- console.log([...range]); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] (unchanged)
328
+ console.log([...range]); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] (unchanged)
329
329
 
330
330
  // Negative indices are supported
331
331
  const lastThree = range.slice(-3);
@@ -337,13 +337,13 @@ console.log([...lastThree]); // [7, 8, 9]
337
337
  It works the same as [**`Array.prototype.reverse`**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse)
338
338
 
339
339
  ```javascript
340
- const range = new PyRange(1, 5); // [1, 2, 3, 4]
341
- console.log([...range]); // [1, 2, 3, 4]
340
+ const range = new PyRange(1, 5); // [1, 2, 3, 4]
341
+ console.log([...range]); // [1, 2, 3, 4]
342
342
 
343
343
  const reversed = range.reverse(); // [4, 3, 2, 1]
344
- console.log([...reversed]); // [4, 3, 2, 1]
344
+ console.log([...reversed]); // [4, 3, 2, 1]
345
345
 
346
- const rangeWithStep = new PyRange(1, 10, 2); // [1, 3, 5, 7, 9]
346
+ const rangeWithStep = new PyRange(1, 10, 2); // [1, 3, 5, 7, 9]
347
347
  const reversedStep = rangeWithStep.reverse(); // [9, 7, 5, 3, 1]
348
348
  console.log([...reversedStep]);
349
349
  ```
@@ -354,7 +354,7 @@ It works the same as [**`Array.prototype.entries`**](https://developer.mozilla.o
354
354
  It returns an iterator of `[index, value]` pairs.
355
355
 
356
356
  ```javascript
357
- const range = new PyRange(1, 4); // [1, 2, 3]
357
+ const range = new PyRange(1, 4); // [1, 2, 3]
358
358
  for (const [index, value] of range.entries()) {
359
359
  console.log(index, value);
360
360
  }
@@ -369,7 +369,7 @@ It works the same as [**`Array.prototype.keys`**](https://developer.mozilla.org/
369
369
  It returns an iterator of the indices of the range.
370
370
 
371
371
  ```javascript
372
- const range = new PyRange(3); // [0, 1, 2]
372
+ const range = new PyRange(3); // [0, 1, 2]
373
373
  console.log([...range.keys()]); // [0, 1, 2]
374
374
  ```
375
375
 
@@ -379,7 +379,7 @@ It works the same as [**`Array.prototype.values`**](https://developer.mozilla.or
379
379
  It returns an iterator of the values in the range.
380
380
 
381
381
  ```javascript
382
- const range = new PyRange(3); // [0, 1, 2]
382
+ const range = new PyRange(3); // [0, 1, 2]
383
383
  console.log([...range.values()]); // [0, 1, 2]
384
384
  ```
385
385
 
@@ -392,7 +392,7 @@ const range = new PyRange(3);
392
392
 
393
393
  // For...of loop
394
394
  for (const num of range) {
395
- console.log(num); // 0, 1, 2
395
+ console.log(num); // 0, 1, 2
396
396
  }
397
397
 
398
398
  // Spread operator
@@ -401,7 +401,7 @@ const array = [...range]; // [0, 1, 2]
401
401
 
402
402
  ### Proxy Access
403
403
 
404
- Proxy access allows access to the array elements using bracket notation, just like a regular array.
404
+ Proxy access allows access to the array elements using bracket notation, just like a regular array. Out-of-bounds access returns `undefined`.
405
405
 
406
406
  ```javascript
407
407
  const range = new PyRange(5);
@@ -409,6 +409,7 @@ const proxy = range.asProxy();
409
409
 
410
410
  console.log(proxy[0]); // 0
411
411
  console.log(proxy[3]); // 3
412
+ console.log(proxy[10]); // undefined
412
413
  ```
413
414
 
414
415
  ### TypeScript Support
@@ -425,13 +426,13 @@ This package is written in TypeScript and provides full type definitions for all
425
426
  const range = new PyRange(5);
426
427
 
427
428
  // TypeScript knows this is a number[]
428
- const numbers = range.map(x => x * 2);
429
+ const numbers = range.map((x) => x * 2);
429
430
 
430
431
  // TypeScript knows this is a string[]
431
- const strings = range.map(x => `Number: ${x}`);
432
+ const strings = range.map((x) => `Number: ${x}`);
432
433
 
433
434
  // TypeScript knows this is a boolean[]
434
- const booleans = range.map(x => x % 2 === 0);
435
+ const booleans = range.map((x) => x % 2 === 0);
435
436
  ```
436
437
 
437
438
  ## Examples
@@ -440,9 +441,7 @@ const booleans = range.map(x => x % 2 === 0);
440
441
 
441
442
  ```javascript
442
443
  const range = new PyRange(1, 6);
443
- const squares = range
444
- .filter(x => x % 2 === 0)
445
- .map(x => x * x);
444
+ const squares = range.filter((x) => x % 2 === 0).map((x) => x * x);
446
445
  console.log(squares); // [4, 16]
447
446
  ```
448
447
 
@@ -6,6 +6,8 @@ declare class PyRange implements Iterable<number> {
6
6
  private _stop;
7
7
  private _step;
8
8
  private _length;
9
+ private static toIntegerOrInfinity;
10
+ private static isArrayIndexProperty;
9
11
  /**
10
12
  * Creates a new PyRange instance.
11
13
  * @param {...number} args - The arguments of the range. The possible forms are
@@ -21,7 +23,9 @@ declare class PyRange implements Iterable<number> {
21
23
  * @property {number} step - The step of the range.
22
24
  * @property {number} length - The length of the range.
23
25
  */
24
- constructor(...args: number[]);
26
+ constructor(stop: number);
27
+ constructor(start: number, stop: number);
28
+ constructor(start: number, stop: number, step: number);
25
29
  /**
26
30
  * Gets the length of this range.
27
31
  *
@@ -52,11 +56,17 @@ declare class PyRange implements Iterable<number> {
52
56
  get step(): number;
53
57
  /**
54
58
  * Gets the value at the specified index in this range.
55
- * @param {number} index - The index of the value to retrieve
56
- * @returns {number} The value at the specified index
57
- * @throws {RangeError} If the index is out of range
59
+ *
60
+ * Negative indices are supported and wrap from the end, matching the
61
+ * behaviour of `Array.prototype.at()`. For example, `at(-1)` returns the
62
+ * last element.
63
+ *
64
+ * @param {number} index - The index of the value to retrieve. Negative
65
+ * indices count from the end of the range.
66
+ * @returns {number|undefined} The value at the specified index, or
67
+ * undefined when the normalised index is out of range.
58
68
  */
59
- at(index: number): number;
69
+ at(index: number): number | undefined;
60
70
  /**
61
71
  * Converts the range to a string.
62
72
  * @returns {string} A string of the form `Range(start, stop, step)`.
@@ -71,6 +81,7 @@ declare class PyRange implements Iterable<number> {
71
81
  * Validates that the callback is a function.
72
82
  * @param {Function} cb - The callback to validate.
73
83
  * @throws {TypeError} If the callback is not a function.
84
+ * @internal
74
85
  */
75
86
  private static validateCb;
76
87
  /**
@@ -92,15 +103,23 @@ declare class PyRange implements Iterable<number> {
92
103
  filter(callback: (value: number, index: number, range: PyRange) => boolean): number[];
93
104
  /**
94
105
  * Reduces the range to a single value.
106
+ *
107
+ * Uses rest parameters to reliably distinguish between "no initial value
108
+ * provided" and "explicit `undefined` passed as initial value", matching
109
+ * the behaviour of `Array.prototype.reduce()`.
110
+ *
95
111
  * @param {function(T, number, number, PyRange): T} callback - The callback
96
112
  * function to apply to every element. The callback should take four
97
113
  * arguments: the accumulator, the current value, the index of the current
98
114
  * value, and the range object.
99
115
  * @param {T} [initialValue] - The initial value of the accumulator.
100
116
  * @returns {T} The final value of the accumulator.
117
+ * @throws {TypeError} If the range is empty and no initial value is provided.
101
118
  * @template T
102
119
  */
103
- reduce<T>(callback: (accumulator: T, value: number, index: number, range: PyRange) => T, initialValue?: T): T;
120
+ reduce(callback: (accumulator: number, value: number, index: number, range: PyRange) => number): number;
121
+ reduce(callback: (accumulator: number, value: number, index: number, range: PyRange) => number, initialValue: number): number;
122
+ reduce<U>(callback: (accumulator: U, value: number, index: number, range: PyRange) => U, initialValue: U): U;
104
123
  /**
105
124
  * Determines whether at least one element of the range satisfies the
106
125
  * provided test.
@@ -149,19 +168,19 @@ declare class PyRange implements Iterable<number> {
149
168
  * @param {any} value - The value to search for.
150
169
  * @returns {boolean} True if the value is present, false otherwise.
151
170
  */
152
- includes(value: any): boolean;
171
+ includes(value: unknown): boolean;
153
172
  /**
154
173
  * Returns the index of the first occurrence of the specified value, or -1 if it is not present.
155
174
  * @param {any} value - The value to search for.
156
175
  * @returns {number} The index of the value, or -1 if it is not present.
157
176
  */
158
- indexOf(value: any): number;
177
+ indexOf(value: unknown): number;
159
178
  /**
160
179
  * Returns the index of the last occurrence of the specified value, or -1 if it is not present.
161
180
  * @param {any} value - The value to search for.
162
181
  * @returns {number} The index of the last occurrence of the value, or -1 if it is not present.
163
182
  */
164
- lastIndexOf(value: any): number;
183
+ lastIndexOf(value: unknown): number;
165
184
  /**
166
185
  * Removes the last element from the range and returns it.
167
186
  *
@@ -189,6 +208,10 @@ declare class PyRange implements Iterable<number> {
189
208
  slice(begin?: number, end?: number): PyRange;
190
209
  /**
191
210
  * Reverses the order of the elements in this range, returning a new PyRange object.
211
+ *
212
+ * The new range starts at the last actual element of this range and steps
213
+ * in the opposite direction. The original range is not modified.
214
+ *
192
215
  * @returns {PyRange} A new PyRange object with the elements in reverse order.
193
216
  */
194
217
  reverse(): PyRange;
@@ -219,17 +242,19 @@ declare class PyRange implements Iterable<number> {
219
242
  values(): IterableIterator<number>;
220
243
  /**
221
244
  * Implements the iterable protocol for this range.
222
- * @returns {Iterator<number>} An iterator for this range.
245
+ * @returns {IterableIterator<number>} An iterator for this range.
223
246
  */
224
- [Symbol.iterator](): Iterator<number>;
247
+ [Symbol.iterator](): IterableIterator<number>;
225
248
  /**
226
249
  * Returns a Proxy for this range, allowing indexed access.
227
250
  * This proxy enables accessing range elements via array-like indexing.
228
251
  * If the property is a number, it will return the element at that index.
229
252
  *
230
- * @returns {any} A proxy for the PyRange instance.
253
+ * @returns {PyRange & { [key: number]: number | undefined }} A proxy for the PyRange instance.
231
254
  */
232
- asProxy(): any;
255
+ asProxy(): PyRange & {
256
+ [key: number]: number | undefined;
257
+ };
233
258
  }
234
259
  export { PyRange };
235
260
  export default PyRange;
package/dist/py-range.js CHANGED
@@ -5,21 +5,22 @@ exports.PyRange = void 0;
5
5
  * A class that simulates Python's range function, combined with several useful JavaScript array methods.
6
6
  */
7
7
  class PyRange {
8
- /**
9
- * Creates a new PyRange instance.
10
- * @param {...number} args - The arguments of the range. The possible forms are
11
- * - `PyRange(stop)`
12
- * - `PyRange(start, stop)`
13
- * - `PyRange(start, stop, step)`
14
- * @throws {TypeError} If any of the arguments is not a number.
15
- * @throws {TypeError} If any of the arguments is not an integer.
16
- * @throws {Error} If the step is zero.
17
- * @throws {Error} If the arguments count is not between 1 and 3.
18
- * @property {number} start - The start of the range, inclusive.
19
- * @property {number} stop - The stop of the range, exclusive.
20
- * @property {number} step - The step of the range.
21
- * @property {number} length - The length of the range.
22
- */
8
+ _start;
9
+ _stop;
10
+ _step;
11
+ _length;
12
+ static toIntegerOrInfinity(value) {
13
+ const numeric = +value;
14
+ return Number.isNaN(numeric) ? 0 : Math.trunc(numeric);
15
+ }
16
+ static isArrayIndexProperty(prop) {
17
+ const index = Number(prop);
18
+ return (prop !== "" &&
19
+ Number.isInteger(index) &&
20
+ index >= 0 &&
21
+ index < 2 ** 32 - 1 &&
22
+ String(index) === prop);
23
+ }
23
24
  constructor(...args) {
24
25
  if (!args.every((arg) => typeof arg === "number")) {
25
26
  throw new TypeError("All arguments must be numbers");
@@ -83,15 +84,23 @@ class PyRange {
83
84
  }
84
85
  /**
85
86
  * Gets the value at the specified index in this range.
86
- * @param {number} index - The index of the value to retrieve
87
- * @returns {number} The value at the specified index
88
- * @throws {RangeError} If the index is out of range
87
+ *
88
+ * Negative indices are supported and wrap from the end, matching the
89
+ * behaviour of `Array.prototype.at()`. For example, `at(-1)` returns the
90
+ * last element.
91
+ *
92
+ * @param {number} index - The index of the value to retrieve. Negative
93
+ * indices count from the end of the range.
94
+ * @returns {number|undefined} The value at the specified index, or
95
+ * undefined when the normalised index is out of range.
89
96
  */
90
97
  at(index) {
91
- if (index < 0 || index >= this._length) {
92
- throw new RangeError("Index out of range");
98
+ const relativeIndex = PyRange.toIntegerOrInfinity(index);
99
+ const normalised = relativeIndex < 0 ? this._length + relativeIndex : relativeIndex;
100
+ if (normalised < 0 || normalised >= this._length) {
101
+ return undefined;
93
102
  }
94
- return this._start + index * this._step;
103
+ return this._start + normalised * this._step;
95
104
  }
96
105
  /**
97
106
  * Converts the range to a string.
@@ -111,6 +120,7 @@ class PyRange {
111
120
  * Validates that the callback is a function.
112
121
  * @param {Function} cb - The callback to validate.
113
122
  * @throws {TypeError} If the callback is not a function.
123
+ * @internal
114
124
  */
115
125
  static validateCb(cb) {
116
126
  if (typeof cb !== "function") {
@@ -151,25 +161,16 @@ class PyRange {
151
161
  }
152
162
  return result;
153
163
  }
154
- /**
155
- * Reduces the range to a single value.
156
- * @param {function(T, number, number, PyRange): T} callback - The callback
157
- * function to apply to every element. The callback should take four
158
- * arguments: the accumulator, the current value, the index of the current
159
- * value, and the range object.
160
- * @param {T} [initialValue] - The initial value of the accumulator.
161
- * @returns {T} The final value of the accumulator.
162
- * @template T
163
- */
164
- reduce(callback, initialValue) {
164
+ reduce(callback, ...args) {
165
165
  PyRange.validateCb(callback);
166
- if (this._length === 0 && initialValue === undefined) {
166
+ const hasInitial = args.length > 0;
167
+ if (this._length === 0 && !hasInitial) {
167
168
  throw new TypeError("Reduce of empty range with no initial value");
168
169
  }
169
170
  let accumulator;
170
171
  let startIndex;
171
- if (initialValue !== undefined) {
172
- accumulator = initialValue;
172
+ if (hasInitial) {
173
+ accumulator = args[0];
173
174
  startIndex = 0;
174
175
  }
175
176
  else {
@@ -373,13 +374,22 @@ class PyRange {
373
374
  }
374
375
  /**
375
376
  * Reverses the order of the elements in this range, returning a new PyRange object.
377
+ *
378
+ * The new range starts at the last actual element of this range and steps
379
+ * in the opposite direction. The original range is not modified.
380
+ *
376
381
  * @returns {PyRange} A new PyRange object with the elements in reverse order.
377
382
  */
378
383
  reverse() {
379
- const result = new PyRange(this._stop, this._start, -this._step);
380
- // Force the length to be the same as the original range
381
- result._length = this._length;
382
- return result;
384
+ if (this._length === 0) {
385
+ // Return an empty range with the same step direction nothing to reverse
386
+ return new PyRange(this._start, this._start, this._step);
387
+ }
388
+ // The new start is the last actual element (not _stop which is exclusive)
389
+ const newStart = this.at(this._length - 1);
390
+ // The new stop is one step past the first element in the new direction
391
+ const newStop = this._start - this._step;
392
+ return new PyRange(newStart, newStop, -this._step);
383
393
  }
384
394
  /**
385
395
  * Returns an iterator of `[index, value]` pairs for each element in the range.
@@ -418,7 +428,7 @@ class PyRange {
418
428
  }
419
429
  /**
420
430
  * Implements the iterable protocol for this range.
421
- * @returns {Iterator<number>} An iterator for this range.
431
+ * @returns {IterableIterator<number>} An iterator for this range.
422
432
  */
423
433
  [Symbol.iterator]() {
424
434
  let index = 0;
@@ -436,6 +446,9 @@ class PyRange {
436
446
  return { value, done: false };
437
447
  }
438
448
  },
449
+ [Symbol.iterator]() {
450
+ return this;
451
+ },
439
452
  };
440
453
  }
441
454
  /**
@@ -443,18 +456,22 @@ class PyRange {
443
456
  * This proxy enables accessing range elements via array-like indexing.
444
457
  * If the property is a number, it will return the element at that index.
445
458
  *
446
- * @returns {any} A proxy for the PyRange instance.
459
+ * @returns {PyRange & { [key: number]: number | undefined }} A proxy for the PyRange instance.
447
460
  */
448
461
  asProxy() {
449
462
  return new Proxy(this, {
450
- get(target, prop) {
463
+ get(target, prop, receiver) {
451
464
  if (typeof prop === "symbol") {
452
- return target[prop];
465
+ return Reflect.get(target, prop, receiver);
453
466
  }
454
- if (!isNaN(Number(prop))) {
455
- return target.at(parseInt(String(prop), 10));
467
+ if (PyRange.isArrayIndexProperty(prop)) {
468
+ const idx = Number(prop);
469
+ // Match Array behaviour: out-of-bounds returns undefined, not throw
470
+ if (idx >= target.length)
471
+ return undefined;
472
+ return target.at(idx);
456
473
  }
457
- return target[prop];
474
+ return Reflect.get(target, prop, receiver);
458
475
  },
459
476
  });
460
477
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "range-pie",
3
- "version": "2.4.0",
3
+ "version": "3.0.0",
4
4
  "description": "A TypeScript class that simulates Python's range function, combined with several useful JavaScript array methods.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -22,22 +22,27 @@
22
22
  ],
23
23
  "scripts": {
24
24
  "build": "tsc",
25
+ "typecheck": "tsc -p tsconfig.test.json --noEmit",
25
26
  "build:prod": "tsc -p tsconfig.prod.json",
26
27
  "build:watch": "tsc --watch",
28
+ "prepare": "husky",
27
29
  "prepublishOnly": "npm run build:prod && npm run test",
28
30
  "test": "jest",
31
+ "test:watch": "jest --watch",
32
+ "test:coverage": "jest --coverage",
29
33
  "lint": "eslint . --ext .ts,.js",
30
34
  "lint:fix": "eslint . --ext .ts,.js --fix",
31
- "test:watch": "jest --watch",
32
35
  "clean": "rimraf dist",
33
- "format": "prettier --write \"src/**/*.{ts,js}\" \"test/**/*.{ts,js}\" \"examples/**/*.{ts,js}\" \"types/**/*.{ts,js,json}\" \"*.{js,json}\"",
34
- "format:check": "prettier --check \"src/**/*.{ts,js}\" \"test/**/*.{ts,js}\" \"examples/**/*.{ts,js}\" \"types/**/*.{ts,js,json}\" \"*.{js,json}\"",
36
+ "format": "prettier --write .",
37
+ "format:check": "prettier --check .",
35
38
  "example:basic": "node examples/basic-usage.js",
36
39
  "example:array": "node examples/array-methods.js",
37
40
  "example:advanced": "node examples/advanced-usage.js",
38
41
  "example:ts": "ts-node examples/typescript-usage.ts",
39
42
  "example:methods": "for file in examples/methods/*.ts; do echo \"\\n=== Running $file ===\"; ts-node \"$file\"; done",
40
- "knip": "knip"
43
+ "knip": "knip",
44
+ "lint-staged": "lint-staged",
45
+ "precommit": "npm run typecheck && npm run lint-staged"
41
46
  },
42
47
  "keywords": [
43
48
  "range",
@@ -57,18 +62,23 @@
57
62
  "url": "git+https://github.com/SkorpionG/range-pie.git"
58
63
  },
59
64
  "devDependencies": {
60
- "@eslint/js": "^9.28.0",
61
- "@types/jest": "^29.5.0",
62
- "@types/node": "^24.0.3",
63
- "eslint": "^9.28.0",
64
- "globals": "^16.2.0",
65
- "jest": "^29.7.0",
66
- "knip": "^5.61.2",
67
- "prettier": "^3.5.3",
68
- "rimraf": "^5.0.0",
69
- "ts-jest": "^29.1.0",
70
- "ts-node": "^10.9.1",
71
- "typescript": "^5.8.3",
72
- "typescript-eslint": "^8.33.1"
65
+ "@eslint/js": "^10.0.1",
66
+ "@types/jest": "^30.0.0",
67
+ "@types/node": "^26.0.1",
68
+ "eslint": "^10.6.0",
69
+ "globals": "^17.7.0",
70
+ "husky": "^9.1.7",
71
+ "jest": "^30.4.2",
72
+ "knip": "^6.23.0",
73
+ "lint-staged": "^17.0.8",
74
+ "prettier": "^3.9.1",
75
+ "rimraf": "^6.1.3",
76
+ "ts-jest": "^29.4.11",
77
+ "ts-node": "^10.9.2",
78
+ "typescript": "^6.0.3",
79
+ "typescript-eslint": "^8.62.0"
80
+ },
81
+ "overrides": {
82
+ "js-yaml": "^4.1.2"
73
83
  }
74
84
  }