@cosyte/terminology 0.0.6 → 0.0.7
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/CHANGELOG.md +61 -0
- package/README.md +4 -1
- package/dist/index.cjs +24 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -6
- package/dist/index.d.ts +12 -6
- package/dist/index.mjs +24 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -25,6 +25,67 @@ this file is maintained by hand (Changesets handles the version bump and publish
|
|
|
25
25
|
|
|
26
26
|
### Fixed
|
|
27
27
|
|
|
28
|
+
- **The UCUM unit table `loadUcumEssence()` hands out was `readonly` to TypeScript only, so a unit's
|
|
29
|
+
definition could be rewritten in place — and doing so fabricated a unit equivalence that outlived
|
|
30
|
+
putting the table back** (`TERMINOLOGY-MUTABLE-ESSENCE-TABLE`). One table is shared by every caller
|
|
31
|
+
and by the engine's own reducer, `essence.ts` froze nothing, and `reduce` memoizes each atom's
|
|
32
|
+
reduction against the atom object. So corrupting a loaded atom **before that atom's first
|
|
33
|
+
reduction** wrote the memo, and the reading survived restoring the table exactly as shipped.
|
|
34
|
+
|
|
35
|
+
Measured on `bf153cb`, and live on published `0.0.5` and `0.0.6` alike — `0.0.6` is that commit plus
|
|
36
|
+
a version bump. Defining the loaded litre as `1` with no prior
|
|
37
|
+
touch of `L`: `ucumEqual("L", "1")` and `ucumEqual("mg/L", "mg")` both answered `true` where the
|
|
38
|
+
same calls without it answered `false`, and `mmol/L` fell from `6.0221407599999985e+23 × m-3` to a
|
|
39
|
+
dimensionless `6.02214076e+20` — **a concentration comparing equal to a mass**. Two further routes
|
|
40
|
+
to the same readings on that build: writing the nested definition object in place
|
|
41
|
+
(`atom.value.unit = "1"`), and replacing an element of the `atoms` array, which is the one
|
|
42
|
+
`parseUcum` scans, so it hands the parser a forged atom without touching the memo at all.
|
|
43
|
+
|
|
44
|
+
This is the half that keying the memo on the atom **object** did not reach. That closed the route
|
|
45
|
+
where the atom was one you had _built_; this is the route where the atom is one you were _handed_,
|
|
46
|
+
and the consequence class is identical. Neither substitutes for the other.
|
|
47
|
+
|
|
48
|
+
The table is frozen when it is parsed, deeply: every atom, every atom's definition object, every
|
|
49
|
+
prefix, and both arrays. Shallow would have been worth nothing — freezing the atom alone leaves
|
|
50
|
+
`atom.value.unit`, which reproduces all three readings. `atomByCode` and `prefixByCode` refuse
|
|
51
|
+
`set`, `delete` and `clear`, which `Object.freeze` cannot do for a `Map`. `parseEssence` freezes
|
|
52
|
+
what it returns on the same rule.
|
|
53
|
+
|
|
54
|
+
**The guarantee is that the table does not change, not that a write throws.** `Object.freeze`
|
|
55
|
+
refuses a write by throwing in strict mode and ignoring it in sloppy mode, and this package ships a
|
|
56
|
+
CJS build a sloppy-mode caller can require; the table is the one that was parsed either way. The
|
|
57
|
+
tests assert the readings, and the `TypeError` only as how a strict-mode caller observes it.
|
|
58
|
+
|
|
59
|
+
**Two things this does not claim, both measured rather than reasoned.** `Map.prototype.set.call()`
|
|
60
|
+
can still force an entry into a lookup map — no `Map` can be made immutable in place — and what
|
|
61
|
+
that reaches is bounded and pinned in both directions: `parseUcum` resolves an atom by scanning
|
|
62
|
+
`atoms`, never through `atomByCode`, so a forced entry changes **no** reduction and no `ucumEqual`
|
|
63
|
+
answer, only what the caller's own `get` returns. And the values `parseUcum` and `reduce` hand back
|
|
64
|
+
are deliberately still per-call and mutable.
|
|
65
|
+
|
|
66
|
+
**No reduction changes.** 3,080 expressions built from the table — every atom bare, squared,
|
|
67
|
+
inverted and annotated; every prefix over rotating metric atoms; rotating ratios, products and
|
|
68
|
+
grouped powers; and the shapes the README and the tests name — plus 312 `ucumEqual` pairs, reduce
|
|
69
|
+
byte-identically on `bf153cb` and on this tree (3,393 output lines, one sha256).
|
|
70
|
+
|
|
71
|
+
One knock-on, stated because getting it wrong is easy: freezing the table removes **a** route to
|
|
72
|
+
`reduce`'s cyclic-definition guard, not the last one. Two routes that look like they should reach
|
|
73
|
+
it do not, and both are now pinned as tests — naming a table atom in a definition (it is resolved
|
|
74
|
+
against the loaded table, so your atom can _name_ one but never _be_ one; a self-referential `Pa`
|
|
75
|
+
taken off a table copy resolves to the shipped pascal and answers `1000 × g.m-1.s-2`), and
|
|
76
|
+
corrupting a loaded atom, which is what is now refused. The route that does reach it needs no table
|
|
77
|
+
at all: `UcumAtom.value` is `readonly` to TypeScript, which an **accessor** satisfies, and the
|
|
78
|
+
definition is read after the atom is marked in progress, so a getter can re-enter `reduce` for the
|
|
79
|
+
same atom. That is a supported public call, and the guard is pinned by it under a 100,000-byte atom
|
|
80
|
+
code — so all three of `reduce`'s messages are again asserted whole, against both `message` and
|
|
81
|
+
`err.stack`, with the caller's own code as the unbounded value in each.
|
|
82
|
+
|
|
83
|
+
Also here: the `/* v8 ignore next */` on `reduce`'s `atom.base` line is removed. It justified
|
|
84
|
+
itself with a property of the bundled table — every base atom there carries a `dim` — for a line
|
|
85
|
+
reachable through the exported surface with a caller-built `{ base: true }` atom that carries none.
|
|
86
|
+
That arm is answered on the atom's own code, is now covered by a test rather than excluded, and
|
|
87
|
+
changes nothing for the table's own base units.
|
|
88
|
+
|
|
28
89
|
- **A `UnitNode` you assembled yourself could change how `reduce` and `ucumEqual` answer for a unit
|
|
29
90
|
parsed out of the bundled UCUM table, for the rest of the process**
|
|
30
91
|
(`TERMINOLOGY-ATOMMEMO-POISONING`). `reduce` is exported and takes a caller-built node, and
|
package/README.md
CHANGED
|
@@ -208,7 +208,10 @@ _names_, listed under [What is bundled](#what-is-bundled).
|
|
|
208
208
|
NDC you asked about, so log the `code` and the locus rather than the whole result — a code _in
|
|
209
209
|
patient context_ can be PHI.
|
|
210
210
|
- **Zero runtime dependencies. Dual ESM + CJS.** Node stdlib only; built with `tsup`, validated with
|
|
211
|
-
`attw`.
|
|
211
|
+
`attw`. The models the engine loads for you are frozen — the bundled unit table deeply so, atom by
|
|
212
|
+
atom and definition by definition, because it is shared with the engine's own reducer. The AST and
|
|
213
|
+
the reduction that `parseUcum` and `reduce` hand back are per-call values you own, and mutating one
|
|
214
|
+
changes nothing for anyone else.
|
|
212
215
|
|
|
213
216
|
## What is bundled
|
|
214
217
|
|
package/dist/index.cjs
CHANGED
|
@@ -3733,7 +3733,29 @@ function parseEssence(xml) {
|
|
|
3733
3733
|
atoms.sort((a, b) => b.code.length - a.code.length);
|
|
3734
3734
|
const atomByCode = new Map(atoms.map((a) => [a.code, a]));
|
|
3735
3735
|
const prefixByCode = new Map(prefixes.map((p) => [p.code, p]));
|
|
3736
|
-
return { prefixes, atoms, atomByCode, prefixByCode };
|
|
3736
|
+
return freezeEssence({ prefixes, atoms, atomByCode, prefixByCode });
|
|
3737
|
+
}
|
|
3738
|
+
function refuseTableWrite() {
|
|
3739
|
+
throw new TypeError("the UCUM unit table is immutable");
|
|
3740
|
+
}
|
|
3741
|
+
function sealLookup(map) {
|
|
3742
|
+
return Object.defineProperties(map, {
|
|
3743
|
+
set: { value: refuseTableWrite },
|
|
3744
|
+
delete: { value: refuseTableWrite },
|
|
3745
|
+
clear: { value: refuseTableWrite }
|
|
3746
|
+
});
|
|
3747
|
+
}
|
|
3748
|
+
function freezeEssence(essence) {
|
|
3749
|
+
for (const atom of essence.atoms) {
|
|
3750
|
+
if (atom.value) Object.freeze(atom.value);
|
|
3751
|
+
Object.freeze(atom);
|
|
3752
|
+
}
|
|
3753
|
+
for (const prefix of essence.prefixes) Object.freeze(prefix);
|
|
3754
|
+
Object.freeze(essence.atoms);
|
|
3755
|
+
Object.freeze(essence.prefixes);
|
|
3756
|
+
sealLookup(essence.atomByCode);
|
|
3757
|
+
sealLookup(essence.prefixByCode);
|
|
3758
|
+
return Object.freeze(essence);
|
|
3737
3759
|
}
|
|
3738
3760
|
var cached;
|
|
3739
3761
|
function loadUcumEssence() {
|
|
@@ -4731,7 +4753,7 @@ function approximateMatch(graph, query, options = {}) {
|
|
|
4731
4753
|
}
|
|
4732
4754
|
|
|
4733
4755
|
// src/index.ts
|
|
4734
|
-
var VERSION = "0.0.
|
|
4756
|
+
var VERSION = "0.0.7";
|
|
4735
4757
|
|
|
4736
4758
|
exports.COMPLEX_MAP_SOURCE_SYSTEM = COMPLEX_MAP_SOURCE_SYSTEM;
|
|
4737
4759
|
exports.DIAGNOSTIC_CODES = DIAGNOSTIC_CODES;
|