@ditojs/utils 2.99.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ditojs/utils",
3
- "version": "2.99.0",
3
+ "version": "3.0.0",
4
4
  "type": "module",
5
5
  "description": "Dito.js Utility Functions – Dito.js is a declarative and modern web framework, based on Objection.js, Koa.js and Vue.js",
6
6
  "repository": "https://github.com/ditojs/dito/tree/main/packages/utils",
@@ -19,9 +19,6 @@
19
19
  "src/",
20
20
  "types/"
21
21
  ],
22
- "scripts": {
23
- "types": "tsc --noEmit types/index.d.ts"
24
- },
25
22
  "engines": {
26
23
  "node": ">= 24.0.0"
27
24
  },
@@ -32,11 +29,8 @@
32
29
  "ie >= 11",
33
30
  "ie_mob >= 11"
34
31
  ],
35
- "dependencies": {
36
- "punycode": "^2.3.1"
37
- },
38
32
  "devDependencies": {
39
33
  "typescript": "^6.0.3"
40
34
  },
41
- "gitHead": "ba1ce26d349a74e8143faa614488182b67416db1"
35
+ "gitHead": "399f683a6ca227a03cfc0978fee9055383e6b3d8"
42
36
  }
@@ -35,6 +35,7 @@ exports[`index > exports all symbols 1`] = `
35
35
  "flatten": [Function],
36
36
  "format": [Function],
37
37
  "formatDate": [Function],
38
+ "formatPlainDate": [Function],
38
39
  "getCommonOffset": [Function],
39
40
  "getCommonPrefix": [Function],
40
41
  "getEntriesAtDataPath": [Function],
@@ -73,6 +74,7 @@ exports[`index > exports all symbols 1`] = `
73
74
  "mixin": [Function],
74
75
  "normalizeDataPath": [Function],
75
76
  "parseDataPath": [Function],
77
+ "parsePlainDate": [Function],
76
78
  "pick": [Function],
77
79
  "pickBy": [Function],
78
80
  "setDataPathEntries": [Function],
@@ -1,7 +1,7 @@
1
1
  export function shuffle(array) {
2
2
  // Do the Fisher-Yates (aka Knuth) Shuffle:
3
3
  const res = array.slice()
4
- for (let i = array.length; i; ) {
4
+ for (let i = array.length; i;) {
5
5
  const r = Math.floor(Math.random() * i)
6
6
  const t = res[--i]
7
7
  res[i] = res[r]
@@ -40,8 +40,11 @@ describe('equals()', () => {
40
40
  [false, '', false],
41
41
 
42
42
  [symbol1, symbol1, true],
43
- [symbol1, Object(symbol1), true],
44
- [Object(symbol1), Object(symbol1), true],
43
+ // TODO: Uncomment once vitest can format boxed symbols in `%o` titles,
44
+ // and remove 'should compare boxed symbols' below:
45
+ // https://github.com/vitest-dev/vitest/issues/11445
46
+ // [symbol1, Object(symbol1), true],
47
+ // [Object(symbol1), Object(symbol1), true],
45
48
  [symbol1, symbol2, false],
46
49
 
47
50
  [null, null, true],
@@ -60,6 +63,12 @@ describe('equals()', () => {
60
63
  }
61
64
  )
62
65
 
66
+ // TODO: Remove once the boxed symbol rows above are uncommented.
67
+ it('should compare boxed symbols', () => {
68
+ expect(equals(symbol1, Object(symbol1))).toBe(true)
69
+ expect(equals(Object(symbol1), Object(symbol1))).toBe(true)
70
+ })
71
+
63
72
  it('should compare arrays', () => {
64
73
  let array1 = [true, null, 1, 'a', undefined]
65
74
  let array2 = [true, null, 1, 'a', undefined]
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Formats a date as its local plain date, e.g. `'2026-05-14'`, without the
3
+ * time and time zone of `toISOString()`, which can shift it to another day.
4
+ */
5
+ export function formatPlainDate(date) {
6
+ // Use the date parts, as shifting by `getTimezoneOffset()` misses offsets
7
+ // with seconds, e.g. local mean time before 1894 in Europe/Zurich.
8
+ const pad = (number, length = 2) => String(number).padStart(length, '0')
9
+ return [
10
+ pad(date.getFullYear(), 4),
11
+ pad(date.getMonth() + 1),
12
+ pad(date.getDate())
13
+ ].join('-')
14
+ }
@@ -0,0 +1,13 @@
1
+ import { formatPlainDate } from './formatPlainDate.js'
2
+
3
+ describe('formatPlainDate()', () => {
4
+ it('should format the local plain date', () => {
5
+ expect(formatPlainDate(new Date(2026, 4, 14))).toBe('2026-05-14')
6
+ expect(formatPlainDate(new Date(2026, 4, 14, 23, 59))).toBe(
7
+ '2026-05-14'
8
+ )
9
+ expect(formatPlainDate(new Date(987, 0, 1))).toBe('0987-01-01')
10
+ // Local mean time, with an offset in seconds in some timezones:
11
+ expect(formatPlainDate(new Date(1850, 6, 1))).toBe('1850-07-01')
12
+ })
13
+ })
@@ -4,6 +4,7 @@ export * from './decamelize.js'
4
4
  export * from './deindent.js'
5
5
  export * from './escapeRegexp.js'
6
6
  export * from './format.js'
7
+ export * from './formatPlainDate.js'
7
8
  export * from './formatDate.js'
8
9
  export * from './getCommonPrefix.js'
9
10
  export * from './isAbsoluteUrl.js'
@@ -13,3 +14,4 @@ export * from './isEmail.js'
13
14
  export * from './isHostname.js'
14
15
  export * from './isUrl.js'
15
16
  export * from './labelize.js'
17
+ export * from './parsePlainDate.js'
@@ -1,9 +1,16 @@
1
- import punycode from 'punycode/punycode.js'
2
-
3
1
  // Best effort approach, allowing Internationalized domain name (with punycode)
4
2
  const domainRegExp =
5
3
  /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/i
6
4
 
7
5
  export function isDomain(str) {
8
- return !!(str && domainRegExp.test(punycode.toASCII(str)))
6
+ // Reject characters that `URL` would treat as delimiters or decode, so that
7
+ // only the domain itself is converted to its ASCII (punycode) form.
8
+ if (!str || /[\s/\\?#@:%]/.test(str)) {
9
+ return false
10
+ }
11
+ try {
12
+ return domainRegExp.test(new URL(`http://${str}`).hostname)
13
+ } catch {
14
+ return false
15
+ }
9
16
  }
@@ -12,4 +12,13 @@ describe('isDomain()', () => {
12
12
  expect(isDomain('line_to.com')).toBe(false)
13
13
  expect(isDomain('lünéto.com')).toBe(true)
14
14
  })
15
+
16
+ it('should return false for anything beyond a domain', () => {
17
+ expect(isDomain('')).toBe(false)
18
+ expect(isDomain('lineto.com:80')).toBe(false)
19
+ expect(isDomain('lineto.com/path')).toBe(false)
20
+ expect(isDomain('user@lineto.com')).toBe(false)
21
+ expect(isDomain('line%74o.com')).toBe(false)
22
+ expect(isDomain('line to.com')).toBe(false)
23
+ })
15
24
  })
@@ -0,0 +1,18 @@
1
+ import { formatPlainDate } from './formatPlainDate.js'
2
+
3
+ /**
4
+ * Parses a plain date like `'2026-05-14'` as local midnight, unlike
5
+ * `new Date()`, which parses it as UTC midnight. Returns `null` for anything
6
+ * else, including invalid dates like `'2026-02-30'`.
7
+ */
8
+ export function parsePlainDate(string) {
9
+ if (/^\d{4}-\d{2}-\d{2}$/.test(string)) {
10
+ // Date-time strings without an offset are parsed as local time.
11
+ const date = new Date(`${string}T00:00`)
12
+ // Reject invalid days, which `Date` rolls over to the next month.
13
+ if (formatPlainDate(date) === string) {
14
+ return date
15
+ }
16
+ }
17
+ return null
18
+ }
@@ -0,0 +1,20 @@
1
+ import { parsePlainDate } from './parsePlainDate.js'
2
+
3
+ describe('parsePlainDate()', () => {
4
+ it('should parse plain dates as local midnight', () => {
5
+ expect(parsePlainDate('2026-05-14')).toEqual(new Date(2026, 4, 14))
6
+ expect(parsePlainDate('1850-07-01')).toEqual(new Date(1850, 6, 1))
7
+ })
8
+
9
+ it('should return null for other values', () => {
10
+ expect(parsePlainDate(null)).toBe(null)
11
+ expect(parsePlainDate('')).toBe(null)
12
+ expect(parsePlainDate('2026-05-14T00:00:00.000Z')).toBe(null)
13
+ expect(parsePlainDate('14.05.2026')).toBe(null)
14
+ })
15
+
16
+ it('should return null for invalid dates', () => {
17
+ expect(parsePlainDate('2026-02-30')).toBe(null)
18
+ expect(parsePlainDate('2026-13-01')).toBe(null)
19
+ })
20
+ })
package/types/index.d.ts CHANGED
@@ -518,6 +518,19 @@ export function format(
518
518
  }
519
519
  }
520
520
  ): string | null | undefined
521
+ /**
522
+ * Formats a date as its local plain date, e.g. `'2026-05-14'`, without the
523
+ * time and time zone of `toISOString()`, which can shift it to another day.
524
+ */
525
+ export function formatPlainDate(date: Date): string
526
+ /**
527
+ * Parses a plain date like `'2026-05-14'` as local midnight, unlike
528
+ * `new Date()`, which parses it as UTC midnight. Returns `null` for anything
529
+ * else, including invalid dates like `'2026-02-30'`.
530
+ */
531
+ export function parsePlainDate(
532
+ string: string | null | undefined
533
+ ): Date | null
521
534
  /**
522
535
  * Formats a date value as a string. If the value is not a Date,
523
536
  * attempts to convert it to a Date first.