@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.
- package/CHANGELOG.md +414 -0
- package/LICENSE +21 -0
- package/README.md +325 -0
- package/dist/astm/index.cjs +847 -0
- package/dist/astm/index.cjs.map +1 -0
- package/dist/astm/index.d.cts +418 -0
- package/dist/astm/index.d.ts +418 -0
- package/dist/astm/index.mjs +828 -0
- package/dist/astm/index.mjs.map +1 -0
- package/dist/ccda/index.cjs +1103 -0
- package/dist/ccda/index.cjs.map +1 -0
- package/dist/ccda/index.d.cts +380 -0
- package/dist/ccda/index.d.ts +380 -0
- package/dist/ccda/index.mjs +1077 -0
- package/dist/ccda/index.mjs.map +1 -0
- package/dist/deid/index.cjs +2809 -0
- package/dist/deid/index.cjs.map +1 -0
- package/dist/deid/index.d.cts +464 -0
- package/dist/deid/index.d.ts +464 -0
- package/dist/deid/index.mjs +2793 -0
- package/dist/deid/index.mjs.map +1 -0
- package/dist/example-codes-DeXcnCSK.d.cts +105 -0
- package/dist/example-codes-DeXcnCSK.d.ts +105 -0
- package/dist/fhir/index.cjs +1429 -0
- package/dist/fhir/index.cjs.map +1 -0
- package/dist/fhir/index.d.cts +772 -0
- package/dist/fhir/index.d.ts +772 -0
- package/dist/fhir/index.mjs +1384 -0
- package/dist/fhir/index.mjs.map +1 -0
- package/dist/hl7/index.cjs +1012 -0
- package/dist/hl7/index.cjs.map +1 -0
- package/dist/hl7/index.d.cts +548 -0
- package/dist/hl7/index.d.ts +548 -0
- package/dist/hl7/index.mjs +990 -0
- package/dist/hl7/index.mjs.map +1 -0
- package/dist/index.cjs +535 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +407 -0
- package/dist/index.d.ts +407 -0
- package/dist/index.mjs +488 -0
- package/dist/index.mjs.map +1 -0
- package/dist/ncpdp/index.cjs +714 -0
- package/dist/ncpdp/index.cjs.map +1 -0
- package/dist/ncpdp/index.d.cts +432 -0
- package/dist/ncpdp/index.d.ts +432 -0
- package/dist/ncpdp/index.mjs +695 -0
- package/dist/ncpdp/index.mjs.map +1 -0
- package/dist/providers-OLz3zAc-.d.cts +343 -0
- package/dist/providers-OLz3zAc-.d.ts +343 -0
- package/dist/quirk-DmkgoZdh.d.cts +239 -0
- package/dist/quirk-JLyO1Ncj.d.ts +239 -0
- package/dist/x12/index.cjs +920 -0
- package/dist/x12/index.cjs.map +1 -0
- package/dist/x12/index.d.cts +484 -0
- package/dist/x12/index.d.ts +484 -0
- package/dist/x12/index.mjs +892 -0
- package/dist/x12/index.mjs.map +1 -0
- 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 };
|