gdc-common-utils-ts 2.3.4 → 2.3.6

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.
@@ -38,6 +38,7 @@ export declare const ActorCapabilities: Readonly<{
38
38
  readonly IndividualImportIps: "individual.import_ips";
39
39
  readonly IndividualGenerateDigitalTwin: "individual.generate_digital_twin";
40
40
  readonly IndividualIngestCommunication: "individual.ingest_communication";
41
+ readonly IndividualReadClinicalSummary: "individual.read_clinical_summary";
41
42
  readonly IndividualUpsertRelatedPerson: "individual.upsert_related_person";
42
43
  readonly IndividualMemberDisable: "individual_member.disable";
43
44
  readonly IndividualMemberPurge: "individual_member.purge";
@@ -40,6 +40,7 @@ export const ActorCapabilities = Object.freeze({
40
40
  IndividualImportIps: 'individual.import_ips',
41
41
  IndividualGenerateDigitalTwin: 'individual.generate_digital_twin',
42
42
  IndividualIngestCommunication: 'individual.ingest_communication',
43
+ IndividualReadClinicalSummary: 'individual.read_clinical_summary',
43
44
  IndividualUpsertRelatedPerson: 'individual.upsert_related_person',
44
45
  IndividualMemberDisable: 'individual_member.disable',
45
46
  IndividualMemberPurge: 'individual_member.purge',
@@ -161,6 +162,12 @@ export const ActorCapabilityDocs = Object.freeze({
161
162
  programmingHint: 'Choose the route family carefully (`api`, `didcomm-plain`, `legacy-fhir`) to match the runtime transport profile.',
162
163
  relatedMethods: ['ingestCommunicationAndUpdateIndex'],
163
164
  },
165
+ [ActorCapabilities.IndividualReadClinicalSummary]: {
166
+ actorKind: ActorKinds.IndividualController,
167
+ summary: 'Reads the clinical summary currently available for one subject through an auditable Communication.',
168
+ programmingHint: 'Use requestClinicalSummary with Subject/$summary and attached FHIR Parameters. Do not route reads through ingestion methods.',
169
+ relatedMethods: ['requestClinicalSummary'],
170
+ },
164
171
  [ActorCapabilities.IndividualUpsertRelatedPerson]: {
165
172
  actorKind: ActorKinds.IndividualController,
166
173
  summary: 'Creates or updates one related-person/member relationship for the individual scope.',
@@ -234,7 +234,7 @@ export class BundleEntryEditor {
234
234
  resourceType: this.bundleEditor.getAllowedResourceType() || EmployeeResourceTypes.employee,
235
235
  meta: { claims: {} },
236
236
  };
237
- entry.resource.meta = entry.resource.meta || {};
237
+ entry.resource.meta = entry.resource.meta || { claims: {} };
238
238
  entry.resource.meta.claims = {
239
239
  ...(entry.resource.meta.claims || {}),
240
240
  [String(key).trim()]: cloneClaimValue(value),
@@ -264,7 +264,7 @@ export class BundleEntryEditor {
264
264
  resourceType: this.bundleEditor.getAllowedResourceType() || EmployeeResourceTypes.employee,
265
265
  meta: { claims: {} },
266
266
  };
267
- entry.resource.meta = entry.resource.meta || {};
267
+ entry.resource.meta = entry.resource.meta || { claims: {} };
268
268
  entry.resource.meta.claims = claims;
269
269
  return this;
270
270
  }
@@ -1,8 +1,25 @@
1
1
  import type { BundleEntry, BundleJsonApi } from '../models/bundle.js';
2
+ /** Canonical date interval shared by clinical Bundle read filters. */
3
+ export type BundleResourceDateFilter = Readonly<{
4
+ start?: string;
5
+ end?: string;
6
+ }>;
7
+ /**
8
+ * Filters Bundle resources by section, resource type and clinical date.
9
+ *
10
+ * Use `types` and `date` in new code. The flat names remain temporary
11
+ * compatibility aliases for callers created before the document readers were
12
+ * aligned with `FhirDocumentFacade`.
13
+ */
2
14
  export type BundleResourceIdFilters = Readonly<{
3
15
  sections?: string | readonly string[];
16
+ types?: string | readonly string[];
17
+ date?: BundleResourceDateFilter;
18
+ /** @deprecated Use `types`. */
4
19
  resourceTypes?: string | readonly string[];
20
+ /** @deprecated Use `date.start`. */
5
21
  dateFrom?: string;
22
+ /** @deprecated Use `date.end`. */
6
23
  dateTo?: string;
7
24
  }>;
8
25
  /**
@@ -82,7 +82,7 @@ export class BundleQuery {
82
82
  }
83
83
  matchesResourceFilters(entry, filters) {
84
84
  const resourceType = asTrimmedString(entry?.resource?.resourceType);
85
- const resourceTypeFilters = normalizeTokenInput(filters.resourceTypes);
85
+ const resourceTypeFilters = normalizeTokenInput(filters.types !== undefined ? filters.types : filters.resourceTypes);
86
86
  if (resourceTypeFilters.length > 0 && !resourceTypeFilters.includes(resourceType)) {
87
87
  return false;
88
88
  }
@@ -92,7 +92,9 @@ export class BundleQuery {
92
92
  return false;
93
93
  }
94
94
  const entryDate = this.resolveEntryDate(claims);
95
- if (!this.matchesDateRange(entryDate, filters.dateFrom, filters.dateTo)) {
95
+ const dateFrom = filters.date !== undefined ? filters.date.start : filters.dateFrom;
96
+ const dateTo = filters.date !== undefined ? filters.date.end : filters.dateTo;
97
+ if (!this.matchesDateRange(entryDate, dateFrom, dateTo)) {
96
98
  return false;
97
99
  }
98
100
  return true;
@@ -116,6 +118,11 @@ export class BundleQuery {
116
118
  const normalized = String(key || '').toLowerCase();
117
119
  if (normalized.endsWith('.date')
118
120
  || normalized.endsWith('.effective')
121
+ || normalized.endsWith('.effective-datetime')
122
+ || normalized.endsWith('.effective-period-start')
123
+ || normalized.endsWith('.onset-datetime')
124
+ || normalized.endsWith('.occurrence-datetime')
125
+ || normalized.endsWith('.recorded-date')
119
126
  || normalized.endsWith('.sent')
120
127
  || normalized.endsWith('.authored-on')) {
121
128
  const dateValue = asTrimmedString(value);
@@ -114,6 +114,21 @@ export declare class BundleReader {
114
114
  getDocumentSectionResourceCount(sectionCodeOrClaim: string): number;
115
115
  /** Returns bundle resource references listed under one document section. */
116
116
  getDocumentSectionResourceReferences(sectionCodeOrClaim: string): string[];
117
+ /**
118
+ * Returns stable resource IDs that both belong to one Composition section
119
+ * and match the optional resource-type/date filters.
120
+ *
121
+ * Use this after `$summary` when a screen or channel needs the concrete
122
+ * resources for one section. `getDocumentSectionResourceCount(...)` counts
123
+ * declared Composition references; this method resolves those references
124
+ * against the returned Bundle and can narrow the result.
125
+ */
126
+ getDocumentSectionResourceIds(sectionCodeOrClaim: string, filters?: BundleResourceIdFilters): string[];
127
+ /**
128
+ * Returns cloned Bundle entries for the resources selected from one
129
+ * Composition section, optionally filtered by type and inclusive date range.
130
+ */
131
+ getDocumentSectionResourceEntries(sectionCodeOrClaim: string, filters?: BundleResourceIdFilters): BundleReaderEntry[];
117
132
  /** Returns the active entry response status when present. */
118
133
  getEntryResponseStatus(): string | undefined;
119
134
  /** Returns all active entry issue severities. */
@@ -150,6 +165,7 @@ export declare class BundleReader {
150
165
  private buildEntrySummary;
151
166
  private buildSeverityBucket;
152
167
  private resolveEntryIdentifier;
168
+ private resolveEntryReferenceCandidates;
153
169
  }
154
170
  export declare function unwrapBundleLikeResponseBody(input: unknown): Record<string, unknown>;
155
171
  export declare function readFirstBundleResourceFromResponseBody(input: unknown): Record<string, unknown> | undefined;
@@ -136,7 +136,13 @@ export class BundleReader {
136
136
  }
137
137
  const entries = this.getEntries();
138
138
  for (let index = 0; index < entries.length; index += 1) {
139
- if (this.resolveEntryIdentifier(entries[index]) === normalizedIdentifier) {
139
+ const resource = asRecord(entries[index].resource);
140
+ const resourceType = asNonEmptyString(resource.resourceType) || 'resource';
141
+ const candidates = new Set([
142
+ ...this.resolveEntryReferenceCandidates(entries[index]),
143
+ `${resourceType}#${index}`,
144
+ ]);
145
+ if (candidates.has(normalizedIdentifier)) {
140
146
  return index;
141
147
  }
142
148
  }
@@ -235,7 +241,9 @@ export class BundleReader {
235
241
  if (!normalized) {
236
242
  return undefined;
237
243
  }
238
- return this.getDocumentSections().find((section) => section.claim === normalized || section.code === normalized);
244
+ const normalizedClaim = normalizeSectionClaim(normalized);
245
+ return this.getDocumentSections().find((section) => section.code === normalized
246
+ || (section.claim !== undefined && normalizeSectionClaim(section.claim) === normalizedClaim));
239
247
  }
240
248
  /** Returns the number of resource references inside one document section. */
241
249
  getDocumentSectionResourceCount(sectionCodeOrClaim) {
@@ -245,6 +253,30 @@ export class BundleReader {
245
253
  getDocumentSectionResourceReferences(sectionCodeOrClaim) {
246
254
  return [...(this.getDocumentSectionByCode(sectionCodeOrClaim)?.entryReferences || [])];
247
255
  }
256
+ /**
257
+ * Returns stable resource IDs that both belong to one Composition section
258
+ * and match the optional resource-type/date filters.
259
+ *
260
+ * Use this after `$summary` when a screen or channel needs the concrete
261
+ * resources for one section. `getDocumentSectionResourceCount(...)` counts
262
+ * declared Composition references; this method resolves those references
263
+ * against the returned Bundle and can narrow the result.
264
+ */
265
+ getDocumentSectionResourceIds(sectionCodeOrClaim, filters = {}) {
266
+ const references = new Set(this.getDocumentSectionResourceReferences(sectionCodeOrClaim));
267
+ if (references.size === 0) {
268
+ return [];
269
+ }
270
+ return this.getResourceIds(filters).filter((resourceId) => this.getEntriesByIds([resourceId]).some((entry) => this.resolveEntryReferenceCandidates(entry)
271
+ .some((reference) => references.has(reference))));
272
+ }
273
+ /**
274
+ * Returns cloned Bundle entries for the resources selected from one
275
+ * Composition section, optionally filtered by type and inclusive date range.
276
+ */
277
+ getDocumentSectionResourceEntries(sectionCodeOrClaim, filters = {}) {
278
+ return this.getEntriesByIds(this.getDocumentSectionResourceIds(sectionCodeOrClaim, filters));
279
+ }
248
280
  /** Returns the active entry response status when present. */
249
281
  getEntryResponseStatus() {
250
282
  const entry = this.getRequiredActiveEntry();
@@ -434,10 +466,32 @@ export class BundleReader {
434
466
  .find((key) => String(key || '').toLowerCase().endsWith('.identifier'));
435
467
  return identifierKey ? normalizeOptionalString(claims[identifierKey]) : undefined;
436
468
  }
469
+ resolveEntryReferenceCandidates(entry) {
470
+ const resource = asRecord(entry.resource);
471
+ const resourceType = asNonEmptyString(resource.resourceType);
472
+ const resourceId = asNonEmptyString(resource.id);
473
+ return Array.from(new Set([
474
+ asNonEmptyString(entry.id),
475
+ asNonEmptyString(entry.fullUrl),
476
+ resourceId,
477
+ resourceType && resourceId ? `${resourceType}/${resourceId}` : undefined,
478
+ this.resolveEntryIdentifier(entry),
479
+ ].filter((value) => Boolean(value))));
480
+ }
437
481
  }
438
482
  function normalizeOptionalString(value) {
439
483
  return typeof value === 'string' && value.trim() ? value.trim() : undefined;
440
484
  }
485
+ function normalizeSectionClaim(value) {
486
+ const [system, ...codeParts] = value.split('|');
487
+ if (codeParts.length === 0) {
488
+ return value.trim().toLowerCase();
489
+ }
490
+ const normalizedSystem = system.trim().toLowerCase() === 'http://loinc.org'
491
+ ? 'loinc'
492
+ : system.trim().toLowerCase();
493
+ return `${normalizedSystem}|${codeParts.join('|').trim().toLowerCase()}`;
494
+ }
441
495
  export function unwrapBundleLikeResponseBody(input) {
442
496
  const body = input && typeof input === 'object' ? input : {};
443
497
  const nested = body.body && typeof body.body === 'object' ? body.body : undefined;
@@ -1,4 +1,4 @@
1
- import { type JsonWebKey } from 'node:crypto';
1
+ import { type JWK } from 'jose';
2
2
  export type ClientAssertionJwtAlgorithm = 'ES256' | 'ES384' | 'ES512' | 'EdDSA';
3
3
  export type BuildClientAssertionJwtInput = {
4
4
  clientId: string;
@@ -34,5 +34,5 @@ export declare function buildClientAssertionJwt(input: BuildClientAssertionJwtIn
34
34
  */
35
35
  export declare function buildClientAssertionFixture(input: BuildClientAssertionJwtInput): Promise<{
36
36
  jwt: string;
37
- publicJwk: JsonWebKey;
37
+ publicJwk: JWK;
38
38
  }>;
@@ -150,9 +150,10 @@ export declare function buildBundleSearchReferenceUrl(input: Readonly<{
150
150
  /**
151
151
  * Creates the canonical semantic parameters for an IPS summary-style request.
152
152
  *
153
- * These parameters are the source of truth. Current search flows flatten them
154
- * to `Communication.content-reference`, while future operation flows may attach
155
- * them directly as FHIR `Parameters`.
153
+ * These parameters are the source of truth. The canonical `$summary` read
154
+ * attaches them as one FHIR `Parameters` resource to an auditable
155
+ * `Communication`. Flattening them into a `Bundle/_search` reference is a
156
+ * compatibility path and must not be taught as the primary 101 read flow.
156
157
  */
157
158
  export declare function createSummaryOperationRequestParameters(subjectIdOrInput: string | CreateSummaryOperationParametersInput, filterSections?: string[]): ReadonlyArray<ParameterData>;
158
159
  /**
@@ -161,8 +162,8 @@ export declare function createSummaryOperationRequestParameters(subjectIdOrInput
161
162
  */
162
163
  export declare function createSummaryOperationRequestReferencePath(parameters: ReadonlyArray<ParameterData>): string;
163
164
  /**
164
- * Builds the preferred FHIR `Parameters` body for the same semantic summary
165
- * search represented by `createSummaryOperationRequestReferencePath(...)`.
165
+ * Builds the canonical FHIR `Parameters` body attached to a `$summary`
166
+ * request `Communication`.
166
167
  */
167
168
  export declare function createSummaryOperationRequestParametersResource(parameters: ReadonlyArray<ParameterData>): FhirParametersResource;
168
169
  export declare function buildCommunicationRequestOperationWithAttachedParametersClaims(input: CreateSummaryOperationCommunicationInput): Record<string, unknown>;
@@ -188,11 +189,11 @@ export declare const flattenParametersToSearchReference: typeof createSummaryOpe
188
189
  * carried inside `Communication`.
189
190
  *
190
191
  * Current split:
191
- * - `newSearchWithReferencePath(...)` keeps the existing `content-reference`
192
- * search-url contract
193
- * - `setRequestSummaryOperation(...)` builds the operation contract where
192
+ * - `setRequestSummaryOperation(...)` is the canonical 101 read contract where
194
193
  * `content-reference` points to the operation path and
195
194
  * `content-attachment-data` carries the serialized FHIR `Parameters`
195
+ * - `newSearchWithReferencePath(...)` keeps the older flattened `_search`
196
+ * compatibility contract
196
197
  */
197
198
  export declare const communication: Readonly<{
198
199
  /**
@@ -217,9 +217,10 @@ export function buildBundleSearchReferenceUrl(input) {
217
217
  /**
218
218
  * Creates the canonical semantic parameters for an IPS summary-style request.
219
219
  *
220
- * These parameters are the source of truth. Current search flows flatten them
221
- * to `Communication.content-reference`, while future operation flows may attach
222
- * them directly as FHIR `Parameters`.
220
+ * These parameters are the source of truth. The canonical `$summary` read
221
+ * attaches them as one FHIR `Parameters` resource to an auditable
222
+ * `Communication`. Flattening them into a `Bundle/_search` reference is a
223
+ * compatibility path and must not be taught as the primary 101 read flow.
223
224
  */
224
225
  export function createSummaryOperationRequestParameters(subjectIdOrInput, filterSections) {
225
226
  const input = typeof subjectIdOrInput === 'string'
@@ -234,10 +235,14 @@ export function createSummaryOperationRequestParameters(subjectIdOrInput, filter
234
235
  if (!documentTypeDescriptor) {
235
236
  throw new Error(`Unsupported documentType: ${String(documentType)}`);
236
237
  }
238
+ const sections = normalizeStringArray(input.filterSections);
239
+ if (sections.includes('*')) {
240
+ throw new Error('Omit filterSections to request all available sections; "*" is reserved for SMART permission scopes.');
241
+ }
237
242
  return [
238
243
  buildSubjectParameter(subjectDid),
239
244
  buildDocumentTypeParameter(documentTypeDescriptor.id, documentTypeDescriptor.attributeValue),
240
- ...buildSectionParameters(input.filterSections),
245
+ ...buildSectionParameters(sections),
241
246
  ];
242
247
  }
243
248
  /**
@@ -267,8 +272,8 @@ export function createSummaryOperationRequestReferencePath(parameters) {
267
272
  return `individual/org.hl7.fhir.r4/Bundle/_search?${params.filter(Boolean).join('&')}`;
268
273
  }
269
274
  /**
270
- * Builds the preferred FHIR `Parameters` body for the same semantic summary
271
- * search represented by `createSummaryOperationRequestReferencePath(...)`.
275
+ * Builds the canonical FHIR `Parameters` body attached to a `$summary`
276
+ * request `Communication`.
272
277
  */
273
278
  export function createSummaryOperationRequestParametersResource(parameters) {
274
279
  return buildFhirParametersResourceFromParameterData(parameters);
@@ -348,11 +353,11 @@ export const flattenParametersToSearchReference = createSummaryOperationRequestR
348
353
  * carried inside `Communication`.
349
354
  *
350
355
  * Current split:
351
- * - `newSearchWithReferencePath(...)` keeps the existing `content-reference`
352
- * search-url contract
353
- * - `setRequestSummaryOperation(...)` builds the operation contract where
356
+ * - `setRequestSummaryOperation(...)` is the canonical 101 read contract where
354
357
  * `content-reference` points to the operation path and
355
358
  * `content-attachment-data` carries the serialized FHIR `Parameters`
359
+ * - `newSearchWithReferencePath(...)` keeps the older flattened `_search`
360
+ * compatibility contract
356
361
  */
357
362
  export const communication = Object.freeze({
358
363
  /**
@@ -35,7 +35,8 @@ export function transformCommunicationClaimsToResourceFhirR4(communicationClaims
35
35
  const hasReference = Boolean(payloadReference);
36
36
  const hasCode = Boolean(payloadCodeRaw);
37
37
  const payloadKinds = [hasAttachment, hasReference, hasCode].filter(Boolean).length;
38
- if (payloadKinds > 1) {
38
+ const isOperationReferenceWithParameters = hasAttachment && hasReference && !hasCode;
39
+ if (payloadKinds > 1 && !isOperationReferenceWithParameters) {
39
40
  const msg = `Communication[${index}] has more than one payload kind (attachment/reference/code).`;
40
41
  if (mode === 'strict')
41
42
  throw new Error(msg);
@@ -50,10 +51,10 @@ export function transformCommunicationClaimsToResourceFhirR4(communicationClaims
50
51
  throw new Error(msg);
51
52
  warnings.push(`${msg} Keeping first note only.`);
52
53
  }
53
- const payload = buildPayload({
54
+ const payload = buildPayloads({
54
55
  hasAttachment,
55
- hasReference,
56
- hasCode,
56
+ hasReference: isOperationReferenceWithParameters || (!hasAttachment && hasReference),
57
+ hasCode: !hasAttachment && !hasReference && hasCode,
57
58
  payloadAttachmentData,
58
59
  payloadAttachmentType,
59
60
  payloadAttachmentTitle,
@@ -95,8 +96,8 @@ export function transformCommunicationClaimsToResourceFhirR4(communicationClaims
95
96
  resource['sender'] = { reference: sender };
96
97
  if (partOf)
97
98
  resource['partOf'] = [{ reference: partOf }];
98
- if (payload)
99
- resource['payload'] = [payload];
99
+ if (payload.length)
100
+ resource['payload'] = payload;
100
101
  if (noteValues.length)
101
102
  resource['note'] = [{ text: noteValues[0] }];
102
103
  return resource;
@@ -126,10 +127,13 @@ export function extractCommunicationClaimsFromResourceFhirR4(resource, options =
126
127
  const partOfRef = resource?.partOf?.[0]?.reference;
127
128
  const noteText = resource?.note?.[0]?.text;
128
129
  const categoryCoding = resource?.category?.[0]?.coding?.[0];
129
- const payload = resource?.payload?.[0];
130
- const contentReference = payload?.contentReference?.reference;
131
- const contentAttachment = payload?.contentAttachment;
132
- const contentCodeableConcept = payload?.contentCodeableConcept?.coding?.[0];
130
+ const payloads = resource?.payload || [];
131
+ const referencePayload = payloads.find((payload) => payload.contentReference !== undefined);
132
+ const attachmentPayload = payloads.find((payload) => payload.contentAttachment !== undefined);
133
+ const codePayload = payloads.find((payload) => payload.contentCodeableConcept !== undefined);
134
+ const contentReference = referencePayload?.contentReference?.reference;
135
+ const contentAttachment = attachmentPayload?.contentAttachment;
136
+ const contentCodeableConcept = codePayload?.contentCodeableConcept?.coding?.[0];
133
137
  setIf(claims, CommunicationClaim.Identifier, identifierValue);
134
138
  setIf(claims, CommunicationClaim.Status, status);
135
139
  setIf(claims, CommunicationClaim.Sent, sent);
@@ -177,8 +181,12 @@ function normalizeNoteValues(raw) {
177
181
  }
178
182
  return [];
179
183
  }
180
- function buildPayload(input) {
184
+ function buildPayloads(input) {
181
185
  const { hasAttachment, hasReference, hasCode, payloadAttachmentData, payloadAttachmentType, payloadAttachmentTitle, payloadAttachmentUrl, payloadReference, payloadCodeRaw, } = input;
186
+ const payloads = [];
187
+ if (hasReference) {
188
+ payloads.push({ contentReference: { reference: payloadReference } });
189
+ }
182
190
  if (hasAttachment) {
183
191
  const value = {};
184
192
  if (payloadAttachmentData)
@@ -189,13 +197,12 @@ function buildPayload(input) {
189
197
  value['title'] = payloadAttachmentTitle;
190
198
  if (payloadAttachmentUrl)
191
199
  value['url'] = payloadAttachmentUrl;
192
- return { contentAttachment: value };
200
+ payloads.push({ contentAttachment: value });
201
+ }
202
+ if (hasCode && payloadCodeRaw) {
203
+ payloads.push({ contentCodeableConcept: { coding: [parseSystemCode(payloadCodeRaw)] } });
193
204
  }
194
- if (hasReference)
195
- return { contentReference: { reference: payloadReference } };
196
- if (hasCode && payloadCodeRaw)
197
- return { contentCodeableConcept: { coding: [parseSystemCode(payloadCodeRaw)] } };
198
- return undefined;
205
+ return payloads;
199
206
  }
200
207
  function parseSystemCode(value) {
201
208
  const trimmed = String(value || '').trim();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdc-common-utils-ts",
3
- "version": "2.3.4",
3
+ "version": "2.3.6",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -19,7 +19,10 @@
19
19
  "test": "jest",
20
20
  "test:coverage": "jest --coverage",
21
21
  "typecheck": "tsc -p tsconfig.json --noEmit",
22
- "build": "tsc -p tsconfig.build.json && node patch-esm-imports.mjs",
22
+ "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
23
+ "build": "npm run clean && tsc -p tsconfig.build.json && node patch-esm-imports.mjs",
24
+ "verify:dist": "node scripts/verify-dist-syntax.mjs",
25
+ "prepack": "npm run verify:dist",
23
26
  "prepublishOnly": "npm run typecheck && npm test -- --watchman=false && npm run build"
24
27
  },
25
28
  "main": "./dist/index.js",