@cosyte/synth 0.0.3 → 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.
Files changed (49) hide show
  1. package/CHANGELOG.md +335 -0
  2. package/README.md +6 -2
  3. package/dist/astm/index.cjs +83 -42
  4. package/dist/astm/index.cjs.map +1 -1
  5. package/dist/astm/index.d.cts +1 -1
  6. package/dist/astm/index.d.ts +1 -1
  7. package/dist/astm/index.mjs +83 -42
  8. package/dist/astm/index.mjs.map +1 -1
  9. package/dist/ccda/index.cjs +89 -44
  10. package/dist/ccda/index.cjs.map +1 -1
  11. package/dist/ccda/index.d.cts +5 -3
  12. package/dist/ccda/index.d.ts +5 -3
  13. package/dist/ccda/index.mjs +89 -44
  14. package/dist/ccda/index.mjs.map +1 -1
  15. package/dist/deid/index.cjs +92 -13
  16. package/dist/deid/index.cjs.map +1 -1
  17. package/dist/deid/index.d.cts +1 -1
  18. package/dist/deid/index.d.ts +1 -1
  19. package/dist/deid/index.mjs +93 -14
  20. package/dist/deid/index.mjs.map +1 -1
  21. package/dist/fhir/index.cjs +78 -5
  22. package/dist/fhir/index.cjs.map +1 -1
  23. package/dist/fhir/index.mjs +78 -5
  24. package/dist/fhir/index.mjs.map +1 -1
  25. package/dist/hl7/index.cjs +89 -41
  26. package/dist/hl7/index.cjs.map +1 -1
  27. package/dist/hl7/index.d.cts +1 -1
  28. package/dist/hl7/index.d.ts +1 -1
  29. package/dist/hl7/index.mjs +89 -41
  30. package/dist/hl7/index.mjs.map +1 -1
  31. package/dist/index.cjs +91 -40
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.d.cts +126 -8
  34. package/dist/index.d.ts +126 -8
  35. package/dist/index.mjs +89 -41
  36. package/dist/index.mjs.map +1 -1
  37. package/dist/ncpdp/index.cjs +56 -4
  38. package/dist/ncpdp/index.cjs.map +1 -1
  39. package/dist/ncpdp/index.mjs +56 -4
  40. package/dist/ncpdp/index.mjs.map +1 -1
  41. package/dist/{quirk-JLyO1Ncj.d.ts → quirk-C9t9CkPS.d.ts} +17 -6
  42. package/dist/{quirk-DmkgoZdh.d.cts → quirk-DYMDojVw.d.cts} +17 -6
  43. package/dist/x12/index.cjs +82 -13
  44. package/dist/x12/index.cjs.map +1 -1
  45. package/dist/x12/index.d.cts +2 -1
  46. package/dist/x12/index.d.ts +2 -1
  47. package/dist/x12/index.mjs +83 -14
  48. package/dist/x12/index.mjs.map +1 -1
  49. package/package.json +2 -2
@@ -1,7 +1,7 @@
1
1
  import { S as SyntheticName, a as SyntheticIdentifier, R as Rng, C as Corpus } from '../providers-OLz3zAc-.cjs';
2
2
  import { CcdaDocument, BuildCcdaPatient, BuildCode, BuildQuantity } from '@cosyte/ccda';
3
3
  import { C as CodeConcept, Q as QuantConcept } from '../example-codes-DeXcnCSK.cjs';
4
- import { Q as QuirkDescriptor, S as SynthProfile, a as QuirkArtifact, b as QuirkRoundTripResult } from '../quirk-DmkgoZdh.cjs';
4
+ import { Q as QuirkDescriptor, S as SynthProfile, a as QuirkArtifact, b as QuirkRoundTripResult } from '../quirk-DYMDojVw.cjs';
5
5
 
6
6
  /**
7
7
  * Synthetic **C-CDA document generation** — a spec-clean Continuity of Care Document
@@ -168,7 +168,8 @@ declare function ccdaPatientIdentity(rng: Rng): CcdaPatientIdentity;
168
168
  *
169
169
  * @param concept - The FHIR-shaped `{ system, code, display }` concept.
170
170
  * @returns The `@cosyte/ccda` `BuildCode`.
171
- * @throws {Error} When the concept's `system` URI has no known OID mapping.
171
+ * @throws SynthError `SYNTH_UNMAPPED_CODE_SYSTEM` when the concept's `system` URI has no known OID
172
+ * mapping. The refusal does not quote the URI: `concept` is caller-supplied.
172
173
  * @example
173
174
  * ```ts
174
175
  * import { toBuildCode } from "@cosyte/synth/ccda";
@@ -258,7 +259,8 @@ declare const CCDA_QUIRKS: Readonly<Record<CcdaQuirkName, QuirkDescriptor>>;
258
259
  * @param quirk - The quirk to inject.
259
260
  * @param cleanXml - The spec-clean C-CDA XML.
260
261
  * @returns The quirked XML.
261
- * @throws Error when the quirk found no structural anchor to mutate.
262
+ * @throws SynthError `SYNTH_UNSUPPORTED_QUIRK` when `quirk` is not a supported C-CDA quirk.
263
+ * @throws SynthError `SYNTH_QUIRK_ANCHOR_ABSENT` when the quirk found no structural anchor to mutate.
262
264
  * @example
263
265
  * ```ts
264
266
  * import { injectCcdaQuirk } from "@cosyte/synth/ccda";
@@ -1,7 +1,7 @@
1
1
  import { S as SyntheticName, a as SyntheticIdentifier, R as Rng, C as Corpus } from '../providers-OLz3zAc-.js';
2
2
  import { CcdaDocument, BuildCcdaPatient, BuildCode, BuildQuantity } from '@cosyte/ccda';
3
3
  import { C as CodeConcept, Q as QuantConcept } from '../example-codes-DeXcnCSK.js';
4
- import { Q as QuirkDescriptor, S as SynthProfile, a as QuirkArtifact, b as QuirkRoundTripResult } from '../quirk-JLyO1Ncj.js';
4
+ import { Q as QuirkDescriptor, S as SynthProfile, a as QuirkArtifact, b as QuirkRoundTripResult } from '../quirk-C9t9CkPS.js';
5
5
 
6
6
  /**
7
7
  * Synthetic **C-CDA document generation** — a spec-clean Continuity of Care Document
@@ -168,7 +168,8 @@ declare function ccdaPatientIdentity(rng: Rng): CcdaPatientIdentity;
168
168
  *
169
169
  * @param concept - The FHIR-shaped `{ system, code, display }` concept.
170
170
  * @returns The `@cosyte/ccda` `BuildCode`.
171
- * @throws {Error} When the concept's `system` URI has no known OID mapping.
171
+ * @throws SynthError `SYNTH_UNMAPPED_CODE_SYSTEM` when the concept's `system` URI has no known OID
172
+ * mapping. The refusal does not quote the URI: `concept` is caller-supplied.
172
173
  * @example
173
174
  * ```ts
174
175
  * import { toBuildCode } from "@cosyte/synth/ccda";
@@ -258,7 +259,8 @@ declare const CCDA_QUIRKS: Readonly<Record<CcdaQuirkName, QuirkDescriptor>>;
258
259
  * @param quirk - The quirk to inject.
259
260
  * @param cleanXml - The spec-clean C-CDA XML.
260
261
  * @returns The quirked XML.
261
- * @throws Error when the quirk found no structural anchor to mutate.
262
+ * @throws SynthError `SYNTH_UNSUPPORTED_QUIRK` when `quirk` is not a supported C-CDA quirk.
263
+ * @throws SynthError `SYNTH_QUIRK_ANCHOR_ABSENT` when the quirk found no structural anchor to mutate.
262
264
  * @example
263
265
  * ```ts
264
266
  * import { injectCcdaQuirk } from "@cosyte/synth/ccda";
@@ -29,6 +29,65 @@ function sfc32Next(s) {
29
29
  return t >>> 0;
30
30
  }
31
31
 
32
+ // src/codes.ts
33
+ var SYNTH_FATAL_CODES = {
34
+ /**
35
+ * A vendor quirk was requested that the target format's profile system does not support. Fatal —
36
+ * never a silent no-op and never a fabricated quirk.
37
+ */
38
+ SYNTH_UNSUPPORTED_QUIRK: "SYNTH_UNSUPPORTED_QUIRK",
39
+ /**
40
+ * A quirk transform found no structural anchor to mutate, so the fixture would not carry the
41
+ * deviation it is labelled with. Fatal — a golden file that lies about its parser verdict is worse
42
+ * than no golden file.
43
+ */
44
+ SYNTH_QUIRK_ANCHOR_ABSENT: "SYNTH_QUIRK_ANCHOR_ABSENT",
45
+ /**
46
+ * A bare parse of a freshly-generated quirk artifact did not produce exactly the declared intended
47
+ * warning code(s). Fatal — never emit a mislabeled fixture.
48
+ */
49
+ SYNTH_INTENDED_WARNING_MISMATCH: "SYNTH_INTENDED_WARNING_MISMATCH",
50
+ /** A concept's code-system URI has no OID mapping in the C-CDA example-code table. Fatal. */
51
+ SYNTH_UNMAPPED_CODE_SYSTEM: "SYNTH_UNMAPPED_CODE_SYSTEM",
52
+ /** An integer range was requested with its maximum below its minimum. Fatal. */
53
+ SYNTH_INVALID_RANGE: "SYNTH_INVALID_RANGE",
54
+ /** A value was drawn from an empty pool. Fatal — never a fabricated substitute. */
55
+ SYNTH_EMPTY_POOL: "SYNTH_EMPTY_POOL",
56
+ /** A `defineSynthProfile` spec was not usable (a missing or blank `name`). Fatal. */
57
+ SYNTH_INVALID_PROFILE: "SYNTH_INVALID_PROFILE",
58
+ /**
59
+ * A caller-supplied selector (a message kind, a document type, a corpus mix entry, a claim
60
+ * variant, a Bundle type, a resource profile) is not in the closed set that governs it. Fatal —
61
+ * see `resolveKind`: a selector union is erased at run time, and a selector that falls through
62
+ * either mislabels the fixture or hands the value to a peer builder that quotes it back.
63
+ */
64
+ SYNTH_UNSUPPORTED_KIND: "SYNTH_UNSUPPORTED_KIND"
65
+ };
66
+ var SYNTH_FATAL_MESSAGES = Object.freeze({
67
+ SYNTH_UNSUPPORTED_FORMAT: "The requested format is not generable by this build. A generator has no byte fallback: it builds through a parser's own serializer or it refuses.",
68
+ SYNTH_UNSUPPORTED_QUIRK: "The requested vendor quirk is not in the target format's quirk registry. Compare the request against that format's exported registry (HL7_QUIRKS, CCDA_QUIRKS, ASTM_QUIRKS).",
69
+ SYNTH_QUIRK_ANCHOR_ABSENT: "The quirk transform found no structural anchor to mutate, so the fixture would not carry the deviation it is labelled with. Refusing to emit a mislabeled fixture.",
70
+ SYNTH_INTENDED_WARNING_MISMATCH: "A bare parse of the generated quirk artifact did not produce exactly the declared intended warning code(s). Refusing to emit a mislabeled fixture.",
71
+ SYNTH_UNMAPPED_CODE_SYSTEM: "The concept's code-system URI has no OID mapping in the C-CDA example-code table.",
72
+ SYNTH_INVALID_DECIMAL: "The value could not be read as an X12 decimal.",
73
+ SYNTH_INVALID_RANGE: "An integer range was requested with its maximum below its minimum.",
74
+ SYNTH_EMPTY_POOL: "A value was drawn from an empty pool.",
75
+ SYNTH_INVALID_PROFILE: "defineSynthProfile requires a non-empty string name.",
76
+ SYNTH_UNSUPPORTED_KIND: "The requested kind, document type, corpus mix entry, variant or profile is not one this generator supports. The supported set is the exported union for that option."
77
+ });
78
+ var SynthError = class extends Error {
79
+ /** The stable fatal code. */
80
+ code;
81
+ /**
82
+ * @param code - The stable {@link SynthFatalCode}. The message comes from the frozen registry.
83
+ */
84
+ constructor(code) {
85
+ super(SYNTH_FATAL_MESSAGES[code]);
86
+ this.name = "SynthError";
87
+ this.code = code;
88
+ }
89
+ };
90
+
32
91
  // src/rng/rng.ts
33
92
  var Sfc32Rng = class {
34
93
  seed;
@@ -46,7 +105,7 @@ var Sfc32Rng = class {
46
105
  return this.nextUint32() / 4294967296;
47
106
  }
48
107
  int(min, max) {
49
- if (max < min) throw new RangeError(`Rng.int: max (${String(max)}) < min (${String(min)})`);
108
+ if (max < min) throw new SynthError(SYNTH_FATAL_CODES.SYNTH_INVALID_RANGE);
50
109
  const span = max - min + 1;
51
110
  return min + Math.floor(this.float() * span);
52
111
  }
@@ -54,7 +113,7 @@ var Sfc32Rng = class {
54
113
  return this.float() < p;
55
114
  }
56
115
  pick(items) {
57
- if (items.length === 0) throw new RangeError("Rng.pick: empty array");
116
+ if (items.length === 0) throw new SynthError(SYNTH_FATAL_CODES.SYNTH_EMPTY_POOL);
58
117
  return items[this.int(0, items.length - 1)];
59
118
  }
60
119
  digits(n) {
@@ -671,7 +730,7 @@ var URI_TO_OID = Object.freeze({
671
730
  function toBuildCode(concept) {
672
731
  const codeSystem = URI_TO_OID[concept.system];
673
732
  if (codeSystem === void 0) {
674
- throw new Error(`ccda example-codes: no OID mapping for code system "${concept.system}"`);
733
+ throw new SynthError(SYNTH_FATAL_CODES.SYNTH_UNMAPPED_CODE_SYSTEM);
675
734
  }
676
735
  return { code: concept.code, codeSystem, displayName: concept.display };
677
736
  }
@@ -728,7 +787,19 @@ function ccdaPatientIdentity(rng) {
728
787
  return { patient, person, mrn };
729
788
  }
730
789
 
790
+ // src/select.ts
791
+ function resolveKind(allowed, requested) {
792
+ const match = allowed.find((value) => value === requested);
793
+ if (match === void 0) throw new SynthError(SYNTH_FATAL_CODES.SYNTH_UNSUPPORTED_KIND);
794
+ return match;
795
+ }
796
+ function resolveMix(allowed, requested, fallback) {
797
+ if (requested === void 0) return fallback;
798
+ return requested.map((entry) => resolveKind(allowed, entry));
799
+ }
800
+
731
801
  // src/ccda/ccd.ts
802
+ var CCDA_DOCUMENT_TYPES = Object.freeze(["ccd", "referralNote"]);
732
803
  function pickN(rng, pool, n) {
733
804
  const take = Math.min(n, pool.length);
734
805
  const indices = pool.map((_v, i) => i);
@@ -811,7 +882,8 @@ function buildInit(rng, documentType) {
811
882
  return base;
812
883
  }
813
884
  function generateCcda(options = {}) {
814
- const { seed = 0, documentType = "ccd" } = options;
885
+ const { seed = 0 } = options;
886
+ const documentType = resolveKind(CCDA_DOCUMENT_TYPES, options.documentType ?? "ccd");
815
887
  const rng = createRng(seed);
816
888
  return buildCcda(buildInit(rng, documentType));
817
889
  }
@@ -837,7 +909,7 @@ function roundTrip(doc) {
837
909
  // src/profile.ts
838
910
  function defineSynthProfile(spec) {
839
911
  if (typeof spec.name !== "string" || spec.name.trim().length === 0) {
840
- throw new TypeError("defineSynthProfile: `name` is required and must be a non-empty string.");
912
+ throw new SynthError(SYNTH_FATAL_CODES.SYNTH_INVALID_PROFILE);
841
913
  }
842
914
  return Object.freeze({
843
915
  name: spec.name,
@@ -847,28 +919,6 @@ function defineSynthProfile(spec) {
847
919
  });
848
920
  }
849
921
 
850
- // src/codes.ts
851
- var SYNTH_FATAL_CODES = {
852
- /**
853
- * A vendor quirk was requested that the target format's profile system does not support. Fatal —
854
- * never a silent no-op and never a fabricated quirk.
855
- */
856
- SYNTH_UNSUPPORTED_QUIRK: "SYNTH_UNSUPPORTED_QUIRK"
857
- };
858
- var SynthError = class extends Error {
859
- /** The stable fatal code. */
860
- code;
861
- /**
862
- * @param code - The stable {@link SynthFatalCode}.
863
- * @param message - A human-readable detail (never contains PHI — there is none).
864
- */
865
- constructor(code, message) {
866
- super(message);
867
- this.name = "SynthError";
868
- this.code = code;
869
- }
870
- };
871
-
872
922
  // src/quirk.ts
873
923
  var PROFILE_QUIRK_APPLIED = "PROFILE_QUIRK_APPLIED";
874
924
  function sameCodeSet(a, b) {
@@ -885,12 +935,8 @@ function sameCodeSet(a, b) {
885
935
  }
886
936
  function resolveQuirk(registry, format, name2) {
887
937
  const descriptor = registry[name2];
888
- if (descriptor === void 0) {
889
- const supported = Object.keys(registry).sort().join(", ");
890
- throw new SynthError(
891
- SYNTH_FATAL_CODES.SYNTH_UNSUPPORTED_QUIRK,
892
- `${format}: unsupported quirk "${name2}". The ${format} profile system supports: ${supported}.`
893
- );
938
+ if (descriptor === void 0 || descriptor.format !== format) {
939
+ throw new SynthError(SYNTH_FATAL_CODES.SYNTH_UNSUPPORTED_QUIRK);
894
940
  }
895
941
  return descriptor;
896
942
  }
@@ -905,11 +951,9 @@ function profileTolerated(disposition, intendedWarnings, warningsUnderProfile) {
905
951
  return false;
906
952
  }
907
953
  }
908
- function assertIntendedWarnings(quirk, intendedWarnings, bareWarnings) {
954
+ function assertIntendedWarnings(intendedWarnings, bareWarnings) {
909
955
  if (!sameCodeSet(bareWarnings, intendedWarnings)) {
910
- throw new Error(
911
- `quirk "${quirk}": the intended-warning contract does not hold \u2014 expected exactly [${intendedWarnings.join(", ")}] but a bare parse produced [${bareWarnings.join(", ")}]. Refusing to emit a mislabeled fixture.`
912
- );
956
+ throw new SynthError(SYNTH_FATAL_CODES.SYNTH_INTENDED_WARNING_MISMATCH);
913
957
  }
914
958
  }
915
959
  function validateProfileQuirks(profile, registry, format) {
@@ -974,11 +1018,10 @@ function applyQuirk(quirk, xml) {
974
1018
  }
975
1019
  }
976
1020
  function injectCcdaQuirk(quirk, cleanXml) {
977
- const content = applyQuirk(quirk, cleanXml);
1021
+ const descriptor = resolveQuirk(CCDA_QUIRKS, "ccda", quirk);
1022
+ const content = applyQuirk(descriptor.name, cleanXml);
978
1023
  if (content === cleanXml) {
979
- throw new Error(
980
- `injectCcdaQuirk: quirk "${quirk}" found no structural anchor to mutate \u2014 refusing to emit a fixture that does not carry the intended deviation.`
981
- );
1024
+ throw new SynthError(SYNTH_FATAL_CODES.SYNTH_QUIRK_ANCHOR_ABSENT);
982
1025
  }
983
1026
  return content;
984
1027
  }
@@ -989,7 +1032,6 @@ function generateCcdaQuirk(options) {
989
1032
  const clean = serializeCcda(generateCcda({ seed, documentType }));
990
1033
  const content = injectCcdaQuirk(options.quirk, clean);
991
1034
  assertIntendedWarnings(
992
- descriptor.name,
993
1035
  descriptor.intendedWarnings,
994
1036
  parseCcda(content).warnings.map((w) => String(w.code))
995
1037
  );
@@ -1032,6 +1074,7 @@ var ALL_CCDA_QUIRKS = Object.freeze(
1032
1074
  function ccdaQuirkCorpus(options) {
1033
1075
  const quirks = options.profile ? validateProfileQuirks(options.profile, CCDA_QUIRKS, "ccda") : options.quirks ?? ALL_CCDA_QUIRKS;
1034
1076
  const names = quirks.length > 0 ? quirks : ALL_CCDA_QUIRKS;
1077
+ for (const name2 of names) resolveQuirk(CCDA_QUIRKS, "ccda", name2);
1035
1078
  const documentType = options.documentType ?? "ccd";
1036
1079
  const count = options.count ?? names.length;
1037
1080
  const seedStream = createRng(options.seed);
@@ -1054,9 +1097,11 @@ var ccdaQuirkProfile = defineSynthProfile({
1054
1097
  });
1055
1098
 
1056
1099
  // src/ccda/index.ts
1057
- var DEFAULT_MIX = Object.freeze(["ccd", "referralNote"]);
1100
+ var ALL_KINDS = Object.freeze(["ccd", "referralNote"]);
1101
+ var DEFAULT_MIX = ALL_KINDS;
1058
1102
  function ccdaCorpus(options) {
1059
- const { seed, count = 1, mix = DEFAULT_MIX } = options;
1103
+ const { seed, count = 1 } = options;
1104
+ const mix = resolveMix(ALL_KINDS, options.mix, DEFAULT_MIX);
1060
1105
  const seedStream = createRng(seed);
1061
1106
  const artifacts = Array.from({ length: count }, (_unused, i) => {
1062
1107
  const documentType = mix[i % mix.length] ?? "ccd";