gdc-common-utils-ts 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/constants/index.d.ts +2 -0
  2. package/dist/constants/index.js +2 -0
  3. package/dist/constants/service-capabilities.d.ts +53 -0
  4. package/dist/constants/service-capabilities.js +72 -0
  5. package/dist/constants/verifiable-credentials.d.ts +34 -0
  6. package/dist/constants/verifiable-credentials.js +42 -0
  7. package/dist/examples/api-flow-examples.d.ts +1 -0
  8. package/dist/examples/api-flow-examples.js +1 -0
  9. package/dist/examples/consent-access.d.ts +12 -128
  10. package/dist/examples/consent-access.js +27 -18
  11. package/dist/examples/contract-examples.d.ts +1 -0
  12. package/dist/examples/contract-examples.js +1 -0
  13. package/dist/examples/frontend-session.d.ts +0 -4
  14. package/dist/examples/frontend-session.js +13 -7
  15. package/dist/examples/ica-activation-proof.d.ts +55 -0
  16. package/dist/examples/ica-activation-proof.js +67 -0
  17. package/dist/examples/index.d.ts +1 -0
  18. package/dist/examples/index.js +1 -0
  19. package/dist/examples/individual-controller.d.ts +19 -15
  20. package/dist/examples/individual-controller.js +16 -31
  21. package/dist/examples/lifecycle.d.ts +6 -4
  22. package/dist/examples/lifecycle.js +9 -7
  23. package/dist/examples/organization-controller.d.ts +5 -0
  24. package/dist/examples/organization-controller.js +17 -8
  25. package/dist/examples/professional.js +8 -7
  26. package/dist/examples/relationship-access.js +2 -2
  27. package/dist/examples/shared.d.ts +61 -7
  28. package/dist/examples/shared.js +55 -7
  29. package/dist/models/consent-rule.d.ts +1 -0
  30. package/dist/models/consent-rule.js +1 -0
  31. package/dist/models/interoperable-claims/allergy-intolerance-claims.js +1 -0
  32. package/dist/models/interoperable-claims/communication-claims.d.ts +14 -0
  33. package/dist/models/interoperable-claims/communication-claims.js +15 -0
  34. package/dist/models/interoperable-claims/condition-claims.js +1 -0
  35. package/dist/models/interoperable-claims/device-use-statement-claims.js +1 -0
  36. package/dist/models/interoperable-claims/document-reference-claims.js +1 -0
  37. package/dist/models/interoperable-claims/medication-statement-claims.d.ts +17 -0
  38. package/dist/models/interoperable-claims/medication-statement-claims.js +18 -0
  39. package/dist/utils/activation-policy.d.ts +8 -3
  40. package/dist/utils/activation-policy.js +26 -12
  41. package/dist/utils/clinical-resource-converters.js +70 -64
  42. package/dist/utils/communication-document-reference.js +10 -4
  43. package/dist/utils/communication-fhir-r4.js +40 -34
  44. package/dist/utils/consent.d.ts +1 -1
  45. package/dist/utils/consent.js +21 -20
  46. package/dist/utils/vp-token.js +8 -6
  47. package/package.json +1 -1
@@ -1,6 +1,19 @@
1
1
  // Copyright 2026 Conéctate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
2
+ // Always create JSDoc, do not use strings inline in keys nor values, use types instead, and reuse the data test examples.
2
3
  // File: src/models/interoperable-claims/communication-claims.ts
4
+ /**
5
+ * Canonical flat claim keys for FHIR `Communication`.
6
+ *
7
+ * Good practice note:
8
+ * - any code, examples, scripts, or tests that write/read reusable
9
+ * `Communication.*` claims must import these keys instead of re-hardcoding
10
+ * string literals inline
11
+ * - legacy aliases such as `Communication.partOf` may still appear in
12
+ * compatibility readers, but new code must use the canonical key constants
13
+ */
3
14
  export const CommunicationClaim = {
15
+ Category: 'Communication.category',
16
+ Status: 'Communication.status',
4
17
  Identifier: 'Communication.identifier',
5
18
  Subject: 'Communication.subject',
6
19
  Recipient: 'Communication.recipient',
@@ -9,8 +22,10 @@ export const CommunicationClaim = {
9
22
  NoteText: 'Communication.note-text',
10
23
  Text: 'Communication.text',
11
24
  ContentReference: 'Communication.content-reference',
25
+ ContentCode: 'Communication.content-code',
12
26
  ContentAttachmentData: 'Communication.content-attachment-data',
13
27
  ContentAttachmentType: 'Communication.content-attachment-type',
14
28
  ContentAttachmentTitle: 'Communication.content-attachment-title',
29
+ ContentAttachmentUrl: 'Communication.content-attachment-url',
15
30
  PartOf: 'Communication.part-of'
16
31
  };
@@ -1,5 +1,6 @@
1
1
  // Copyright 2026 Conéctate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
2
2
  // File: src/models/interoperable-claims/condition-claims.ts
3
+ // Always create JSDoc, do not use strings inline in keys nor values, use types instead, and reuse the data test examples.
3
4
  export const ConditionClaim = {
4
5
  Identifier: 'Condition.identifier',
5
6
  Subject: 'Condition.subject',
@@ -1,5 +1,6 @@
1
1
  // Copyright 2026 Conéctate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
2
2
  // File: src/models/interoperable-claims/device-use-statement-claims.ts
3
+ // Always create JSDoc, do not use strings inline in keys nor values, use types instead, and reuse the data test examples.
3
4
  export const DeviceUseStatementClaim = {
4
5
  Identifier: 'DeviceUseStatement.identifier',
5
6
  Subject: 'DeviceUseStatement.subject',
@@ -1,5 +1,6 @@
1
1
  // Copyright 2026 Conéctate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
2
2
  // File: src/models/interoperable-claims/document-reference-claims.ts
3
+ // Always create JSDoc, do not use strings inline in keys nor values, use types instead, and reuse the data test examples.
3
4
  export const DocumentReferenceClaim = {
4
5
  Attester: 'DocumentReference.attester',
5
6
  Author: 'DocumentReference.author',
@@ -1,3 +1,20 @@
1
+ /**
2
+ * Canonical flat claim keys for the lightweight `MedicationStatement.*` mapping
3
+ * used by shared examples, GW ingestion, and converter roundtrip tests.
4
+ */
5
+ export declare const MedicationStatementClaim: {
6
+ readonly Identifier: "MedicationStatement.identifier";
7
+ readonly Subject: "MedicationStatement.subject";
8
+ readonly Status: "MedicationStatement.status";
9
+ readonly Effective: "MedicationStatement.effective";
10
+ readonly Code: "MedicationStatement.code";
11
+ readonly MedicationText: "MedicationStatement.medication-text";
12
+ readonly Note: "MedicationStatement.note";
13
+ readonly DosageInstruction: "MedicationStatement.dosage-instruction";
14
+ readonly MedicationIdentifier: "MedicationStatement.medication-identifier";
15
+ readonly MedicationSerialNumber: "MedicationStatement.medication-serial-number";
16
+ readonly MedicationExpirationDate: "MedicationStatement.medication-expiration-date";
17
+ };
1
18
  /**
2
19
  * Flat claims contract for MedicationStatement using FHIR API-like search params.
3
20
  *
@@ -1,4 +1,22 @@
1
1
  // src/models/fhir/MedicationStatement.claims.ts
2
+ // Always create JSDoc, do not use strings inline in keys nor values, use types instead, and reuse the data test examples.
3
+ /**
4
+ * Canonical flat claim keys for the lightweight `MedicationStatement.*` mapping
5
+ * used by shared examples, GW ingestion, and converter roundtrip tests.
6
+ */
7
+ export const MedicationStatementClaim = {
8
+ Identifier: 'MedicationStatement.identifier',
9
+ Subject: 'MedicationStatement.subject',
10
+ Status: 'MedicationStatement.status',
11
+ Effective: 'MedicationStatement.effective',
12
+ Code: 'MedicationStatement.code',
13
+ MedicationText: 'MedicationStatement.medication-text',
14
+ Note: 'MedicationStatement.note',
15
+ DosageInstruction: 'MedicationStatement.dosage-instruction',
16
+ MedicationIdentifier: 'MedicationStatement.medication-identifier',
17
+ MedicationSerialNumber: 'MedicationStatement.medication-serial-number',
18
+ MedicationExpirationDate: 'MedicationStatement.medication-expiration-date',
19
+ };
2
20
  /**
3
21
  * Flat claims contract for MedicationStatement using FHIR API-like search params.
4
22
  *
@@ -1,4 +1,4 @@
1
- export type ActivationRepresentativePolicyErrorCode = 'MISSING_REPRESENTATIVE_DID_WEB' | 'MISSING_REPRESENTATIVE_ROLE_RESPRSN' | 'MISSING_REPRESENTATIVE_CREDENTIAL_MATERIAL' | 'REPRESENTATIVE_TAXID_MISMATCH';
1
+ export type ActivationRepresentativePolicyErrorCode = 'MISSING_REPRESENTATIVE_DID_WEB' | 'MISSING_REPRESENTATIVE_ROLE_RESPRSN' | 'MISSING_REPRESENTATIVE_CREDENTIAL_BINDING' | 'REPRESENTATIVE_TAXID_MISMATCH';
2
2
  export type ActivationRepresentativePolicyError = {
3
3
  code: ActivationRepresentativePolicyErrorCode;
4
4
  message: string;
@@ -41,11 +41,16 @@ export declare function extractRepresentativeRoleCode(representativeCredential:
41
41
  */
42
42
  export declare function hasRoleCode(roleCode: string | undefined, requiredCode?: string): boolean;
43
43
  /**
44
- * Extracts representative credential material from `credentialSubject.hasCredential`.
44
+ * Extracts representative signing/binding continuity data from `credentialSubject.hasCredential`.
45
+ *
46
+ * Compatibility rules:
47
+ * - legacy VC payloads may still carry `hasCredential.material`
48
+ * - newer payloads may carry the same semantic value in `hasCredential.value`
49
+ * or `hasCredential.identifier.value`
45
50
  *
46
51
  * @param representativeCredential Candidate representative credential.
47
52
  */
48
- export declare function extractRepresentativeCredentialMaterial(representativeCredential: unknown): string | undefined;
53
+ export declare function extractRepresentativeCredentialBinding(representativeCredential: unknown): string | undefined;
49
54
  /**
50
55
  * Extracts a `did:web:` identifier from a VC-like credential.
51
56
  *
@@ -99,29 +99,43 @@ export function hasRoleCode(roleCode, requiredCode = 'RESPRSN') {
99
99
  return normalizedRole.includes(normalizedRequired);
100
100
  }
101
101
  /**
102
- * Extracts representative credential material from `credentialSubject.hasCredential`.
102
+ * Extracts representative signing/binding continuity data from `credentialSubject.hasCredential`.
103
+ *
104
+ * Compatibility rules:
105
+ * - legacy VC payloads may still carry `hasCredential.material`
106
+ * - newer payloads may carry the same semantic value in `hasCredential.value`
107
+ * or `hasCredential.identifier.value`
103
108
  *
104
109
  * @param representativeCredential Candidate representative credential.
105
110
  */
106
- export function extractRepresentativeCredentialMaterial(representativeCredential) {
111
+ export function extractRepresentativeCredentialBinding(representativeCredential) {
107
112
  const subject = extractCredentialSubject(representativeCredential) || {};
108
113
  const credentialData = subject.hasCredential;
109
114
  if (typeof credentialData === 'string')
110
115
  return credentialData.trim() || undefined;
111
116
  if (Array.isArray(credentialData)) {
112
117
  for (const item of credentialData) {
113
- const mat = String(item?.material || '').trim();
114
- if (mat)
115
- return mat;
118
+ const candidate = extractCredentialBindingValue(item);
119
+ if (candidate)
120
+ return candidate;
116
121
  }
117
122
  }
118
123
  if (credentialData && typeof credentialData === 'object') {
119
- const mat = String(credentialData?.material || '').trim();
120
- if (mat)
121
- return mat;
124
+ const candidate = extractCredentialBindingValue(credentialData);
125
+ if (candidate)
126
+ return candidate;
122
127
  }
123
128
  return undefined;
124
129
  }
130
+ function extractCredentialBindingValue(value) {
131
+ const obj = asObject(value) || {};
132
+ const identifier = asObject(obj.identifier);
133
+ const candidate = String(obj.material
134
+ || obj.value
135
+ || identifier?.value
136
+ || '').trim();
137
+ return candidate || undefined;
138
+ }
125
139
  /**
126
140
  * Extracts a `did:web:` identifier from a VC-like credential.
127
141
  *
@@ -193,11 +207,11 @@ export function validateActivationRepresentativePolicy(input) {
193
207
  message: 'ICA-issued representative credential must include Responsible Party role (RESPRSN) in credentialSubject.hasOccupation.',
194
208
  });
195
209
  }
196
- const material = extractRepresentativeCredentialMaterial(input.representativeCredential);
197
- if (!material) {
210
+ const binding = extractRepresentativeCredentialBinding(input.representativeCredential);
211
+ if (!binding) {
198
212
  errors.push({
199
- code: 'MISSING_REPRESENTATIVE_CREDENTIAL_MATERIAL',
200
- message: 'ICA-issued representative credential is missing credentialSubject.hasCredential.material.',
213
+ code: 'MISSING_REPRESENTATIVE_CREDENTIAL_BINDING',
214
+ message: 'ICA-issued representative credential is missing credentialSubject.hasCredential binding data (material, value, or identifier.value).',
201
215
  });
202
216
  }
203
217
  return errors;
@@ -1,6 +1,11 @@
1
1
  // Copyright 2026 Conéctate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
2
2
  // File: src/utils/clinical-resource-converters.ts
3
+ // Always create JSDoc, do not use strings inline in keys nor values, use types instead, and reuse the data test examples.
4
+ import { AllergyIntoleranceClaim } from '../models/interoperable-claims/allergy-intolerance-claims.js';
5
+ import { ConditionClaim } from '../models/interoperable-claims/condition-claims.js';
6
+ import { DeviceUseStatementClaim } from '../models/interoperable-claims/device-use-statement-claims.js';
3
7
  import { DocumentReferenceClaim } from '../models/interoperable-claims/document-reference-claims.js';
8
+ import { MedicationStatementClaim } from '../models/interoperable-claims/medication-statement-claims.js';
4
9
  function codingFromValue(value) {
5
10
  if (!value) {
6
11
  return undefined;
@@ -40,13 +45,13 @@ function requireDidWeb(value, key) {
40
45
  * @param claims Flat medication claims map.
41
46
  */
42
47
  export function medicationStatementFlatToFhir(claims) {
43
- const subject = requireClaim(claims, 'MedicationStatement.subject');
44
- const status = requireClaim(claims, 'MedicationStatement.status');
45
- const effectiveDateTime = claims['MedicationStatement.effective'];
46
- const medicationText = claims['MedicationStatement.medication-text'];
47
- const medicationIdentifier = claims['MedicationStatement.medication-identifier'];
48
- const medicationSerialNumber = claims['MedicationStatement.medication-serial-number'];
49
- const medicationExpirationDate = claims['MedicationStatement.medication-expiration-date'];
48
+ const subject = requireClaim(claims, MedicationStatementClaim.Subject);
49
+ const status = requireClaim(claims, MedicationStatementClaim.Status);
50
+ const effectiveDateTime = claims[MedicationStatementClaim.Effective];
51
+ const medicationText = claims[MedicationStatementClaim.MedicationText];
52
+ const medicationIdentifier = claims[MedicationStatementClaim.MedicationIdentifier];
53
+ const medicationSerialNumber = claims[MedicationStatementClaim.MedicationSerialNumber];
54
+ const medicationExpirationDate = claims[MedicationStatementClaim.MedicationExpirationDate];
50
55
  const hasContainedMedication = Boolean(medicationIdentifier)
51
56
  || Boolean(medicationSerialNumber)
52
57
  || Boolean(medicationExpirationDate)
@@ -66,16 +71,16 @@ export function medicationStatementFlatToFhir(claims) {
66
71
  : undefined,
67
72
  }
68
73
  : undefined;
69
- const dosageInstructionText = claims['MedicationStatement.dosage-instruction'];
70
- const noteText = claims['MedicationStatement.note'];
74
+ const dosageInstructionText = claims[MedicationStatementClaim.DosageInstruction];
75
+ const noteText = claims[MedicationStatementClaim.Note];
71
76
  return {
72
77
  resourceType: 'MedicationStatement',
73
- identifier: claims['MedicationStatement.identifier'] ? [{ value: claims['MedicationStatement.identifier'] }] : undefined,
78
+ identifier: claims[MedicationStatementClaim.Identifier] ? [{ value: claims[MedicationStatementClaim.Identifier] }] : undefined,
74
79
  status,
75
80
  subject: { reference: subject },
76
81
  effectiveDateTime,
77
- medicationCodeableConcept: claims['MedicationStatement.code']
78
- ? { coding: codingFromValue(claims['MedicationStatement.code']) }
82
+ medicationCodeableConcept: claims[MedicationStatementClaim.Code]
83
+ ? { coding: codingFromValue(claims[MedicationStatementClaim.Code]) }
79
84
  : (!hasContainedMedication && medicationText ? { text: medicationText } : undefined),
80
85
  medicationReference: hasContainedMedication ? { reference: `#${containedMedicationId}` } : undefined,
81
86
  contained: containedMedication ? [containedMedication] : undefined,
@@ -104,17 +109,17 @@ export function medicationStatementFhirToFlat(resource) {
104
109
  || resource.medicationCodeableConcept?.text
105
110
  || undefined;
106
111
  return {
107
- 'MedicationStatement.identifier': resource.identifier?.[0]?.value,
108
- 'MedicationStatement.subject': resource.subject?.reference,
109
- 'MedicationStatement.status': resource.status,
110
- 'MedicationStatement.effective': resource.effectiveDateTime,
111
- 'MedicationStatement.code': medicationCode,
112
- 'MedicationStatement.medication-text': medicationText,
113
- 'MedicationStatement.note': noteText,
114
- 'MedicationStatement.dosage-instruction': dosageInstructionText,
115
- 'MedicationStatement.medication-identifier': containedMedicationIdentifier,
116
- 'MedicationStatement.medication-serial-number': batch?.lotNumber,
117
- 'MedicationStatement.medication-expiration-date': batch?.expirationDate,
112
+ [MedicationStatementClaim.Identifier]: resource.identifier?.[0]?.value,
113
+ [MedicationStatementClaim.Subject]: resource.subject?.reference,
114
+ [MedicationStatementClaim.Status]: resource.status,
115
+ [MedicationStatementClaim.Effective]: resource.effectiveDateTime,
116
+ [MedicationStatementClaim.Code]: medicationCode,
117
+ [MedicationStatementClaim.MedicationText]: medicationText,
118
+ [MedicationStatementClaim.Note]: noteText,
119
+ [MedicationStatementClaim.DosageInstruction]: dosageInstructionText,
120
+ [MedicationStatementClaim.MedicationIdentifier]: containedMedicationIdentifier,
121
+ [MedicationStatementClaim.MedicationSerialNumber]: batch?.lotNumber,
122
+ [MedicationStatementClaim.MedicationExpirationDate]: batch?.expirationDate,
118
123
  };
119
124
  }
120
125
  /**
@@ -123,26 +128,27 @@ export function medicationStatementFhirToFlat(resource) {
123
128
  * @param claims Flat allergy/intolerance claims map.
124
129
  */
125
130
  export function allergyIntoleranceFlatToFhir(claims) {
126
- const patient = claims['AllergyIntolerance.subject'] ?? claims['AllergyIntolerance.patient'];
131
+ const patient = claims[AllergyIntoleranceClaim.Subject] ?? claims[AllergyIntoleranceClaim.Patient];
132
+ const recorder = claims[AllergyIntoleranceClaim.Recorder];
127
133
  if (!patient) {
128
- throw new Error('Missing required claim: AllergyIntolerance.subject');
134
+ throw new Error(`Missing required claim: ${AllergyIntoleranceClaim.Subject}`);
129
135
  }
130
- requireSubjectIdentifier(patient, 'AllergyIntolerance.subject');
131
- if (claims['AllergyIntolerance.recorder']) {
132
- requireDidWeb(claims['AllergyIntolerance.recorder'], 'AllergyIntolerance.recorder');
136
+ requireSubjectIdentifier(patient, AllergyIntoleranceClaim.Subject);
137
+ if (recorder) {
138
+ requireDidWeb(recorder, AllergyIntoleranceClaim.Recorder);
133
139
  }
134
140
  return {
135
141
  resourceType: 'AllergyIntolerance',
136
- identifier: claims['AllergyIntolerance.identifier'] ? [{ value: claims['AllergyIntolerance.identifier'] }] : undefined,
142
+ identifier: claims[AllergyIntoleranceClaim.Identifier] ? [{ value: claims[AllergyIntoleranceClaim.Identifier] }] : undefined,
137
143
  patient: { reference: patient },
138
- code: claims['AllergyIntolerance.code'] ? { coding: codingFromValue(claims['AllergyIntolerance.code']) } : undefined,
139
- clinicalStatus: claims['AllergyIntolerance.clinical-status']
140
- ? { coding: [{ code: claims['AllergyIntolerance.clinical-status'] }] }
144
+ code: claims[AllergyIntoleranceClaim.Code] ? { coding: codingFromValue(claims[AllergyIntoleranceClaim.Code]) } : undefined,
145
+ clinicalStatus: claims[AllergyIntoleranceClaim.ClinicalStatus]
146
+ ? { coding: [{ code: claims[AllergyIntoleranceClaim.ClinicalStatus] }] }
141
147
  : undefined,
142
- verificationStatus: claims['AllergyIntolerance.verification-status']
143
- ? { coding: [{ code: claims['AllergyIntolerance.verification-status'] }] }
148
+ verificationStatus: claims[AllergyIntoleranceClaim.VerificationStatus]
149
+ ? { coding: [{ code: claims[AllergyIntoleranceClaim.VerificationStatus] }] }
144
150
  : undefined,
145
- recorder: claims['AllergyIntolerance.recorder'] ? { reference: claims['AllergyIntolerance.recorder'] } : undefined,
151
+ recorder: recorder ? { reference: recorder } : undefined,
146
152
  };
147
153
  }
148
154
  /**
@@ -153,13 +159,13 @@ export function allergyIntoleranceFlatToFhir(claims) {
153
159
  export function allergyIntoleranceFhirToFlat(resource) {
154
160
  const subject = resource.patient?.reference;
155
161
  return {
156
- 'AllergyIntolerance.identifier': resource.identifier?.[0]?.value,
157
- 'AllergyIntolerance.subject': subject,
158
- 'AllergyIntolerance.patient': subject,
159
- 'AllergyIntolerance.code': codingToValue(resource.code?.coding?.[0]),
160
- 'AllergyIntolerance.clinical-status': resource.clinicalStatus?.coding?.[0]?.code,
161
- 'AllergyIntolerance.verification-status': resource.verificationStatus?.coding?.[0]?.code,
162
- 'AllergyIntolerance.recorder': resource.recorder?.reference,
162
+ [AllergyIntoleranceClaim.Identifier]: resource.identifier?.[0]?.value,
163
+ [AllergyIntoleranceClaim.Subject]: subject,
164
+ [AllergyIntoleranceClaim.Patient]: subject,
165
+ [AllergyIntoleranceClaim.Code]: codingToValue(resource.code?.coding?.[0]),
166
+ [AllergyIntoleranceClaim.ClinicalStatus]: resource.clinicalStatus?.coding?.[0]?.code,
167
+ [AllergyIntoleranceClaim.VerificationStatus]: resource.verificationStatus?.coding?.[0]?.code,
168
+ [AllergyIntoleranceClaim.Recorder]: resource.recorder?.reference,
163
169
  };
164
170
  }
165
171
  /**
@@ -168,14 +174,14 @@ export function allergyIntoleranceFhirToFlat(resource) {
168
174
  * @param claims Flat condition claims map.
169
175
  */
170
176
  export function conditionFlatToFhir(claims) {
171
- const subject = requireClaim(claims, 'Condition.subject');
177
+ const subject = requireClaim(claims, ConditionClaim.Subject);
172
178
  return {
173
179
  resourceType: 'Condition',
174
- identifier: claims['Condition.identifier'] ? [{ value: claims['Condition.identifier'] }] : undefined,
180
+ identifier: claims[ConditionClaim.Identifier] ? [{ value: claims[ConditionClaim.Identifier] }] : undefined,
175
181
  subject: { reference: subject },
176
- code: claims['Condition.code'] ? { coding: codingFromValue(claims['Condition.code']) } : undefined,
177
- clinicalStatus: claims['Condition.clinical-status'] ? { coding: [{ code: claims['Condition.clinical-status'] }] } : undefined,
178
- verificationStatus: claims['Condition.verification-status'] ? { coding: [{ code: claims['Condition.verification-status'] }] } : undefined,
182
+ code: claims[ConditionClaim.Code] ? { coding: codingFromValue(claims[ConditionClaim.Code]) } : undefined,
183
+ clinicalStatus: claims[ConditionClaim.ClinicalStatus] ? { coding: [{ code: claims[ConditionClaim.ClinicalStatus] }] } : undefined,
184
+ verificationStatus: claims[ConditionClaim.VerificationStatus] ? { coding: [{ code: claims[ConditionClaim.VerificationStatus] }] } : undefined,
179
185
  };
180
186
  }
181
187
  /**
@@ -185,11 +191,11 @@ export function conditionFlatToFhir(claims) {
185
191
  */
186
192
  export function conditionFhirToFlat(resource) {
187
193
  return {
188
- 'Condition.identifier': resource.identifier?.[0]?.value,
189
- 'Condition.subject': resource.subject?.reference,
190
- 'Condition.code': codingToValue(resource.code?.coding?.[0]),
191
- 'Condition.clinical-status': resource.clinicalStatus?.coding?.[0]?.code,
192
- 'Condition.verification-status': resource.verificationStatus?.coding?.[0]?.code,
194
+ [ConditionClaim.Identifier]: resource.identifier?.[0]?.value,
195
+ [ConditionClaim.Subject]: resource.subject?.reference,
196
+ [ConditionClaim.Code]: codingToValue(resource.code?.coding?.[0]),
197
+ [ConditionClaim.ClinicalStatus]: resource.clinicalStatus?.coding?.[0]?.code,
198
+ [ConditionClaim.VerificationStatus]: resource.verificationStatus?.coding?.[0]?.code,
193
199
  };
194
200
  }
195
201
  /**
@@ -198,17 +204,17 @@ export function conditionFhirToFlat(resource) {
198
204
  * @param claims Flat device-use claims map.
199
205
  */
200
206
  export function deviceUseStatementFlatToFhir(claims) {
201
- const subject = requireClaim(claims, 'DeviceUseStatement.subject');
202
- const device = requireClaim(claims, 'DeviceUseStatement.device');
203
- const status = requireClaim(claims, 'DeviceUseStatement.status');
207
+ const subject = requireClaim(claims, DeviceUseStatementClaim.Subject);
208
+ const device = requireClaim(claims, DeviceUseStatementClaim.Device);
209
+ const status = requireClaim(claims, DeviceUseStatementClaim.Status);
204
210
  return {
205
211
  resourceType: 'DeviceUseStatement',
206
- identifier: claims['DeviceUseStatement.identifier'] ? [{ value: claims['DeviceUseStatement.identifier'] }] : undefined,
212
+ identifier: claims[DeviceUseStatementClaim.Identifier] ? [{ value: claims[DeviceUseStatementClaim.Identifier] }] : undefined,
207
213
  subject: { reference: subject },
208
214
  device: { reference: device },
209
215
  status,
210
- recordedOn: claims['DeviceUseStatement.recordedon'],
211
- timingDateTime: claims['DeviceUseStatement.timing-datetime'],
216
+ recordedOn: claims[DeviceUseStatementClaim.RecordedOn],
217
+ timingDateTime: claims[DeviceUseStatementClaim.TimingDateTime],
212
218
  };
213
219
  }
214
220
  /**
@@ -218,12 +224,12 @@ export function deviceUseStatementFlatToFhir(claims) {
218
224
  */
219
225
  export function deviceUseStatementFhirToFlat(resource) {
220
226
  return {
221
- 'DeviceUseStatement.identifier': resource.identifier?.[0]?.value,
222
- 'DeviceUseStatement.subject': resource.subject?.reference,
223
- 'DeviceUseStatement.device': resource.device?.reference,
224
- 'DeviceUseStatement.status': resource.status,
225
- 'DeviceUseStatement.recordedon': resource.recordedOn,
226
- 'DeviceUseStatement.timing-datetime': resource.timingDateTime,
227
+ [DeviceUseStatementClaim.Identifier]: resource.identifier?.[0]?.value,
228
+ [DeviceUseStatementClaim.Subject]: resource.subject?.reference,
229
+ [DeviceUseStatementClaim.Device]: resource.device?.reference,
230
+ [DeviceUseStatementClaim.Status]: resource.status,
231
+ [DeviceUseStatementClaim.RecordedOn]: resource.recordedOn,
232
+ [DeviceUseStatementClaim.TimingDateTime]: resource.timingDateTime,
227
233
  };
228
234
  }
229
235
  /**
@@ -1,5 +1,8 @@
1
+ // Always create JSDoc, do not use strings inline in keys nor values, use types instead, and reuse the data test examples.
1
2
  import { sha256 } from '@noble/hashes/sha2.js';
2
3
  import { utf8ToBytes } from '@noble/hashes/utils.js';
4
+ import { CommunicationClaim } from '../models/interoperable-claims/communication-claims.js';
5
+ import { DocumentReferenceClaim } from '../models/interoperable-claims/document-reference-claims.js';
3
6
  import { encodeMultibase58btc } from './multibase58.js';
4
7
  import { canonicalizeFhirResource, fhirResourceToCid } from './fhir-cid.js';
5
8
  const CID_V1 = 0x01;
@@ -130,16 +133,19 @@ export function buildDocumentReferenceFromCommunicationPayload(communication, op
130
133
  if (!contentCid) {
131
134
  throw new Error('Unable to generate content CID from attachment.');
132
135
  }
133
- const subject = communication?.meta?.claims?.['Communication.subject'];
136
+ // Good practice note:
137
+ // reusable Communication claim keys consumed here must come from
138
+ // `CommunicationClaim`, not re-hardcoded inline in readers/tests.
139
+ const subject = communication?.meta?.claims?.[CommunicationClaim.Subject];
134
140
  const documentReference = {
135
141
  resourceType: 'DocumentReference',
136
142
  status: 'current',
137
143
  meta: {
138
144
  versionId: contentCid,
139
145
  claims: {
140
- 'DocumentReference.subject': subject,
141
- 'DocumentReference.contenttype': contentType,
142
- 'DocumentReference.identifier': contentCid,
146
+ [DocumentReferenceClaim.Subject]: subject,
147
+ [DocumentReferenceClaim.ContentType]: contentType,
148
+ [DocumentReferenceClaim.Identifier]: contentCid,
143
149
  },
144
150
  },
145
151
  subject: subject ? { reference: subject } : undefined,
@@ -1,3 +1,5 @@
1
+ // Always create JSDoc, do not use strings inline in keys nor values, use types instead, and reuse the data test examples.
2
+ import { CommunicationClaim } from '../models/interoperable-claims/communication-claims.js';
1
3
  import { validateFhirResource } from './fhir-validator.js';
2
4
  /**
3
5
  * Validates a FHIR R4 `Communication` resource through the configured validator pipeline.
@@ -15,16 +17,20 @@ export async function validateCommunicationResourceFhirR4(resource) {
15
17
  * @param options.defaultStatus Default FHIR status when none is provided.
16
18
  */
17
19
  export function transformCommunicationClaimsToResourceFhirR4(communicationClaims, options = {}) {
20
+ // Good practice note:
21
+ // use `CommunicationClaim` for canonical claim keys whenever this helper
22
+ // writes or reads reusable Communication claims. Keep string literals only
23
+ // for explicit legacy-compatibility aliases such as `Communication.partOf`.
18
24
  const mode = options.mode ?? 'strict';
19
25
  const defaultStatus = options.defaultStatus ?? 'completed';
20
26
  const warnings = [];
21
27
  const resources = communicationClaims.map((claims, index) => {
22
- const payloadAttachmentData = toStringOrUndefined(claims['Communication.content-attachment-data']);
23
- const payloadAttachmentType = toStringOrUndefined(claims['Communication.content-attachment-type']);
24
- const payloadAttachmentTitle = toStringOrUndefined(claims['Communication.content-attachment-title']);
25
- const payloadAttachmentUrl = toStringOrUndefined(claims['Communication.content-attachment-url']);
26
- const payloadReference = toStringOrUndefined(claims['Communication.content-reference']);
27
- const payloadCodeRaw = toStringOrUndefined(claims['Communication.content-code']);
28
+ const payloadAttachmentData = toStringOrUndefined(claims[CommunicationClaim.ContentAttachmentData]);
29
+ const payloadAttachmentType = toStringOrUndefined(claims[CommunicationClaim.ContentAttachmentType]);
30
+ const payloadAttachmentTitle = toStringOrUndefined(claims[CommunicationClaim.ContentAttachmentTitle]);
31
+ const payloadAttachmentUrl = toStringOrUndefined(claims[CommunicationClaim.ContentAttachmentUrl]);
32
+ const payloadReference = toStringOrUndefined(claims[CommunicationClaim.ContentReference]);
33
+ const payloadCodeRaw = toStringOrUndefined(claims[CommunicationClaim.ContentCode]);
28
34
  const hasAttachment = Boolean(payloadAttachmentData || payloadAttachmentType || payloadAttachmentTitle || payloadAttachmentUrl);
29
35
  const hasReference = Boolean(payloadReference);
30
36
  const hasCode = Boolean(payloadCodeRaw);
@@ -35,9 +41,9 @@ export function transformCommunicationClaimsToResourceFhirR4(communicationClaims
35
41
  throw new Error(msg);
36
42
  warnings.push(`${msg} Keeping attachment > reference > code.`);
37
43
  }
38
- const noteValues = normalizeNoteValues(claims['Communication.note-text']
44
+ const noteValues = normalizeNoteValues(claims[CommunicationClaim.NoteText]
39
45
  ?? claims['Communication.note']
40
- ?? claims['Communication.text']);
46
+ ?? claims[CommunicationClaim.Text]);
41
47
  if (noteValues.length > 1) {
42
48
  const msg = `Communication[${index}] has more than one note.`;
43
49
  if (mode === 'strict')
@@ -55,7 +61,7 @@ export function transformCommunicationClaimsToResourceFhirR4(communicationClaims
55
61
  payloadReference,
56
62
  payloadCodeRaw,
57
63
  });
58
- const partOf = toStringOrUndefined(claims['Communication.part-of'] ?? claims['Communication.partOf']);
64
+ const partOf = toStringOrUndefined(claims[CommunicationClaim.PartOf] ?? claims['Communication.partOf']);
59
65
  if (claims['Communication.partOf'] !== undefined) {
60
66
  const msg = `Communication[${index}] uses legacy key 'Communication.partOf'. Use 'Communication.part-of'.`;
61
67
  if (mode === 'strict')
@@ -64,27 +70,27 @@ export function transformCommunicationClaimsToResourceFhirR4(communicationClaims
64
70
  }
65
71
  const resource = {
66
72
  resourceType: 'Communication',
67
- status: toStringOrUndefined(claims['Communication.status']) || defaultStatus,
73
+ status: toStringOrUndefined(claims[CommunicationClaim.Status]) || defaultStatus,
68
74
  meta: {
69
75
  claims: { ...claims },
70
76
  },
71
77
  };
72
- const identifier = toStringOrUndefined(claims['Communication.identifier']);
78
+ const identifier = toStringOrUndefined(claims[CommunicationClaim.Identifier]);
73
79
  if (identifier)
74
80
  resource['identifier'] = [{ value: identifier }];
75
- const sent = toStringOrUndefined(claims['Communication.sent']);
81
+ const sent = toStringOrUndefined(claims[CommunicationClaim.Sent]);
76
82
  if (sent)
77
83
  resource['sent'] = sent;
78
- const category = toStringOrUndefined(claims['Communication.category']);
84
+ const category = toStringOrUndefined(claims[CommunicationClaim.Category]);
79
85
  if (category)
80
86
  resource['category'] = [{ coding: [parseSystemCode(category)] }];
81
- const subject = toStringOrUndefined(claims['Communication.subject']);
87
+ const subject = toStringOrUndefined(claims[CommunicationClaim.Subject]);
82
88
  if (subject)
83
89
  resource['subject'] = { reference: subject };
84
- const recipient = toStringOrUndefined(claims['Communication.recipient']);
90
+ const recipient = toStringOrUndefined(claims[CommunicationClaim.Recipient]);
85
91
  if (recipient)
86
92
  resource['recipient'] = [{ reference: recipient }];
87
- const sender = toStringOrUndefined(claims['Communication.sender']);
93
+ const sender = toStringOrUndefined(claims[CommunicationClaim.Sender]);
88
94
  if (sender)
89
95
  resource['sender'] = { reference: sender };
90
96
  if (partOf)
@@ -124,37 +130,37 @@ export function extractCommunicationClaimsFromResourceFhirR4(resource, options =
124
130
  const contentReference = payload?.contentReference?.reference;
125
131
  const contentAttachment = payload?.contentAttachment;
126
132
  const contentCodeableConcept = payload?.contentCodeableConcept?.coding?.[0];
127
- setIf(claims, 'Communication.identifier', identifierValue);
128
- setIf(claims, 'Communication.status', status);
129
- setIf(claims, 'Communication.sent', sent);
130
- setIf(claims, 'Communication.subject', subjectRef);
131
- setIf(claims, 'Communication.recipient', recipientRef);
132
- setIf(claims, 'Communication.sender', senderRef);
133
- setIf(claims, 'Communication.part-of', partOfRef);
134
- setIf(claims, 'Communication.note-text', noteText);
135
- setIf(claims, 'Communication.text', noteText);
133
+ setIf(claims, CommunicationClaim.Identifier, identifierValue);
134
+ setIf(claims, CommunicationClaim.Status, status);
135
+ setIf(claims, CommunicationClaim.Sent, sent);
136
+ setIf(claims, CommunicationClaim.Subject, subjectRef);
137
+ setIf(claims, CommunicationClaim.Recipient, recipientRef);
138
+ setIf(claims, CommunicationClaim.Sender, senderRef);
139
+ setIf(claims, CommunicationClaim.PartOf, partOfRef);
140
+ setIf(claims, CommunicationClaim.NoteText, noteText);
141
+ setIf(claims, CommunicationClaim.Text, noteText);
136
142
  if (categoryCoding) {
137
143
  const system = toStringOrUndefined(categoryCoding.system);
138
144
  const code = toStringOrUndefined(categoryCoding.code);
139
145
  if (system && code)
140
- claims['Communication.category'] = `${system}|${code}`;
146
+ claims[CommunicationClaim.Category] = `${system}|${code}`;
141
147
  else if (code)
142
- claims['Communication.category'] = code;
148
+ claims[CommunicationClaim.Category] = code;
143
149
  }
144
- setIf(claims, 'Communication.content-reference', contentReference);
150
+ setIf(claims, CommunicationClaim.ContentReference, contentReference);
145
151
  if (contentAttachment) {
146
- setIf(claims, 'Communication.content-attachment-data', contentAttachment.data);
147
- setIf(claims, 'Communication.content-attachment-type', contentAttachment.contentType);
148
- setIf(claims, 'Communication.content-attachment-title', contentAttachment.title);
149
- setIf(claims, 'Communication.content-attachment-url', contentAttachment.url);
152
+ setIf(claims, CommunicationClaim.ContentAttachmentData, contentAttachment.data);
153
+ setIf(claims, CommunicationClaim.ContentAttachmentType, contentAttachment.contentType);
154
+ setIf(claims, CommunicationClaim.ContentAttachmentTitle, contentAttachment.title);
155
+ setIf(claims, CommunicationClaim.ContentAttachmentUrl, contentAttachment.url);
150
156
  }
151
157
  if (contentCodeableConcept) {
152
158
  const system = toStringOrUndefined(contentCodeableConcept.system);
153
159
  const code = toStringOrUndefined(contentCodeableConcept.code);
154
160
  if (system && code)
155
- claims['Communication.content-code'] = `${system}|${code}`;
161
+ claims[CommunicationClaim.ContentCode] = `${system}|${code}`;
156
162
  else if (code)
157
- claims['Communication.content-code'] = code;
163
+ claims[CommunicationClaim.ContentCode] = code;
158
164
  }
159
165
  return claims;
160
166
  }
@@ -1,5 +1,5 @@
1
1
  import type { ActiveConsentView, ConsentActorDescriptor, ConsentActorKind, ConsentCoverageRequest, EffectiveAccessEvaluation, NormalizedConsentTarget, ResolvedConsentActor } from '../models/consent-access.js';
2
- import type { ConsentRule } from '../models/consent-rule.js';
2
+ import { type ConsentRule } from '../models/consent-rule.js';
3
3
  /**
4
4
  * Legacy structured actor selector kept for backwards compatibility while the
5
5
  * SDK converges on the canonical flat consent actor identifier contract.