@cosyte/synth 0.0.3 → 0.0.4

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 +297 -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
@@ -31,6 +31,65 @@ function sfc32Next(s) {
31
31
  return t >>> 0;
32
32
  }
33
33
 
34
+ // src/codes.ts
35
+ var SYNTH_FATAL_CODES = {
36
+ /**
37
+ * A vendor quirk was requested that the target format's profile system does not support. Fatal —
38
+ * never a silent no-op and never a fabricated quirk.
39
+ */
40
+ SYNTH_UNSUPPORTED_QUIRK: "SYNTH_UNSUPPORTED_QUIRK",
41
+ /**
42
+ * A quirk transform found no structural anchor to mutate, so the fixture would not carry the
43
+ * deviation it is labelled with. Fatal — a golden file that lies about its parser verdict is worse
44
+ * than no golden file.
45
+ */
46
+ SYNTH_QUIRK_ANCHOR_ABSENT: "SYNTH_QUIRK_ANCHOR_ABSENT",
47
+ /**
48
+ * A bare parse of a freshly-generated quirk artifact did not produce exactly the declared intended
49
+ * warning code(s). Fatal — never emit a mislabeled fixture.
50
+ */
51
+ SYNTH_INTENDED_WARNING_MISMATCH: "SYNTH_INTENDED_WARNING_MISMATCH",
52
+ /** A concept's code-system URI has no OID mapping in the C-CDA example-code table. Fatal. */
53
+ SYNTH_UNMAPPED_CODE_SYSTEM: "SYNTH_UNMAPPED_CODE_SYSTEM",
54
+ /** An integer range was requested with its maximum below its minimum. Fatal. */
55
+ SYNTH_INVALID_RANGE: "SYNTH_INVALID_RANGE",
56
+ /** A value was drawn from an empty pool. Fatal — never a fabricated substitute. */
57
+ SYNTH_EMPTY_POOL: "SYNTH_EMPTY_POOL",
58
+ /** A `defineSynthProfile` spec was not usable (a missing or blank `name`). Fatal. */
59
+ SYNTH_INVALID_PROFILE: "SYNTH_INVALID_PROFILE",
60
+ /**
61
+ * A caller-supplied selector (a message kind, a document type, a corpus mix entry, a claim
62
+ * variant, a Bundle type, a resource profile) is not in the closed set that governs it. Fatal —
63
+ * see `resolveKind`: a selector union is erased at run time, and a selector that falls through
64
+ * either mislabels the fixture or hands the value to a peer builder that quotes it back.
65
+ */
66
+ SYNTH_UNSUPPORTED_KIND: "SYNTH_UNSUPPORTED_KIND"
67
+ };
68
+ var SYNTH_FATAL_MESSAGES = Object.freeze({
69
+ 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.",
70
+ 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).",
71
+ 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.",
72
+ 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.",
73
+ SYNTH_UNMAPPED_CODE_SYSTEM: "The concept's code-system URI has no OID mapping in the C-CDA example-code table.",
74
+ SYNTH_INVALID_DECIMAL: "The value could not be read as an X12 decimal.",
75
+ SYNTH_INVALID_RANGE: "An integer range was requested with its maximum below its minimum.",
76
+ SYNTH_EMPTY_POOL: "A value was drawn from an empty pool.",
77
+ SYNTH_INVALID_PROFILE: "defineSynthProfile requires a non-empty string name.",
78
+ 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."
79
+ });
80
+ var SynthError = class extends Error {
81
+ /** The stable fatal code. */
82
+ code;
83
+ /**
84
+ * @param code - The stable {@link SynthFatalCode}. The message comes from the frozen registry.
85
+ */
86
+ constructor(code) {
87
+ super(SYNTH_FATAL_MESSAGES[code]);
88
+ this.name = "SynthError";
89
+ this.code = code;
90
+ }
91
+ };
92
+
34
93
  // src/rng/rng.ts
35
94
  var Sfc32Rng = class {
36
95
  seed;
@@ -48,7 +107,7 @@ var Sfc32Rng = class {
48
107
  return this.nextUint32() / 4294967296;
49
108
  }
50
109
  int(min, max) {
51
- if (max < min) throw new RangeError(`Rng.int: max (${String(max)}) < min (${String(min)})`);
110
+ if (max < min) throw new SynthError(SYNTH_FATAL_CODES.SYNTH_INVALID_RANGE);
52
111
  const span = max - min + 1;
53
112
  return min + Math.floor(this.float() * span);
54
113
  }
@@ -56,7 +115,7 @@ var Sfc32Rng = class {
56
115
  return this.float() < p;
57
116
  }
58
117
  pick(items) {
59
- if (items.length === 0) throw new RangeError("Rng.pick: empty array");
118
+ if (items.length === 0) throw new SynthError(SYNTH_FATAL_CODES.SYNTH_EMPTY_POOL);
60
119
  return items[this.int(0, items.length - 1)];
61
120
  }
62
121
  digits(n) {
@@ -673,7 +732,7 @@ var URI_TO_OID = Object.freeze({
673
732
  function toBuildCode(concept) {
674
733
  const codeSystem = URI_TO_OID[concept.system];
675
734
  if (codeSystem === void 0) {
676
- throw new Error(`ccda example-codes: no OID mapping for code system "${concept.system}"`);
735
+ throw new SynthError(SYNTH_FATAL_CODES.SYNTH_UNMAPPED_CODE_SYSTEM);
677
736
  }
678
737
  return { code: concept.code, codeSystem, displayName: concept.display };
679
738
  }
@@ -730,7 +789,19 @@ function ccdaPatientIdentity(rng) {
730
789
  return { patient, person, mrn };
731
790
  }
732
791
 
792
+ // src/select.ts
793
+ function resolveKind(allowed, requested) {
794
+ const match = allowed.find((value) => value === requested);
795
+ if (match === void 0) throw new SynthError(SYNTH_FATAL_CODES.SYNTH_UNSUPPORTED_KIND);
796
+ return match;
797
+ }
798
+ function resolveMix(allowed, requested, fallback) {
799
+ if (requested === void 0) return fallback;
800
+ return requested.map((entry) => resolveKind(allowed, entry));
801
+ }
802
+
733
803
  // src/ccda/ccd.ts
804
+ var CCDA_DOCUMENT_TYPES = Object.freeze(["ccd", "referralNote"]);
734
805
  function pickN(rng, pool, n) {
735
806
  const take = Math.min(n, pool.length);
736
807
  const indices = pool.map((_v, i) => i);
@@ -813,7 +884,8 @@ function buildInit(rng, documentType) {
813
884
  return base;
814
885
  }
815
886
  function generateCcda(options = {}) {
816
- const { seed = 0, documentType = "ccd" } = options;
887
+ const { seed = 0 } = options;
888
+ const documentType = resolveKind(CCDA_DOCUMENT_TYPES, options.documentType ?? "ccd");
817
889
  const rng = createRng(seed);
818
890
  return ccda.buildCcda(buildInit(rng, documentType));
819
891
  }
@@ -839,7 +911,7 @@ function roundTrip(doc) {
839
911
  // src/profile.ts
840
912
  function defineSynthProfile(spec) {
841
913
  if (typeof spec.name !== "string" || spec.name.trim().length === 0) {
842
- throw new TypeError("defineSynthProfile: `name` is required and must be a non-empty string.");
914
+ throw new SynthError(SYNTH_FATAL_CODES.SYNTH_INVALID_PROFILE);
843
915
  }
844
916
  return Object.freeze({
845
917
  name: spec.name,
@@ -849,28 +921,6 @@ function defineSynthProfile(spec) {
849
921
  });
850
922
  }
851
923
 
852
- // src/codes.ts
853
- var SYNTH_FATAL_CODES = {
854
- /**
855
- * A vendor quirk was requested that the target format's profile system does not support. Fatal —
856
- * never a silent no-op and never a fabricated quirk.
857
- */
858
- SYNTH_UNSUPPORTED_QUIRK: "SYNTH_UNSUPPORTED_QUIRK"
859
- };
860
- var SynthError = class extends Error {
861
- /** The stable fatal code. */
862
- code;
863
- /**
864
- * @param code - The stable {@link SynthFatalCode}.
865
- * @param message - A human-readable detail (never contains PHI — there is none).
866
- */
867
- constructor(code, message) {
868
- super(message);
869
- this.name = "SynthError";
870
- this.code = code;
871
- }
872
- };
873
-
874
924
  // src/quirk.ts
875
925
  var PROFILE_QUIRK_APPLIED = "PROFILE_QUIRK_APPLIED";
876
926
  function sameCodeSet(a, b) {
@@ -887,12 +937,8 @@ function sameCodeSet(a, b) {
887
937
  }
888
938
  function resolveQuirk(registry, format, name2) {
889
939
  const descriptor = registry[name2];
890
- if (descriptor === void 0) {
891
- const supported = Object.keys(registry).sort().join(", ");
892
- throw new SynthError(
893
- SYNTH_FATAL_CODES.SYNTH_UNSUPPORTED_QUIRK,
894
- `${format}: unsupported quirk "${name2}". The ${format} profile system supports: ${supported}.`
895
- );
940
+ if (descriptor === void 0 || descriptor.format !== format) {
941
+ throw new SynthError(SYNTH_FATAL_CODES.SYNTH_UNSUPPORTED_QUIRK);
896
942
  }
897
943
  return descriptor;
898
944
  }
@@ -907,11 +953,9 @@ function profileTolerated(disposition, intendedWarnings, warningsUnderProfile) {
907
953
  return false;
908
954
  }
909
955
  }
910
- function assertIntendedWarnings(quirk, intendedWarnings, bareWarnings) {
956
+ function assertIntendedWarnings(intendedWarnings, bareWarnings) {
911
957
  if (!sameCodeSet(bareWarnings, intendedWarnings)) {
912
- throw new Error(
913
- `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.`
914
- );
958
+ throw new SynthError(SYNTH_FATAL_CODES.SYNTH_INTENDED_WARNING_MISMATCH);
915
959
  }
916
960
  }
917
961
  function validateProfileQuirks(profile, registry, format) {
@@ -976,11 +1020,10 @@ function applyQuirk(quirk, xml) {
976
1020
  }
977
1021
  }
978
1022
  function injectCcdaQuirk(quirk, cleanXml) {
979
- const content = applyQuirk(quirk, cleanXml);
1023
+ const descriptor = resolveQuirk(CCDA_QUIRKS, "ccda", quirk);
1024
+ const content = applyQuirk(descriptor.name, cleanXml);
980
1025
  if (content === cleanXml) {
981
- throw new Error(
982
- `injectCcdaQuirk: quirk "${quirk}" found no structural anchor to mutate \u2014 refusing to emit a fixture that does not carry the intended deviation.`
983
- );
1026
+ throw new SynthError(SYNTH_FATAL_CODES.SYNTH_QUIRK_ANCHOR_ABSENT);
984
1027
  }
985
1028
  return content;
986
1029
  }
@@ -991,7 +1034,6 @@ function generateCcdaQuirk(options) {
991
1034
  const clean = ccda.serializeCcda(generateCcda({ seed, documentType }));
992
1035
  const content = injectCcdaQuirk(options.quirk, clean);
993
1036
  assertIntendedWarnings(
994
- descriptor.name,
995
1037
  descriptor.intendedWarnings,
996
1038
  ccda.parseCcda(content).warnings.map((w) => String(w.code))
997
1039
  );
@@ -1034,6 +1076,7 @@ var ALL_CCDA_QUIRKS = Object.freeze(
1034
1076
  function ccdaQuirkCorpus(options) {
1035
1077
  const quirks = options.profile ? validateProfileQuirks(options.profile, CCDA_QUIRKS, "ccda") : options.quirks ?? ALL_CCDA_QUIRKS;
1036
1078
  const names = quirks.length > 0 ? quirks : ALL_CCDA_QUIRKS;
1079
+ for (const name2 of names) resolveQuirk(CCDA_QUIRKS, "ccda", name2);
1037
1080
  const documentType = options.documentType ?? "ccd";
1038
1081
  const count = options.count ?? names.length;
1039
1082
  const seedStream = createRng(options.seed);
@@ -1056,9 +1099,11 @@ var ccdaQuirkProfile = defineSynthProfile({
1056
1099
  });
1057
1100
 
1058
1101
  // src/ccda/index.ts
1059
- var DEFAULT_MIX = Object.freeze(["ccd", "referralNote"]);
1102
+ var ALL_KINDS = Object.freeze(["ccd", "referralNote"]);
1103
+ var DEFAULT_MIX = ALL_KINDS;
1060
1104
  function ccdaCorpus(options) {
1061
- const { seed, count = 1, mix = DEFAULT_MIX } = options;
1105
+ const { seed, count = 1 } = options;
1106
+ const mix = resolveMix(ALL_KINDS, options.mix, DEFAULT_MIX);
1062
1107
  const seedStream = createRng(seed);
1063
1108
  const artifacts = Array.from({ length: count }, (_unused, i) => {
1064
1109
  const documentType = mix[i % mix.length] ?? "ccd";