@cosyte/synth 0.0.1

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 (58) hide show
  1. package/CHANGELOG.md +414 -0
  2. package/LICENSE +21 -0
  3. package/README.md +325 -0
  4. package/dist/astm/index.cjs +847 -0
  5. package/dist/astm/index.cjs.map +1 -0
  6. package/dist/astm/index.d.cts +418 -0
  7. package/dist/astm/index.d.ts +418 -0
  8. package/dist/astm/index.mjs +828 -0
  9. package/dist/astm/index.mjs.map +1 -0
  10. package/dist/ccda/index.cjs +1103 -0
  11. package/dist/ccda/index.cjs.map +1 -0
  12. package/dist/ccda/index.d.cts +380 -0
  13. package/dist/ccda/index.d.ts +380 -0
  14. package/dist/ccda/index.mjs +1077 -0
  15. package/dist/ccda/index.mjs.map +1 -0
  16. package/dist/deid/index.cjs +2809 -0
  17. package/dist/deid/index.cjs.map +1 -0
  18. package/dist/deid/index.d.cts +464 -0
  19. package/dist/deid/index.d.ts +464 -0
  20. package/dist/deid/index.mjs +2793 -0
  21. package/dist/deid/index.mjs.map +1 -0
  22. package/dist/example-codes-DeXcnCSK.d.cts +105 -0
  23. package/dist/example-codes-DeXcnCSK.d.ts +105 -0
  24. package/dist/fhir/index.cjs +1429 -0
  25. package/dist/fhir/index.cjs.map +1 -0
  26. package/dist/fhir/index.d.cts +772 -0
  27. package/dist/fhir/index.d.ts +772 -0
  28. package/dist/fhir/index.mjs +1384 -0
  29. package/dist/fhir/index.mjs.map +1 -0
  30. package/dist/hl7/index.cjs +1012 -0
  31. package/dist/hl7/index.cjs.map +1 -0
  32. package/dist/hl7/index.d.cts +548 -0
  33. package/dist/hl7/index.d.ts +548 -0
  34. package/dist/hl7/index.mjs +990 -0
  35. package/dist/hl7/index.mjs.map +1 -0
  36. package/dist/index.cjs +535 -0
  37. package/dist/index.cjs.map +1 -0
  38. package/dist/index.d.cts +407 -0
  39. package/dist/index.d.ts +407 -0
  40. package/dist/index.mjs +488 -0
  41. package/dist/index.mjs.map +1 -0
  42. package/dist/ncpdp/index.cjs +714 -0
  43. package/dist/ncpdp/index.cjs.map +1 -0
  44. package/dist/ncpdp/index.d.cts +432 -0
  45. package/dist/ncpdp/index.d.ts +432 -0
  46. package/dist/ncpdp/index.mjs +695 -0
  47. package/dist/ncpdp/index.mjs.map +1 -0
  48. package/dist/providers-OLz3zAc-.d.cts +343 -0
  49. package/dist/providers-OLz3zAc-.d.ts +343 -0
  50. package/dist/quirk-DmkgoZdh.d.cts +239 -0
  51. package/dist/quirk-JLyO1Ncj.d.ts +239 -0
  52. package/dist/x12/index.cjs +920 -0
  53. package/dist/x12/index.cjs.map +1 -0
  54. package/dist/x12/index.d.cts +484 -0
  55. package/dist/x12/index.d.ts +484 -0
  56. package/dist/x12/index.mjs +892 -0
  57. package/dist/x12/index.mjs.map +1 -0
  58. package/package.json +210 -0
@@ -0,0 +1,343 @@
1
+ /**
2
+ * The `Corpus` abstraction — a seed plus a self-describing manifest of what was generated, so a
3
+ * fixture set is itself reproducible and regenerable. A downstream repo pins a seed
4
+ * and gets a stable fixture set that regenerates identically.
5
+ *
6
+ * Generated artifacts and the `Corpus` are **deep-frozen** — this is where the archetype's immutability
7
+ * invariant lives in a generator: a consumer cannot mutate a shared fixture out from under
8
+ * another test.
9
+ *
10
+ * @module
11
+ */
12
+ /** The format an artifact was generated for. */
13
+ type SynthFormat = "hl7v2" | "fhir" | "ccda" | "x12" | "ncpdp" | "astm";
14
+ /**
15
+ * One generated artifact — the serialized wire text plus the metadata needed to reproduce and check
16
+ * it. `warnings` records what the artifact's own parser reported on the round-trip (zero for a
17
+ * spec-clean artifact).
18
+ */
19
+ interface Artifact {
20
+ /** The format this artifact belongs to. */
21
+ readonly format: SynthFormat;
22
+ /** A format-specific kind label (e.g. `"ADT^A01"`). */
23
+ readonly kind: string;
24
+ /** The serialized wire text, produced by the parser's own conservative serializer. */
25
+ readonly content: string;
26
+ /** The warning codes the parser emitted when the artifact was round-tripped (empty = spec-clean). */
27
+ readonly warnings: readonly string[];
28
+ }
29
+ /** A self-describing manifest of a {@link Corpus}. */
30
+ interface CorpusManifest {
31
+ /** The formats present in the corpus. */
32
+ readonly formats: readonly SynthFormat[];
33
+ /** Per-kind artifact counts (e.g. `{ "ADT^A01": 3 }`). */
34
+ readonly counts: Readonly<Record<string, number>>;
35
+ /** The quirk names applied. */
36
+ readonly quirks: readonly string[];
37
+ }
38
+ /** A reproducible, self-describing set of generated artifacts. */
39
+ interface Corpus {
40
+ /** The seed the corpus was generated from — regenerating from it yields byte-identical artifacts. */
41
+ readonly seed: number;
42
+ /** The manifest describing what was generated. */
43
+ readonly manifest: CorpusManifest;
44
+ /** The generated artifacts, in generation order. */
45
+ readonly artifacts: readonly Artifact[];
46
+ }
47
+ /**
48
+ * Assemble a deep-frozen {@link Corpus} from a seed and its artifacts, deriving the manifest.
49
+ *
50
+ * @param seed - The seed the artifacts were generated from.
51
+ * @param artifacts - The generated artifacts, in order.
52
+ * @param quirks - The quirk names applied (default none).
53
+ * @returns A deep-frozen, self-describing {@link Corpus}.
54
+ * @example
55
+ * ```ts
56
+ * import { makeCorpus } from "@cosyte/synth";
57
+ * const corpus = makeCorpus(1, [{ format: "hl7v2", kind: "ADT^A01", content, warnings: [] }]);
58
+ * corpus.manifest.counts["ADT^A01"]; // 1
59
+ * ```
60
+ */
61
+ declare function makeCorpus(seed: number, artifacts: readonly Artifact[], quirks?: readonly string[]): Corpus;
62
+
63
+ /**
64
+ * `Rng` — the seeded, deterministic random source every `@cosyte/synth` provider draws from.
65
+ *
66
+ * **The reproducibility contract.** A seed — and only the seed — determines the output.
67
+ * `createRng(seed)` expands the integer seed through {@link ./splitmix32.splitmix32} into the four
68
+ * `sfc32` state words, then every draw advances that state via {@link ./sfc32.sfc32Next}. Two `Rng`s
69
+ * created from the same seed emit the **identical** sequence on any machine, any run — the property
70
+ * the parsers', `transform`'s, and `deid`'s regression suites depend on.
71
+ *
72
+ * **Explicit, never global.** An `Rng` is a value you thread through a build; there is no ambient
73
+ * shared generator and **`Math.random` is lint-banned** in `src/` (it is not seedable — its seed is
74
+ * engine-chosen and cannot be reset, so a corpus built on it is not reproducible). Because each
75
+ * generation creates a fresh `Rng` from its seed, generations are independent and parallel-safe.
76
+ *
77
+ * The `Rng` object is stateful by nature (a PRNG advances). Immutability in this library lives where it
78
+ * is testable and matters: the generated **artifacts and the `Corpus` are deep-frozen** (see
79
+ * `../corpus.ts`). Determinism, not object-immutability, is the `Rng`'s guarantee.
80
+ *
81
+ * @module
82
+ */
83
+ /**
84
+ * A seeded, deterministic random source. Created via {@link createRng}; passed explicitly to every
85
+ * provider. All draw methods advance the internal state deterministically.
86
+ */
87
+ interface Rng {
88
+ /** The integer seed this generator was created from (part of the `Corpus` manifest). */
89
+ readonly seed: number;
90
+ /** The next unsigned 32-bit integer. */
91
+ nextUint32(): number;
92
+ /** The next float in `[0, 1)`. */
93
+ float(): number;
94
+ /**
95
+ * A uniformly-distributed integer in the inclusive range `[min, max]`.
96
+ *
97
+ * @param min - Inclusive lower bound (integer).
98
+ * @param max - Inclusive upper bound (integer, `>= min`).
99
+ */
100
+ int(min: number, max: number): number;
101
+ /** `true` with probability `p` (default `0.5`). */
102
+ bool(p?: number): boolean;
103
+ /**
104
+ * Pick one element from a non-empty array.
105
+ *
106
+ * @param items - A non-empty readonly array.
107
+ */
108
+ pick<T>(items: readonly T[]): T;
109
+ /**
110
+ * A string of `n` decimal digits (`0`–`9`), each drawn uniformly.
111
+ *
112
+ * @param n - The number of digits (`>= 0`).
113
+ */
114
+ digits(n: number): string;
115
+ }
116
+ /**
117
+ * Create a seeded, deterministic {@link Rng}. The same `seed` yields the same sequence everywhere.
118
+ *
119
+ * @param seed - The integer seed. Coerced to a 32-bit integer.
120
+ * @returns A fresh, independent {@link Rng}.
121
+ * @example
122
+ * ```ts
123
+ * import { createRng } from "@cosyte/synth";
124
+ * const rng = createRng(12345);
125
+ * rng.int(1, 6); // deterministic for seed 12345
126
+ * ```
127
+ */
128
+ declare function createRng(seed: number): Rng;
129
+
130
+ /**
131
+ * The synthetic-safety provider layer — every identifier, contact point, name, and date
132
+ * `@cosyte/synth` emits is minted here, and **only** from a guaranteed-non-colliding source. There is no code
133
+ * path that returns a value not drawn from a reserved range or the
134
+ * shipped fake-name pool. This is the inverse of a parser's liberality: the generator is *closed-world*
135
+ * on its data sources, so no output *can* be real or plausibly-real PHI.
136
+ *
137
+ * All providers are pure functions of an explicit {@link ../rng/rng.Rng} — same seed, same values.
138
+ *
139
+ * @module
140
+ */
141
+
142
+ /** A synthetic person name drawn from the shipped fake-name pool. */
143
+ interface SyntheticName {
144
+ /** A clearly-fake given name. */
145
+ readonly given: string;
146
+ /** A clearly-fake family name. */
147
+ readonly family: string;
148
+ }
149
+ /** A synthetic postal address — synthetic street + city, a fixed non-real ZIP. */
150
+ interface SyntheticAddress {
151
+ /** A clearly-fake street line. */
152
+ readonly street: string;
153
+ /** A clearly-fake city. */
154
+ readonly city: string;
155
+ /** A US state abbreviation (structural only; never combined with a real street + name + DOB). */
156
+ readonly state: string;
157
+ /** A reserved non-real ZIP (`00000`). */
158
+ readonly zip: string;
159
+ }
160
+ /** A synthetic identifier scoped to the synthetic assigning authority. */
161
+ interface SyntheticIdentifier {
162
+ /** The identifier value (digits) — unique only within the synthetic namespace. */
163
+ readonly value: string;
164
+ /** HL7 identifier type code (`MR` = medical record, `AN` = account, `MB` = member). */
165
+ readonly typeCode: "MR" | "AN" | "MB";
166
+ /** The synthetic assigning-authority namespace id. */
167
+ readonly assigningAuthority: string;
168
+ /** The synthetic assigning-authority OID (HL7 example arc). */
169
+ readonly assigningAuthorityOid: string;
170
+ }
171
+ /** Which SSN reserved space to draw from. */
172
+ type SsnBlock = "never-issued" | "advertising";
173
+ /**
174
+ * A **synthetic SSN** — dashed `AAA-GG-SSSS`. Default draws the SSA never-issued area space
175
+ * (`900–999`); `block: "advertising"` draws SSA's reserved advertising block (`987-65-4320…4329`).
176
+ * A value from this function can never be a real SSN.
177
+ *
178
+ * @param rng - The seeded generator.
179
+ * @param block - Which reserved space to draw from. Defaults to `"never-issued"`.
180
+ * @returns A dashed synthetic SSN string.
181
+ * @example
182
+ * ```ts
183
+ * import { createRng, ssn } from "@cosyte/synth";
184
+ * ssn(createRng(1)); // e.g. a 900-area, never-issued SSN
185
+ * ```
186
+ */
187
+ declare function ssn(rng: Rng, block?: SsnBlock): string;
188
+ /**
189
+ * A **synthetic phone** in the NANP reserved fictional block — `(AAA) 555-01NN`. The reserved
190
+ * guarantee is the `555-01NN` tail (exchange 555, line 0100–0199); the area code is any NANP-valid
191
+ * `NXX`. Can never be a working number.
192
+ *
193
+ * @param rng - The seeded generator.
194
+ * @returns A formatted synthetic phone string.
195
+ * @example
196
+ * ```ts
197
+ * import { createRng, phone } from "@cosyte/synth";
198
+ * phone(createRng(1)); // e.g. "(2XX) 555-01NN"
199
+ * ```
200
+ */
201
+ declare function phone(rng: Rng): string;
202
+ /**
203
+ * A **synthetic name** drawn from the shipped clearly-fake pool.
204
+ *
205
+ * @param rng - The seeded generator.
206
+ * @returns A {@link SyntheticName}.
207
+ * @example
208
+ * ```ts
209
+ * import { createRng, name } from "@cosyte/synth";
210
+ * const { given, family } = name(createRng(1));
211
+ * ```
212
+ */
213
+ declare function name(rng: Rng): SyntheticName;
214
+ /**
215
+ * A **synthetic email** at an RFC 2606 / 6761 reserved domain — `<slug>@example.com`.
216
+ *
217
+ * @param rng - The seeded generator.
218
+ * @param person - Optional name to derive the local-part slug from; otherwise a random slug is used.
219
+ * @returns A synthetic email address.
220
+ * @example
221
+ * ```ts
222
+ * import { createRng, email, name } from "@cosyte/synth";
223
+ * email(createRng(1), name(createRng(1))); // "<given>.<family>@example.com"
224
+ * ```
225
+ */
226
+ declare function email(rng: Rng, person?: SyntheticName): string;
227
+ /**
228
+ * A **synthetic IPv4** in an RFC 5737 TEST-NET block — never routable.
229
+ *
230
+ * @param rng - The seeded generator.
231
+ * @returns A TEST-NET IPv4 address string.
232
+ * @example
233
+ * ```ts
234
+ * import { createRng, ipv4 } from "@cosyte/synth";
235
+ * ipv4(createRng(1)); // e.g. "192.0.2.NN"
236
+ * ```
237
+ */
238
+ declare function ipv4(rng: Rng): string;
239
+ /**
240
+ * A **synthetic IPv6** in the RFC 3849 documentation prefix `2001:db8::/32` — never routable.
241
+ *
242
+ * @param rng - The seeded generator.
243
+ * @returns A documentation-prefix IPv6 address string.
244
+ * @example
245
+ * ```ts
246
+ * import { createRng, ipv6 } from "@cosyte/synth";
247
+ * ipv6(createRng(1)); // e.g. "2001:db8::NNNN"
248
+ * ```
249
+ */
250
+ declare function ipv6(rng: Rng): string;
251
+ /**
252
+ * A **deterministic UUIDv4-shaped** surrogate key from the seeded generator. Because it is seeded (not
253
+ * from `node:crypto`, which is not reproducible), the cryptographic non-collision argument is weaker —
254
+ * acceptable because the identifier namespace is synthetic anyway, and noted honestly.
255
+ *
256
+ * @param rng - The seeded generator.
257
+ * @returns A canonical `8-4-4-4-12` lowercase-hex UUID string with version `4` and RFC 4122 variant.
258
+ * @example
259
+ * ```ts
260
+ * import { createRng, uuid } from "@cosyte/synth";
261
+ * uuid(createRng(1)); // "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"
262
+ * ```
263
+ */
264
+ declare function uuid(rng: Rng): string;
265
+ /**
266
+ * A **synthetic NPI** — a 10-digit National Provider Identifier with a **deliberately-invalid Luhn
267
+ * check digit**, so it can never be a NPPES-issued NPI (a real NPI must satisfy the `80840`-prefixed
268
+ * Luhn check). The 9-digit base is drawn from the seeded generator; the check digit is
269
+ * set to `(correct + 1) mod 10`, guaranteeing the full value fails validation.
270
+ *
271
+ * @param rng - The seeded generator.
272
+ * @returns A 10-digit NPI-shaped string that is provably not a real NPI.
273
+ * @example
274
+ * ```ts
275
+ * import { createRng, npi, isSyntheticNpi } from "@cosyte/synth";
276
+ * isSyntheticNpi(npi(createRng(1))); // true — invalid check digit by construction
277
+ * ```
278
+ */
279
+ declare function npi(rng: Rng): string;
280
+ /**
281
+ * A **synthetic DEA number** — `<registrant-type><initial>` + 7 digits with a **deliberately-invalid
282
+ * checksum**, so it can never be a validly-issued DEA registration (a real DEA number's 7th digit
283
+ * satisfies the published DEA checksum). The first letter is a registrant-type letter, the
284
+ * second is derived from `person` (its family initial) when supplied so the number reads plausibly; the
285
+ * 6-digit base is seeded and the check digit is set to `(correct + 1) mod 10`, guaranteeing the value
286
+ * fails validation. NCPDP carries prescriber DEA, and this is the identity locus a refuter attacks
287
+ * hardest, so — like {@link npi} — non-collision is a construction-level guarantee, not a heuristic.
288
+ *
289
+ * @param rng - The seeded generator.
290
+ * @param person - Optional name whose family initial becomes the DEA's second letter.
291
+ * @returns A DEA-shaped string that is provably not a real DEA registration.
292
+ * @example
293
+ * ```ts
294
+ * import { createRng, dea, isSyntheticDea } from "@cosyte/synth";
295
+ * isSyntheticDea(dea(createRng(1))); // true — invalid checksum by construction
296
+ * ```
297
+ */
298
+ declare function dea(rng: Rng, person?: SyntheticName): string;
299
+ /**
300
+ * A **synthetic identifier** (MRN / account / member id) scoped to the synthetic assigning authority.
301
+ * There is no reserved MRN range, so non-collision is guaranteed by the *namespace*, not the value: the
302
+ * identifier lives under a `SYNTH` authority no real facility uses.
303
+ *
304
+ * @param rng - The seeded generator.
305
+ * @param typeCode - The HL7 identifier type: `MR` (default), `AN`, or `MB`.
306
+ * @returns A {@link SyntheticIdentifier}.
307
+ * @example
308
+ * ```ts
309
+ * import { createRng, identifier } from "@cosyte/synth";
310
+ * identifier(createRng(1), "MR"); // { value, typeCode: "MR", assigningAuthority: "COSYTE-SYNTH", ... }
311
+ * ```
312
+ */
313
+ declare function identifier(rng: Rng, typeCode?: SyntheticIdentifier["typeCode"]): SyntheticIdentifier;
314
+ /**
315
+ * A **synthetic address** — a fake street + city, a reserved non-real ZIP (`00000`). A real state
316
+ * abbreviation may appear (structural only) but is never combined with a real street + name + DOB.
317
+ *
318
+ * @param rng - The seeded generator.
319
+ * @returns A {@link SyntheticAddress}.
320
+ * @example
321
+ * ```ts
322
+ * import { createRng, address } from "@cosyte/synth";
323
+ * address(createRng(1)); // { street, city, state, zip: "00000" }
324
+ * ```
325
+ */
326
+ declare function address(rng: Rng): SyntheticAddress;
327
+ /**
328
+ * A **synthetic date** in HL7 `YYYYMMDD` form, drawn uniformly within an inclusive year range. Comes
329
+ * from the seeded generator (never wall-clock), so it is reproducible and implies no real event.
330
+ *
331
+ * @param rng - The seeded generator.
332
+ * @param minYear - Inclusive lower year bound (default `1930`).
333
+ * @param maxYear - Inclusive upper year bound (default `2010`).
334
+ * @returns An `YYYYMMDD` date string (always a valid calendar day).
335
+ * @example
336
+ * ```ts
337
+ * import { createRng, dateYmd } from "@cosyte/synth";
338
+ * dateYmd(createRng(1), 1970, 2000); // "YYYYMMDD"
339
+ * ```
340
+ */
341
+ declare function dateYmd(rng: Rng, minYear?: number, maxYear?: number): string;
342
+
343
+ export { type Artifact as A, type Corpus as C, type Rng as R, type SyntheticName as S, type SyntheticIdentifier as a, type SyntheticAddress as b, ipv6 as c, identifier as d, email as e, address as f, dateYmd as g, npi as h, ipv4 as i, dea as j, type CorpusManifest as k, type SsnBlock as l, type SynthFormat as m, name as n, createRng as o, phone as p, makeCorpus as q, ssn as s, uuid as u };
@@ -0,0 +1,239 @@
1
+ import { m as SynthFormat } from './providers-OLz3zAc-.cjs';
2
+
3
+ /**
4
+ * `defineSynthProfile` — the growth-loop hook for site/vendor fixture recipes. A profile bundles the
5
+ * value pools and the quirk recipe a fixture set should use, authored through the same public API as
6
+ * the built-ins: a validated, frozen `SynthProfile` carrying a name, optional value overrides, and the
7
+ * quirk names a format's quirk corpus should apply.
8
+ *
9
+ * @module
10
+ */
11
+ /** The user-authored spec passed to {@link defineSynthProfile}. */
12
+ interface SynthProfileSpec {
13
+ /** A stable, human-readable profile name (e.g. `"acme-hospital"`). Required, non-empty. */
14
+ readonly name: string;
15
+ /** Optional given-name pool override (clearly-synthetic names only — see the safety invariant). */
16
+ readonly givenNames?: readonly string[];
17
+ /** Optional family-name pool override (clearly-synthetic names only). */
18
+ readonly familyNames?: readonly string[];
19
+ /**
20
+ * The vendor quirk recipe names this profile requests. Validated against the target format's quirk
21
+ * registry when the profile drives a quirk corpus (an unsupported quirk is a fatal
22
+ * `SYNTH_UNSUPPORTED_QUIRK`, never a silent no-op).
23
+ */
24
+ readonly quirks?: readonly string[];
25
+ }
26
+ /** A frozen, validated fixture recipe produced by {@link defineSynthProfile}. */
27
+ interface SynthProfile {
28
+ /** The profile name. */
29
+ readonly name: string;
30
+ /** The given-name pool this profile draws from (overrides or the built-in default). */
31
+ readonly givenNames?: readonly string[];
32
+ /** The family-name pool this profile draws from. */
33
+ readonly familyNames?: readonly string[];
34
+ /** The requested quirk recipe names. */
35
+ readonly quirks: readonly string[];
36
+ }
37
+ /**
38
+ * Define a reusable, frozen synthetic-fixture profile.
39
+ *
40
+ * @param spec - The profile spec; `name` is required and non-empty.
41
+ * @returns A deep-frozen {@link SynthProfile}.
42
+ * @throws TypeError when `name` is missing or blank.
43
+ * @example
44
+ * ```ts
45
+ * import { defineSynthProfile } from "@cosyte/synth";
46
+ * const acme = defineSynthProfile({ name: "acme-hospital", quirks: [] });
47
+ * ```
48
+ */
49
+ declare function defineSynthProfile(spec: SynthProfileSpec): SynthProfile;
50
+
51
+ /**
52
+ * The **quirk core**. Where the spec-clean generators prove
53
+ * *synthetic-by-construction* through each parser's own builder, the quirk layer proves the mirror
54
+ * property: a **deliberately off-spec** fixture round-trips to **exactly the intended parser warning
55
+ * code(s)** — no more, no fewer. The quirk vocabulary **is the parsers' own profile systems**
56
+ * (`hl7.defineProfile`, `ccda.defineCcdaProfile`, `astm.defineAstmProfile`): a quirk exercises exactly
57
+ * the tolerance the corresponding parser profile encodes, so a quirk fixture is never a fiction — it
58
+ * targets a documented, coded leniency (the **intended-warning contract**).
59
+ *
60
+ * This module is the **format-agnostic** part: the descriptor a quirk carries, the artifact a quirk
61
+ * generator returns, the round-trip verdict shape, and the `SYNTH_UNSUPPORTED_QUIRK` fail-closed. Each
62
+ * format's concrete quirk recipes + transforms live behind its own subpath (`@cosyte/synth/hl7`, …).
63
+ *
64
+ * @module
65
+ */
66
+
67
+ /**
68
+ * How the parser's matching profile treats a quirk once it is active — the three shapes the parsers'
69
+ * profile systems actually exhibit (verified firsthand against each parser):
70
+ *
71
+ * - `"suppressed"` — the profile makes the warning **disappear** (HL7 v2: a `defineProfile`
72
+ * `customSegments` claim suppresses `UNKNOWN_SEGMENT` for a declared Z-segment).
73
+ * - `"rebadged"` — the profile **downgrades** the warning to the value-free `PROFILE_QUIRK_APPLIED`
74
+ * marker with `expected: true` (C-CDA `defineCcdaProfile` / ASTM `defineAstmProfile`
75
+ * `profileQuirkApplied`).
76
+ * - `"bare"` — no shipped profile tolerates it; the quirk targets a real coded leniency a consumer can
77
+ * tolerate via their own `defineProfile`/`defineAstmProfile`, but no built-in re-badges it.
78
+ */
79
+ type QuirkProfileDisposition = "suppressed" | "rebadged" | "bare";
80
+ /**
81
+ * The stable, value-free re-badge code the C-CDA and ASTM parsers emit when a profile tolerates a
82
+ * quirk. HL7 v2 has no equivalent (it suppresses instead — see {@link QuirkProfileDisposition}).
83
+ */
84
+ declare const PROFILE_QUIRK_APPLIED = "PROFILE_QUIRK_APPLIED";
85
+ /**
86
+ * A public, grounded description of one vendor quirk — the metadata that binds a quirk recipe to a real
87
+ * parser warning code and a **publicly-groundable** deviation (cited-public, never a private
88
+ * vendor corpus).
89
+ */
90
+ interface QuirkDescriptor {
91
+ /** The quirk recipe name (e.g. `"unknown-zsegment"`). Stable; part of the public contract. */
92
+ readonly name: string;
93
+ /** The format this quirk applies to. */
94
+ readonly format: SynthFormat;
95
+ /**
96
+ * The **exact** parser warning code(s) a bare parse (no profile) surfaces for this quirk — the
97
+ * intended-warning contract. A quirk that produces any other code, or none, is a generation bug.
98
+ */
99
+ readonly intendedWarnings: readonly string[];
100
+ /**
101
+ * The **public** grounding for this quirk — the spec clause or the parser's public profile that
102
+ * documents the tolerance. Never a private vendor-attributed corpus.
103
+ */
104
+ readonly grounding: string;
105
+ /** The parser profile that tolerates this quirk (when a built-in public one exists). */
106
+ readonly toleratingProfile?: string;
107
+ /** How {@link toleratingProfile} treats the quirk. */
108
+ readonly disposition: QuirkProfileDisposition;
109
+ }
110
+ /** One generated quirk artifact — the off-spec wire text plus the contract it is meant to satisfy. */
111
+ interface QuirkArtifact {
112
+ /** The format this artifact belongs to. */
113
+ readonly format: SynthFormat;
114
+ /** The quirk recipe applied. */
115
+ readonly quirk: string;
116
+ /** The underlying spec-clean message kind the quirk was injected into (e.g. `"ORU^R01"`). */
117
+ readonly kind: string;
118
+ /** The **quirked** wire text (deterministic in the seed + quirk). */
119
+ readonly content: string;
120
+ /** The exact parser warning code(s) this artifact is meant to round-trip to. */
121
+ readonly intendedWarnings: readonly string[];
122
+ }
123
+ /** The verdict of a bare parse under the tolerating profile, if any. */
124
+ interface QuirkProfiledVerdict {
125
+ /** The profile applied. */
126
+ readonly profileName: string;
127
+ /** How the profile treats the quirk. */
128
+ readonly disposition: QuirkProfileDisposition;
129
+ /** The warning codes the parser emitted with the profile active. */
130
+ readonly warnings: readonly string[];
131
+ /**
132
+ * `true` iff the profile handled the quirk as its disposition declares: `"suppressed"` ⇒ the intended
133
+ * code is gone; `"rebadged"` ⇒ the intended code is gone and `PROFILE_QUIRK_APPLIED` is present.
134
+ */
135
+ readonly tolerated: boolean;
136
+ }
137
+ /** The verdict of round-tripping a quirk artifact through its parser. */
138
+ interface QuirkRoundTripResult {
139
+ /** The quirked wire text that was parsed. */
140
+ readonly content: string;
141
+ /** The warning codes a **bare** parse (no profile) emitted. */
142
+ readonly warnings: readonly string[];
143
+ /** The exact code(s) the quirk is meant to produce. */
144
+ readonly intendedWarnings: readonly string[];
145
+ /**
146
+ * `true` iff the bare parse produced **exactly** the intended code(s) — the intended-warning contract.
147
+ */
148
+ readonly intendedWarningHeld: boolean;
149
+ /** The verdict under the tolerating profile, when a built-in public one exists. */
150
+ readonly withProfile?: QuirkProfiledVerdict;
151
+ }
152
+ /**
153
+ * Exact multiset (order-independent) equality of two code lists — the intended-warning comparison.
154
+ *
155
+ * @param a - The first code list.
156
+ * @param b - The second code list.
157
+ * @returns `true` iff the two lists contain the same codes with the same multiplicities.
158
+ * @example
159
+ * ```ts
160
+ * import { sameCodeSet } from "@cosyte/synth";
161
+ * sameCodeSet(["A", "B"], ["B", "A"]); // true
162
+ * ```
163
+ */
164
+ declare function sameCodeSet(a: readonly string[], b: readonly string[]): boolean;
165
+ /**
166
+ * Resolve a requested quirk name against a format's registry, or **fail closed**. A quirk the format's
167
+ * profile system does not support is a fatal `SYNTH_UNSUPPORTED_QUIRK` — never a silent no-op and never
168
+ * a fabricated quirk with a made-up warning.
169
+ *
170
+ * @param registry - The format's quirk descriptors, keyed by name.
171
+ * @param format - The format being generated (for the error message).
172
+ * @param name - The requested quirk name.
173
+ * @returns The matching {@link QuirkDescriptor}.
174
+ * @throws SynthError with code `SYNTH_UNSUPPORTED_QUIRK` when `name` is not a supported quirk.
175
+ * @example
176
+ * ```ts
177
+ * import { resolveQuirk } from "@cosyte/synth";
178
+ * import { HL7_QUIRKS } from "@cosyte/synth/hl7";
179
+ * resolveQuirk(HL7_QUIRKS, "hl7v2", "unknown-zsegment").intendedWarnings; // ["UNKNOWN_SEGMENT"]
180
+ * ```
181
+ */
182
+ declare function resolveQuirk(registry: Readonly<Record<string, QuirkDescriptor>>, format: SynthFormat, name: string): QuirkDescriptor;
183
+ /**
184
+ * Evaluate whether a profiled parse tolerated a quirk as its disposition declares. Shared across the
185
+ * formats so the "suppressed vs re-badged" logic lives in exactly one place.
186
+ *
187
+ * @param disposition - The quirk's declared profile disposition.
188
+ * @param intendedWarnings - The bare-parse intended code(s).
189
+ * @param warningsUnderProfile - The code(s) the parser emitted with the profile active.
190
+ * @returns `true` iff the profile handled the quirk correctly for its disposition.
191
+ * @example
192
+ * ```ts
193
+ * import { profileTolerated } from "@cosyte/synth";
194
+ * profileTolerated("suppressed", ["UNKNOWN_SEGMENT"], []); // true — the profile suppressed it
195
+ * ```
196
+ */
197
+ declare function profileTolerated(disposition: QuirkProfileDisposition, intendedWarnings: readonly string[], warningsUnderProfile: readonly string[]): boolean;
198
+ /**
199
+ * Assert a freshly-generated quirk artifact **actually** round-trips to its intended warning(s), or
200
+ * **fail closed**. This is the generator's self-check on the intended-warning contract: a
201
+ * fixture whose bare parse does not produce exactly the declared code(s) is a *mislabeled* fixture — a
202
+ * golden file that lies about the parser verdict it anchors — and must never be emitted. It is a
203
+ * stronger guard than "the transform changed some bytes": a transform can mutate the wrong element (a
204
+ * template a given document type does not key its warning on) and still change bytes while producing no
205
+ * warning. Every format's `generate*Quirk` calls this after transforming, so the contract is enforced at
206
+ * generation time, not merely at round-trip time.
207
+ *
208
+ * @param quirk - The quirk name (for the error message).
209
+ * @param intendedWarnings - The declared intended code(s).
210
+ * @param bareWarnings - The code(s) a bare parse of the generated artifact actually produced.
211
+ * @throws Error when the bare parse did not produce exactly the intended code(s).
212
+ * @example
213
+ * ```ts
214
+ * import { assertIntendedWarnings } from "@cosyte/synth";
215
+ * assertIntendedWarnings("unknown-zsegment", ["UNKNOWN_SEGMENT"], ["UNKNOWN_SEGMENT"]); // ok
216
+ * ```
217
+ */
218
+ declare function assertIntendedWarnings(quirk: string, intendedWarnings: readonly string[], bareWarnings: readonly string[]): void;
219
+ /**
220
+ * Validate the quirk names carried by a {@link SynthProfile} against a format's registry, failing closed
221
+ * on the first unsupported one. Lets a consumer author a fixture recipe with `defineSynthProfile` and
222
+ * have its quirks checked against the *parser's* real tolerance before any fixture is generated.
223
+ *
224
+ * @param profile - The synth profile whose `quirks` to validate.
225
+ * @param registry - The format's quirk descriptors.
226
+ * @param format - The format being generated.
227
+ * @returns The validated quirk names (the profile's, in order).
228
+ * @throws SynthError `SYNTH_UNSUPPORTED_QUIRK` for the first unsupported quirk.
229
+ * @example
230
+ * ```ts
231
+ * import { validateProfileQuirks, defineSynthProfile } from "@cosyte/synth";
232
+ * import { HL7_QUIRKS } from "@cosyte/synth/hl7";
233
+ * const p = defineSynthProfile({ name: "site", quirks: ["unknown-zsegment"] });
234
+ * validateProfileQuirks(p, HL7_QUIRKS, "hl7v2"); // ["unknown-zsegment"]
235
+ * ```
236
+ */
237
+ declare function validateProfileQuirks(profile: SynthProfile, registry: Readonly<Record<string, QuirkDescriptor>>, format: SynthFormat): readonly string[];
238
+
239
+ export { PROFILE_QUIRK_APPLIED as P, type QuirkDescriptor as Q, type SynthProfile as S, type QuirkArtifact as a, type QuirkRoundTripResult as b, type QuirkProfileDisposition as c, type QuirkProfiledVerdict as d, type SynthProfileSpec as e, assertIntendedWarnings as f, defineSynthProfile as g, profileTolerated as p, resolveQuirk as r, sameCodeSet as s, validateProfileQuirks as v };