@cosyte/x12 0.0.4 → 0.0.5
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 +37 -0
- package/README.md +1 -1
- package/dist/index.cjs +57 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +138 -7
- package/dist/index.d.ts +138 -7
- package/dist/index.mjs +55 -27
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
### Added
|
|
11
11
|
|
|
12
|
+
- **`renderCallerValue`**, plus the `BUILD_REFUSAL_VALUE_MAX_LENGTH` (63) and
|
|
13
|
+
`BUILD_REFUSAL_VALUE_MAX_RENDERED` (90) bounds. This is the single sanctioned route a
|
|
14
|
+
caller-supplied value takes into a `build*` refusal message, and both ceilings are exported so a
|
|
15
|
+
consumer can assert them rather than take them on trust - the builder-side counterpart to
|
|
16
|
+
`ALL_WARNING_MESSAGES` on the parse side. `BUILD_REFUSAL_VALUE_MAX_RENDERED` bounds the rendered
|
|
17
|
+
**fragment**, not the whole message: a message is that plus the site's own fixed template text.
|
|
12
18
|
- **`X12OrphanSegment.anchor`**, plus the `X12OrphanAnchor` / `X12OrphanAnchorKind` types. Every
|
|
13
19
|
retained orphan now records **where it sat in the structure** rather than only where it sat in the
|
|
14
20
|
byte stream: `{ kind: "interchange", groupIndex }` for a segment outside every functional group,
|
|
@@ -28,6 +34,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
28
34
|
|
|
29
35
|
### Fixed
|
|
30
36
|
|
|
37
|
+
- **A `build*` refusal message no longer grows with the value you passed in.** All twenty-three
|
|
38
|
+
caller-value slots across the ten builder modules route through `renderCallerValue`, capping the
|
|
39
|
+
rendered fragment at 90 characters. Nine are the `control number "…" exceeds the N-char spec limit`
|
|
40
|
+
refusal, where the branch fires _because_ the value is over-long; seven had no length gate at all
|
|
41
|
+
(`build999`'s ST-02 trace twice, `buildInterchange`'s transaction-set id, `build837`'s service-line
|
|
42
|
+
variant, `build834`'s INS-03 and HD-01 maintenance types, `buildTA1`'s note code); and seven are in
|
|
43
|
+
`build999`, found by adversarial review rather than by the census - the AK9-02 / AK9-03 / AK9-04
|
|
44
|
+
counts, typed `number` but reachable with a string from a `JSON.parse`d spec at 120,063 characters,
|
|
45
|
+
and three `.length` reads on caller-supplied arrays that a forged `{ length: … }` drove to 120,152.
|
|
46
|
+
Measured: a 120,000-character control number produced a **120,066-character**
|
|
47
|
+
`X12BuildError.message` from `buildInterchange` and now produces a **150-character** one. (150, not
|
|
48
|
+
90: the 90 is the ceiling on the interpolated fragment, not on the message.) **This is robustness
|
|
49
|
+
and log hygiene, not redaction, and the docs now say so** - you passed the value in and still hold
|
|
50
|
+
it, so bounding it hides nothing from you; what it buys is a fixed ceiling on anything that reaches
|
|
51
|
+
a log line or a JSON error envelope. The surviving characters are bounded but **not escaped**, and
|
|
52
|
+
on the ack path the value is not always strictly your own (TR3 005010X231A1 has AK2-02 echo the
|
|
53
|
+
acknowledged ST-02 verbatim). `err.code` remains the thing to branch on.
|
|
54
|
+
- **A builder refusal handed a non-string where the types say `string` stays a typed error.**
|
|
55
|
+
`renderCallerValue` coerces rather than reading `.length` off whatever it is given, so a spec built
|
|
56
|
+
from `JSON.parse` that carries a numeric control number still raises `X12BuildError` with its
|
|
57
|
+
`code`, instead of an uncaught `TypeError` with none.
|
|
58
|
+
- **The 835 remit-total balance warning points at the BPR instead of at the ST.** Its
|
|
59
|
+
`position.segmentIndex` was a literal `0`, which reads like "no segment" but is not one:
|
|
60
|
+
`tx.segments[0]` is the `ST`, so a consumer resolving the position landed on a segment with nothing
|
|
61
|
+
to do with the invariant. It is now the BPR's own 1-based body index, so
|
|
62
|
+
`tx.segments[w.position.segmentIndex]` is the segment carrying the BPR-02 the equation compares
|
|
63
|
+
against, matching the treatment claim-level and service-line warnings already had. The only
|
|
64
|
+
remaining `0` is a transaction that carries no BPR at all. The corresponding position inside
|
|
65
|
+
`build835` stays synthetic and is now named and documented as such: the builder has no parsed
|
|
66
|
+
segment stream to index into, and it consumes only the warning's `message`, which is a
|
|
67
|
+
registry lookup keyed by the invariant and therefore position-independent.
|
|
31
68
|
- **A segment outside a transaction now survives a round trip, and so does its warning.**
|
|
32
69
|
`serializeX12` re-emits every entry of `ix.orphanSegments` at its structural `anchor`, so a
|
|
33
70
|
consumer who serializes an interchange and re-derives warnings from the copy no longer loses the
|
package/README.md
CHANGED
|
@@ -45,7 +45,7 @@ That's the pitch: no schema upload, no spec knowledge. The parser accepts vendor
|
|
|
45
45
|
- **Postel's Law**: a lenient parser (deviations → warnings with a stable code + positional context) and a conservative serializer. `serializeX12` is byte-faithful **for the segments on the model** by default: each comes back verbatim, including element padding, composites, and `?`-release escapes. **`serialize(parse(s)) === s` is not guaranteed in general.** Six constructs are known not to survive: line breaks between segments (any run of CR / LF between segments is absorbed at parse, so a pretty-printed or double-spaced file emits compact), a doubled terminator outside a transaction, a missing final terminator (the emit supplies one), post-IEA `trailingBytes` (re-joined, not verbatim), a TA1 that followed a functional group (emitted right after the ISA, so it is **reordered**, though nothing is lost), and a segment whose first element is empty outside a transaction (skipped entirely, with no warning at all). A **segment outside a transaction is not on that list**: it is reported as `X12_UNEXPECTED_SEGMENT`, kept on the model at `ix.orphanSegments` with a structural `anchor`, and re-emitted at that anchor, so the segment, its value and its warning all survive the round trip - placement is by the anchor and never by `segmentIndex`, which indexes the input stream the emit does not follow. The last five break the round trip on inputs with no line breaks at all, so do not treat "no line breaks" as sufficient; five of the six are silent, so a clean warnings list does not mean byte-exact either. Measured across this repo's 56 fixtures: every emit is a fixed point and re-parses to an identical model with an identical warning stream, the 14 with no line breaks return byte-identical, and the other 42 differ by line breaks and nothing else. See [Line endings between segments](./docs-content/spec-notes-envelope.md). `{ specClean: true }` reconciles the envelope, and `{ specClean: true, recomputeCounts: true }` also emits the corrected counts (`recomputeCounts` does nothing on its own). A mismatch is always warned, never silently corrected. Only **4 structural failures** are ever fatal.
|
|
46
46
|
- **Money is exact**: every monetary/percent/quantity field decodes as `X12Decimal` (string-backed, BigInt arithmetic). The library **never `parseFloat`s** an EDI amount.
|
|
47
47
|
- **Safety-critical fidelity**: TRN reassociation traces, 835 balance invariants, 837 HL hierarchy integrity, 834 maintenance types, and 278 certification actions are preserved **verbatim** and never inferred; ambiguity yields a warning or a typed refusal, never a confident wrong answer.
|
|
48
|
-
- **PHI-disciplined**: synthetic-only fixtures, a PHI commit-gate, and a warning `message` that is a **lookup into a frozen registry**, not something built from your document. No warning factory in the library takes a value parameter, so no element can reach a diagnostic: the code and `position` say what and where, and the bytes stay on the model. `ALL_WARNING_MESSAGES` is exported so you can assert it. **Builder refusals are a different, and weaker, surface:**
|
|
48
|
+
- **PHI-disciplined**: synthetic-only fixtures, a PHI commit-gate, and a warning `message` that is a **lookup into a frozen registry**, not something built from your document. No warning factory in the library takes a value parameter, so no element can reach a diagnostic: the code and `position` say what and where, and the bytes stay on the model. `ALL_WARNING_MESSAGES` is exported so you can assert it. **Builder refusals are a different, and deliberately weaker, surface:** twenty-three refusal sites across ten builder modules name a value you passed in, so you can see which control number, count or code was refused. Each goes through `renderCallerValue`, capping the rendered **fragment** at `BUILD_REFUSAL_VALUE_MAX_RENDERED` (90 characters); all three names are exported so you can assert the ceiling. The whole `message` is that plus the site's own fixed text, so it is bounded by a constant but a larger one: a 120,000-character control number gave a 120,066-character `X12BuildError.message` before this and gives 150 now. That is robustness, not redaction: the value is one you supplied and bounding it hides nothing from you. It is also not escaped, and on the **ack** path it is not always strictly your own, since `build999`'s AK2-02 and `buildTA1` echo an inbound document's control numbers by design. Log `err.code`, not `err.message`, from a builder. Tracked in [KNOWN-LIMITATIONS.md](./KNOWN-LIMITATIONS.md). **The one deliberate exception:** `X12ParseError.snippet` on the four Tier-3 structural fatals is a bounded (≤ 64 character) copy of the start of the input, so on real traffic it can carry PHI. The library does not redact it. Redact at your call site, or log `err.code` and `err.position` instead. See [Keeping PHI out of logs](./docs-content/troubleshooting.md).
|
|
49
49
|
|
|
50
50
|
See the [**Cookbook**](./docs-content/cookbook.md) for task-oriented recipes (post an 835, route 277CA rejections, round-trip a 271, walk an 837, read a 999) and [**KNOWN-LIMITATIONS.md**](./KNOWN-LIMITATIONS.md) for the honest do-not-over-trust list.
|
|
51
51
|
|
package/dist/index.cjs
CHANGED
|
@@ -1888,6 +1888,7 @@ function get835(delimiters, tx) {
|
|
|
1888
1888
|
const warnings = [];
|
|
1889
1889
|
const body = tx.se === void 0 ? tx.segments.slice(1) : tx.segments.slice(1, -1);
|
|
1890
1890
|
let payment = EMPTY_HEADER;
|
|
1891
|
+
let bprSegmentIndex = 0;
|
|
1891
1892
|
const traces = [];
|
|
1892
1893
|
let payer;
|
|
1893
1894
|
let payee;
|
|
@@ -1916,6 +1917,7 @@ function get835(delimiters, tx) {
|
|
|
1916
1917
|
switch (seg.id) {
|
|
1917
1918
|
case "BPR": {
|
|
1918
1919
|
payment = decodeBpr(seg, delimiters);
|
|
1920
|
+
bprSegmentIndex = position.segmentIndex;
|
|
1919
1921
|
break;
|
|
1920
1922
|
}
|
|
1921
1923
|
case "TRN": {
|
|
@@ -2083,7 +2085,7 @@ function get835(delimiters, tx) {
|
|
|
2083
2085
|
payment.totalActualPayment,
|
|
2084
2086
|
finalClaims,
|
|
2085
2087
|
providerAdjustments,
|
|
2086
|
-
{ segmentIndex:
|
|
2088
|
+
{ segmentIndex: bprSegmentIndex, transactionIndex: 0 }
|
|
2087
2089
|
);
|
|
2088
2090
|
if (remitTotalWarn !== void 0) warnings.push(remitTotalWarn);
|
|
2089
2091
|
return Object.freeze({
|
|
@@ -2444,10 +2446,32 @@ var Remit835BuildError = class extends Error {
|
|
|
2444
2446
|
}
|
|
2445
2447
|
};
|
|
2446
2448
|
|
|
2449
|
+
// src/builder/caller-value.ts
|
|
2450
|
+
var BUILD_REFUSAL_VALUE_MAX_LENGTH = 63;
|
|
2451
|
+
var MAX_LENGTH_DIGITS = 10;
|
|
2452
|
+
var BUILD_REFUSAL_VALUE_MAX_RENDERED = BUILD_REFUSAL_VALUE_MAX_LENGTH + 2 + 1 + " ( characters)".length + MAX_LENGTH_DIGITS;
|
|
2453
|
+
function renderCallerValue(value) {
|
|
2454
|
+
const text = coerce(value);
|
|
2455
|
+
if (text.length <= BUILD_REFUSAL_VALUE_MAX_LENGTH) return `"${text}"`;
|
|
2456
|
+
return `"${text.slice(0, BUILD_REFUSAL_VALUE_MAX_LENGTH)}\u2026" (${String(text.length)} characters)`;
|
|
2457
|
+
}
|
|
2458
|
+
function coerce(value) {
|
|
2459
|
+
if (typeof value === "string") return value;
|
|
2460
|
+
try {
|
|
2461
|
+
return String(value);
|
|
2462
|
+
} catch {
|
|
2463
|
+
return "[unrenderable value]";
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2447
2467
|
// src/transactions/remit/build-835.ts
|
|
2448
2468
|
var X221A1_VERSION_RELEASE = "005010X221A1";
|
|
2449
2469
|
var X12_835_FUNCTIONAL_ID = "HP";
|
|
2450
2470
|
var X12_AGENCY_CODE = "X";
|
|
2471
|
+
var UNANCHORED_BUILD_POSITION = Object.freeze({
|
|
2472
|
+
segmentIndex: 0,
|
|
2473
|
+
transactionIndex: 0
|
|
2474
|
+
});
|
|
2451
2475
|
function build835(spec) {
|
|
2452
2476
|
const { envelope } = spec;
|
|
2453
2477
|
enforceStructuralSpec(spec);
|
|
@@ -2621,7 +2645,7 @@ function enforceStructuralSpec(spec) {
|
|
|
2621
2645
|
}
|
|
2622
2646
|
}
|
|
2623
2647
|
function enforceBalance(spec) {
|
|
2624
|
-
const position =
|
|
2648
|
+
const position = UNANCHORED_BUILD_POSITION;
|
|
2625
2649
|
const claims = spec.claims.map(materializeClaim);
|
|
2626
2650
|
for (const claim of claims) {
|
|
2627
2651
|
const claimWarn = checkClaimBalance(claim, position);
|
|
@@ -2927,7 +2951,7 @@ function padControl(value, width) {
|
|
|
2927
2951
|
if (value.length < width) return "0".repeat(width - value.length) + value;
|
|
2928
2952
|
throw new Remit835BuildError(
|
|
2929
2953
|
REMIT_835_BUILD_ERROR_CODES.X12_835_BUILD_INVALID_SPEC,
|
|
2930
|
-
`build835: control number
|
|
2954
|
+
`build835: control number ${renderCallerValue(value)} exceeds the ${String(width)}-char spec limit.`
|
|
2931
2955
|
);
|
|
2932
2956
|
}
|
|
2933
2957
|
function expandYY(yymmdd) {
|
|
@@ -4276,7 +4300,7 @@ function enforceClaim(variant, claim, locator) {
|
|
|
4276
4300
|
if (line.variant !== variant) {
|
|
4277
4301
|
throw new Claim837BuildError(
|
|
4278
4302
|
CLAIM_837_BUILD_ERROR_CODES.X12_837_BUILD_INVALID_SPEC,
|
|
4279
|
-
`build837${variant}: claim at ${locator} has a
|
|
4303
|
+
`build837${variant}: claim at ${locator} has a ${renderCallerValue(line.variant)} service line at index ${String(l)}; every line must be "${variant}".`
|
|
4280
4304
|
);
|
|
4281
4305
|
}
|
|
4282
4306
|
const code = line.variant === "I" ? line.revenueCode : line.procedureCode;
|
|
@@ -4620,7 +4644,7 @@ function padControl2(value, width) {
|
|
|
4620
4644
|
if (value.length < width) return "0".repeat(width - value.length) + value;
|
|
4621
4645
|
throw new Claim837BuildError(
|
|
4622
4646
|
CLAIM_837_BUILD_ERROR_CODES.X12_837_BUILD_INVALID_SPEC,
|
|
4623
|
-
`build837: control number
|
|
4647
|
+
`build837: control number ${renderCallerValue(value)} exceeds the ${String(width)}-char spec limit.`
|
|
4624
4648
|
);
|
|
4625
4649
|
}
|
|
4626
4650
|
function expandYY2(yymmdd) {
|
|
@@ -5485,7 +5509,7 @@ function padControl3(value, width) {
|
|
|
5485
5509
|
if (value.length < width) return "0".repeat(width - value.length) + value;
|
|
5486
5510
|
throw new Eligibility271BuildError(
|
|
5487
5511
|
ELIGIBILITY_271_BUILD_ERROR_CODES.X12_271_BUILD_INVALID_SPEC,
|
|
5488
|
-
`build271: control number
|
|
5512
|
+
`build271: control number ${renderCallerValue(value)} exceeds the ${String(width)}-char spec limit.`
|
|
5489
5513
|
);
|
|
5490
5514
|
}
|
|
5491
5515
|
function expandYY3(yymmdd) {
|
|
@@ -6259,7 +6283,7 @@ function padControl4(value, width) {
|
|
|
6259
6283
|
if (value.length < width) return "0".repeat(width - value.length) + value;
|
|
6260
6284
|
throw new ClaimStatus277BuildError(
|
|
6261
6285
|
CLAIM_STATUS_277_BUILD_ERROR_CODES.X12_277_BUILD_INVALID_SPEC,
|
|
6262
|
-
`build277: control number
|
|
6286
|
+
`build277: control number ${renderCallerValue(value)} exceeds the ${String(width)}-char spec limit.`
|
|
6263
6287
|
);
|
|
6264
6288
|
}
|
|
6265
6289
|
function expandYY4(yymmdd) {
|
|
@@ -6916,7 +6940,7 @@ function padControl5(value, width) {
|
|
|
6916
6940
|
if (value.length < width) return "0".repeat(width - value.length) + value;
|
|
6917
6941
|
throw new Premium820BuildError(
|
|
6918
6942
|
PREMIUM_820_BUILD_ERROR_CODES.X12_820_BUILD_INVALID_SPEC,
|
|
6919
|
-
`build820: control number
|
|
6943
|
+
`build820: control number ${renderCallerValue(value)} exceeds the ${String(width)}-char spec limit.`
|
|
6920
6944
|
);
|
|
6921
6945
|
}
|
|
6922
6946
|
function expandYY5(yymmdd) {
|
|
@@ -7431,7 +7455,7 @@ function enforceStructuralSpec6(spec) {
|
|
|
7431
7455
|
if (lookupMaintenanceType(member.maintenanceTypeCode) === void 0) {
|
|
7432
7456
|
throw new Enrollment834BuildError(
|
|
7433
7457
|
ENROLLMENT_834_BUILD_ERROR_CODES.X12_834_BUILD_UNKNOWN_MAINTENANCE_TYPE,
|
|
7434
|
-
`build834: member at index ${String(m)} has an unknown INS-03 maintenance type code
|
|
7458
|
+
`build834: member at index ${String(m)} has an unknown INS-03 maintenance type code ${renderCallerValue(member.maintenanceTypeCode)} (outside X12 Code Source 875).`
|
|
7435
7459
|
);
|
|
7436
7460
|
}
|
|
7437
7461
|
for (let c = 0; c < (member.healthCoverages ?? []).length; c += 1) {
|
|
@@ -7441,7 +7465,7 @@ function enforceStructuralSpec6(spec) {
|
|
|
7441
7465
|
if (hd01 !== void 0 && hd01 !== "" && lookupMaintenanceType(hd01) === void 0) {
|
|
7442
7466
|
throw new Enrollment834BuildError(
|
|
7443
7467
|
ENROLLMENT_834_BUILD_ERROR_CODES.X12_834_BUILD_UNKNOWN_MAINTENANCE_TYPE,
|
|
7444
|
-
`build834: coverage at member[${String(m)}].healthCoverages[${String(c)}] has an unknown HD-01 maintenance type code
|
|
7468
|
+
`build834: coverage at member[${String(m)}].healthCoverages[${String(c)}] has an unknown HD-01 maintenance type code ${renderCallerValue(hd01)} (outside X12 Code Source 875).`
|
|
7445
7469
|
);
|
|
7446
7470
|
}
|
|
7447
7471
|
}
|
|
@@ -7590,7 +7614,7 @@ function padControl6(value, width) {
|
|
|
7590
7614
|
if (value.length < width) return "0".repeat(width - value.length) + value;
|
|
7591
7615
|
throw new Enrollment834BuildError(
|
|
7592
7616
|
ENROLLMENT_834_BUILD_ERROR_CODES.X12_834_BUILD_INVALID_SPEC,
|
|
7593
|
-
`build834: control number
|
|
7617
|
+
`build834: control number ${renderCallerValue(value)} exceeds the ${String(width)}-char spec limit.`
|
|
7594
7618
|
);
|
|
7595
7619
|
}
|
|
7596
7620
|
function expandYY6(yymmdd) {
|
|
@@ -8246,7 +8270,7 @@ function padControl7(value, width) {
|
|
|
8246
8270
|
if (value.length < width) return "0".repeat(width - value.length) + value;
|
|
8247
8271
|
throw new ServicesReview278BuildError(
|
|
8248
8272
|
AUTH_278_BUILD_ERROR_CODES.X12_278_BUILD_INVALID_SPEC,
|
|
8249
|
-
`build278: control number
|
|
8273
|
+
`build278: control number ${renderCallerValue(value)} exceeds the ${String(width)}-char spec limit.`
|
|
8250
8274
|
);
|
|
8251
8275
|
}
|
|
8252
8276
|
function expandYY7(yymmdd) {
|
|
@@ -8848,7 +8872,7 @@ function enforceAcceptIsClean(group) {
|
|
|
8848
8872
|
if (functionalIsAccept && functionalHasErrorCodes) {
|
|
8849
8873
|
throw new AckBuildError(
|
|
8850
8874
|
ACK_BUILD_ERROR_CODES.X12_ACK_ACCEPT_WITH_ERRORS,
|
|
8851
|
-
`build999: AK9-01 was 'A' (Accept) but AK9-05..AK9-09 carried ${
|
|
8875
|
+
`build999: AK9-01 was 'A' (Accept) but AK9-05..AK9-09 carried ${renderCallerValue(
|
|
8852
8876
|
(group.syntaxErrorCodes ?? []).length
|
|
8853
8877
|
)} syntax error code(s). Use 'E' (Accept, with errors noted) or a reject disposition instead.`
|
|
8854
8878
|
);
|
|
@@ -8859,13 +8883,13 @@ function enforceAcceptIsClean(group) {
|
|
|
8859
8883
|
if (responseIsAccept && responseHasErrors) {
|
|
8860
8884
|
throw new AckBuildError(
|
|
8861
8885
|
ACK_BUILD_ERROR_CODES.X12_ACK_ACCEPT_WITH_ERRORS,
|
|
8862
|
-
`build999: AK2 (ST-02 ${response.transactionSetControlNumber}) IK5-01 was 'A' (Accept) but the response carried error payload. Use 'E' (Accept, with errors noted) or a reject disposition instead.`
|
|
8886
|
+
`build999: AK2 (ST-02 ${renderCallerValue(response.transactionSetControlNumber)}) IK5-01 was 'A' (Accept) but the response carried error payload. Use 'E' (Accept, with errors noted) or a reject disposition instead.`
|
|
8863
8887
|
);
|
|
8864
8888
|
}
|
|
8865
8889
|
if (functionalIsAccept && (!responseIsAccept || responseHasErrors)) {
|
|
8866
8890
|
throw new AckBuildError(
|
|
8867
8891
|
ACK_BUILD_ERROR_CODES.X12_ACK_ACCEPT_WITH_ERRORS,
|
|
8868
|
-
`build999: AK9-01 was 'A' (Accept) but transaction response (ST-02 ${response.transactionSetControlNumber}) reported a non-accept disposition or carried errors. Lift the functional disposition to 'P' (Partial), 'E' (Accept, errors noted), or a reject.`
|
|
8892
|
+
`build999: AK9-01 was 'A' (Accept) but transaction response (ST-02 ${renderCallerValue(response.transactionSetControlNumber)}) reported a non-accept disposition or carried errors. Lift the functional disposition to 'P' (Partial), 'E' (Accept, errors noted), or a reject.`
|
|
8869
8893
|
);
|
|
8870
8894
|
}
|
|
8871
8895
|
}
|
|
@@ -8877,25 +8901,25 @@ function enforceCountInvariants(group) {
|
|
|
8877
8901
|
if (declared < 0 || received < 0 || accepted < 0) {
|
|
8878
8902
|
throw new AckBuildError(
|
|
8879
8903
|
ACK_BUILD_ERROR_CODES.X12_ACK_COUNT_MISMATCH,
|
|
8880
|
-
`build999: AK9 counts must be non-negative (declared=${
|
|
8904
|
+
`build999: AK9 counts must be non-negative (declared=${renderCallerValue(declared)}, received=${renderCallerValue(received)}, accepted=${renderCallerValue(accepted)}).`
|
|
8881
8905
|
);
|
|
8882
8906
|
}
|
|
8883
8907
|
if (accepted > received) {
|
|
8884
8908
|
throw new AckBuildError(
|
|
8885
8909
|
ACK_BUILD_ERROR_CODES.X12_ACK_COUNT_MISMATCH,
|
|
8886
|
-
`build999: AK9-04 accepted (${
|
|
8910
|
+
`build999: AK9-04 accepted (${renderCallerValue(accepted)}) cannot exceed AK9-03 received (${renderCallerValue(received)}).`
|
|
8887
8911
|
);
|
|
8888
8912
|
}
|
|
8889
8913
|
if (received > declared) {
|
|
8890
8914
|
throw new AckBuildError(
|
|
8891
8915
|
ACK_BUILD_ERROR_CODES.X12_ACK_COUNT_MISMATCH,
|
|
8892
|
-
`build999: AK9-03 received (${
|
|
8916
|
+
`build999: AK9-03 received (${renderCallerValue(received)}) cannot exceed AK9-02 declared (${renderCallerValue(declared)}).`
|
|
8893
8917
|
);
|
|
8894
8918
|
}
|
|
8895
8919
|
if (group.transactionResponses.length !== received) {
|
|
8896
8920
|
throw new AckBuildError(
|
|
8897
8921
|
ACK_BUILD_ERROR_CODES.X12_ACK_COUNT_MISMATCH,
|
|
8898
|
-
`build999: AK9-03 received (${
|
|
8922
|
+
`build999: AK9-03 received (${renderCallerValue(received)}) must equal the number of supplied transaction responses (${renderCallerValue(group.transactionResponses.length)}).`
|
|
8899
8923
|
);
|
|
8900
8924
|
}
|
|
8901
8925
|
}
|
|
@@ -8964,7 +8988,7 @@ function buildIk5(response, esc, elementSeparator, segmentTerminator) {
|
|
|
8964
8988
|
if (codes.length > 5) {
|
|
8965
8989
|
throw new AckBuildError(
|
|
8966
8990
|
ACK_BUILD_ERROR_CODES.X12_ACK_COUNT_MISMATCH,
|
|
8967
|
-
`build999: IK5 accepts up to 5 syntax error codes (IK5-02..IK5-06); ${
|
|
8991
|
+
`build999: IK5 accepts up to 5 syntax error codes (IK5-02..IK5-06); ${renderCallerValue(codes.length)} supplied.`
|
|
8968
8992
|
);
|
|
8969
8993
|
}
|
|
8970
8994
|
for (const code of codes) parts.push(esc(code));
|
|
@@ -8982,7 +9006,7 @@ function buildAk9(group, esc, elementSeparator, segmentTerminator) {
|
|
|
8982
9006
|
if (codes.length > 5) {
|
|
8983
9007
|
throw new AckBuildError(
|
|
8984
9008
|
ACK_BUILD_ERROR_CODES.X12_ACK_COUNT_MISMATCH,
|
|
8985
|
-
`build999: AK9 accepts up to 5 syntax error codes (AK9-05..AK9-09); ${
|
|
9009
|
+
`build999: AK9 accepts up to 5 syntax error codes (AK9-05..AK9-09); ${renderCallerValue(codes.length)} supplied.`
|
|
8986
9010
|
);
|
|
8987
9011
|
}
|
|
8988
9012
|
for (const code of codes) parts.push(esc(code));
|
|
@@ -9001,7 +9025,7 @@ function padControl8(value, width) {
|
|
|
9001
9025
|
if (value.length < width) return "0".repeat(width - value.length) + value;
|
|
9002
9026
|
throw new AckBuildError(
|
|
9003
9027
|
ACK_BUILD_ERROR_CODES.X12_ACK_INVALID_SPEC,
|
|
9004
|
-
`build999: control number
|
|
9028
|
+
`build999: control number ${renderCallerValue(value)} exceeds the ${String(width)}-char spec limit.`
|
|
9005
9029
|
);
|
|
9006
9030
|
}
|
|
9007
9031
|
function expandYY8(yymmdd) {
|
|
@@ -9031,7 +9055,7 @@ function enforceAcceptIsClean2(spec) {
|
|
|
9031
9055
|
if (spec.ackCode === TA1_ACK_CODES.A && spec.noteCode !== "000") {
|
|
9032
9056
|
throw new AckBuildError(
|
|
9033
9057
|
ACK_BUILD_ERROR_CODES.X12_TA1_ACCEPT_WITH_NOTE,
|
|
9034
|
-
`buildTA1: TA1-04 was "A" (Accept) but TA1-05 carried note
|
|
9058
|
+
`buildTA1: TA1-04 was "A" (Accept) but TA1-05 carried note ${renderCallerValue(spec.noteCode)}. An accept must cite "000" (no error). Use ackCode "E" (accept, errors noted) when the inbound had defects you elected to ignore.`
|
|
9035
9059
|
);
|
|
9036
9060
|
}
|
|
9037
9061
|
}
|
|
@@ -9322,7 +9346,11 @@ function buildTransaction(tx, esc, elementSeparator, segmentTerminator) {
|
|
|
9322
9346
|
if (segment.length === 0 || (segment[0] ?? "") === "") {
|
|
9323
9347
|
throw new X12BuildError(
|
|
9324
9348
|
X12_BUILD_ERROR_CODES.X12_BUILD_INVALID_SPEC,
|
|
9325
|
-
|
|
9349
|
+
// Rendered from the RAW id, not the `?`-escaped one. `esc` can double
|
|
9350
|
+
// the length, so escaping first made the reported "(N characters)" the
|
|
9351
|
+
// escaped length rather than the caller's: a 100-character all-`?` id
|
|
9352
|
+
// reported 200. The escape exists for the wire, and this is a message.
|
|
9353
|
+
`buildInterchange: a segment spec in transaction ${renderCallerValue(tx.transactionSetIdCode)} has no segment id.`
|
|
9326
9354
|
);
|
|
9327
9355
|
}
|
|
9328
9356
|
bodySegments += joinSeg2(segment.map(esc), elementSeparator, segmentTerminator);
|
|
@@ -9348,7 +9376,7 @@ function padControl9(value, width) {
|
|
|
9348
9376
|
if (value.length < width) return "0".repeat(width - value.length) + value;
|
|
9349
9377
|
throw new X12BuildError(
|
|
9350
9378
|
X12_BUILD_ERROR_CODES.X12_BUILD_INVALID_SPEC,
|
|
9351
|
-
`buildInterchange: control number
|
|
9379
|
+
`buildInterchange: control number ${renderCallerValue(value)} exceeds the ${String(width)}-char spec limit.`
|
|
9352
9380
|
);
|
|
9353
9381
|
}
|
|
9354
9382
|
function expandYY9(yymmdd) {
|
|
@@ -9642,7 +9670,7 @@ var profiles = Object.freeze({
|
|
|
9642
9670
|
});
|
|
9643
9671
|
|
|
9644
9672
|
// src/index.ts
|
|
9645
|
-
var VERSION = "0.0.
|
|
9673
|
+
var VERSION = "0.0.5";
|
|
9646
9674
|
|
|
9647
9675
|
exports.ACK_BUILD_ERROR_CODES = ACK_BUILD_ERROR_CODES;
|
|
9648
9676
|
exports.ALL_WARNING_MESSAGES = ALL_WARNING_MESSAGES;
|
|
@@ -9655,6 +9683,8 @@ exports.AUTH_278_LOOP_2000E = AUTH_278_LOOP_2000E;
|
|
|
9655
9683
|
exports.AUTH_278_LOOP_2000F = AUTH_278_LOOP_2000F;
|
|
9656
9684
|
exports.AckBuildError = AckBuildError;
|
|
9657
9685
|
exports.BALANCE_INVARIANTS = BALANCE_INVARIANTS;
|
|
9686
|
+
exports.BUILD_REFUSAL_VALUE_MAX_LENGTH = BUILD_REFUSAL_VALUE_MAX_LENGTH;
|
|
9687
|
+
exports.BUILD_REFUSAL_VALUE_MAX_RENDERED = BUILD_REFUSAL_VALUE_MAX_RENDERED;
|
|
9658
9688
|
exports.CARC = CARC;
|
|
9659
9689
|
exports.CLAIM_837D_LOOP_2000A = CLAIM_837D_LOOP_2000A;
|
|
9660
9690
|
exports.CLAIM_837D_LOOP_2300 = CLAIM_837D_LOOP_2300;
|
|
@@ -9816,6 +9846,7 @@ exports.partitionWarnings = partitionWarnings;
|
|
|
9816
9846
|
exports.pre005010 = pre005010;
|
|
9817
9847
|
exports.profiles = profiles;
|
|
9818
9848
|
exports.remitBalanceMismatch = remitBalanceMismatch;
|
|
9849
|
+
exports.renderCallerValue = renderCallerValue;
|
|
9819
9850
|
exports.resolveHiQualifier = resolveHiQualifier;
|
|
9820
9851
|
exports.segmentCountMismatch = segmentCountMismatch;
|
|
9821
9852
|
exports.serializeX12 = serializeX12;
|