@cosyte/synth 0.1.8 → 0.1.10

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 (55) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +28 -17
  3. package/dist/animal-code-Bdl_9Jma.d.cts +30 -0
  4. package/dist/animal-code-Bdl_9Jma.d.ts +30 -0
  5. package/dist/astm/index.cjs +150 -15
  6. package/dist/astm/index.cjs.map +1 -1
  7. package/dist/astm/index.d.cts +22 -9
  8. package/dist/astm/index.d.ts +22 -9
  9. package/dist/astm/index.mjs +150 -15
  10. package/dist/astm/index.mjs.map +1 -1
  11. package/dist/ccda/index.cjs +150 -15
  12. package/dist/ccda/index.cjs.map +1 -1
  13. package/dist/ccda/index.d.cts +12 -3
  14. package/dist/ccda/index.d.ts +12 -3
  15. package/dist/ccda/index.mjs +150 -15
  16. package/dist/ccda/index.mjs.map +1 -1
  17. package/dist/deid/index.cjs +540 -28
  18. package/dist/deid/index.cjs.map +1 -1
  19. package/dist/deid/index.d.cts +2 -1
  20. package/dist/deid/index.d.ts +2 -1
  21. package/dist/deid/index.mjs +540 -28
  22. package/dist/deid/index.mjs.map +1 -1
  23. package/dist/fhir/index.cjs +145 -12
  24. package/dist/fhir/index.cjs.map +1 -1
  25. package/dist/fhir/index.d.cts +4 -1
  26. package/dist/fhir/index.d.ts +4 -1
  27. package/dist/fhir/index.mjs +145 -12
  28. package/dist/fhir/index.mjs.map +1 -1
  29. package/dist/hl7/index.cjs +569 -37
  30. package/dist/hl7/index.cjs.map +1 -1
  31. package/dist/hl7/index.d.cts +402 -111
  32. package/dist/hl7/index.d.ts +402 -111
  33. package/dist/hl7/index.mjs +560 -38
  34. package/dist/hl7/index.mjs.map +1 -1
  35. package/dist/index.cjs +124 -13
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.d.cts +183 -10
  38. package/dist/index.d.ts +183 -10
  39. package/dist/index.mjs +115 -14
  40. package/dist/index.mjs.map +1 -1
  41. package/dist/ncpdp/index.cjs +145 -12
  42. package/dist/ncpdp/index.cjs.map +1 -1
  43. package/dist/ncpdp/index.d.cts +4 -1
  44. package/dist/ncpdp/index.d.ts +4 -1
  45. package/dist/ncpdp/index.mjs +145 -12
  46. package/dist/ncpdp/index.mjs.map +1 -1
  47. package/dist/{quirk-BfObsiWV.d.ts → quirk-CzknuYUo.d.ts} +16 -5
  48. package/dist/{quirk-BCSs1jPt.d.cts → quirk-Q5AxzHSC.d.cts} +16 -5
  49. package/dist/x12/index.cjs +145 -12
  50. package/dist/x12/index.cjs.map +1 -1
  51. package/dist/x12/index.d.cts +4 -1
  52. package/dist/x12/index.d.ts +4 -1
  53. package/dist/x12/index.mjs +145 -12
  54. package/dist/x12/index.mjs.map +1 -1
  55. package/package.json +17 -6
@@ -73,7 +73,11 @@ var SYNTH_FATAL_CODES = Object.freeze({
73
73
  SYNTH_INVALID_DECIMAL: "SYNTH_INVALID_DECIMAL",
74
74
  /** An integer range was requested with its maximum below its minimum. Fatal. */
75
75
  SYNTH_INVALID_RANGE: "SYNTH_INVALID_RANGE",
76
- /** A value was drawn from an empty pool. Fatal, never a fabricated substitute. */
76
+ /**
77
+ * A value was drawn from an empty pool. Fatal, never a fabricated substitute. A corpus given an
78
+ * empty `mix`, `triggers` or `quirks` list (or a profile whose `quirks` is an empty list) raises it
79
+ * too: there is no kind to cycle through, and an empty list is never read as the default.
80
+ */
77
81
  SYNTH_EMPTY_POOL: "SYNTH_EMPTY_POOL",
78
82
  /** A `defineSynthProfile` spec was not usable (a missing or blank `name`). Fatal. */
79
83
  SYNTH_INVALID_PROFILE: "SYNTH_INVALID_PROFILE",
@@ -92,7 +96,21 @@ var SYNTH_FATAL_CODES = Object.freeze({
92
96
  * apart without matching message text. Fatal, and raised **before** anything is generated: a
93
97
  * mislabelled artifact is worse than no artifact.
94
98
  */
95
- SYNTH_PROFILE_NOT_GENERATED: "SYNTH_PROFILE_NOT_GENERATED"
99
+ SYNTH_PROFILE_NOT_GENERATED: "SYNTH_PROFILE_NOT_GENERATED",
100
+ /**
101
+ * The sibling parser a generator builds through is not installed. Raised on the first call that
102
+ * needs it, by both builds, only when the missing package is the parser itself: a missing
103
+ * dependency of an installed parser is thrown as Node.js raised it. The error carries the parser's
104
+ * package name on `peer`, a message from {@link SYNTH_PEER_MESSAGES} that names its install
105
+ * command, and the error Node.js raised on `cause`.
106
+ */
107
+ SYNTH_PEER_NOT_INSTALLED: "SYNTH_PEER_NOT_INSTALLED",
108
+ /**
109
+ * An HL7 v2 `ACK` was asked for an inbound message that carries no MSH-10 message control id.
110
+ * MSA-2 must echo that id, and an acknowledgment that cannot be matched to the message it answers
111
+ * is not a fixture worth having, so nothing is generated.
112
+ */
113
+ SYNTH_ACK_UNCORRELATED: "SYNTH_ACK_UNCORRELATED"
96
114
  });
97
115
  var SYNTH_FATAL_MESSAGES = Object.freeze({
98
116
  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.",
@@ -105,18 +123,38 @@ var SYNTH_FATAL_MESSAGES = Object.freeze({
105
123
  SYNTH_EMPTY_POOL: "A value was drawn from an empty pool.",
106
124
  SYNTH_INVALID_PROFILE: "defineSynthProfile requires a non-empty string name.",
107
125
  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.",
108
- SYNTH_PROFILE_NOT_GENERATED: "The requested profile is published by the adopted implementation guide, and this build does not generate it. The coverage surface reports, per adopted profile, whether it is generated."
126
+ SYNTH_PROFILE_NOT_GENERATED: "The requested profile is published by the adopted implementation guide, and this build does not generate it. The coverage surface reports, per adopted profile, whether it is generated.",
127
+ SYNTH_PEER_NOT_INSTALLED: "A sibling @cosyte parser this generator builds through is not installed. Install it beside @cosyte/synth.",
128
+ SYNTH_ACK_UNCORRELATED: (
129
+ // Stryker disable next-line all: a module-level table runs once, at import; the docs test holds it.
130
+ "The inbound message has no MSH-10 control id for an ACK to echo in MSA-2."
131
+ )
132
+ });
133
+ var SYNTH_PEER_MESSAGES = Object.freeze({
134
+ "@cosyte/hl7": "@cosyte/hl7 is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/hl7",
135
+ "@cosyte/fhir": "@cosyte/fhir is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/fhir",
136
+ "@cosyte/ccda": "@cosyte/ccda is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/ccda",
137
+ "@cosyte/x12": "@cosyte/x12 is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/x12",
138
+ "@cosyte/ncpdp": "@cosyte/ncpdp is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/ncpdp",
139
+ "@cosyte/astm": "@cosyte/astm is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/astm",
140
+ "@cosyte/deid": "@cosyte/deid is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/deid"
109
141
  });
110
142
  var SynthError = class extends Error {
111
143
  /** The stable fatal code. */
112
144
  code;
113
145
  /**
114
146
  * @param code - The stable {@link SynthFatalCode}. The message comes from the frozen registry.
147
+ * @param options - The error that caused this one, and for a missing parser its package.
115
148
  */
116
- constructor(code) {
117
- super(SYNTH_FATAL_MESSAGES[code]);
149
+ constructor(code, options = {}) {
150
+ const peer = code === SYNTH_FATAL_CODES.SYNTH_PEER_NOT_INSTALLED ? options.peer : void 0;
151
+ super(
152
+ peer === void 0 ? SYNTH_FATAL_MESSAGES[code] : SYNTH_PEER_MESSAGES[peer],
153
+ "cause" in options ? { cause: options.cause } : void 0
154
+ );
118
155
  this.name = "SynthError";
119
156
  this.code = code;
157
+ if (peer !== void 0) Object.defineProperty(this, "peer", { value: peer, enumerable: true });
120
158
  }
121
159
  };
122
160
 
@@ -202,7 +240,7 @@ function luhnMod10(digits) {
202
240
  if (d < 0 || d > 9) continue;
203
241
  if (double) {
204
242
  d *= 2;
205
- if (d > 9) d -= 9;
243
+ if (d >= 10) d -= 9;
206
244
  }
207
245
  sum += d;
208
246
  double = !double;
@@ -299,7 +337,7 @@ var SYNTHETIC_CITY_NAMES = Object.freeze([
299
337
  ]);
300
338
 
301
339
  // src/safe/providers.ts
302
- function ssn(rng, block = "never-issued") {
340
+ function ssn(rng, block) {
303
341
  if (block === "advertising") {
304
342
  return `987-00-432${String(rng.int(0, 9))}`;
305
343
  }
@@ -426,6 +464,63 @@ var US_STATES = Object.freeze([
426
464
  "WY"
427
465
  ]);
428
466
 
467
+ // src/safe/animal-code.ts
468
+ var ANIMAL_CODE_SYSTEM = "99COSYTE";
469
+ function animalCode(value, text) {
470
+ return Object.freeze({ code: value, text, system: ANIMAL_CODE_SYSTEM });
471
+ }
472
+ function animalSpecies(value, text, breeds) {
473
+ return Object.freeze({ ...animalCode(value, text), breeds: Object.freeze([...breeds]) });
474
+ }
475
+
476
+ // src/safe/animal.ts
477
+ var SYNTHETIC_ANIMAL_SPECIES = Object.freeze([
478
+ animalSpecies("DOG", "Dog", [
479
+ animalCode("DOG-LAB", "Labrador Retriever"),
480
+ animalCode("DOG-BGL", "Beagle"),
481
+ animalCode("DOG-MIX", "Mixed breed dog")
482
+ ]),
483
+ animalSpecies("CAT", "Cat", [animalCode("CAT-DSH", "Domestic Shorthair"), animalCode("CAT-SIA", "Siamese")]),
484
+ animalSpecies("HORSE", "Horse", [animalCode("HRS-QTR", "Quarter Horse"), animalCode("HRS-TB", "Thoroughbred")]),
485
+ animalSpecies("CATTLE", "Cattle", [animalCode("CTL-HOL", "Holstein"), animalCode("CTL-ANG", "Angus")])
486
+ ]);
487
+ var SYNTHETIC_PET_NAMES = Object.freeze([
488
+ "Fixturepaw",
489
+ "Mockwhisker",
490
+ "Stubtail",
491
+ "Seedpup",
492
+ "Testbiscuit",
493
+ "Placepurr",
494
+ "Synthfur",
495
+ "Dummymane",
496
+ "Examplehoof",
497
+ "Fauxfluff",
498
+ "Samplesnout",
499
+ "Voidwag"
500
+ ]);
501
+ var MICROCHIP_TEST_PREFIX = "999";
502
+ function petName(rng) {
503
+ return rng.pick(SYNTHETIC_PET_NAMES);
504
+ }
505
+ function animalBreed(rng) {
506
+ const picked = rng.pick(SYNTHETIC_ANIMAL_SPECIES);
507
+ const { breeds, ...bare } = picked;
508
+ return { species: Object.freeze(bare), breed: rng.pick(breeds) };
509
+ }
510
+ function microchip(rng) {
511
+ return `${MICROCHIP_TEST_PREFIX}${rng.digits(12)}`;
512
+ }
513
+ function animalBirthDate(rng, onDate) {
514
+ const year = Number(onDate.slice(0, 4));
515
+ const month = Number(onDate.slice(4, 6)) - 1;
516
+ const day = Number(onDate.slice(6, 8));
517
+ const latest = Date.UTC(year, month, day);
518
+ const earliest = Date.UTC(year - 20, month, day);
519
+ const span = Math.round((latest - earliest) / 864e5);
520
+ const born = new Date(latest - rng.int(0, span) * 864e5);
521
+ return born.toISOString().slice(0, 10).replace(/-/g, "");
522
+ }
523
+
429
524
  // src/safe/index.ts
430
525
  var safe = Object.freeze({
431
526
  ssn,
@@ -439,7 +534,11 @@ var safe = Object.freeze({
439
534
  address,
440
535
  dateYmd,
441
536
  npi,
442
- dea
537
+ dea,
538
+ petName,
539
+ animalBreed,
540
+ microchip,
541
+ animalBirthDate
443
542
  });
444
543
 
445
544
  // src/fhir/us-core.ts
@@ -564,14 +663,44 @@ function defaultHost() {
564
663
  if (typeof undefined !== "function") return { require: __require, resolve: void 0 };
565
664
  return { require: module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))), resolve: (s) => undefined(s) };
566
665
  }
666
+ function isDenoResolveRefusal(error) {
667
+ return error instanceof TypeError && !("code" in error) && error.message === "Invalid arguments";
668
+ }
567
669
  function isRequireEsmRefusal(error) {
568
670
  return error?.code === "ERR_REQUIRE_ESM";
569
671
  }
672
+ function peerPackage(specifier) {
673
+ const name2 = specifier.split("/").slice(0, 2).join("/");
674
+ return Object.hasOwn(SYNTH_PEER_MESSAGES, name2) ? name2 : void 0;
675
+ }
676
+ function isPeerNotFound(error, specifier, peer) {
677
+ const { code, message } = error ?? {};
678
+ if (typeof message !== "string") return false;
679
+ if (code === "ERR_MODULE_NOT_FOUND") return message.startsWith(`Cannot find package '${peer}'`);
680
+ if (code === "MODULE_NOT_FOUND") return message.startsWith(`Cannot find module '${specifier}'`);
681
+ return false;
682
+ }
570
683
  function loadPeer(specifier, host) {
684
+ try {
685
+ return loadFrom(specifier, host);
686
+ } catch (error) {
687
+ const peer = peerPackage(specifier);
688
+ if (peer === void 0 || !isPeerNotFound(error, specifier, peer)) throw error;
689
+ throw new SynthError(SYNTH_FATAL_CODES.SYNTH_PEER_NOT_INSTALLED, { peer, cause: error });
690
+ }
691
+ }
692
+ function loadFrom(specifier, host) {
571
693
  if (host.resolve === void 0) return host.require(specifier);
572
- const entry = url.fileURLToPath(host.resolve(specifier));
694
+ let url$1;
573
695
  try {
574
- return host.require(entry);
696
+ url$1 = host.resolve(specifier);
697
+ } catch (error) {
698
+ if (!isDenoResolveRefusal(error)) throw error;
699
+ return host.require(specifier);
700
+ }
701
+ if (!url$1.startsWith("file:")) return host.require(specifier);
702
+ try {
703
+ return host.require(url.fileURLToPath(url$1));
575
704
  } catch (error) {
576
705
  if (!isRequireEsmRefusal(error)) throw error;
577
706
  return host.require(specifier);
@@ -942,6 +1071,10 @@ function resolveMix(allowed, requested, fallback) {
942
1071
  if (requested === void 0) return fallback;
943
1072
  return requested.map((entry) => resolveKind(allowed, entry));
944
1073
  }
1074
+ function requireSelection(selection) {
1075
+ if (selection.length === 0) throw new SynthError(SYNTH_FATAL_CODES.SYNTH_EMPTY_POOL);
1076
+ return selection;
1077
+ }
945
1078
 
946
1079
  // src/fhir/patient.ts
947
1080
  var PATIENT_PROFILES = Object.freeze(["base", "us-core"]);
@@ -1687,10 +1820,10 @@ function generateKind(kind, seed) {
1687
1820
  }
1688
1821
  function fhirCorpus(options) {
1689
1822
  const { seed, count = 1, verify = true } = options;
1690
- const kinds = resolveMix(ALL_KINDS, options.mix, DEFAULT_MIX);
1823
+ const kinds = requireSelection(resolveMix(ALL_KINDS, options.mix, DEFAULT_MIX));
1691
1824
  const seedStream = createRng(seed);
1692
1825
  const artifacts = Array.from({ length: count }, (_unused, i) => {
1693
- const kind = kinds[i % kinds.length] ?? "USCorePatient";
1826
+ const kind = kinds[i % kinds.length];
1694
1827
  const rt = fhirCorpusArtifact(generateKind(kind, seedStream.nextUint32()), verify);
1695
1828
  return { format: "fhir", kind, content: rt.content, warnings: rt.warnings };
1696
1829
  });