@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,57 @@ 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 bare parse of a freshly-generated quirk artifact did not produce exactly the declared intended
43
+ * warning code(s). Fatal — never emit a mislabeled fixture.
44
+ */
45
+ SYNTH_INTENDED_WARNING_MISMATCH: "SYNTH_INTENDED_WARNING_MISMATCH",
46
+ /** An integer range was requested with its maximum below its minimum. Fatal. */
47
+ SYNTH_INVALID_RANGE: "SYNTH_INVALID_RANGE",
48
+ /** A value was drawn from an empty pool. Fatal — never a fabricated substitute. */
49
+ SYNTH_EMPTY_POOL: "SYNTH_EMPTY_POOL",
50
+ /** A `defineSynthProfile` spec was not usable (a missing or blank `name`). Fatal. */
51
+ SYNTH_INVALID_PROFILE: "SYNTH_INVALID_PROFILE",
52
+ /**
53
+ * A caller-supplied selector (a message kind, a document type, a corpus mix entry, a claim
54
+ * variant, a Bundle type, a resource profile) is not in the closed set that governs it. Fatal —
55
+ * see `resolveKind`: a selector union is erased at run time, and a selector that falls through
56
+ * either mislabels the fixture or hands the value to a peer builder that quotes it back.
57
+ */
58
+ SYNTH_UNSUPPORTED_KIND: "SYNTH_UNSUPPORTED_KIND"
59
+ };
60
+ var SYNTH_FATAL_MESSAGES = Object.freeze({
61
+ 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.",
62
+ 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).",
63
+ 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.",
64
+ 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.",
65
+ SYNTH_UNMAPPED_CODE_SYSTEM: "The concept's code-system URI has no OID mapping in the C-CDA example-code table.",
66
+ SYNTH_INVALID_DECIMAL: "The value could not be read as an X12 decimal.",
67
+ SYNTH_INVALID_RANGE: "An integer range was requested with its maximum below its minimum.",
68
+ SYNTH_EMPTY_POOL: "A value was drawn from an empty pool.",
69
+ SYNTH_INVALID_PROFILE: "defineSynthProfile requires a non-empty string name.",
70
+ 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."
71
+ });
72
+ var SynthError = class extends Error {
73
+ /** The stable fatal code. */
74
+ code;
75
+ /**
76
+ * @param code - The stable {@link SynthFatalCode}. The message comes from the frozen registry.
77
+ */
78
+ constructor(code) {
79
+ super(SYNTH_FATAL_MESSAGES[code]);
80
+ this.name = "SynthError";
81
+ this.code = code;
82
+ }
83
+ };
84
+
34
85
  // src/rng/rng.ts
35
86
  var Sfc32Rng = class {
36
87
  seed;
@@ -48,7 +99,7 @@ var Sfc32Rng = class {
48
99
  return this.nextUint32() / 4294967296;
49
100
  }
50
101
  int(min, max) {
51
- if (max < min) throw new RangeError(`Rng.int: max (${String(max)}) < min (${String(min)})`);
102
+ if (max < min) throw new SynthError(SYNTH_FATAL_CODES.SYNTH_INVALID_RANGE);
52
103
  const span = max - min + 1;
53
104
  return min + Math.floor(this.float() * span);
54
105
  }
@@ -56,7 +107,7 @@ var Sfc32Rng = class {
56
107
  return this.float() < p;
57
108
  }
58
109
  pick(items) {
59
- if (items.length === 0) throw new RangeError("Rng.pick: empty array");
110
+ if (items.length === 0) throw new SynthError(SYNTH_FATAL_CODES.SYNTH_EMPTY_POOL);
60
111
  return items[this.int(0, items.length - 1)];
61
112
  }
62
113
  digits(n) {
@@ -447,10 +498,22 @@ function pidSegment(id) {
447
498
  ];
448
499
  }
449
500
 
501
+ // src/select.ts
502
+ function resolveKind(allowed, requested) {
503
+ const match = allowed.find((value) => value === requested);
504
+ if (match === void 0) throw new SynthError(SYNTH_FATAL_CODES.SYNTH_UNSUPPORTED_KIND);
505
+ return match;
506
+ }
507
+ function resolveMix(allowed, requested, fallback) {
508
+ if (requested === void 0) return fallback;
509
+ return requested.map((entry) => resolveKind(allowed, entry));
510
+ }
511
+
450
512
  // src/hl7/adt.ts
513
+ var ADT_TRIGGERS = Object.freeze(["A01", "A04", "A08"]);
451
514
  function generateAdt(options = {}) {
452
515
  const seed = options.seed ?? 0;
453
- const trigger = options.trigger ?? "A01";
516
+ const trigger = resolveKind(ADT_TRIGGERS, options.trigger ?? "A01");
454
517
  const rng = createRng(seed);
455
518
  const { message, timestamp } = mshScaffold(rng, `ADT^${trigger}`);
456
519
  message.addSegment("EVN", [trigger, timestamp]);
@@ -737,7 +800,7 @@ function roundTrip(message) {
737
800
  // src/profile.ts
738
801
  function defineSynthProfile(spec) {
739
802
  if (typeof spec.name !== "string" || spec.name.trim().length === 0) {
740
- throw new TypeError("defineSynthProfile: `name` is required and must be a non-empty string.");
803
+ throw new SynthError(SYNTH_FATAL_CODES.SYNTH_INVALID_PROFILE);
741
804
  }
742
805
  return Object.freeze({
743
806
  name: spec.name,
@@ -747,28 +810,6 @@ function defineSynthProfile(spec) {
747
810
  });
748
811
  }
749
812
 
750
- // src/codes.ts
751
- var SYNTH_FATAL_CODES = {
752
- /**
753
- * A vendor quirk was requested that the target format's profile system does not support. Fatal —
754
- * never a silent no-op and never a fabricated quirk.
755
- */
756
- SYNTH_UNSUPPORTED_QUIRK: "SYNTH_UNSUPPORTED_QUIRK"
757
- };
758
- var SynthError = class extends Error {
759
- /** The stable fatal code. */
760
- code;
761
- /**
762
- * @param code - The stable {@link SynthFatalCode}.
763
- * @param message - A human-readable detail (never contains PHI — there is none).
764
- */
765
- constructor(code, message) {
766
- super(message);
767
- this.name = "SynthError";
768
- this.code = code;
769
- }
770
- };
771
-
772
813
  // src/quirk.ts
773
814
  var PROFILE_QUIRK_APPLIED = "PROFILE_QUIRK_APPLIED";
774
815
  function sameCodeSet(a, b) {
@@ -785,12 +826,8 @@ function sameCodeSet(a, b) {
785
826
  }
786
827
  function resolveQuirk(registry, format, name2) {
787
828
  const descriptor = registry[name2];
788
- if (descriptor === void 0) {
789
- const supported = Object.keys(registry).sort().join(", ");
790
- throw new SynthError(
791
- SYNTH_FATAL_CODES.SYNTH_UNSUPPORTED_QUIRK,
792
- `${format}: unsupported quirk "${name2}". The ${format} profile system supports: ${supported}.`
793
- );
829
+ if (descriptor === void 0 || descriptor.format !== format) {
830
+ throw new SynthError(SYNTH_FATAL_CODES.SYNTH_UNSUPPORTED_QUIRK);
794
831
  }
795
832
  return descriptor;
796
833
  }
@@ -805,11 +842,9 @@ function profileTolerated(disposition, intendedWarnings, warningsUnderProfile) {
805
842
  return false;
806
843
  }
807
844
  }
808
- function assertIntendedWarnings(quirk, intendedWarnings, bareWarnings) {
845
+ function assertIntendedWarnings(intendedWarnings, bareWarnings) {
809
846
  if (!sameCodeSet(bareWarnings, intendedWarnings)) {
810
- throw new Error(
811
- `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.`
812
- );
847
+ throw new SynthError(SYNTH_FATAL_CODES.SYNTH_INTENDED_WARNING_MISMATCH);
813
848
  }
814
849
  }
815
850
  function validateProfileQuirks(profile, registry, format) {
@@ -850,6 +885,15 @@ function applyQuirk(quirk, wire) {
850
885
  return [...segments, "NTE|1||\\Zff\\"].join("\r");
851
886
  }
852
887
  }
888
+ var ALL_QUIRK_KINDS = Object.freeze([
889
+ "ADT^A01",
890
+ "ADT^A04",
891
+ "ADT^A08",
892
+ "ORU^R01",
893
+ "ORM^O01",
894
+ "SIU^S12",
895
+ "VXU^V04"
896
+ ]);
853
897
  function baseMessage(kind, seed) {
854
898
  switch (kind) {
855
899
  case "ADT^A01":
@@ -870,11 +914,10 @@ function baseMessage(kind, seed) {
870
914
  }
871
915
  function generateHl7Quirk(options) {
872
916
  const seed = options.seed ?? 0;
873
- const kind = options.kind ?? "ORU^R01";
917
+ const kind = resolveKind(ALL_QUIRK_KINDS, options.kind ?? "ORU^R01");
874
918
  const descriptor = resolveQuirk(HL7_QUIRKS, "hl7v2", options.quirk);
875
919
  const content = applyQuirk(options.quirk, baseMessage(kind, seed).toString());
876
920
  assertIntendedWarnings(
877
- descriptor.name,
878
921
  descriptor.intendedWarnings,
879
922
  hl7.parseHL7(content).warnings.map((w) => String(w.code))
880
923
  );
@@ -920,6 +963,7 @@ var ALL_HL7_QUIRKS = Object.freeze(
920
963
  function hl7QuirkCorpus(options) {
921
964
  const quirks = options.profile ? validateProfileQuirks(options.profile, HL7_QUIRKS, "hl7v2") : options.quirks ?? ALL_HL7_QUIRKS;
922
965
  const names = quirks.length > 0 ? quirks : ALL_HL7_QUIRKS;
966
+ for (const name2 of names) resolveQuirk(HL7_QUIRKS, "hl7v2", name2);
923
967
  const kind = options.kind ?? "ORU^R01";
924
968
  const count = options.count ?? names.length;
925
969
  const seedStream = createRng(options.seed);
@@ -942,7 +986,7 @@ var hl7QuirkProfile = defineSynthProfile({
942
986
  });
943
987
 
944
988
  // src/hl7/index.ts
945
- var DEFAULT_MIX = Object.freeze([
989
+ var ALL_KINDS = Object.freeze([
946
990
  "ADT^A01",
947
991
  "ADT^A04",
948
992
  "ADT^A08",
@@ -951,8 +995,10 @@ var DEFAULT_MIX = Object.freeze([
951
995
  "SIU^S12",
952
996
  "VXU^V04"
953
997
  ]);
998
+ var DEFAULT_MIX = ALL_KINDS;
999
+ var ADT_TRIGGERS2 = Object.freeze(["A01", "A04", "A08"]);
954
1000
  function generateHl7(kind, seed) {
955
- switch (kind) {
1001
+ switch (resolveKind(ALL_KINDS, kind)) {
956
1002
  case "ADT^A01":
957
1003
  return generateAdt({ seed, trigger: "A01" });
958
1004
  case "ADT^A04":
@@ -971,7 +1017,9 @@ function generateHl7(kind, seed) {
971
1017
  }
972
1018
  function hl7Corpus(options) {
973
1019
  const { seed, count = 1 } = options;
974
- const kinds = options.triggers !== void 0 ? options.triggers.map((t) => `ADT^${t}`) : options.mix ?? DEFAULT_MIX;
1020
+ const kinds = options.triggers !== void 0 ? options.triggers.map(
1021
+ (t) => `ADT^${resolveKind(ADT_TRIGGERS2, t)}`
1022
+ ) : resolveMix(ALL_KINDS, options.mix, DEFAULT_MIX);
975
1023
  const seedStream = createRng(seed);
976
1024
  const artifacts = Array.from({ length: count }, (_unused, i) => {
977
1025
  const kind = kinds[i % kinds.length] ?? "ADT^A01";