@glion/util-semver 0.15.0 → 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.
- package/README.md +61 -163
- package/package.json +13 -5
package/README.md
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
# @glion/util-semver
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Parser, comparators, and range matcher for HL7v2-style numeric version strings.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## What it does
|
|
6
6
|
|
|
7
|
-
|
|
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
|
-
##
|
|
9
|
+
## Install
|
|
14
10
|
|
|
15
11
|
```bash
|
|
16
12
|
npm install @glion/util-semver
|
|
17
13
|
```
|
|
18
14
|
|
|
19
|
-
##
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
58
|
+
## API
|
|
59
59
|
|
|
60
|
-
### Parsing
|
|
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
|
-
|
|
72
|
+
Throws `VersionParseError` if the format is invalid.
|
|
73
73
|
|
|
74
74
|
#### `clean(version: string): string`
|
|
75
75
|
|
|
76
|
-
Converts a version to canonical
|
|
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
|
-
|
|
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
|
|
108
|
+
#### Comparison operators
|
|
109
109
|
|
|
110
|
-
- `eq(a, b)`
|
|
111
|
-
- `lt(a, b)`
|
|
112
|
-
- `lte(a, b)`
|
|
113
|
-
- `gt(a, b)`
|
|
114
|
-
- `gte(a, b)`
|
|
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
|
|
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
|
-
|
|
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`
|
|
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
|
|
160
|
+
### Collection operations
|
|
175
161
|
|
|
176
162
|
#### `sort(versions: string[]): string[]`
|
|
177
163
|
|
|
178
|
-
Sorts versions in ascending order. Returns new array
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
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
|
|
208
|
+
diff("2.5.1", "2.5.1"); // null
|
|
227
209
|
```
|
|
228
210
|
|
|
229
|
-
|
|
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);
|
|
251
|
-
console.log(e.reason);
|
|
229
|
+
console.log(e.token);
|
|
230
|
+
console.log(e.reason);
|
|
252
231
|
}
|
|
253
232
|
}
|
|
254
233
|
```
|
|
255
234
|
|
|
256
|
-
|
|
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
|
-
##
|
|
238
|
+
## Version comparison rules
|
|
265
239
|
|
|
266
|
-
###
|
|
240
|
+
### Version format
|
|
267
241
|
|
|
268
|
-
-
|
|
269
|
-
- `
|
|
270
|
-
-
|
|
271
|
-
-
|
|
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
|
-
###
|
|
248
|
+
### Range syntax
|
|
274
249
|
|
|
275
|
-
|
|
250
|
+
Operators:
|
|
276
251
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
272
|
+
## Part of Glion
|
|
374
273
|
|
|
375
|
-
|
|
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
|
-
[
|
|
378
|
-
[
|
|
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.
|
|
3
|
+
"version": "0.15.2",
|
|
4
4
|
"description": "Tiny, fast HL7v2 version and range utilities (basic comparators only)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"health",
|
|
@@ -16,7 +16,11 @@
|
|
|
16
16
|
"name": "Melek Somai",
|
|
17
17
|
"email": "melek@rethinkhealth.io"
|
|
18
18
|
},
|
|
19
|
-
"repository":
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/rethinkhealth/glion.git",
|
|
22
|
+
"directory": "packages/util-semver"
|
|
23
|
+
},
|
|
20
24
|
"files": [
|
|
21
25
|
"dist"
|
|
22
26
|
],
|
|
@@ -31,11 +35,13 @@
|
|
|
31
35
|
"devDependencies": {
|
|
32
36
|
"@types/node": "^25.5.0",
|
|
33
37
|
"@vitest/coverage-v8": "4.1.2",
|
|
38
|
+
"publint": "0.3.18",
|
|
34
39
|
"tsdown": "0.21.7",
|
|
35
40
|
"typescript": "^5.9.3",
|
|
36
41
|
"vitest": "4.1.2",
|
|
37
|
-
"@glion/testing": "0.15.
|
|
38
|
-
"@glion/
|
|
42
|
+
"@glion/testing": "0.15.2",
|
|
43
|
+
"@glion/check-readme": "0.15.2",
|
|
44
|
+
"@glion/tsconfig": "0.15.2"
|
|
39
45
|
},
|
|
40
46
|
"engines": {
|
|
41
47
|
"node": ">=18"
|
|
@@ -44,8 +50,10 @@
|
|
|
44
50
|
"scripts": {
|
|
45
51
|
"build": "tsdown && tsc --emitDeclarationOnly",
|
|
46
52
|
"check-types": "tsc --noEmit",
|
|
53
|
+
"check:readme": "glion-check-readme",
|
|
47
54
|
"test": "vitest run",
|
|
48
55
|
"test:coverage": "vitest run --coverage",
|
|
49
|
-
"test:watch": "vitest"
|
|
56
|
+
"test:watch": "vitest",
|
|
57
|
+
"publint": "publint --strict"
|
|
50
58
|
}
|
|
51
59
|
}
|