@glion/util-semver 0.15.1 → 0.15.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 (2) hide show
  1. package/README.md +61 -163
  2. package/package.json +8 -4
package/README.md CHANGED
@@ -1,31 +1,31 @@
1
1
  # @glion/util-semver
2
2
 
3
- A tiny, fast, and secure version utility for HL7v2-like version strings. Intentionally simplified for our ecosystem while maintaining excellent performance and type safety.
3
+ Parser, comparators, and range matcher for HL7v2-style numeric version strings.
4
4
 
5
- ## Features
5
+ ## What it does
6
6
 
7
- ✨ **Simple & Elegant** - Clean API, no unnecessary complexity
8
- ⚡ **High Performance** - O(n) algorithms, Range caching for hot paths
9
- 🔒 **Secure** - ReDoS protection, integer overflow guards
10
- 📘 **Type Safe** - Full TypeScript support with structured errors
11
- 🪶 **Tiny** - ~6KB, zero dependencies
7
+ Parses, compares, and matches HL7v2 versions like `2.5.1` or `2.3`. Supports the `=`, `<`, `<=`, `>`, `>=` comparators and AND-combined ranges (`">=2.0 <3.0"`), plus helpers for sorting, increment, diff, and finding max/min versions that satisfy a range. Intentionally narrower than full semver: no prerelease, build metadata, wildcards, or `^`/`~` operators. Versions are capped at int32 and input lengths are bounded to protect against malformed input.
12
8
 
13
- ## Installation
9
+ ## Install
14
10
 
15
11
  ```bash
16
12
  npm install @glion/util-semver
17
13
  ```
18
14
 
19
- ## Quick Start
15
+ ## Use
20
16
 
21
17
  ```typescript
22
18
  import {
23
19
  parse,
24
20
  clean,
21
+ valid,
25
22
  compare,
23
+ eq,
24
+ gt,
26
25
  satisfies,
27
26
  sort,
28
27
  maxSatisfying,
28
+ minSatisfying,
29
29
  increment,
30
30
  diff,
31
31
  Range,
@@ -37,11 +37,11 @@ clean("2"); // "2.0.0"
37
37
  valid("2.5.1"); // true
38
38
 
39
39
  // Compare versions
40
- compare("2.3.1", "2.4"); // -1 (less than)
40
+ compare("2.3.1", "2.4"); // -1
41
41
  eq("2.5.0", "2.5"); // true
42
42
  gt("2.5.1", "2.5.0"); // true
43
43
 
44
- // Version ranges
44
+ // Ranges
45
45
  satisfies("2.5.1", ">=2.0 <3.0"); // true
46
46
  satisfies("2.5.1", "2.5.1"); // true (exact match)
47
47
 
@@ -50,14 +50,14 @@ sort(["2.5", "2.3.1", "2.10"]); // ["2.3.1", "2.5", "2.10"]
50
50
  maxSatisfying(["2.3", "2.5", "3.0"], ">=2.0 <3.0"); // "2.5"
51
51
  minSatisfying(["2.3", "2.5", "3.0"], ">=2.0 <3.0"); // "2.3"
52
52
 
53
- // Version manipulation
53
+ // Manipulation
54
54
  increment("2.5.1", "minor"); // "2.6.0"
55
55
  diff("2.5.1", "2.6.0"); // "minor"
56
56
  ```
57
57
 
58
- ## API Reference
58
+ ## API
59
59
 
60
- ### Parsing & Validation
60
+ ### Parsing and validation
61
61
 
62
62
  #### `parse(version: string): Hl7Version`
63
63
 
@@ -69,11 +69,11 @@ parse("2.3"); // { major: 2, minor: 3, patch: 0 }
69
69
  parse("2"); // { major: 2, minor: 0, patch: 0 }
70
70
  ```
71
71
 
72
- **Throws:** `VersionParseError` if invalid format
72
+ Throws `VersionParseError` if the format is invalid.
73
73
 
74
74
  #### `clean(version: string): string`
75
75
 
76
- Converts a version to canonical format (major.minor.patch).
76
+ Converts a version to canonical `major.minor.patch` format.
77
77
 
78
78
  ```typescript
79
79
  clean("2"); // "2.0.0"
@@ -81,7 +81,7 @@ clean("2.5"); // "2.5.0"
81
81
  clean(" 2.5 "); // "2.5.0"
82
82
  ```
83
83
 
84
- **Throws:** `VersionParseError` if invalid format
84
+ Throws `VersionParseError` if the format is invalid.
85
85
 
86
86
  #### `valid(version: string): boolean`
87
87
 
@@ -105,13 +105,13 @@ compare("2.5.1", "2.5.1"); // 0 (a = b)
105
105
  compare("2.6", "2.5.9"); // 1 (a > b)
106
106
  ```
107
107
 
108
- #### Comparison Operators
108
+ #### Comparison operators
109
109
 
110
- - `eq(a, b)` - Equal to
111
- - `lt(a, b)` - Less than
112
- - `lte(a, b)` - Less than or equal
113
- - `gt(a, b)` - Greater than
114
- - `gte(a, b)` - Greater than or equal
110
+ - `eq(a, b)` — equal to
111
+ - `lt(a, b)` — less than
112
+ - `lte(a, b)` — less than or equal
113
+ - `gt(a, b)` — greater than
114
+ - `gte(a, b)` — greater than or equal
115
115
 
116
116
  ```typescript
117
117
  eq("2.5.0", "2.5"); // true
@@ -119,35 +119,21 @@ lt("2.3", "2.4"); // true
119
119
  gte("2.5.1", "2.5"); // true
120
120
  ```
121
121
 
122
- ### Range Matching
122
+ ### Range matching
123
123
 
124
124
  #### `satisfies(version, range): boolean`
125
125
 
126
126
  Checks if a version satisfies a range expression.
127
127
 
128
- **Range Syntax:**
129
-
130
- - Operators: `=`, `<`, `<=`, `>`, `>=`
131
- - Multiple comparators: Space-separated (AND logic)
132
- - Default operator: `=` (exact match)
133
-
134
128
  ```typescript
135
129
  satisfies("2.5.1", ">=2.0 <3.0"); // true
136
130
  satisfies("2.5.1", "2.5.1"); // true (exact match)
137
131
  satisfies("2.5.1", ">=2.6"); // false
138
132
  ```
139
133
 
140
- **Performance Tip:** Use `Range` objects for repeated checks:
134
+ For repeated checks, use a `Range` object to avoid re-parsing:
141
135
 
142
136
  ```typescript
143
- // Slow: Parse range every time
144
- for (const version of versions) {
145
- if (satisfies(version, ">=2.0 <3.0")) {
146
- /* ... */
147
- }
148
- }
149
-
150
- // Fast: Parse once, reuse many times
151
137
  const range = new Range(">=2.0 <3.0");
152
138
  for (const version of versions) {
153
139
  if (satisfies(version, range)) {
@@ -156,7 +142,7 @@ for (const version of versions) {
156
142
  }
157
143
  ```
158
144
 
159
- #### `Range` Class
145
+ #### `Range` class
160
146
 
161
147
  Pre-parsed range for efficient reuse.
162
148
 
@@ -171,15 +157,15 @@ maxSatisfying(versions, range);
171
157
  minSatisfying(versions, range);
172
158
  ```
173
159
 
174
- ### Collection Operations
160
+ ### Collection operations
175
161
 
176
162
  #### `sort(versions: string[]): string[]`
177
163
 
178
- Sorts versions in ascending order. Returns new array (does not mutate).
164
+ Sorts versions in ascending order. Returns a new array; does not mutate.
179
165
 
180
166
  ```typescript
181
167
  sort(["2.5", "2.3.1", "2.10.0"]);
182
- // => ["2.3.1", "2.5", "2.10.0"]
168
+ // ["2.3.1", "2.5", "2.10.0"]
183
169
  ```
184
170
 
185
171
  #### `maxSatisfying(versions, range): string | null`
@@ -187,11 +173,8 @@ sort(["2.5", "2.3.1", "2.10.0"]);
187
173
  Finds the highest version that satisfies a range.
188
174
 
189
175
  ```typescript
190
- maxSatisfying(["2.3.0", "2.5.1", "3.0.0"], ">=2.0 <3.0");
191
- // => "2.5.1"
192
-
193
- maxSatisfying(["2.3.0", "2.5.1"], ">=3.0");
194
- // => null (no matches)
176
+ maxSatisfying(["2.3.0", "2.5.1", "3.0.0"], ">=2.0 <3.0"); // "2.5.1"
177
+ maxSatisfying(["2.3.0", "2.5.1"], ">=3.0"); // null
195
178
  ```
196
179
 
197
180
  #### `minSatisfying(versions, range): string | null`
@@ -199,11 +182,10 @@ maxSatisfying(["2.3.0", "2.5.1"], ">=3.0");
199
182
  Finds the lowest version that satisfies a range.
200
183
 
201
184
  ```typescript
202
- minSatisfying(["2.3.0", "2.5.1", "3.0.0"], ">=2.0 <3.0");
203
- // => "2.3.0"
185
+ minSatisfying(["2.3.0", "2.5.1", "3.0.0"], ">=2.0 <3.0"); // "2.3.0"
204
186
  ```
205
187
 
206
- ### Version Manipulation
188
+ ### Version manipulation
207
189
 
208
190
  #### `increment(version, release): string`
209
191
 
@@ -223,12 +205,10 @@ Determines which component differs between versions.
223
205
  diff("2.5.1", "3.0.0"); // "major"
224
206
  diff("2.5.1", "2.6.0"); // "minor"
225
207
  diff("2.5.1", "2.5.2"); // "patch"
226
- diff("2.5.1", "2.5.1"); // null (equal)
208
+ diff("2.5.1", "2.5.1"); // null
227
209
  ```
228
210
 
229
- ## Error Handling
230
-
231
- All parsing/operation functions throw structured errors for invalid input:
211
+ ### Errors
232
212
 
233
213
  ```typescript
234
214
  import { VersionParseError, RangeParseError } from "@glion/util-semver";
@@ -239,7 +219,6 @@ try {
239
219
  if (e instanceof VersionParseError) {
240
220
  console.log(e.input); // "INVALID"
241
221
  console.log(e.reason); // "expected format: major.minor.patch..."
242
- console.log(e.message); // Full formatted message
243
222
  }
244
223
  }
245
224
 
@@ -247,105 +226,36 @@ try {
247
226
  satisfies("2.5.1", "INVALID_RANGE");
248
227
  } catch (e) {
249
228
  if (e instanceof RangeParseError) {
250
- console.log(e.token); // "INVALID_RANGE"
251
- console.log(e.reason); // "expected format: [operator]version..."
229
+ console.log(e.token);
230
+ console.log(e.reason);
252
231
  }
253
232
  }
254
233
  ```
255
234
 
256
- **Validation functions never throw:**
257
-
258
- - `valid(version)` - Returns `false` for invalid input
259
-
260
- **Operation functions throw on invalid input:**
261
-
262
- - `parse()`, `clean()`, `compare()`, `satisfies()`, etc.
235
+ - `valid(version)` never throws — returns `false` for invalid input.
236
+ - `parse()`, `clean()`, `compare()`, `satisfies()`, etc. throw on invalid input.
263
237
 
264
- ## Performance
238
+ ## Version comparison rules
265
239
 
266
- ### Algorithm Complexity
240
+ ### Version format
267
241
 
268
- - `parse()`, `compare()`: O(1)
269
- - `sort()`: O(n log n)
270
- - `maxSatisfying()`, `minSatisfying()`: O(n) - optimized to avoid full sort
271
- - Range parsing: O(k) where k = number of comparators
242
+ - Generic numeric versions: `major[.minor][.patch]`.
243
+ - Missing components default to 0: `"2"` → `{ major: 2, minor: 0, patch: 0 }`.
244
+ - Whitespace is trimmed: `" 2.5.1 "` → `"2.5.1"`.
245
+ - No prerelease or build metadata.
246
+ - No wildcards, hyphen ranges, or `^`/`~` operators.
272
247
 
273
- ### Optimization Tips
248
+ ### Range syntax
274
249
 
275
- **1. Reuse Range objects**
250
+ Operators:
276
251
 
277
- ```typescript
278
- // ❌ Slow: Parse range on every call
279
- for (const version of manyVersions) {
280
- if (satisfies(version, ">=2.0 <3.0")) {
281
- /* ... */
282
- }
283
- }
284
-
285
- // ✅ Fast: Parse once, reuse unlimited times
286
- const range = new Range(">=2.0 <3.0");
287
- for (const version of manyVersions) {
288
- if (satisfies(version, range)) {
289
- /* ... */
290
- }
291
- }
292
- ```
252
+ - `=` or omitted — exact match
253
+ - `<` — less than
254
+ - `<=` — less than or equal
255
+ - `>` — greater than
256
+ - `>=` — greater than or equal
293
257
 
294
- **2. Use appropriate algorithms**
295
-
296
- ```typescript
297
- // ❌ Unnecessary: Full sort just to find max
298
- const sorted = sort(versions);
299
- const max = sorted[sorted.length - 1];
300
-
301
- // ✅ Efficient: O(n) instead of O(n log n)
302
- const max = maxSatisfying(versions, ">=0.0.0");
303
- ```
304
-
305
- ## Security
306
-
307
- Built-in protections against common vulnerabilities:
308
-
309
- **ReDoS Protection:**
310
-
311
- - Maximum input length: 100 chars (versions), 1000 chars (ranges)
312
- - Throws `VersionParseError` or `RangeParseError` if exceeded
313
-
314
- **Integer Overflow Protection:**
315
-
316
- - Version components capped at 2³¹-1 (max safe int32)
317
- - Ensures cross-platform interoperability
318
- - Throws `VersionParseError` if exceeded
319
-
320
- **Input Sanitization:**
321
-
322
- - Comprehensive validation rejects malformed input early
323
- - Structured errors for debugging
324
-
325
- ## Version Format
326
-
327
- - **Generic numeric versions:** `major[.minor][.patch]`
328
- - **Missing components default to 0:** `"2"` → `{ major: 2, minor: 0, patch: 0 }`
329
- - **Whitespace is trimmed:** `" 2.5.1 "` → `"2.5.1"`
330
- - **No prerelease/build metadata**
331
- - **No wildcards, hyphen ranges, or `^`/`~` operators**
332
-
333
- ## Range Syntax
334
-
335
- **Operators:**
336
-
337
- - `=` or omitted - Exact match
338
- - `<` - Less than
339
- - `<=` - Less than or equal
340
- - `>` - Greater than
341
- - `>=` - Greater than or equal
342
-
343
- **Multiple comparators:**
344
-
345
- - Space-separated for AND logic: `">=2.3 <3.0"`
346
- - All comparators must be satisfied
347
-
348
- **Examples:**
258
+ Multiple comparators are space-separated with AND semantics — all must be satisfied:
349
259
 
350
260
  ```typescript
351
261
  "2.5.1"; // Exact match
@@ -354,26 +264,14 @@ Built-in protections against common vulnerabilities:
354
264
  ">=2.3 <2.6 >2.4"; // Multiple AND conditions
355
265
  ```
356
266
 
357
- ## Contributing
358
-
359
- We welcome contributions! Please see our [Contributing Guide][github-contributing] for more details.
360
-
361
- 1. Fork the repository
362
- 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
363
- 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
364
- 4. Push to the branch (`git push origin feature/amazing-feature`)
365
- 5. Open a Pull Request
366
-
367
- ## Code of Conduct
368
-
369
- To ensure a welcoming and positive environment, we have a [Code of Conduct][github-code-of-conduct] that all contributors and participants are expected to adhere to.
267
+ ### Input limits
370
268
 
371
- ## License
269
+ - Versions are capped at 100 characters; ranges at 1000 characters. Longer inputs throw `VersionParseError` or `RangeParseError`.
270
+ - Version components are capped at 2³¹−1; overflow throws `VersionParseError`.
372
271
 
373
- Copyright 2025 Rethink Health, SUARL. All rights reserved.
272
+ ## Part of Glion
374
273
 
375
- This program is licensed to you under the terms of the [MIT License](https://opensource.org/licenses/MIT). This program is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the [LICENSE][github-license] file for details.
274
+ `@glion/util-semver` is part of **[Glion]**, the application framework for HL7v2. See the [Glion README] for the full package catalog and architecture.
376
275
 
377
- [github-code-of-conduct]: https://github.com/rethinkhealth/glion/blob/main/CODE_OF_CONDUCT.md
378
- [github-license]: https://github.com/rethinkhealth/glion/blob/main/LICENSE
379
- [github-contributing]: https://github.com/rethinkhealth/glion/blob/main/CONTRIBUTING.md
276
+ [Glion]: https://github.com/rethinkhealth/glion#readme
277
+ [Glion README]: https://github.com/rethinkhealth/glion#readme
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glion/util-semver",
3
- "version": "0.15.1",
3
+ "version": "0.15.2",
4
4
  "description": "Tiny, fast HL7v2 version and range utilities (basic comparators only)",
5
5
  "keywords": [
6
6
  "health",
@@ -35,11 +35,13 @@
35
35
  "devDependencies": {
36
36
  "@types/node": "^25.5.0",
37
37
  "@vitest/coverage-v8": "4.1.2",
38
+ "publint": "0.3.18",
38
39
  "tsdown": "0.21.7",
39
40
  "typescript": "^5.9.3",
40
41
  "vitest": "4.1.2",
41
- "@glion/testing": "0.15.1",
42
- "@glion/tsconfig": "0.15.1"
42
+ "@glion/testing": "0.15.2",
43
+ "@glion/check-readme": "0.15.2",
44
+ "@glion/tsconfig": "0.15.2"
43
45
  },
44
46
  "engines": {
45
47
  "node": ">=18"
@@ -48,8 +50,10 @@
48
50
  "scripts": {
49
51
  "build": "tsdown && tsc --emitDeclarationOnly",
50
52
  "check-types": "tsc --noEmit",
53
+ "check:readme": "glion-check-readme",
51
54
  "test": "vitest run",
52
55
  "test:coverage": "vitest run --coverage",
53
- "test:watch": "vitest"
56
+ "test:watch": "vitest",
57
+ "publint": "publint --strict"
54
58
  }
55
59
  }