@greendrake/util 0.6.0 → 0.7.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/README.md CHANGED
@@ -173,10 +173,13 @@ const { latitude, longitude } = await getUserLocation()
173
173
  - `dClone(data)` — deep clone via a JSON round-trip (JSON-representable data only).
174
174
  - `isEmpty(o, emptyArraysAreNotEmpty?)` — true for `null`, `undefined`, `''`, `NaN`, an empty array (unless the flag is set) and an empty plain object.
175
175
  - `arrayRemove(a, el)` — removes the first occurrence of `el` in place.
176
+ - `arraysEqual(a, b, same = Object.is)` — same length and `same` holds element-wise, in order.
176
177
  - `capitalise(s)` — upper-cases the first character.
177
178
  - `humanSeconds(seconds)` — compact duration, two most significant units: `3661` → `'1h 1m'`.
178
179
  - `humanFileSize(bytes, si = false, dp = 1)` — binary units (`KiB`, `MiB`, …) by default, decimal units (`kB`, `MB`, …) with `si`; `dp` decimal places.
179
180
  - `fileMime(file)` — `File.type`, falling back to an extension map (`heic`, `heif`, `avif`, `webp`) when the OS reports none, then `application/octet-stream`.
181
+ - `Declared<Map, Tag, Value>` (type) — the union a host's declaration map discriminates, one member per key: the key under `Tag`, its type under `Value` (absent for a key declared `never`); `{ [Tag]: string; [Value]?: unknown }` while the map is undeclared. For a package exporting an empty interface for its host to augment.
182
+ - `configuration<T>(owner)` — a package's boot-time configuration: `{ configure, get }`, where `get` throws under `owner`'s name until the host has called `configure`.
180
183
  - `createDeferred<T = void>()` — a `Deferred<T>`: `{ promise, resolve, reject }`.
181
184
 
182
185
  ```ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@greendrake/util",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Framework-free, Node-safe utilities: typed event emitter, timing, retry, staleness, comparators, fetch helpers, misc",
5
5
  "license": "MIT",
6
6
  "author": "Eugene Greendrake <eugene@greendrake.nz>",
@@ -38,7 +38,7 @@
38
38
  },
39
39
  "prettier": "@greendrake/dev-config/prettier",
40
40
  "devDependencies": {
41
- "@greendrake/dev-config": "0.6.0",
41
+ "@greendrake/dev-config": "0.7.0",
42
42
  "typescript": "6.0.3"
43
43
  }
44
44
  }
package/src/main.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  export { Emitter, type EventMap } from './Emitter'
2
- export { randomString, dClone, isEmpty, arrayRemove, capitalise, humanSeconds, humanFileSize, fileMime, createDeferred, type Deferred } from './misc'
2
+ export { randomString, dClone, isEmpty, arrayRemove, arraysEqual, capitalise, humanSeconds, humanFileSize, fileMime, configuration, createDeferred, type Deferred } from './misc'
3
3
  export { TimeOutPromise, Debouncer, Delayed } from './timing'
4
4
  export { Retrier, RetryStopped, type RetrierOptions, type RetrierEvents } from './Retrier'
5
5
  export { StalenessDetector, type StalenessEvents } from './StalenessDetector'
6
6
  export { composeComparators, byProp, byPreset, type Comparator } from './compare'
7
7
  export { NetworkError, HttpError, safeFetch, fetchJson } from './net'
8
8
  export { RTL_LANGUAGES, isRTLLocale } from './locale'
9
+ export type { Declared } from './types'
package/src/misc.ts CHANGED
@@ -13,6 +13,10 @@ export const arrayRemove = <T>(a: T[], el: T): void => {
13
13
  }
14
14
  }
15
15
 
16
+ // Same length, and `same` holds element-wise in order (the equal lengths make
17
+ // every b[i] an element).
18
+ export const arraysEqual = <T>(a: readonly T[], b: readonly T[], same: (x: T, y: T) => boolean = Object.is): boolean => a.length === b.length && a.every((x, i) => same(x, b[i] as T))
19
+
16
20
  export const capitalise = (s: string): string => (s ? s.charAt(0).toUpperCase() + s.slice(1) : '')
17
21
 
18
22
  // Compact human duration, two most significant units: 3661s → "1h 1m".
@@ -70,6 +74,22 @@ export interface Deferred<T> {
70
74
  reject: (reason?: unknown) => void
71
75
  }
72
76
 
77
+ // A package's boot-time configuration: `configure` for the host to call once,
78
+ // and `get` for the package to read it through — throwing, under `owner`'s
79
+ // name, while the host has not.
80
+ export const configuration = <T>(owner: string): { configure: (value: T) => void; get: () => T } => {
81
+ let current: T | undefined
82
+ return {
83
+ configure: value => {
84
+ current = value
85
+ },
86
+ get: () => {
87
+ if (current === undefined) throw new Error(`${owner}: used before it is configured`)
88
+ return current
89
+ }
90
+ }
91
+ }
92
+
73
93
  export const createDeferred = <T = void>(): Deferred<T> => {
74
94
  let resolve!: (value: T | PromiseLike<T>) => void
75
95
  let reject!: (reason?: unknown) => void
package/src/types.ts ADDED
@@ -0,0 +1,6 @@
1
+ // The union a host's declaration map discriminates, one member per key: the
2
+ // key under `Tag`, and the key's type under `Value` — absent for a key declared
3
+ // `never`. Undeclared (an empty map), any string tags it and its value is
4
+ // unknown. The map is an interface a package exports empty for the host to
5
+ // augment, so the package's own code compiles against the undeclared form.
6
+ export type Declared<Map, Tag extends PropertyKey, Value extends PropertyKey> = [keyof Map] extends [never] ? { [P in Tag]: string } & { [P in Value]?: unknown } : { [K in keyof Map]: { [P in Tag]: K } & ([Map[K]] extends [never] ? unknown : { [P in Value]: Map[K] }) }[keyof Map]