@cosyte/x12 0.0.17 → 0.1.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/CHANGELOG.md CHANGED
@@ -7,8 +7,150 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ **What 0.1 means for you.** This release is the first whose public API we treat as settled: the
11
+ exported readers, builders, types and warning codes are the surface we keep stable, and the warning
12
+ registry only ever gains codes. It covers typed read and typed emit for 270, 271, 276, 277 and
13
+ 277CA, 278 request and response, 820, 834, 835, 837P, 837I and 837D, 999 and TA1, plus the 277
14
+ request for additional information (`006020X313`) and the 275 that answers it (`006020X314`) at the
15
+ base 006020 structure; every amount as an exact `X12Decimal`; and a lenient parser whose only fatal
16
+ failures are the four structural ones. While the package is below 1.0, a breaking change bumps the
17
+ minor version and is called out here with its migration; a fix that changes no public value ships
18
+ as a patch. Upgrading from 0.0.x is itself breaking in five places, each described in the entries
19
+ below: `CodeListMeta`, `X12Eligibility` and `X12ProfileDescription` gain a required member,
20
+ `X12ClaimStatusResponse` gains a third `transactionType`, and `build278Response` declares
21
+ `005010X217`. Not covered yet: a byte-exact round trip in general (`KNOWN-LIMITATIONS.md` lists the
22
+ cases), implementation-guide usage checks and attachment decoding for the two 006020 guides,
23
+ non-healthcare sets, EDIFACT, transport, revisions before 005010, and any bundled CORE Code
24
+ Combinations table.
25
+
10
26
  ### Added
11
27
 
28
+ - **`toObject`, `toISO` and `toDate`: one date conversion surface, shared with every `@cosyte/*`
29
+ parser.** Three new package-root exports read a date off any typed reader, plus `DateParts`,
30
+ `ToDateOptions` and `X12DateValue`. Purely additive: no exported name is removed, renamed or
31
+ altered, `parseDocumentDate` keeps its own contract including its throwing one, no parse and no
32
+ emit behaviour moves, and there is still not a single runtime dependency.
33
+
34
+ The parameter is structural, so all eight `X12*Date` types are accepted through the same three
35
+ names with no per-transaction variant. What is decoded is exactly what this package already parses
36
+ or builds and nothing more: `D8`, a single `CCYYMMDD` day, and `RD8`, a `CCYYMMDD-CCYYMMDD` range.
37
+ `D8` converts to `{ year, month, day }` with `month` spec-native 1 to 12. Everything else answers
38
+ `undefined` and nothing ever throws: a qualifier outside that pair, an absent one (which is how
39
+ `X12PremiumDate` and `X12EnrollmentDate` reach this surface, since neither surfaces DTP-02 or
40
+ DTM-03 at all), a value whose digits do not match the shape its qualifier declares, and a day that
41
+ is not on the calendar.
42
+
43
+ **`RD8` converts to `undefined` from all three, deliberately.** An interval is not a point in
44
+ time; returning the first endpoint would be a quiet wrong answer with a right-looking shape.
45
+ **`toDate` returns an instant only when the caller states the zone**, through
46
+ `assumeOffsetMinutes`: no X12 date element decoded here carries a UTC offset, the host machine's
47
+ timezone is never read, and UTC is never assumed. A four-digit year below 100 stays that year.
48
+
49
+ Held by `test/conversion-surface.test.ts`, the same eleven-row case table every sibling parser
50
+ carries, five of whose rows are recorded as skips with a written reason and a live test measuring
51
+ the property behind each.
52
+
53
+ - **`pnpm check:no-internal-refs` - a gate on internal project bookkeeping reaching a consumer**
54
+ (`X12-NO-INTERNAL-REFS-GATE`), with `.github/workflows/no-internal-refs.yml` running it on every
55
+ push and PR. Documentation and tooling only: the `src/` half of the diff is comment-only and
56
+ `dist/index.mjs` / `dist/index.cjs` are byte-identical base to head.
57
+
58
+ It scans `README.md`, `TRADEMARKS.md`, `LICENSE`, `KNOWN-LIMITATIONS.md`, `docs-content/`, the npm
59
+ `description` and `keywords`, and the `/** */` doc comments under `src/` (which tsup compiles into
60
+ `dist/index.d.ts` and `dist/index.d.cts`), against six rules: item identifiers, phase and wave
61
+ language, ADR references, "slice" as our word for a unit of work, meta-repo paths, and bracketed
62
+ traceability markers. Every rule runs line by line and again over paragraph-joined text, because
63
+ this repo hard-wraps and a multi-token violation that straddles a wrap is invisible to a line
64
+ scan: on the sweep this shipped with, 52 of 124 phase-rule hits and 29 of 76 identifier hits in
65
+ `src/` were visible only to the reflowed pass. `CHANGELOG.md`, `.changeset/`, `CLAUDE.md`,
66
+ `documentation/`, `scripts/`, `test/` and `//` comments are deliberately out of scope: the
67
+ convention names those as where the identifiers belong.
68
+
69
+ **It is derived from the sibling parsers' copy, not transcribed from it.** Every sibling excludes
70
+ `X12-\d{3}[A-Z]?` and `X12-\d{6}` as standards designations to protect. In this repo `X12` is also
71
+ our own project prefix. Counted here with two independent tools that agreed exactly, that pattern
72
+ matches 141 times across tracked files and **every one of the 141 is an internal identifier**, in
73
+ eight spellings, with no standards designation among them; the hyphenated standards spelling it
74
+ exists to protect occurs zero times, because this corpus writes transaction sets and guides bare
75
+ (`837P`, `005010X222A1`). Carrying the sibling line verbatim reported OK over six live violations
76
+ on shipping carriers. Both the item spellings and the hyphenated forms are now asserted
77
+ individually in the self-test, each on its own, after a control run showed a shared positive
78
+ sample was satisfied by its first entry and covered none of the rest.
79
+
80
+ `KNOWN-LIMITATIONS.md` is scanned and is the entry no sibling copy has: this package ships it in
81
+ `files`, and it held most of what the gate first found.
82
+
83
+ **What it cannot do**, stated so a green run is not over-read: it reads the `src/` doc comments
84
+ that are the SOURCE of the published declarations, never `dist/` itself, which is untracked build
85
+ output this script does not build. It catches identifiers and named patterns, not ordinary English
86
+ sentences about how the software came to exist, so the reviewer still owns half the rule. It reads
87
+ file contents and never file names. It is not yet a required check on this repo, so it reds
88
+ visibly without blocking a merge.
89
+
90
+ ### Changed
91
+
92
+ - **`build278Response` declares `005010X217` in GS-08 and ST-03, where it declared `005010X216`.**
93
+ This changes what goes on the wire: a trading partner that routed a built 278 response on either
94
+ element sees the new value. `005010X217` is the one guide 45 CFR 162.920 names for the 278 in both
95
+ directions and the one `build278Request` already wrote; `005010X216` is the 278 notification
96
+ guide, so a receiver routing on it could file a prior authorization decision as an admission or
97
+ discharge notification. Only those two identifiers move: the 278 body is unchanged, and so is the
98
+ read side, where a 278 declaring `005010X216` still carries `X12_GUIDE_NOT_IMPLEMENTED` on both
99
+ 278 readers. The builder's own output now reads back through `get278Response` with no
100
+ guide-mismatch warning, and the 278 response row of `X12_TR3_CONFORMANCE` records no divergence:
101
+ its `note` is `null`, like the request row's.
102
+
103
+ - **`WARNING_MESSAGES.X12_PRE_005010` stops asserting what ISA-12 declares.** The message a consumer
104
+ reads off `w.message` said _"ISA-12 declares a version other than the HIPAA baseline `00501`, so
105
+ the input may diverge from 005010 semantics. The declared version is preserved verbatim on the
106
+ model."_ The guard behind it tests the twelfth element of the ISA split, so both the assertion
107
+ about ISA-12 and the phrase "the declared version" presuppose a header that framed. **This is a
108
+ runtime value, not a comment: `dist/index.mjs` and `dist/index.cjs` change here, and that is the
109
+ difference from the comment-only slice above.** No guard, no code, no position and no control flow
110
+ moved: the same interchanges raise the same codes at the same positions as before.
111
+
112
+ Measured on one interchange per row, reading ISA-12 at its own fixed byte offset and
113
+ again off the split:
114
+
115
+ ```text
116
+ construction ISA-12 at its fixed offset elements[12] X12_PRE_005010 other codes
117
+ spec-clean "00501" "00501" silent -
118
+ ISA-12 declares 00401 "00401" "00401" FIRES -
119
+ ISA-05 carries `*` "00501" "^" FIRES X12_ISA_EXTRA_ELEMENT_SEPARATOR, X12_CONTROL_NUMBER_MISMATCH
120
+ ISA-06 carries `*` "00501" "^" FIRES X12_ISA_EXTRA_ELEMENT_SEPARATOR, X12_CONTROL_NUMBER_MISMATCH
121
+ ISA-08 carries `*` "00501" "^" FIRES X12_ISA_EXTRA_ELEMENT_SEPARATOR, X12_CONTROL_NUMBER_MISMATCH
122
+ ISA-13 carries `*` "00501" "00501" silent X12_ISA_EXTRA_ELEMENT_SEPARATOR
123
+ ```
124
+
125
+ The replacement names the element the guard read, states that raising the code does not establish
126
+ that the header split into `ISA` plus 16 elements, and points at `isa.raw`. It echoes no value, so
127
+ the message stays a static table lookup with no consumer bytes in it. `pre005010`'s docblock twin
128
+ of the same clause moves with it, which is why the two were left in agreement rather than
129
+ corrected one at a time. The shipped troubleshooting table carried a third spelling of the same
130
+ assertion ("ISA-12 declares a version family other than `00501`") and is corrected too.
131
+
132
+ - **Internal project bookkeeping removed from the markdown surface, the npm metadata and the
133
+ shipped doc comments.** Deliberately not "from every surface a consumer reads": source string
134
+ literals are outside this gate, and five bundled code-list snapshots still carry build-order
135
+ framing in an exported `note` field, which reaches `dist/index.mjs` and `dist/index.cjs` as a
136
+ runtime value. That is a different carrier with a different review path, it reproduces on the base,
137
+ and it is filed rather than folded in here. Item identifiers, "phase" build-order framing and
138
+ "slice" as a unit of work were translated into what the software does and what changed, in
139
+ `KNOWN-LIMITATIONS.md`, `docs-content/cookbook.md`, `docs-content/troubleshooting.md` and the doc
140
+ comments across `src/`. Nothing was deleted to
141
+ satisfy the gate: a doc comment removed rather than reworded would be a regression, since JSDoc
142
+ with `@example` on every public export is a guardrail neither lint nor coverage protects. Two
143
+ pages sent the reader to the package's `CLAUDE.md`, which the tarball does not ship; they now
144
+ point at `README.md` and the Cookbook. Measured on a local build of base commit `a3e081d`,
145
+ `dist/index.d.ts` carried 13 lines of item identifiers, 64 of phase and wave framing, 2 of
146
+ "slice" jargon and 1 meta-repo path, with `dist/index.d.cts` byte-identical to it; at head all
147
+ four counts are zero and the two twins remain byte-identical to each other. Zero on the rules is
148
+ the weaker statement: six lines of phase framing survived the first sweep while every rule read
149
+ zero, four ending in punctuation and two using the PLURAL `Phases`, which rule 2 cannot see because
150
+ it requires `phase` followed by an alphanumeric. All six were then translated, so the built
151
+ declaration file now contains no occurrence of the word at all. The plural form remains ungated and
152
+ is disclosed as such.
153
+
12
154
  - **🩺 `X12_ISA_EXTRA_ELEMENT_SEPARATOR` - the ISA element split now has an arity check**
13
155
  (`X12-ISA-ELEMENT-ARITY`). Additions-only: no decoding moved, no existing warning was suppressed or
14
156
  narrowed, and nothing is re-framed.
@@ -71,6 +213,49 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
71
213
 
72
214
  ### Documentation
73
215
 
216
+ - **`IsaSegment` and the `X12_PRE_005010` warning factory no longer document a raw ISA element as the
217
+ value it holds** (`X12-ISA-VALUE-POINTERS`). **Documentation only: the diff is comment-only, and
218
+ `dist/index.mjs` and `dist/index.cjs` are byte-identical before and after, so no behaviour moved.**
219
+ The corrected text ships in `dist/index.d.ts` and `dist/index.d.cts`.
220
+
221
+ `isa.elements` holds raw byte text. `IsaSegment` called it "the 16 ISA values" and its `@example`
222
+ handed out `isa.elements[12]; // ISA-12 - version, expected "00501"` and
223
+ `isa.elements[13]; // ISA-13 - interchange control number`, while `pre005010`'s JSDoc said "The
224
+ declared version stays on `isa.elements[12]`". Measured on 005010 interchanges, one per row, plus
225
+ two rows this library emitted and read back:
226
+
227
+ ```text
228
+ construction cell stated / at that ISA offset elements[n] warnings
229
+ spec-clean elements[12] "00501" "00501" []
230
+ spec-clean elements[13] "000000001" "000000001" []
231
+ ISA-12 declares 00401 elements[12] "00401" "00401" X12_PRE_005010
232
+ ISA-06 carries `*` elements[12] "00501" "^" X12_ISA_EXTRA_ELEMENT_SEPARATOR, X12_PRE_005010, X12_CONTROL_NUMBER_MISMATCH
233
+ ISA-06 carries `*` elements[13] "000000001" "00501" X12_ISA_EXTRA_ELEMENT_SEPARATOR, X12_PRE_005010, X12_CONTROL_NUMBER_MISMATCH
234
+ ISA-13 carries `*` elements[12] "00501" "00501" X12_ISA_EXTRA_ELEMENT_SEPARATOR
235
+ ISA-13 carries `*` elements[13] "0000*0001" "0000" X12_ISA_EXTRA_ELEMENT_SEPARATOR
236
+ ISA-13 carries `?` elements[13] "00000001?" "00000001?" X12_CONTROL_NUMBER_MISMATCH
237
+ this library's own emit, stated " " elements[13] " " "00000000 " []
238
+ this library's own emit, stated "1" elements[13] "1" "000000001" []
239
+ ```
240
+
241
+ On the `ISA-06` rows the interchange declares `00501` at ISA-12's own fixed offset,
242
+ `X12_PRE_005010` fires anyway, and the sentence the consumer was holding sent them to an element
243
+ that answers `"^"`. On the `ISA-13 carries ?` row the `?` is content, because the ISA split is
244
+ deliberately not release-aware; its `X12_CONTROL_NUMBER_MISMATCH` is the fixture and not a finding.
245
+
246
+ **Two mechanisms falsify a cell on their own** - fixed-width padding, which needs no `?` and no
247
+ anomaly at all, and the arity displacement `X12_ISA_EXTRA_ELEMENT_SEPARATOR` reports - **so no
248
+ mechanism is named as the reason, the set of them is not published as closed, and nothing here says
249
+ which ISA element is special.**
250
+
251
+ The `pre005010` pointer is deleted outright, with nothing put back. `IsaSegment` gains the raw
252
+ label, keeps ISA-12's and ISA-13's semantics restated at segment level where they are true, and its
253
+ `@example` keeps one indexed pointer plus `.raw`, the same shape the four sibling envelope types
254
+ already carry. **The label is NOT the siblings' label**: theirs reads "pre-`?`-unescape", which is
255
+ false on the ISA. **The 1-indexed mapping onto ISA-01..ISA-16 is scoped to a 17-entry split rather
256
+ than deleted, and no shift is quantified.** No accessor, guard or test was added, and no route is
257
+ named as the route for reading an ISA element.
258
+
74
259
  - **`IeaSegment`, `GsSegment`, `GeSegment`, `Ta1Segment` and `X12FunctionalGroup` no longer document
75
260
  a raw envelope element as the value it holds** (`X12-ENVELOPE-VALUE-EXAMPLES`). **Documentation
76
261
  only: the diff is comment-only, and `dist/index.mjs` and `dist/index.cjs` are byte-identical
@@ -108,9 +293,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
108
293
  fields rather than about a raw element. No accessor, guard or test was added, and **no route is
109
294
  named as the route.**
110
295
 
111
- **The `IsaSegment` block is deliberately unchanged**, and no rule about the ISA is stated here in
112
- either direction.
113
-
114
296
  - **The documentation carriers that told you to read an envelope element's logical value through
115
297
  `getSegmentValue` are corrected** (`X12-ENVELOPE-VALUE-ROUTES`). **Documentation and tests only: no
116
298
  runtime line changed and the emitted JS is byte-identical, so no behaviour moved.**
@@ -210,6 +392,46 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
210
392
 
211
393
  ### Fixed
212
394
 
395
+ - **🩺 A BDS or BIN segment's binary data is framed by the octet count its length element declares,
396
+ not by delimiters.** A BDS-03 or BIN-02 carrying the segment terminator used to end the segment
397
+ inside the data, and one carrying the element or component separator was split, so an attachment
398
+ came back in fragments and every segment after it was mis-framed. The data element now holds
399
+ exactly the declared octets whatever bytes they are, `getSegmentValue` / `getAllSegmentValues` /
400
+ `elementValue` return it verbatim, and `serializeX12` writes the segment back unchanged in both
401
+ modes. Four warning codes are added, additions only, each value-free and anchored at the segment:
402
+ **`X12_BINARY_DATA_TRUNCATED`**, **`X12_BINARY_LENGTH_INVALID`** (no length is inferred; the
403
+ segment is framed by its delimiters), **`X12_BINARY_LENGTH_MISMATCH`** (the data is the declared
404
+ span and the bytes after it stay on `raw`) and **`X12_BINARY_LENGTH_UNVERIFIABLE`** (a string span
405
+ holding a character above U+00FF; a `Buffer` never raises it). The length element is never
406
+ rewritten, and BDS-01's filter is not applied.
407
+
408
+ - **🩺 Every typed reader now checks the guide a transaction set declares, and warns when it is not
409
+ one that reader implements** (`X12-17`). Each reader used to admit a transaction set on ST-01
410
+ alone, so a 277 declaring `006020X313` (a claim request for additional information) came back
411
+ from `get277Status` labelled `claim-status`, and a 004010, 006020 or 008020 document of any type
412
+ was decoded against 005010 positions with nothing on the warning channel.
413
+
414
+ Two warning codes are added, additions only: **`X12_GUIDE_NOT_IMPLEMENTED`** where the declared
415
+ guide is outside the reader's set, and **`X12_GUIDE_NOT_DECLARED`** where nothing is declared.
416
+ Both are anchored at the ST, both messages are frozen literals that echo nothing, and their
417
+ factories `guideNotImplemented` / `guideNotDeclared` take a position only. The declaration is
418
+ ST-03, decoded of release escapes; **where ST-03 is absent or empty, GS-08 of the enclosing group,
419
+ decoded the same way**, which a parsed `X12TransactionSet` now carries on an optional `gs` member so
420
+ every reader keeps its `(delimiters, tx)` call. Where ST-03 is non-empty it alone decides, and a
421
+ disagreement with GS-08 is not reconciled. The implemented set is derived from
422
+ `X12_TR3_CONFORMANCE` (`tr3` plus `cfrAdopted` per row), plus every identifier the 837 variant
423
+ table resolves. The reading is still decoded and returned; nothing is refused, dropped or
424
+ re-decoded, and a document declaring an implemented guide reads exactly as before.
425
+
426
+ **Three things move for a consumer.** `X12ClaimStatusResponse.transactionType` widens to
427
+ `"claim-status" | "claim-acknowledgment" | "unrecognized-guide"`, the third set where
428
+ `get277Status`'s declared guide is outside its set or absent, **so an exhaustive `switch` over the
429
+ old two values stops compiling**; `get277CADisposition` admits what it admitted before. **A 278
430
+ declaring `005010X216` now warns on both 278 readers**, since the regulation names `005010X217` for
431
+ both directions, and `build278Response` still writes `005010X216`, so its round trip carries the
432
+ code until that builder moves. A 999 declaring the base `005010X231` warns too, because the
433
+ conformance row names only the errata.
434
+
213
435
  - **🩺 `implementationConventionReference` is POST-`?`-unescape in every typed reader that publishes
214
436
  it** (`X12-ST03-READ-NOT-RELEASE-AWARE`). A behaviour change on any document whose `ST-03` carries
215
437
  a release escape.
@@ -3403,10 +3625,14 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
3403
3625
  result types. Six synthetic fixtures + unit tests + byte-flip fuzz
3404
3626
  (never-throw outside the 4 Tier-3 fatals) across every Phase 6
3405
3627
  fixture.
3406
- - **Known limitations (deferred):** AAA request-validation segments,
3407
- HSD detail, and III / LS / LE markers in the 271, plus QTY / AMT
3408
- claim-summary roll-ups in a 277CA Loop 2200, are preserved on
3409
- `tx.segments` verbatim but not yet typed onto the model.
3628
+ - **Known limitations recorded for this release:** AAA
3629
+ request-validation segments, HSD detail, and III / LS / LE markers in
3630
+ the 271, plus QTY / AMT claim-summary roll-ups in a 277CA Loop 2200,
3631
+ are preserved on `tx.segments` verbatim, and this release carried
3632
+ none of them onto the typed model. The 271 AAA half of that list is
3633
+ CLOSED by a later release, which surfaces every AAA
3634
+ request-validation segment on `X12Eligibility.aaaConditions` with its
3635
+ level and both codes; the rest of the list stands.
3410
3636
  - **Phase 5: 837 Healthcare Claim, TR3s `005010X222A2` (Professional),
3411
3637
  `005010X223A3` (Institutional), `005010X224A2` (Dental).** The
3412
3638
  claim-creation surface: the volume side of HIPAA EDI traffic.