mybase 1.1.51 → 1.2.2

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.
Files changed (46) hide show
  1. package/ip6addr.d.ts +10 -1
  2. package/jest.config.js +8 -1
  3. package/mybase.d.ts +57 -0
  4. package/mybase.js +401 -684
  5. package/mybase.test.ts +647 -0
  6. package/mybase.ts +397 -0
  7. package/package.json +3 -2
  8. package/ts/funcs/Geoip2Paths.js +7 -5
  9. package/ts/funcs/Geoip2Paths.ts +6 -4
  10. package/ts/funcs/asJSON.d.ts +1 -1
  11. package/ts/funcs/asJSON.js +6 -4
  12. package/ts/funcs/asJSON.ts +8 -5
  13. package/ts/funcs/hash_sha512.d.ts +1 -1
  14. package/ts/funcs/hash_sha512.js +4 -4
  15. package/ts/funcs/hash_sha512.ts +3 -4
  16. package/ts/funcs/isLANIp.d.ts +2 -3
  17. package/ts/funcs/isLANIp.js +14 -15
  18. package/ts/funcs/isLANIp.test.ts +7 -8
  19. package/ts/funcs/isLANIp.ts +25 -28
  20. package/ts/funcs/isLoopbackIP.d.ts +2 -3
  21. package/ts/funcs/isLoopbackIP.js +15 -16
  22. package/ts/funcs/isLoopbackIP.test.ts +7 -7
  23. package/ts/funcs/isLoopbackIP.ts +21 -23
  24. package/ts/funcs/validEmail.d.ts +1 -1
  25. package/ts/funcs/validEmail.js +0 -3
  26. package/ts/funcs/validEmail.ts +1 -3
  27. package/ts/funcs/vaultFill.js +1 -1
  28. package/ts/funcs/vaultFill.ts +1 -1
  29. package/ts/funcs/vaultRead.js +9 -3
  30. package/ts/funcs/vaultRead.ts +8 -3
  31. package/ts/index.d.ts +1 -0
  32. package/ts/index.js +1 -0
  33. package/ts/index.ts +1 -1
  34. package/ts/models/DateIterator.d.ts +33 -0
  35. package/ts/models/DateIterator.js +76 -0
  36. package/ts/models/DateIterator.test.ts +149 -0
  37. package/ts/models/DateIterator.ts +80 -0
  38. package/ts/models/IPAddress.d.ts +13 -13
  39. package/ts/models/IPAddress.ts +4 -4
  40. package/ts/models/OTPGenerator.test.ts +1 -1
  41. package/ts/types.d.ts +35 -0
  42. package/ts/types.js +1 -0
  43. package/ts/types.ts +42 -1
  44. package/tsconfig.jest.json +11 -0
  45. package/tsconfig.json +2 -1
  46. package/types/third-party.d.ts +21 -0
@@ -2,36 +2,33 @@ import ip6addr from '@7c/node-ip6addr'
2
2
  import net from 'net'
3
3
 
4
4
  export const private_network_cidrs = [
5
- ip6addr.createCIDR('10.0.0.0/8'),
6
- ip6addr.createCIDR('172.16.0.0/12'),
7
- ip6addr.createCIDR('192.168.0.0/16'),
8
- ip6addr.createCIDR('fd00::/8'), // Reserved by IETF for future use, but not currently in active use.
9
- ip6addr.createCIDR('fc00::/8'), // The range currently in use for local communications within a site or organization.
5
+ ip6addr.createCIDR('10.0.0.0/8'),
6
+ ip6addr.createCIDR('172.16.0.0/12'),
7
+ ip6addr.createCIDR('192.168.0.0/16'),
8
+ ip6addr.createCIDR('fd00::/8'), // Reserved by IETF for future use, but not currently in active use.
9
+ ip6addr.createCIDR('fc00::/8'), // The range currently in use for local communications within a site or organization.
10
10
  ]
11
11
 
12
- export function isLANIp(ip:string) : boolean {
13
- ip=normalizeIp(ip)
14
- if (net.isIP(ip)===0) return false
15
- try {
16
- // speed optimized
17
- let ipVersion = net.isIPv4(ip) ? 'ipv4' : 'ipv6'
18
- for (let cidr of private_network_cidrs) {
19
- if (!cidr) continue
20
-
21
- let first = cidr.first()
22
- if (first.kind() !== ipVersion) continue
23
- if (cidr.contains(ip))
24
- return true
25
- }
26
- } catch (err) {
27
- console.log(err)
28
- }
29
- return false
12
+ function normalizeIp(ip: string): string | false {
13
+ if (net.isIP(ip) === 0) return false
14
+ if (net.isIPv6(ip)) return ip.toLowerCase().replace(/^::ffff:/, '')
15
+ return ip
30
16
  }
31
17
 
32
- function normalizeIp(ip:string):string {
33
- // also support ipv6
34
- if (net.isIP(ip) === 0) return ''
35
- if (net.isIPv6(ip)) return ip.toLowerCase().replace(/^::ffff:/, '')
36
- return ip
18
+ export function isLANIp(ip: string): boolean | null {
19
+ const n = normalizeIp(ip)
20
+ if (n === false || net.isIP(n) === 0) return false
21
+ try {
22
+ const ipVersion = net.isIPv4(n) ? 'ipv4' : 'ipv6'
23
+ for (const cidr of private_network_cidrs) {
24
+ if (!cidr) continue
25
+ const first = cidr.first()
26
+ if (first.kind() !== ipVersion) continue
27
+ if (cidr.contains(n)) return true
28
+ }
29
+ return null
30
+ } catch (err) {
31
+ console.log(err)
32
+ }
33
+ return false
37
34
  }
@@ -1,3 +1,2 @@
1
- import ip6addr from '@7c/node-ip6addr';
2
- export declare const local_network_cidrs: ip6addr.CIDR[];
3
- export declare function isLoopbackIP(ip: string): boolean;
1
+ export declare const local_network_cidrs: import("@7c/node-ip6addr").CIDR[];
2
+ export declare function isLoopbackIP(ip: string): boolean | null;
@@ -9,34 +9,33 @@ const node_ip6addr_1 = __importDefault(require("@7c/node-ip6addr"));
9
9
  const net_1 = __importDefault(require("net"));
10
10
  exports.local_network_cidrs = [
11
11
  node_ip6addr_1.default.createCIDR('127.0.0.0/8'),
12
- node_ip6addr_1.default.createCIDR('::1/128')
12
+ node_ip6addr_1.default.createCIDR('::1/128'),
13
13
  ];
14
- function isLoopbackIP(ip) {
15
- ip = normalizeIp(ip);
14
+ function normalizeIp(ip) {
16
15
  if (net_1.default.isIP(ip) === 0)
17
16
  return false;
17
+ if (net_1.default.isIPv6(ip))
18
+ return ip.toLowerCase().replace(/^::ffff:/, '');
19
+ return ip;
20
+ }
21
+ function isLoopbackIP(ip) {
22
+ const n = normalizeIp(ip);
23
+ if (n === false || net_1.default.isIP(n) === 0)
24
+ return false;
18
25
  try {
19
- // speed optimized
20
- let ipVersion = net_1.default.isIPv4(ip) ? 'ipv4' : 'ipv6';
21
- for (let cidr of exports.local_network_cidrs) {
22
- let first = cidr.first();
26
+ const ipVersion = net_1.default.isIPv4(n) ? 'ipv4' : 'ipv6';
27
+ for (const cidr of exports.local_network_cidrs) {
28
+ const first = cidr.first();
23
29
  if (first.kind() !== ipVersion)
24
30
  continue;
25
- if (cidr.contains(ip))
31
+ if (cidr.contains(n))
26
32
  return true;
27
33
  }
34
+ return null;
28
35
  }
29
36
  catch (err) {
30
37
  console.log(err);
31
38
  }
32
39
  return false;
33
40
  }
34
- function normalizeIp(ip) {
35
- // also support ipv6
36
- if (net_1.default.isIP(ip) === 0)
37
- return '';
38
- if (net_1.default.isIPv6(ip))
39
- return ip.toLowerCase().replace(/^::ffff:/, '');
40
- return ip;
41
- }
42
41
  //# sourceMappingURL=isLoopbackIP.js.map
@@ -10,12 +10,12 @@ describe('isLoopbackIP', () => {
10
10
  expect(isLoopbackIP('::1')).toBe(true);
11
11
  });
12
12
 
13
- test('should return false for non-loopback IPv4 address', () => {
14
- expect(isLoopbackIP('192.168.0.1')).toBe(false);
13
+ test('should return null for non-loopback IPv4 address', () => {
14
+ expect(isLoopbackIP('192.168.0.1')).toBeNull();
15
15
  });
16
16
 
17
- test('should return false for non-loopback IPv6 address', () => {
18
- expect(isLoopbackIP('2001:0db8:85a3:0000:0000:8a2e:0370:7334')).toBe(false);
17
+ test('should return null for non-loopback IPv6 address', () => {
18
+ expect(isLoopbackIP('2001:0db8:85a3:0000:0000:8a2e:0370:7334')).toBeNull();
19
19
  });
20
20
 
21
21
  test('should return false for invalid IP address', () => {
@@ -49,12 +49,12 @@ describe('isLoopbackIP', () => {
49
49
 
50
50
  test('should return null for non-local IPv4', () => {
51
51
  const ip = '8.8.8.8'
52
- expect(isLoopbackIP(ip)).toBe(false)
52
+ expect(isLoopbackIP(ip)).toBeNull()
53
53
  })
54
54
 
55
55
  test('should return null for non-local IPv6', () => {
56
56
  const ip = '2001:4860:4860::8888'
57
- expect(isLoopbackIP(ip)).toBe(false)
57
+ expect(isLoopbackIP(ip)).toBeNull()
58
58
  })
59
59
 
60
60
  test('should return false for invalid IP', () => {
@@ -65,7 +65,7 @@ describe('isLoopbackIP', () => {
65
65
  test('random ips should not be Loopback', () => {
66
66
  for(let i=0;i<10;i++){
67
67
  let ip = randomIP()
68
- expect(isLoopbackIP(ip)).toBe(false)
68
+ expect(isLoopbackIP(ip)).toBeNull()
69
69
  }
70
70
 
71
71
  })
@@ -2,31 +2,29 @@ import ip6addr from '@7c/node-ip6addr'
2
2
  import net from 'net'
3
3
 
4
4
  export const local_network_cidrs = [
5
- ip6addr.createCIDR('127.0.0.0/8'),
6
- ip6addr.createCIDR('::1/128')
5
+ ip6addr.createCIDR('127.0.0.0/8'),
6
+ ip6addr.createCIDR('::1/128'),
7
7
  ]
8
8
 
9
- export function isLoopbackIP(ip:string) : boolean {
10
- ip=normalizeIp(ip)
11
- if (net.isIP(ip)===0) return false
12
- try {
13
- // speed optimized
14
- let ipVersion = net.isIPv4(ip) ? 'ipv4' : 'ipv6'
15
- for (let cidr of local_network_cidrs) {
16
- let first = cidr.first()
17
- if (first.kind() !== ipVersion) continue
18
- if (cidr.contains(ip))
19
- return true
20
- }
21
- } catch (err) {
22
- console.log(err)
23
- }
24
- return false
9
+ function normalizeIp(ip: string): string | false {
10
+ if (net.isIP(ip) === 0) return false
11
+ if (net.isIPv6(ip)) return ip.toLowerCase().replace(/^::ffff:/, '')
12
+ return ip
25
13
  }
26
14
 
27
- function normalizeIp(ip:string):string {
28
- // also support ipv6
29
- if (net.isIP(ip) === 0) return ''
30
- if (net.isIPv6(ip)) return ip.toLowerCase().replace(/^::ffff:/, '')
31
- return ip
15
+ export function isLoopbackIP(ip: string): boolean | null {
16
+ const n = normalizeIp(ip)
17
+ if (n === false || net.isIP(n) === 0) return false
18
+ try {
19
+ const ipVersion = net.isIPv4(n) ? 'ipv4' : 'ipv6'
20
+ for (const cidr of local_network_cidrs) {
21
+ const first = cidr.first()
22
+ if (first.kind() !== ipVersion) continue
23
+ if (cidr.contains(n)) return true
24
+ }
25
+ return null
26
+ } catch (err) {
27
+ console.log(err)
28
+ }
29
+ return false
32
30
  }
@@ -1 +1 @@
1
- export declare function validEmail(email: string): boolean;
1
+ export declare function validEmail(email: unknown): boolean;
@@ -13,8 +13,5 @@ function validEmail(email) {
13
13
  // taken from https://www.w3resource.com/javascript/form/email-validation.php
14
14
  // strange looking emails might be indeed valid
15
15
  // check https://www.w3resource.com/javascript/form/example-javascript-form-validation-email-REC-2822.html
16
- if (typeof email === 'string')
17
- email = email.toLowerCase().trim();
18
- return (/(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/).test(email);
19
16
  }
20
17
  //# sourceMappingURL=validEmail.js.map
@@ -1,6 +1,6 @@
1
1
  import validator from 'validator'
2
2
 
3
- export function validEmail(email:string) {
3
+ export function validEmail(email: unknown): boolean {
4
4
  if (typeof email==='string')
5
5
  return validator.isEmail(email) // validator needs a string
6
6
  return false
@@ -8,6 +8,4 @@ export function validEmail(email:string) {
8
8
  // taken from https://www.w3resource.com/javascript/form/email-validation.php
9
9
  // strange looking emails might be indeed valid
10
10
  // check https://www.w3resource.com/javascript/form/example-javascript-form-validation-email-REC-2822.html
11
- if (typeof email==='string') email=email.toLowerCase().trim()
12
- return (/(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/).test(email)
13
11
  }
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.vaultFill = vaultFill;
4
4
  const debug_1 = require("debug");
5
5
  const vaultRead_1 = require("./vaultRead");
6
- const dbg = (0, debug_1.debug)('vaultFill');
6
+ const dbg = (0, debug_1.debug)('mybase');
7
7
  function vaultFill(vaultInstance, inputObject, ignoreError = false, keepCache = true) {
8
8
  // v2.1
9
9
  // fills all strings with ^vault@/secret/... with proper vault values
@@ -1,7 +1,7 @@
1
1
  import { debug } from "debug"
2
2
  import nodeVault from "node-vault";
3
3
  import { vaultRead } from "./vaultRead";
4
- const dbg = debug('vaultFill')
4
+ const dbg = debug('mybase')
5
5
 
6
6
  export function vaultFill(vaultInstance: nodeVault.client, inputObject: any, ignoreError = false, keepCache = true) {
7
7
  // v2.1
@@ -5,18 +5,23 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.vaultRead = vaultRead;
7
7
  const debug_1 = require("debug");
8
+ const chalk_1 = __importDefault(require("chalk"));
8
9
  const path_1 = __importDefault(require("path"));
9
10
  const fs_1 = __importDefault(require("fs"));
10
11
  const fs_2 = require("fs");
11
12
  const fileCacheIsValid_1 = require("./fileCacheIsValid");
12
13
  const global_1 = require("../global");
13
14
  const hash_sha512_1 = require("./hash_sha512");
14
- const dbg = (0, debug_1.debug)('vaultRead');
15
+ const dbg = (0, debug_1.debug)('mybase');
15
16
  // need to implement it better
16
17
  function vaultRead(vaultInstance, vaultKey, cache_in_minutes = 10) {
17
18
  // v2 - supports caching
18
19
  return new Promise((resolve, reject) => {
19
- let cache_file = path_1.default.join(global_1.vault_cache_folder, (0, hash_sha512_1.hash_sha512)(`${vaultInstance.endpoint}/${vaultKey}`));
20
+ const cacheKeyMaterial = `${vaultInstance.endpoint}/${vaultKey}`;
21
+ const cacheHash = (0, hash_sha512_1.hash_sha512)(cacheKeyMaterial);
22
+ if (cacheHash === false)
23
+ return reject(new Error('vault cache key hash failed'));
24
+ let cache_file = path_1.default.join(global_1.vault_cache_folder, cacheHash);
20
25
  if (cache_in_minutes > 0 && (0, fileCacheIsValid_1.fileCacheIsValid)(cache_file, cache_in_minutes))
21
26
  return resolve(JSON.parse((0, fs_2.readFileSync)(cache_file).toString()));
22
27
  vaultInstance.read(vaultKey).then((r) => {
@@ -33,10 +38,11 @@ function vaultRead(vaultInstance, vaultKey, cache_in_minutes = 10) {
33
38
  dbg(`vault ${vaultKey} - failed`);
34
39
  reject(r);
35
40
  }).catch(e => {
41
+ console.log(e);
36
42
  dbg(`exception inside vaultRead`, e);
37
43
  // we will still return latest information from cache
38
44
  try {
39
- dbg(`returning vault@${vaultKey} from cache due to error`);
45
+ console.log(chalk_1.default.bgRed(`returning vault@${vaultKey} from cache due to error`));
40
46
  if (fs_1.default.existsSync(cache_file))
41
47
  return resolve(JSON.parse(fs_1.default.readFileSync(cache_file).toString()));
42
48
  }
@@ -1,4 +1,5 @@
1
1
  import { debug } from "debug"
2
+ import chalk from "chalk"
2
3
  import nodeVault from "node-vault";
3
4
  import path from "path";
4
5
  import fs from "fs";
@@ -6,14 +7,17 @@ import { readFileSync } from 'fs';
6
7
  import { fileCacheIsValid } from "./fileCacheIsValid";
7
8
  import { vault_cache_folder } from "../global";
8
9
  import { hash_sha512 } from "./hash_sha512";
9
- const dbg = debug('vaultRead')
10
+ const dbg = debug('mybase')
10
11
 
11
12
  // need to implement it better
12
13
 
13
14
  export function vaultRead(vaultInstance: nodeVault.client, vaultKey: string, cache_in_minutes = 10) : Promise<any> {
14
15
  // v2 - supports caching
15
16
  return new Promise((resolve, reject) => {
16
- let cache_file = path.join(vault_cache_folder, hash_sha512(`${vaultInstance.endpoint}/${vaultKey}`))
17
+ const cacheKeyMaterial = `${vaultInstance.endpoint}/${vaultKey}`
18
+ const cacheHash = hash_sha512(cacheKeyMaterial)
19
+ if (cacheHash === false) return reject(new Error('vault cache key hash failed'))
20
+ let cache_file = path.join(vault_cache_folder, cacheHash)
17
21
 
18
22
  if (cache_in_minutes > 0 && fileCacheIsValid(cache_file, cache_in_minutes))
19
23
  return resolve(JSON.parse(readFileSync(cache_file as string).toString()))
@@ -28,10 +32,11 @@ export function vaultRead(vaultInstance: nodeVault.client, vaultKey: string, cac
28
32
  dbg(`vault ${vaultKey} - failed`)
29
33
  reject(r);
30
34
  }).catch(e => {
35
+ console.log(e)
31
36
  dbg(`exception inside vaultRead`, e)
32
37
  // we will still return latest information from cache
33
38
  try {
34
- dbg(`returning vault@${vaultKey} from cache due to error`)
39
+ console.log(chalk.bgRed(`returning vault@${vaultKey} from cache due to error`))
35
40
  if (fs.existsSync(cache_file))
36
41
  return resolve(JSON.parse(fs.readFileSync(cache_file as string).toString()))
37
42
  } catch (e2) {
package/ts/index.d.ts CHANGED
@@ -32,3 +32,4 @@ export * from "./models/Timespan";
32
32
  export * from "./models/IPAddress";
33
33
  export * from "./models/OTPGenerator";
34
34
  export * from "./models/Interfaces";
35
+ export * from "./models/DateIterator";
package/ts/index.js CHANGED
@@ -49,4 +49,5 @@ __exportStar(require("./models/Timespan"), exports);
49
49
  __exportStar(require("./models/IPAddress"), exports);
50
50
  __exportStar(require("./models/OTPGenerator"), exports);
51
51
  __exportStar(require("./models/Interfaces"), exports);
52
+ __exportStar(require("./models/DateIterator"), exports);
52
53
  //# sourceMappingURL=index.js.map
package/ts/index.ts CHANGED
@@ -36,4 +36,4 @@ export * from "./models/Timespan"
36
36
  export * from "./models/IPAddress"
37
37
  export * from "./models/OTPGenerator"
38
38
  export * from "./models/Interfaces"
39
-
39
+ export * from "./models/DateIterator"
@@ -0,0 +1,33 @@
1
+ import { Unixtime } from "./Unixtime";
2
+ /**
3
+ * Iterates day-by-day backwards from `start` toward `end` (end-exclusive),
4
+ * emitting each date as a `yyyy{sep}mm{sep}dd` string.
5
+ *
6
+ * @example
7
+ * const it = new DateIterator(Unixtime.now(), Unixtime.now().addDays(-5))
8
+ * it.next() // "20260417"
9
+ * it.next() // "20260416"
10
+ * // ...
11
+ * it.next() // null
12
+ *
13
+ * @example separator
14
+ * new DateIterator(start, end, "-").toArray()
15
+ * // ["2026-04-17", "2026-04-16", ...]
16
+ *
17
+ * @example for..of
18
+ * for (const d of new DateIterator(start, end, "/")) console.log(d)
19
+ */
20
+ export declare class DateIterator implements Iterable<string> {
21
+ private readonly start;
22
+ private readonly end;
23
+ private readonly separator;
24
+ private cursor;
25
+ constructor(start: Unixtime, end: Unixtime, separator?: string);
26
+ /** Returns the next formatted date going backwards, or `null` when exhausted. */
27
+ next(): string | null;
28
+ /** Reset the iterator so it can be consumed again from `start`. */
29
+ reset(): this;
30
+ /** Collect all remaining dates into an array. */
31
+ toArray(): string[];
32
+ [Symbol.iterator](): Iterator<string>;
33
+ }
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DateIterator = void 0;
4
+ /**
5
+ * Iterates day-by-day backwards from `start` toward `end` (end-exclusive),
6
+ * emitting each date as a `yyyy{sep}mm{sep}dd` string.
7
+ *
8
+ * @example
9
+ * const it = new DateIterator(Unixtime.now(), Unixtime.now().addDays(-5))
10
+ * it.next() // "20260417"
11
+ * it.next() // "20260416"
12
+ * // ...
13
+ * it.next() // null
14
+ *
15
+ * @example separator
16
+ * new DateIterator(start, end, "-").toArray()
17
+ * // ["2026-04-17", "2026-04-16", ...]
18
+ *
19
+ * @example for..of
20
+ * for (const d of new DateIterator(start, end, "/")) console.log(d)
21
+ */
22
+ class DateIterator {
23
+ constructor(start, end, separator = "") {
24
+ if (start.lessThan(end)) {
25
+ throw new Error('DateIterator: `start` must be newer than or equal to `end`');
26
+ }
27
+ // Defensive clones: callers' instances must not be mutated, and
28
+ // Unixtime.addDays() mutates the underlying Date.
29
+ this.start = start.clone();
30
+ this.end = end.clone();
31
+ this.separator = separator;
32
+ this.cursor = this.start.clone();
33
+ }
34
+ /** Returns the next formatted date going backwards, or `null` when exhausted. */
35
+ next() {
36
+ if (this.cursor === null)
37
+ return null;
38
+ if (!this.cursor.greaterThan(this.end)) {
39
+ this.cursor = null;
40
+ return null;
41
+ }
42
+ const value = this.cursor.yyyymmdd(this.separator);
43
+ this.cursor.addDays(-1);
44
+ return value;
45
+ }
46
+ /** Reset the iterator so it can be consumed again from `start`. */
47
+ reset() {
48
+ this.cursor = this.start.clone();
49
+ return this;
50
+ }
51
+ /** Collect all remaining dates into an array. */
52
+ toArray() {
53
+ const out = [];
54
+ let d;
55
+ while ((d = this.next()) !== null)
56
+ out.push(d);
57
+ return out;
58
+ }
59
+ [Symbol.iterator]() {
60
+ let cursor = this.start.clone();
61
+ const end = this.end;
62
+ const sep = this.separator;
63
+ return {
64
+ next() {
65
+ if (cursor === null || !cursor.greaterThan(end)) {
66
+ return { value: undefined, done: true };
67
+ }
68
+ const value = cursor.yyyymmdd(sep);
69
+ cursor.addDays(-1);
70
+ return { value, done: false };
71
+ }
72
+ };
73
+ }
74
+ }
75
+ exports.DateIterator = DateIterator;
76
+ //# sourceMappingURL=DateIterator.js.map
@@ -0,0 +1,149 @@
1
+ import { DateIterator } from './DateIterator';
2
+ import { Unixtime } from './Unixtime';
3
+
4
+ describe('DateIterator', () => {
5
+ const start = () => Unixtime.fromYYYYMMDD('20240110');
6
+ const end = () => Unixtime.fromYYYYMMDD('20240105');
7
+
8
+ it('should iterate backwards day-by-day (end-exclusive) with default separator', () => {
9
+ const it = new DateIterator(start(), end());
10
+ expect(it.toArray()).toEqual([
11
+ '20240110',
12
+ '20240109',
13
+ '20240108',
14
+ '20240107',
15
+ '20240106',
16
+ ]);
17
+ });
18
+
19
+ it('should honor a custom separator', () => {
20
+ const it = new DateIterator(start(), end(), '-');
21
+ expect(it.toArray()).toEqual([
22
+ '2024-01-10',
23
+ '2024-01-09',
24
+ '2024-01-08',
25
+ '2024-01-07',
26
+ '2024-01-06',
27
+ ]);
28
+ });
29
+
30
+ it('should honor a slash separator', () => {
31
+ const it = new DateIterator(start(), end(), '/');
32
+ expect(it.toArray()).toEqual([
33
+ '2024/01/10',
34
+ '2024/01/09',
35
+ '2024/01/08',
36
+ '2024/01/07',
37
+ '2024/01/06',
38
+ ]);
39
+ });
40
+
41
+ it('next() should return null when exhausted', () => {
42
+ const s = Unixtime.fromYYYYMMDD('20240103');
43
+ const e = Unixtime.fromYYYYMMDD('20240101');
44
+ const it = new DateIterator(s, e);
45
+ expect(it.next()).toEqual('20240103');
46
+ expect(it.next()).toEqual('20240102');
47
+ expect(it.next()).toBeNull();
48
+ expect(it.next()).toBeNull();
49
+ });
50
+
51
+ it('should return empty array when start equals end (end-exclusive)', () => {
52
+ const s = Unixtime.fromYYYYMMDD('20240105');
53
+ const e = Unixtime.fromYYYYMMDD('20240105');
54
+ const it = new DateIterator(s, e);
55
+ expect(it.toArray()).toEqual([]);
56
+ expect(it.next()).toBeNull();
57
+ });
58
+
59
+ it('should throw when start is older than end', () => {
60
+ const s = Unixtime.fromYYYYMMDD('20240101');
61
+ const e = Unixtime.fromYYYYMMDD('20240110');
62
+ expect(() => new DateIterator(s, e)).toThrow(
63
+ 'DateIterator: `start` must be newer than or equal to `end`'
64
+ );
65
+ });
66
+
67
+ it('should not mutate the caller-supplied Unixtime instances', () => {
68
+ const s = Unixtime.fromYYYYMMDD('20240110');
69
+ const e = Unixtime.fromYYYYMMDD('20240105');
70
+ const sBefore = s.toLongUnixtime();
71
+ const eBefore = e.toLongUnixtime();
72
+ const it = new DateIterator(s, e);
73
+ it.toArray();
74
+ expect(s.toLongUnixtime()).toEqual(sBefore);
75
+ expect(e.toLongUnixtime()).toEqual(eBefore);
76
+ });
77
+
78
+ it('reset() should allow the iterator to be consumed again', () => {
79
+ const it = new DateIterator(start(), end());
80
+ const first = it.toArray();
81
+ expect(it.next()).toBeNull();
82
+ it.reset();
83
+ const second = it.toArray();
84
+ expect(second).toEqual(first);
85
+ });
86
+
87
+ it('reset() should return the iterator for chaining', () => {
88
+ const it = new DateIterator(start(), end());
89
+ expect(it.reset()).toBe(it);
90
+ });
91
+
92
+ it('should be iterable via for..of', () => {
93
+ const it = new DateIterator(start(), end(), '-');
94
+ const out: string[] = [];
95
+ for (const d of it) out.push(d);
96
+ expect(out).toEqual([
97
+ '2024-01-10',
98
+ '2024-01-09',
99
+ '2024-01-08',
100
+ '2024-01-07',
101
+ '2024-01-06',
102
+ ]);
103
+ });
104
+
105
+ it('for..of should be independent of next()/toArray() cursor', () => {
106
+ const it = new DateIterator(start(), end());
107
+ expect(it.next()).toEqual('20240110');
108
+ const collected: string[] = [];
109
+ for (const d of it) collected.push(d);
110
+ expect(collected).toEqual([
111
+ '20240110',
112
+ '20240109',
113
+ '20240108',
114
+ '20240107',
115
+ '20240106',
116
+ ]);
117
+ expect(it.next()).toEqual('20240109');
118
+ });
119
+
120
+ it('should support spread syntax', () => {
121
+ const it = new DateIterator(
122
+ Unixtime.fromYYYYMMDD('20240103'),
123
+ Unixtime.fromYYYYMMDD('20240101')
124
+ );
125
+ expect([...it]).toEqual(['20240103', '20240102']);
126
+ });
127
+
128
+ it('should cross month boundaries correctly', () => {
129
+ const s = Unixtime.fromYYYYMMDD('20240302');
130
+ const e = Unixtime.fromYYYYMMDD('20240227');
131
+ const it = new DateIterator(s, e, '-');
132
+ expect(it.toArray()).toEqual([
133
+ '2024-03-02',
134
+ '2024-03-01',
135
+ '2024-02-29',
136
+ '2024-02-28',
137
+ ]);
138
+ });
139
+
140
+ it('should produce N entries for a span of N+1 days (end-exclusive)', () => {
141
+ const s = Unixtime.fromYYYYMMDD('20240620');
142
+ const e = Unixtime.fromYYYYMMDD('20240610');
143
+ const it = new DateIterator(s, e);
144
+ const arr = it.toArray();
145
+ expect(arr.length).toEqual(10);
146
+ expect(arr[0]).toEqual('20240620');
147
+ expect(arr[arr.length - 1]).toEqual('20240611');
148
+ });
149
+ });