gdc-common-utils-ts 1.4.14 → 1.4.15

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.
@@ -64,6 +64,7 @@ export interface BundleEntryResource {
64
64
  [key: string]: any;
65
65
  };
66
66
  resourceType?: string;
67
+ [key: string]: any;
67
68
  }
68
69
  /**
69
70
  * @deprecated Use `BundleEntry` instead. This will be removed in a future version.
@@ -0,0 +1,24 @@
1
+ import type { ClaimSpec } from './types';
2
+ export declare const AllergyIntoleranceClaim: {
3
+ readonly Identifier: "AllergyIntolerance.identifier";
4
+ readonly Subject: "AllergyIntolerance.subject";
5
+ /**
6
+ * @deprecated Use `AllergyIntolerance.subject`.
7
+ * Kept as compatibility alias because FHIR native field is `patient`.
8
+ */
9
+ readonly Patient: "AllergyIntolerance.patient";
10
+ readonly Code: "AllergyIntolerance.code";
11
+ readonly ClinicalStatus: "AllergyIntolerance.clinical-status";
12
+ readonly VerificationStatus: "AllergyIntolerance.verification-status";
13
+ readonly Category: "AllergyIntolerance.category";
14
+ readonly Criticality: "AllergyIntolerance.criticality";
15
+ readonly OnsetDateTime: "AllergyIntolerance.onset-datetime";
16
+ readonly Recorder: "AllergyIntolerance.recorder";
17
+ };
18
+ export type AllergyIntoleranceClaimKey = typeof AllergyIntoleranceClaim[keyof typeof AllergyIntoleranceClaim];
19
+ /**
20
+ * http://hl7.org/fhir/uv/ips/ValueSet/allergies-intolerances-uv-ips
21
+ * Snomed IPS codes for allergy and intolerance categories: food, medication, environment, biologic.
22
+ * WHO ATC codes V01AA for Allergen extracts
23
+ */
24
+ export declare const AllergyIntoleranceClaimSpecs: ClaimSpec[];
@@ -0,0 +1,35 @@
1
+ // Copyright 2026 Conéctate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
2
+ // File: src/models/interoperable-claims/allergy-intolerance-claims.ts
3
+ export const AllergyIntoleranceClaim = {
4
+ Identifier: 'AllergyIntolerance.identifier',
5
+ Subject: 'AllergyIntolerance.subject',
6
+ /**
7
+ * @deprecated Use `AllergyIntolerance.subject`.
8
+ * Kept as compatibility alias because FHIR native field is `patient`.
9
+ */
10
+ Patient: 'AllergyIntolerance.patient',
11
+ Code: 'AllergyIntolerance.code',
12
+ ClinicalStatus: 'AllergyIntolerance.clinical-status',
13
+ VerificationStatus: 'AllergyIntolerance.verification-status',
14
+ Category: 'AllergyIntolerance.category',
15
+ Criticality: 'AllergyIntolerance.criticality',
16
+ OnsetDateTime: 'AllergyIntolerance.onset-datetime',
17
+ Recorder: 'AllergyIntolerance.recorder',
18
+ };
19
+ /**
20
+ * http://hl7.org/fhir/uv/ips/ValueSet/allergies-intolerances-uv-ips
21
+ * Snomed IPS codes for allergy and intolerance categories: food, medication, environment, biologic.
22
+ * WHO ATC codes V01AA for Allergen extracts
23
+ */
24
+ export const AllergyIntoleranceClaimSpecs = [
25
+ { key: AllergyIntoleranceClaim.Identifier, meaning: 'Business identifier for allergy record.', example: 'ALG-0001' },
26
+ { key: AllergyIntoleranceClaim.Subject, meaning: 'Canonical subject reference (maps to FHIR AllergyIntolerance.patient.reference).', example: 'urn:uuid:<UUID-V4>' },
27
+ { key: AllergyIntoleranceClaim.Patient, meaning: 'Deprecated alias of subject for compatibility.', example: 'urn:uuid:<UUID-V4>' },
28
+ { key: AllergyIntoleranceClaim.Code, meaning: 'Allergy or intolerance code.', example: 'http://snomed.info/sct|227493005' },
29
+ { key: AllergyIntoleranceClaim.ClinicalStatus, meaning: 'Clinical status code.', example: 'active' },
30
+ { key: AllergyIntoleranceClaim.VerificationStatus, meaning: 'Verification status code.', example: 'confirmed' },
31
+ { key: AllergyIntoleranceClaim.Category, meaning: 'Category value.', example: 'food' },
32
+ { key: AllergyIntoleranceClaim.Criticality, meaning: 'Criticality level.', example: 'high' },
33
+ { key: AllergyIntoleranceClaim.OnsetDateTime, meaning: 'Onset date/time.', example: '2026-01-10T10:00:00Z' },
34
+ { key: AllergyIntoleranceClaim.Recorder, meaning: 'Recorder reference.', example: 'did:web:<domain>:organization:taxid:<TAXID>:member:<MEMBER_ID>:<roleCode>' },
35
+ ];
@@ -10,6 +10,5 @@ export declare const CommunicationClaim: {
10
10
  readonly ContentAttachmentType: "Communication.content-attachment-type";
11
11
  readonly ContentAttachmentTitle: "Communication.content-attachment-title";
12
12
  readonly PartOf: "Communication.part-of";
13
- readonly PartOfLegacy: "Communication.partOf";
14
13
  };
15
14
  export type CommunicationClaimKey = typeof CommunicationClaim[keyof typeof CommunicationClaim];
@@ -11,6 +11,5 @@ export const CommunicationClaim = {
11
11
  ContentAttachmentData: 'Communication.content-attachment-data',
12
12
  ContentAttachmentType: 'Communication.content-attachment-type',
13
13
  ContentAttachmentTitle: 'Communication.content-attachment-title',
14
- PartOf: 'Communication.part-of',
15
- PartOfLegacy: 'Communication.partOf',
14
+ PartOf: 'Communication.part-of'
16
15
  };
@@ -0,0 +1,14 @@
1
+ import type { ClaimSpec } from './types';
2
+ export declare const ConditionClaim: {
3
+ readonly Identifier: "Condition.identifier";
4
+ readonly Subject: "Condition.subject";
5
+ readonly ClinicalStatus: "Condition.clinical-status";
6
+ readonly VerificationStatus: "Condition.verification-status";
7
+ readonly Category: "Condition.category";
8
+ readonly Code: "Condition.code";
9
+ readonly Severity: "Condition.severity";
10
+ readonly OnsetDateTime: "Condition.onset-datetime";
11
+ readonly Recorder: "Condition.recorder";
12
+ };
13
+ export type ConditionClaimKey = typeof ConditionClaim[keyof typeof ConditionClaim];
14
+ export declare const ConditionClaimSpecs: ClaimSpec[];
@@ -0,0 +1,24 @@
1
+ // Copyright 2026 Conéctate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
2
+ // File: src/models/interoperable-claims/condition-claims.ts
3
+ export const ConditionClaim = {
4
+ Identifier: 'Condition.identifier',
5
+ Subject: 'Condition.subject',
6
+ ClinicalStatus: 'Condition.clinical-status',
7
+ VerificationStatus: 'Condition.verification-status',
8
+ Category: 'Condition.category',
9
+ Code: 'Condition.code',
10
+ Severity: 'Condition.severity',
11
+ OnsetDateTime: 'Condition.onset-datetime',
12
+ Recorder: 'Condition.recorder',
13
+ };
14
+ export const ConditionClaimSpecs = [
15
+ { key: ConditionClaim.Identifier, meaning: 'Business identifier for condition record.', example: 'COND-0001' },
16
+ { key: ConditionClaim.Subject, meaning: 'Patient subject reference.', example: 'Patient/pat-123' },
17
+ { key: ConditionClaim.ClinicalStatus, meaning: 'Clinical status code.', example: 'active' },
18
+ { key: ConditionClaim.VerificationStatus, meaning: 'Verification status code.', example: 'confirmed' },
19
+ { key: ConditionClaim.Category, meaning: 'Condition category code.', example: 'problem-list-item' },
20
+ { key: ConditionClaim.Code, meaning: 'Condition code.', example: 'http://snomed.info/sct|44054006' },
21
+ { key: ConditionClaim.Severity, meaning: 'Condition severity code.', example: 'http://snomed.info/sct|24484000' },
22
+ { key: ConditionClaim.OnsetDateTime, meaning: 'Onset date/time.', example: '2026-01-03T09:30:00Z' },
23
+ { key: ConditionClaim.Recorder, meaning: 'Recorder reference.', example: 'Practitioner/prac-2' },
24
+ ];
@@ -0,0 +1,13 @@
1
+ import type { ClaimSpec } from './types';
2
+ export declare const DeviceUseStatementClaim: {
3
+ readonly Identifier: "DeviceUseStatement.identifier";
4
+ readonly Subject: "DeviceUseStatement.subject";
5
+ readonly Status: "DeviceUseStatement.status";
6
+ readonly Device: "DeviceUseStatement.device";
7
+ readonly RecordedOn: "DeviceUseStatement.recordedon";
8
+ readonly TimingDateTime: "DeviceUseStatement.timing-datetime";
9
+ readonly ReasonCode: "DeviceUseStatement.reasoncode";
10
+ readonly Source: "DeviceUseStatement.source";
11
+ };
12
+ export type DeviceUseStatementClaimKey = typeof DeviceUseStatementClaim[keyof typeof DeviceUseStatementClaim];
13
+ export declare const DeviceUseStatementClaimSpecs: ClaimSpec[];
@@ -0,0 +1,22 @@
1
+ // Copyright 2026 Conéctate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
2
+ // File: src/models/interoperable-claims/device-use-statement-claims.ts
3
+ export const DeviceUseStatementClaim = {
4
+ Identifier: 'DeviceUseStatement.identifier',
5
+ Subject: 'DeviceUseStatement.subject',
6
+ Status: 'DeviceUseStatement.status',
7
+ Device: 'DeviceUseStatement.device',
8
+ RecordedOn: 'DeviceUseStatement.recordedon',
9
+ TimingDateTime: 'DeviceUseStatement.timing-datetime',
10
+ ReasonCode: 'DeviceUseStatement.reasoncode',
11
+ Source: 'DeviceUseStatement.source',
12
+ };
13
+ export const DeviceUseStatementClaimSpecs = [
14
+ { key: DeviceUseStatementClaim.Identifier, meaning: 'Business identifier for device-use record.', example: 'DUS-0001' },
15
+ { key: DeviceUseStatementClaim.Subject, meaning: 'Patient subject reference.', example: 'Patient/pat-123' },
16
+ { key: DeviceUseStatementClaim.Status, meaning: 'Use statement status.', example: 'active' },
17
+ { key: DeviceUseStatementClaim.Device, meaning: 'Device reference.', example: 'Device/dev-777' },
18
+ { key: DeviceUseStatementClaim.RecordedOn, meaning: 'Record creation date/time.', example: '2026-03-01T12:00:00Z' },
19
+ { key: DeviceUseStatementClaim.TimingDateTime, meaning: 'When the use happened.', example: '2026-03-01T08:00:00Z' },
20
+ { key: DeviceUseStatementClaim.ReasonCode, meaning: 'Reason code for use.', example: 'http://snomed.info/sct|182840001' },
21
+ { key: DeviceUseStatementClaim.Source, meaning: 'Source actor reference.', example: 'Practitioner/prac-8' },
22
+ ];
@@ -5,6 +5,7 @@ export declare const DocumentReferenceClaim: {
5
5
  readonly BasedOn: "DocumentReference.basedOn";
6
6
  readonly Category: "DocumentReference.category";
7
7
  readonly ContentData: "DocumentReference.contentdata";
8
+ readonly ContentHash: "DocumentReference.contenthash";
8
9
  readonly ContentType: "DocumentReference.contenttype";
9
10
  readonly Context: "DocumentReference.context";
10
11
  readonly Creation: "DocumentReference.creation";
@@ -6,6 +6,7 @@ export const DocumentReferenceClaim = {
6
6
  BasedOn: 'DocumentReference.basedOn',
7
7
  Category: 'DocumentReference.category',
8
8
  ContentData: 'DocumentReference.contentdata',
9
+ ContentHash: 'DocumentReference.contenthash',
9
10
  ContentType: 'DocumentReference.contenttype',
10
11
  Context: 'DocumentReference.context',
11
12
  Creation: 'DocumentReference.creation',
@@ -33,6 +34,7 @@ export const DocumentReferenceClaimSpecs = [
33
34
  { key: DocumentReferenceClaim.BasedOn, meaning: 'URL of the source FHIR resource.', example: 'https://ehr.example.com/fhir/ServiceRequest/sr-991' },
34
35
  { key: DocumentReferenceClaim.Category, meaning: 'Higher-level document grouping.', example: 'http://hl7.org/fhir/ValueSet/document-classcodes|LP173418-7' },
35
36
  { key: DocumentReferenceClaim.ContentData, meaning: 'Embedded attachment as base64.', example: 'JVBERi0xLjc...' },
37
+ { key: DocumentReferenceClaim.ContentHash, meaning: 'Attachment content hash/CID used for retrieval and integrity checks.', example: 'z4EBG9jDwAxbThHs9AMGBy6dTN1P9fGEcXq6tCm1ugw3pV89Nsc' },
36
38
  { key: DocumentReferenceClaim.ContentType, meaning: 'Attachment MIME type.', example: 'application/pdf' },
37
39
  { key: DocumentReferenceClaim.Context, meaning: 'Context such as Appointment | Encounter | EpisodeOfCare.', example: 'Encounter/enc-123' },
38
40
  { key: DocumentReferenceClaim.Creation, meaning: 'When source information was created.', example: '2026-02-10T08:20:00Z' },
@@ -63,6 +65,10 @@ export const DocumentReferenceClaimToFhirPath = {
63
65
  [DocumentReferenceClaim.BasedOn]: 'DocumentReference.basedOn.reference',
64
66
  [DocumentReferenceClaim.Category]: 'DocumentReference.category.coding',
65
67
  [DocumentReferenceClaim.ContentData]: 'DocumentReference.content.attachment.data',
68
+ [DocumentReferenceClaim.ContentHash]: [
69
+ 'DocumentReference.content.attachment.hash',
70
+ 'DocumentReference.meta.versionId',
71
+ ],
66
72
  [DocumentReferenceClaim.ContentType]: 'DocumentReference.content.attachment.contentType',
67
73
  [DocumentReferenceClaim.Context]: 'DocumentReference.context',
68
74
  [DocumentReferenceClaim.Creation]: 'DocumentReference.content.attachment.creation',
@@ -3,5 +3,8 @@ export * from './composition-claims';
3
3
  export * from './communication-claims';
4
4
  export * from './document-reference-claims';
5
5
  export * from './medication-statement-claims';
6
+ export * from './allergy-intolerance-claims';
7
+ export * from './condition-claims';
8
+ export * from './device-use-statement-claims';
6
9
  export * from './appointment-claims';
7
10
  export * from './appointment-response-claims';
@@ -5,5 +5,8 @@ export * from './composition-claims.js';
5
5
  export * from './communication-claims.js';
6
6
  export * from './document-reference-claims.js';
7
7
  export * from './medication-statement-claims.js';
8
+ export * from './allergy-intolerance-claims.js';
9
+ export * from './condition-claims.js';
10
+ export * from './device-use-statement-claims.js';
8
11
  export * from './appointment-claims.js';
9
12
  export * from './appointment-response-claims.js';
@@ -0,0 +1,20 @@
1
+ export type FlatClaims = Record<string, string | undefined>;
2
+ export type FhirResource = Record<string, unknown> & {
3
+ resourceType: string;
4
+ };
5
+ /**
6
+ * Converts flat claims into a minimal MedicationStatement resource.
7
+ */
8
+ export declare function medicationStatementFlatToFhir(claims: FlatClaims): FhirResource;
9
+ /**
10
+ * Converts a minimal MedicationStatement resource to flat claims.
11
+ */
12
+ export declare function medicationStatementFhirToFlat(resource: FhirResource): FlatClaims;
13
+ export declare function allergyIntoleranceFlatToFhir(claims: FlatClaims): FhirResource;
14
+ export declare function allergyIntoleranceFhirToFlat(resource: FhirResource): FlatClaims;
15
+ export declare function conditionFlatToFhir(claims: FlatClaims): FhirResource;
16
+ export declare function conditionFhirToFlat(resource: FhirResource): FlatClaims;
17
+ export declare function deviceUseStatementFlatToFhir(claims: FlatClaims): FhirResource;
18
+ export declare function deviceUseStatementFhirToFlat(resource: FhirResource): FlatClaims;
19
+ export declare function documentReferenceFlatToFhir(claims: FlatClaims): FhirResource;
20
+ export declare function documentReferenceFhirToFlat(resource: FhirResource): FlatClaims;
@@ -0,0 +1,179 @@
1
+ // Copyright 2026 Conéctate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
2
+ // File: src/utils/clinical-resource-converters.ts
3
+ import { DocumentReferenceClaim } from '../models/interoperable-claims/document-reference-claims.js';
4
+ function codingFromValue(value) {
5
+ if (!value) {
6
+ return undefined;
7
+ }
8
+ const [system, code] = value.split('|');
9
+ if (!code) {
10
+ return [{ code: system }];
11
+ }
12
+ return [{ system, code }];
13
+ }
14
+ function codingToValue(coding) {
15
+ if (!coding?.code) {
16
+ return undefined;
17
+ }
18
+ return coding.system ? `${coding.system}|${coding.code}` : coding.code;
19
+ }
20
+ function requireClaim(claims, key) {
21
+ const value = claims[key];
22
+ if (!value) {
23
+ throw new Error(`Missing required claim: ${key}`);
24
+ }
25
+ return value;
26
+ }
27
+ function requireSubjectIdentifier(value, key) {
28
+ if (!value.startsWith('urn:') && !value.startsWith('did:web:')) {
29
+ throw new Error(`Invalid ${key}: expected urn:* or did:web:*`);
30
+ }
31
+ }
32
+ function requireDidWeb(value, key) {
33
+ if (!value.startsWith('did:web:')) {
34
+ throw new Error(`Invalid ${key}: expected did:web:*`);
35
+ }
36
+ }
37
+ /**
38
+ * Converts flat claims into a minimal MedicationStatement resource.
39
+ */
40
+ export function medicationStatementFlatToFhir(claims) {
41
+ const subject = requireClaim(claims, 'MedicationStatement.subject');
42
+ const status = requireClaim(claims, 'MedicationStatement.status');
43
+ return {
44
+ resourceType: 'MedicationStatement',
45
+ identifier: claims['MedicationStatement.identifier'] ? [{ value: claims['MedicationStatement.identifier'] }] : undefined,
46
+ status,
47
+ subject: { reference: subject },
48
+ effectiveDateTime: claims['MedicationStatement.effective'],
49
+ medicationCodeableConcept: claims['MedicationStatement.code']
50
+ ? { coding: codingFromValue(claims['MedicationStatement.code']) }
51
+ : undefined,
52
+ };
53
+ }
54
+ /**
55
+ * Converts a minimal MedicationStatement resource to flat claims.
56
+ */
57
+ export function medicationStatementFhirToFlat(resource) {
58
+ return {
59
+ 'MedicationStatement.identifier': resource.identifier?.[0]?.value,
60
+ 'MedicationStatement.subject': resource.subject?.reference,
61
+ 'MedicationStatement.status': resource.status,
62
+ 'MedicationStatement.effective': resource.effectiveDateTime,
63
+ 'MedicationStatement.code': codingToValue(resource.medicationCodeableConcept?.coding?.[0]),
64
+ };
65
+ }
66
+ export function allergyIntoleranceFlatToFhir(claims) {
67
+ const patient = claims['AllergyIntolerance.subject'] ?? claims['AllergyIntolerance.patient'];
68
+ if (!patient) {
69
+ throw new Error('Missing required claim: AllergyIntolerance.subject');
70
+ }
71
+ requireSubjectIdentifier(patient, 'AllergyIntolerance.subject');
72
+ if (claims['AllergyIntolerance.recorder']) {
73
+ requireDidWeb(claims['AllergyIntolerance.recorder'], 'AllergyIntolerance.recorder');
74
+ }
75
+ return {
76
+ resourceType: 'AllergyIntolerance',
77
+ identifier: claims['AllergyIntolerance.identifier'] ? [{ value: claims['AllergyIntolerance.identifier'] }] : undefined,
78
+ patient: { reference: patient },
79
+ code: claims['AllergyIntolerance.code'] ? { coding: codingFromValue(claims['AllergyIntolerance.code']) } : undefined,
80
+ clinicalStatus: claims['AllergyIntolerance.clinical-status']
81
+ ? { coding: [{ code: claims['AllergyIntolerance.clinical-status'] }] }
82
+ : undefined,
83
+ verificationStatus: claims['AllergyIntolerance.verification-status']
84
+ ? { coding: [{ code: claims['AllergyIntolerance.verification-status'] }] }
85
+ : undefined,
86
+ recorder: claims['AllergyIntolerance.recorder'] ? { reference: claims['AllergyIntolerance.recorder'] } : undefined,
87
+ };
88
+ }
89
+ export function allergyIntoleranceFhirToFlat(resource) {
90
+ const subject = resource.patient?.reference;
91
+ return {
92
+ 'AllergyIntolerance.identifier': resource.identifier?.[0]?.value,
93
+ 'AllergyIntolerance.subject': subject,
94
+ 'AllergyIntolerance.patient': subject,
95
+ 'AllergyIntolerance.code': codingToValue(resource.code?.coding?.[0]),
96
+ 'AllergyIntolerance.clinical-status': resource.clinicalStatus?.coding?.[0]?.code,
97
+ 'AllergyIntolerance.verification-status': resource.verificationStatus?.coding?.[0]?.code,
98
+ 'AllergyIntolerance.recorder': resource.recorder?.reference,
99
+ };
100
+ }
101
+ export function conditionFlatToFhir(claims) {
102
+ const subject = requireClaim(claims, 'Condition.subject');
103
+ return {
104
+ resourceType: 'Condition',
105
+ identifier: claims['Condition.identifier'] ? [{ value: claims['Condition.identifier'] }] : undefined,
106
+ subject: { reference: subject },
107
+ code: claims['Condition.code'] ? { coding: codingFromValue(claims['Condition.code']) } : undefined,
108
+ clinicalStatus: claims['Condition.clinical-status'] ? { coding: [{ code: claims['Condition.clinical-status'] }] } : undefined,
109
+ verificationStatus: claims['Condition.verification-status'] ? { coding: [{ code: claims['Condition.verification-status'] }] } : undefined,
110
+ };
111
+ }
112
+ export function conditionFhirToFlat(resource) {
113
+ return {
114
+ 'Condition.identifier': resource.identifier?.[0]?.value,
115
+ 'Condition.subject': resource.subject?.reference,
116
+ 'Condition.code': codingToValue(resource.code?.coding?.[0]),
117
+ 'Condition.clinical-status': resource.clinicalStatus?.coding?.[0]?.code,
118
+ 'Condition.verification-status': resource.verificationStatus?.coding?.[0]?.code,
119
+ };
120
+ }
121
+ export function deviceUseStatementFlatToFhir(claims) {
122
+ const subject = requireClaim(claims, 'DeviceUseStatement.subject');
123
+ const device = requireClaim(claims, 'DeviceUseStatement.device');
124
+ const status = requireClaim(claims, 'DeviceUseStatement.status');
125
+ return {
126
+ resourceType: 'DeviceUseStatement',
127
+ identifier: claims['DeviceUseStatement.identifier'] ? [{ value: claims['DeviceUseStatement.identifier'] }] : undefined,
128
+ subject: { reference: subject },
129
+ device: { reference: device },
130
+ status,
131
+ recordedOn: claims['DeviceUseStatement.recordedon'],
132
+ timingDateTime: claims['DeviceUseStatement.timing-datetime'],
133
+ };
134
+ }
135
+ export function deviceUseStatementFhirToFlat(resource) {
136
+ return {
137
+ 'DeviceUseStatement.identifier': resource.identifier?.[0]?.value,
138
+ 'DeviceUseStatement.subject': resource.subject?.reference,
139
+ 'DeviceUseStatement.device': resource.device?.reference,
140
+ 'DeviceUseStatement.status': resource.status,
141
+ 'DeviceUseStatement.recordedon': resource.recordedOn,
142
+ 'DeviceUseStatement.timing-datetime': resource.timingDateTime,
143
+ };
144
+ }
145
+ export function documentReferenceFlatToFhir(claims) {
146
+ const subject = requireClaim(claims, DocumentReferenceClaim.Subject);
147
+ return {
148
+ resourceType: 'DocumentReference',
149
+ identifier: claims[DocumentReferenceClaim.Identifier] ? [{ value: claims[DocumentReferenceClaim.Identifier] }] : undefined,
150
+ subject: { reference: subject },
151
+ description: claims[DocumentReferenceClaim.Description],
152
+ date: claims[DocumentReferenceClaim.Date],
153
+ content: [
154
+ {
155
+ attachment: {
156
+ contentType: claims[DocumentReferenceClaim.ContentType],
157
+ data: claims[DocumentReferenceClaim.ContentData],
158
+ url: claims[DocumentReferenceClaim.Location],
159
+ hash: claims[DocumentReferenceClaim.ContentHash],
160
+ language: claims[DocumentReferenceClaim.Language],
161
+ },
162
+ },
163
+ ],
164
+ };
165
+ }
166
+ export function documentReferenceFhirToFlat(resource) {
167
+ const attachment = resource.content?.[0]?.attachment;
168
+ return {
169
+ [DocumentReferenceClaim.Identifier]: resource.identifier?.[0]?.value,
170
+ [DocumentReferenceClaim.Subject]: resource.subject?.reference,
171
+ [DocumentReferenceClaim.Description]: resource.description,
172
+ [DocumentReferenceClaim.Date]: resource.date,
173
+ [DocumentReferenceClaim.ContentType]: attachment?.contentType,
174
+ [DocumentReferenceClaim.ContentData]: attachment?.data,
175
+ [DocumentReferenceClaim.Location]: attachment?.url,
176
+ [DocumentReferenceClaim.ContentHash]: attachment?.hash,
177
+ [DocumentReferenceClaim.Language]: attachment?.language,
178
+ };
179
+ }
@@ -0,0 +1,29 @@
1
+ import { EvidenceObjectDLT } from '../models/oidc4ida.evidence.model';
2
+ export type AttachmentKind = 'fhir' | 'pdf' | 'png' | 'jpg' | 'binary';
3
+ export type BuildMode = 'strict' | 'normalize';
4
+ export type BuildDocumentReferenceOptions = {
5
+ /**
6
+ * strict: validation errors throw.
7
+ * normalize: best-effort defaults + warnings.
8
+ */
9
+ mode?: BuildMode;
10
+ };
11
+ export type BuildDocumentReferenceResult = {
12
+ /**
13
+ * Generated or normalized DocumentReference projection.
14
+ *
15
+ * Current simplified profile:
16
+ * - one Communication payload attachment -> one DocumentReference
17
+ * - one DocumentReference content[0].attachment
18
+ */
19
+ documentReference: Record<string, any>;
20
+ /**
21
+ * CID for the attached artifact content.
22
+ * Used as version-like content fingerprint in this profile.
23
+ */
24
+ contentCid: string;
25
+ evidence: EvidenceObjectDLT[];
26
+ warnings: string[];
27
+ };
28
+ export declare function detectAttachmentKind(contentType?: string): AttachmentKind;
29
+ export declare function buildDocumentReferenceFromCommunicationPayload(communication: Record<string, any>, options?: BuildDocumentReferenceOptions): BuildDocumentReferenceResult;
@@ -0,0 +1,152 @@
1
+ import { sha256 } from '@noble/hashes/sha2.js';
2
+ import { utf8ToBytes } from '@noble/hashes/utils.js';
3
+ import { encodeMultibase58btc } from './multibase58.js';
4
+ import { canonicalizeFhirResource, fhirResourceToCid } from './fhir-cid.js';
5
+ const CID_V1 = 0x01;
6
+ const MULTICODEC_RAW = 0x55;
7
+ const MULTIHASH_SHA2_256_CODE = 0x12;
8
+ const MULTIHASH_SHA2_256_LEN = 32;
9
+ function encodeVarint(value) {
10
+ const out = [];
11
+ let n = value >>> 0;
12
+ while (n >= 0x80) {
13
+ out.push((n & 0x7f) | 0x80);
14
+ n >>>= 7;
15
+ }
16
+ out.push(n);
17
+ return Uint8Array.from(out);
18
+ }
19
+ function concatBytes(...parts) {
20
+ const total = parts.reduce((acc, part) => acc + part.length, 0);
21
+ const out = new Uint8Array(total);
22
+ let offset = 0;
23
+ for (const part of parts) {
24
+ out.set(part, offset);
25
+ offset += part.length;
26
+ }
27
+ return out;
28
+ }
29
+ function cidFromBytes(bytes) {
30
+ const digest = sha256(bytes);
31
+ const multihash = concatBytes(Uint8Array.from([MULTIHASH_SHA2_256_CODE, MULTIHASH_SHA2_256_LEN]), digest);
32
+ const cidBytes = concatBytes(encodeVarint(CID_V1), encodeVarint(MULTICODEC_RAW), multihash);
33
+ return encodeMultibase58btc(cidBytes);
34
+ }
35
+ function decodeBase64(data) {
36
+ return Uint8Array.from(Buffer.from(data, 'base64'));
37
+ }
38
+ function toStringOrUndefined(value) {
39
+ if (value === undefined || value === null)
40
+ return undefined;
41
+ const text = String(value).trim();
42
+ return text || undefined;
43
+ }
44
+ export function detectAttachmentKind(contentType) {
45
+ const ct = String(contentType || '').toLowerCase();
46
+ if (ct.includes('fhir') || ct.includes('application/json+fhir'))
47
+ return 'fhir';
48
+ if (ct === 'application/pdf')
49
+ return 'pdf';
50
+ if (ct === 'image/png')
51
+ return 'png';
52
+ if (ct === 'image/jpg' || ct === 'image/jpeg')
53
+ return 'jpg';
54
+ return 'binary';
55
+ }
56
+ function buildEvidenceForKind(kind, cid) {
57
+ if (kind === 'pdf') {
58
+ return {
59
+ type: 'electronic_signature',
60
+ signature_type: 'detached',
61
+ issuer: 'did:web:unknown',
62
+ serial_number: cid,
63
+ created_at: new Date().toISOString(),
64
+ attachments: [{ content_type: 'cid', content: cid }],
65
+ };
66
+ }
67
+ return {
68
+ type: 'electronic_record',
69
+ record: { type: kind.toUpperCase(), source: 'Communication.payload.contentAttachment', digest: { alg: 'sha-256', value: cid } },
70
+ check_details: [],
71
+ };
72
+ }
73
+ function isCidLike(value) {
74
+ return !!value && value.startsWith('z') && value.length > 10;
75
+ }
76
+ export function buildDocumentReferenceFromCommunicationPayload(communication, options = {}) {
77
+ /**
78
+ * Data model notes:
79
+ * - FHIR DocumentReference logical identifier and version are distinct concerns.
80
+ * - Current implementation uses content CID for claims identifier/version-like tracking.
81
+ * - Future target profile should keep stable DocumentReference.identifier (UUID/URN) and
82
+ * store evolving content CID in meta.versionId/content hash fields across versions.
83
+ * - Multi-attachment DocumentReference content[i] is intentionally out of scope for now;
84
+ * roadmap can introduce indexed flat claims such as DocumentReference.attachment[i]-*.
85
+ */
86
+ const mode = options.mode ?? 'strict';
87
+ const warnings = [];
88
+ const payload = communication?.payload?.[0];
89
+ const attachment = payload?.contentAttachment;
90
+ if (!attachment) {
91
+ throw new Error('Communication payload with contentAttachment is required.');
92
+ }
93
+ const contentType = toStringOrUndefined(attachment.contentType) || 'application/octet-stream';
94
+ if (!attachment.contentType && mode === 'normalize') {
95
+ warnings.push('Missing contentAttachment.contentType. Defaulting to application/octet-stream.');
96
+ }
97
+ const dataBase64 = toStringOrUndefined(attachment.data);
98
+ const url = toStringOrUndefined(attachment.url);
99
+ if (!dataBase64 && !url && mode === 'strict') {
100
+ throw new Error('contentAttachment requires data or url.');
101
+ }
102
+ const kind = detectAttachmentKind(contentType);
103
+ let contentCid = toStringOrUndefined(attachment.id);
104
+ if (!isCidLike(contentCid)) {
105
+ if (kind === 'fhir' && dataBase64) {
106
+ const json = Buffer.from(dataBase64, 'base64').toString('utf8');
107
+ const parsed = JSON.parse(json);
108
+ const canonical = canonicalizeFhirResource(parsed);
109
+ contentCid = fhirResourceToCid(JSON.parse(canonical)).cid;
110
+ }
111
+ else if (dataBase64) {
112
+ contentCid = cidFromBytes(decodeBase64(dataBase64));
113
+ }
114
+ else {
115
+ contentCid = cidFromBytes(utf8ToBytes(url || 'empty-content'));
116
+ }
117
+ }
118
+ if (!contentCid) {
119
+ throw new Error('Unable to generate content CID from attachment.');
120
+ }
121
+ const subject = communication?.meta?.claims?.['Communication.subject'];
122
+ const documentReference = {
123
+ resourceType: 'DocumentReference',
124
+ status: 'current',
125
+ meta: {
126
+ versionId: contentCid,
127
+ claims: {
128
+ 'DocumentReference.subject': subject,
129
+ 'DocumentReference.contenttype': contentType,
130
+ 'DocumentReference.identifier': contentCid,
131
+ },
132
+ },
133
+ subject: subject ? { reference: subject } : undefined,
134
+ content: [
135
+ {
136
+ attachment: {
137
+ id: contentCid,
138
+ contentType,
139
+ data: dataBase64,
140
+ url,
141
+ title: toStringOrUndefined(attachment.title),
142
+ },
143
+ },
144
+ ],
145
+ };
146
+ return {
147
+ documentReference,
148
+ contentCid,
149
+ evidence: [buildEvidenceForKind(kind, contentCid)],
150
+ warnings,
151
+ };
152
+ }
@@ -11,6 +11,8 @@ export * from './didcomm-submit-policy';
11
11
  export * from './format-converter';
12
12
  export * from './fhir-cid';
13
13
  export * from './communication-fhir-r4';
14
+ export * from './communication-document-reference';
15
+ export * from './clinical-resource-converters';
14
16
  export * from './fhir-validator';
15
17
  export * from './jwt';
16
18
  export * from './manager-error';
@@ -11,6 +11,8 @@ export * from './didcomm-submit-policy.js';
11
11
  export * from './format-converter.js';
12
12
  export * from './fhir-cid.js';
13
13
  export * from './communication-fhir-r4.js';
14
+ export * from './communication-document-reference.js';
15
+ export * from './clinical-resource-converters.js';
14
16
  export * from './fhir-validator.js';
15
17
  export * from './jwt.js';
16
18
  export * from './manager-error.js';
@@ -28,6 +28,9 @@ export declare function buildEpochWindow(ttlSeconds?: number): {
28
28
  };
29
29
  export declare function createVP(input?: Partial<VpTokenPayload>): VpTokenPayload;
30
30
  export declare function addVC(vpPayload: VpTokenPayload, vcJwt: string): VpTokenPayload;
31
+ export declare function addVCs(vpPayload: VpTokenPayload, vcs: string[]): VpTokenPayload;
32
+ export declare function addOrganizationCredential(vpPayload: VpTokenPayload, vc: string): VpTokenPayload;
33
+ export declare function addLegalRepresentativeCredential(vpPayload: VpTokenPayload, vc: string): VpTokenPayload;
31
34
  export declare function prepareForSignature(header: VpTokenHeader, payload: VpTokenPayload): {
32
35
  encodedHeader: string;
33
36
  encodedPayload: string;
@@ -42,6 +42,58 @@ export function addVC(vpPayload, vcJwt) {
42
42
  vpPayload.vp.verifiableCredential.push(v);
43
43
  return vpPayload;
44
44
  }
45
+ export function addVCs(vpPayload, vcs) {
46
+ for (const vc of vcs || [])
47
+ addVC(vpPayload, vc);
48
+ return vpPayload;
49
+ }
50
+ function decodeVcPayload(vc) {
51
+ const raw = String(vc || '').trim();
52
+ if (!raw)
53
+ return undefined;
54
+ if (raw.startsWith('{')) {
55
+ try {
56
+ const parsed = JSON.parse(raw);
57
+ return parsed && typeof parsed === 'object' ? parsed : undefined;
58
+ }
59
+ catch {
60
+ return undefined;
61
+ }
62
+ }
63
+ const parts = raw.split('.');
64
+ if (parts.length !== 3 || !parts[1])
65
+ return undefined;
66
+ try {
67
+ const payloadJson = Buffer.from(parts[1], 'base64url').toString('utf8');
68
+ const parsed = JSON.parse(payloadJson);
69
+ return parsed && typeof parsed === 'object' ? parsed : undefined;
70
+ }
71
+ catch {
72
+ return undefined;
73
+ }
74
+ }
75
+ function vcHasAnyType(vcPayload, acceptedTypes) {
76
+ if (!vcPayload)
77
+ return false;
78
+ const typeRaw = vcPayload?.type
79
+ ?? vcPayload?.vc?.type
80
+ ?? vcPayload?.credential?.type;
81
+ const types = Array.isArray(typeRaw) ? typeRaw.map(String) : [String(typeRaw || '')];
82
+ return acceptedTypes.some((t) => types.includes(t));
83
+ }
84
+ function addTypedVC(vpPayload, vc, acceptedTypes, label) {
85
+ const payload = decodeVcPayload(vc);
86
+ if (!vcHasAnyType(payload, acceptedTypes)) {
87
+ throw new Error(`${label} VC must include one of types: ${acceptedTypes.join(', ')}`);
88
+ }
89
+ return addVC(vpPayload, vc);
90
+ }
91
+ export function addOrganizationCredential(vpPayload, vc) {
92
+ return addTypedVC(vpPayload, vc, ['OrganizationCredential', 'LegalOrganizationCredential'], 'Organization');
93
+ }
94
+ export function addLegalRepresentativeCredential(vpPayload, vc) {
95
+ return addTypedVC(vpPayload, vc, ['LegalRepresentativeCredential', 'PersonCredential'], 'LegalRepresentative');
96
+ }
45
97
  export function prepareForSignature(header, payload) {
46
98
  const encodedHeader = Content.objectToRawBase64UrlSafe(header);
47
99
  const encodedPayload = Content.objectToRawBase64UrlSafe(payload);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdc-common-utils-ts",
3
- "version": "1.4.14",
3
+ "version": "1.4.15",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },