@cosyte/synth 0.1.9 → 0.1.11

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 +39 -0
  2. package/README.md +39 -19
  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 +147 -17
  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 +147 -17
  10. package/dist/astm/index.mjs.map +1 -1
  11. package/dist/ccda/index.cjs +147 -17
  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 +147 -17
  16. package/dist/ccda/index.mjs.map +1 -1
  17. package/dist/deid/index.cjs +537 -30
  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 +537 -30
  22. package/dist/deid/index.mjs.map +1 -1
  23. package/dist/fhir/index.cjs +142 -14
  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 +142 -14
  28. package/dist/fhir/index.mjs.map +1 -1
  29. package/dist/hl7/index.cjs +566 -39
  30. package/dist/hl7/index.cjs.map +1 -1
  31. package/dist/hl7/index.d.cts +416 -116
  32. package/dist/hl7/index.d.ts +416 -116
  33. package/dist/hl7/index.mjs +557 -40
  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 +142 -14
  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 +142 -14
  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 +142 -14
  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 +142 -14
  54. package/dist/x12/index.mjs.map +1 -1
  55. package/package.json +9 -5
@@ -464,7 +464,10 @@ interface X12CorpusOptions {
464
464
  readonly seed: number;
465
465
  /** How many transactions to generate. Defaults to the length of the mix. */
466
466
  readonly count?: number;
467
- /** The transaction kinds to cycle through. Defaults to one of each. */
467
+ /**
468
+ * The transaction kinds to cycle through. Defaults to one of each. An empty list throws
469
+ * `SYNTH_EMPTY_POOL`.
470
+ */
468
471
  readonly mix?: readonly X12CorpusKind[];
469
472
  /**
470
473
  * Whether each artifact is read back through its parser and the parser's findings recorded as its
@@ -464,7 +464,10 @@ interface X12CorpusOptions {
464
464
  readonly seed: number;
465
465
  /** How many transactions to generate. Defaults to the length of the mix. */
466
466
  readonly count?: number;
467
- /** The transaction kinds to cycle through. Defaults to one of each. */
467
+ /**
468
+ * The transaction kinds to cycle through. Defaults to one of each. An empty list throws
469
+ * `SYNTH_EMPTY_POOL`.
470
+ */
468
471
  readonly mix?: readonly X12CorpusKind[];
469
472
  /**
470
473
  * Whether each artifact is read back through its parser and the parser's findings recorded as its
@@ -70,7 +70,11 @@ var SYNTH_FATAL_CODES = Object.freeze({
70
70
  SYNTH_INVALID_DECIMAL: "SYNTH_INVALID_DECIMAL",
71
71
  /** An integer range was requested with its maximum below its minimum. Fatal. */
72
72
  SYNTH_INVALID_RANGE: "SYNTH_INVALID_RANGE",
73
- /** A value was drawn from an empty pool. Fatal, never a fabricated substitute. */
73
+ /**
74
+ * A value was drawn from an empty pool. Fatal, never a fabricated substitute. A corpus given an
75
+ * empty `mix`, `triggers` or `quirks` list (or a profile whose `quirks` is an empty list) raises it
76
+ * too: there is no kind to cycle through, and an empty list is never read as the default.
77
+ */
74
78
  SYNTH_EMPTY_POOL: "SYNTH_EMPTY_POOL",
75
79
  /** A `defineSynthProfile` spec was not usable (a missing or blank `name`). Fatal. */
76
80
  SYNTH_INVALID_PROFILE: "SYNTH_INVALID_PROFILE",
@@ -89,7 +93,21 @@ var SYNTH_FATAL_CODES = Object.freeze({
89
93
  * apart without matching message text. Fatal, and raised **before** anything is generated: a
90
94
  * mislabelled artifact is worse than no artifact.
91
95
  */
92
- SYNTH_PROFILE_NOT_GENERATED: "SYNTH_PROFILE_NOT_GENERATED"
96
+ SYNTH_PROFILE_NOT_GENERATED: "SYNTH_PROFILE_NOT_GENERATED",
97
+ /**
98
+ * The sibling parser a generator builds through is not installed. Raised on the first call that
99
+ * needs it, by both builds, only when the missing package is the parser itself: a missing
100
+ * dependency of an installed parser is thrown as Node.js raised it. The error carries the parser's
101
+ * package name on `peer`, a message from {@link SYNTH_PEER_MESSAGES} that names its install
102
+ * command, and the error Node.js raised on `cause`.
103
+ */
104
+ SYNTH_PEER_NOT_INSTALLED: "SYNTH_PEER_NOT_INSTALLED",
105
+ /**
106
+ * An HL7 v2 `ACK` was asked for an inbound message that carries no MSH-10 message control id.
107
+ * MSA-2 must echo that id, and an acknowledgment that cannot be matched to the message it answers
108
+ * is not a fixture worth having, so nothing is generated.
109
+ */
110
+ SYNTH_ACK_UNCORRELATED: "SYNTH_ACK_UNCORRELATED"
93
111
  });
94
112
  var SYNTH_FATAL_MESSAGES = Object.freeze({
95
113
  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.",
@@ -102,18 +120,38 @@ var SYNTH_FATAL_MESSAGES = Object.freeze({
102
120
  SYNTH_EMPTY_POOL: "A value was drawn from an empty pool.",
103
121
  SYNTH_INVALID_PROFILE: "defineSynthProfile requires a non-empty string name.",
104
122
  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.",
105
- 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."
123
+ 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.",
124
+ SYNTH_PEER_NOT_INSTALLED: "A sibling @cosyte parser this generator builds through is not installed. Install it beside @cosyte/synth.",
125
+ SYNTH_ACK_UNCORRELATED: (
126
+ // Stryker disable next-line all: a module-level table runs once, at import; the docs test holds it.
127
+ "The inbound message has no MSH-10 control id for an ACK to echo in MSA-2."
128
+ )
129
+ });
130
+ var SYNTH_PEER_MESSAGES = Object.freeze({
131
+ "@cosyte/hl7": "@cosyte/hl7 is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/hl7",
132
+ "@cosyte/fhir": "@cosyte/fhir is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/fhir",
133
+ "@cosyte/ccda": "@cosyte/ccda is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/ccda",
134
+ "@cosyte/x12": "@cosyte/x12 is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/x12",
135
+ "@cosyte/ncpdp": "@cosyte/ncpdp is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/ncpdp",
136
+ "@cosyte/astm": "@cosyte/astm is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/astm",
137
+ "@cosyte/deid": "@cosyte/deid is not installed, and this generator builds through it. Install it beside @cosyte/synth: npm install @cosyte/deid"
106
138
  });
107
139
  var SynthError = class extends Error {
108
140
  /** The stable fatal code. */
109
141
  code;
110
142
  /**
111
143
  * @param code - The stable {@link SynthFatalCode}. The message comes from the frozen registry.
144
+ * @param options - The error that caused this one, and for a missing parser its package.
112
145
  */
113
- constructor(code) {
114
- super(SYNTH_FATAL_MESSAGES[code]);
146
+ constructor(code, options = {}) {
147
+ const peer = code === SYNTH_FATAL_CODES.SYNTH_PEER_NOT_INSTALLED ? options.peer : void 0;
148
+ super(
149
+ peer === void 0 ? SYNTH_FATAL_MESSAGES[code] : SYNTH_PEER_MESSAGES[peer],
150
+ "cause" in options ? { cause: options.cause } : void 0
151
+ );
115
152
  this.name = "SynthError";
116
153
  this.code = code;
154
+ if (peer !== void 0) Object.defineProperty(this, "peer", { value: peer, enumerable: true });
117
155
  }
118
156
  };
119
157
 
@@ -180,19 +218,44 @@ function defaultHost() {
180
218
  if (typeof import.meta.resolve !== "function") return { require: __require, resolve: void 0 };
181
219
  return { require: createRequire(import.meta.url), resolve: (s) => import.meta.resolve(s) };
182
220
  }
221
+ function isDenoResolveRefusal(error) {
222
+ return error instanceof TypeError && !("code" in error) && error.message === "Invalid arguments";
223
+ }
183
224
  function isRequireEsmRefusal(error) {
184
225
  return error?.code === "ERR_REQUIRE_ESM";
185
226
  }
227
+ function peerPackage(specifier) {
228
+ const name2 = specifier.split("/").slice(0, 2).join("/");
229
+ return Object.hasOwn(SYNTH_PEER_MESSAGES, name2) ? name2 : void 0;
230
+ }
231
+ function isPeerNotFound(error, specifier, peer) {
232
+ const { code, message } = error ?? {};
233
+ if (typeof message !== "string") return false;
234
+ if (code === "ERR_MODULE_NOT_FOUND") return message.startsWith(`Cannot find package '${peer}'`);
235
+ if (code === "MODULE_NOT_FOUND") return message.startsWith(`Cannot find module '${specifier}'`);
236
+ return false;
237
+ }
186
238
  function loadPeer(specifier, host) {
239
+ try {
240
+ return loadFrom(specifier, host);
241
+ } catch (error) {
242
+ const peer = peerPackage(specifier);
243
+ if (peer === void 0 || !isPeerNotFound(error, specifier, peer)) throw error;
244
+ throw new SynthError(SYNTH_FATAL_CODES.SYNTH_PEER_NOT_INSTALLED, { peer, cause: error });
245
+ }
246
+ }
247
+ function loadFrom(specifier, host) {
187
248
  if (host.resolve === void 0) return host.require(specifier);
188
- let entry;
249
+ let url;
189
250
  try {
190
- entry = fileURLToPath(host.resolve(specifier));
191
- } catch {
251
+ url = host.resolve(specifier);
252
+ } catch (error) {
253
+ if (!isDenoResolveRefusal(error)) throw error;
192
254
  return host.require(specifier);
193
255
  }
256
+ if (!url.startsWith("file:")) return host.require(specifier);
194
257
  try {
195
- return host.require(entry);
258
+ return host.require(fileURLToPath(url));
196
259
  } catch (error) {
197
260
  if (!isRequireEsmRefusal(error)) throw error;
198
261
  return host.require(specifier);
@@ -258,7 +321,7 @@ function luhnMod10(digits) {
258
321
  if (d < 0 || d > 9) continue;
259
322
  if (double) {
260
323
  d *= 2;
261
- if (d > 9) d -= 9;
324
+ if (d >= 10) d -= 9;
262
325
  }
263
326
  sum += d;
264
327
  double = !double;
@@ -355,7 +418,7 @@ var SYNTHETIC_CITY_NAMES = Object.freeze([
355
418
  ]);
356
419
 
357
420
  // src/safe/providers.ts
358
- function ssn(rng, block = "never-issued") {
421
+ function ssn(rng, block) {
359
422
  if (block === "advertising") {
360
423
  return `987-00-432${String(rng.int(0, 9))}`;
361
424
  }
@@ -482,6 +545,63 @@ var US_STATES = Object.freeze([
482
545
  "WY"
483
546
  ]);
484
547
 
548
+ // src/safe/animal-code.ts
549
+ var ANIMAL_CODE_SYSTEM = "99COSYTE";
550
+ function animalCode(value, text) {
551
+ return Object.freeze({ code: value, text, system: ANIMAL_CODE_SYSTEM });
552
+ }
553
+ function animalSpecies(value, text, breeds) {
554
+ return Object.freeze({ ...animalCode(value, text), breeds: Object.freeze([...breeds]) });
555
+ }
556
+
557
+ // src/safe/animal.ts
558
+ var SYNTHETIC_ANIMAL_SPECIES = Object.freeze([
559
+ animalSpecies("DOG", "Dog", [
560
+ animalCode("DOG-LAB", "Labrador Retriever"),
561
+ animalCode("DOG-BGL", "Beagle"),
562
+ animalCode("DOG-MIX", "Mixed breed dog")
563
+ ]),
564
+ animalSpecies("CAT", "Cat", [animalCode("CAT-DSH", "Domestic Shorthair"), animalCode("CAT-SIA", "Siamese")]),
565
+ animalSpecies("HORSE", "Horse", [animalCode("HRS-QTR", "Quarter Horse"), animalCode("HRS-TB", "Thoroughbred")]),
566
+ animalSpecies("CATTLE", "Cattle", [animalCode("CTL-HOL", "Holstein"), animalCode("CTL-ANG", "Angus")])
567
+ ]);
568
+ var SYNTHETIC_PET_NAMES = Object.freeze([
569
+ "Fixturepaw",
570
+ "Mockwhisker",
571
+ "Stubtail",
572
+ "Seedpup",
573
+ "Testbiscuit",
574
+ "Placepurr",
575
+ "Synthfur",
576
+ "Dummymane",
577
+ "Examplehoof",
578
+ "Fauxfluff",
579
+ "Samplesnout",
580
+ "Voidwag"
581
+ ]);
582
+ var MICROCHIP_TEST_PREFIX = "999";
583
+ function petName(rng) {
584
+ return rng.pick(SYNTHETIC_PET_NAMES);
585
+ }
586
+ function animalBreed(rng) {
587
+ const picked = rng.pick(SYNTHETIC_ANIMAL_SPECIES);
588
+ const { breeds, ...bare } = picked;
589
+ return { species: Object.freeze(bare), breed: rng.pick(breeds) };
590
+ }
591
+ function microchip(rng) {
592
+ return `${MICROCHIP_TEST_PREFIX}${rng.digits(12)}`;
593
+ }
594
+ function animalBirthDate(rng, onDate) {
595
+ const year = Number(onDate.slice(0, 4));
596
+ const month = Number(onDate.slice(4, 6)) - 1;
597
+ const day = Number(onDate.slice(6, 8));
598
+ const latest = Date.UTC(year, month, day);
599
+ const earliest = Date.UTC(year - 20, month, day);
600
+ const span = Math.round((latest - earliest) / 864e5);
601
+ const born = new Date(latest - rng.int(0, span) * 864e5);
602
+ return born.toISOString().slice(0, 10).replace(/-/g, "");
603
+ }
604
+
485
605
  // src/safe/index.ts
486
606
  var safe = Object.freeze({
487
607
  ssn,
@@ -495,7 +615,11 @@ var safe = Object.freeze({
495
615
  address,
496
616
  dateYmd,
497
617
  npi,
498
- dea
618
+ dea,
619
+ petName,
620
+ animalBreed,
621
+ microchip,
622
+ animalBirthDate
499
623
  });
500
624
 
501
625
  // src/x12/identity.ts
@@ -617,6 +741,10 @@ function resolveMix(allowed, requested, fallback) {
617
741
  if (requested === void 0) return fallback;
618
742
  return requested.map((entry) => resolveKind(allowed, entry));
619
743
  }
744
+ function requireSelection(selection) {
745
+ if (selection.length === 0) throw new SynthError(SYNTH_FATAL_CODES.SYNTH_EMPTY_POOL);
746
+ return selection;
747
+ }
620
748
 
621
749
  // src/x12/claim-837.ts
622
750
  var CLAIM_837_VARIANTS = Object.freeze(["P", "I", "D"]);
@@ -1053,11 +1181,11 @@ function generateKind(kind, seed) {
1053
1181
  }
1054
1182
  function x12Corpus(options) {
1055
1183
  const { seed, verify = true } = options;
1056
- const mix = resolveMix(ALL_KINDS, options.mix, DEFAULT_MIX);
1184
+ const mix = requireSelection(resolveMix(ALL_KINDS, options.mix, DEFAULT_MIX));
1057
1185
  const count = options.count ?? mix.length;
1058
1186
  const seedStream = createRng(seed);
1059
1187
  const artifacts = Array.from({ length: count }, (_unused, i) => {
1060
- const kind = mix[i % mix.length] ?? "837P";
1188
+ const kind = mix[i % mix.length];
1061
1189
  const txSeed = seedStream.nextUint32();
1062
1190
  const rt = x12CorpusArtifact(generateKind(kind, txSeed), verify);
1063
1191
  return {