gdc-common-utils-ts 2.9.9 → 2.9.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -283,13 +283,23 @@ blockchain asset.
283
283
 
284
284
  ### Convert
285
285
 
286
- The `convert` export exposes resource-specific FHIR R4 conversion helpers such as:
286
+ The `convert` export exposes resource-specific FHIR conversion helpers such as:
287
287
 
288
288
  - `medicationStatementFlatToFhirR4`
289
289
  - `medicationStatementFhirR4ToFlat`
290
290
  - `observationFromFlatToFhirR4`
291
291
  - `documentReferenceFlatToFhirR4`
292
292
  - `compositionFlatToFhirR4`
293
+ - `consentFlatToFhirR4` / `consentFhirR4ToFlat`
294
+ - `consentFlatToFhirR5` / `consentFhirR5ToFlat`
295
+
296
+ Consent claims are version-neutral. Consent validity, data validity, grantor,
297
+ grantee, manager, enforcer, actor role, action, purpose, resource/content codes
298
+ and an optional source attachment stay in `resource.meta.claims`. Explicit R4
299
+ or R5 export maps only concepts supported by that version. For example, R5
300
+ exports `Consent.controller`, while R4 does not invent an equivalent field.
301
+ Computable policy content such as ODRL remains in `sourceAttachment`; its
302
+ profile must validate semantic parity before projection.
293
303
 
294
304
  Example:
295
305
 
@@ -554,8 +564,12 @@ The canonical API contract should live in JSDoc on exported code. The README act
554
564
  the document author; a registered PractitionerRole or RelatedPerson is the
555
565
  attester, including for content entered by that authenticated person.
556
566
  - [`normalizeClinicalCreatorBinding(...)`](src/utils/fhir-ips-creator-identity.ts)
557
- - Accept stable UUIDs and governed bare role codes at the BFF boundary, then
558
- add canonical UUID URNs and HL7/ISCO coding systems before transport.
567
+ - Accept stable person, assignment and owner UUIDs plus governed bare role
568
+ codes at the BFF boundary, then add canonical UUID URNs and HL7/ISCO coding
569
+ systems before transport. Use `assignmentIdentifier` for the registered
570
+ `RelatedPerson`/`PractitionerRole`; the deprecated wire property
571
+ `authorIdentifier` names that assignment and does not decide
572
+ `Composition.author`.
559
573
  - [`src/examples/frontend-session.ts`](src/examples/frontend-session.ts)
560
574
  - Frontend profile/session bootstrap examples.
561
575
  - [`src/examples/lifecycle.ts`](src/examples/lifecycle.ts)
@@ -673,6 +687,9 @@ when the active runtime variable is really the subject identifier.
673
687
  - [`__tests__/utils-consent-claim-helpers.test.ts`](__tests__/utils-consent-claim-helpers.test.ts)
674
688
 
675
689
  These helpers are the shared base for consent claim construction across GW and SDKs.
690
+ The canonical claim catalogue also distinguishes `Consent.period-*` (the
691
+ Consent's effective period) from `Consent.data-period-*` (the period of the
692
+ governed data).
676
693
 
677
694
  ### Public module surfaces
678
695
 
@@ -41,6 +41,7 @@ export declare const ResourceTypesFhirR4: Readonly<{
41
41
  readonly PractitionerRole: "PractitionerRole";
42
42
  readonly Procedure: "Procedure";
43
43
  readonly RelatedPerson: "RelatedPerson";
44
+ readonly ResearchStudy: "ResearchStudy";
44
45
  readonly ResearchSubject: "ResearchSubject";
45
46
  readonly Specimen: "Specimen";
46
47
  }>;
@@ -89,6 +90,7 @@ export declare const FhirResourceTypes: Readonly<{
89
90
  readonly PractitionerRole: "PractitionerRole";
90
91
  readonly Procedure: "Procedure";
91
92
  readonly RelatedPerson: "RelatedPerson";
93
+ readonly ResearchStudy: "ResearchStudy";
92
94
  readonly ResearchSubject: "ResearchSubject";
93
95
  readonly Specimen: "Specimen";
94
96
  }>;
@@ -131,6 +133,7 @@ export declare const ResourceTypesFhirR5: Readonly<{
131
133
  readonly PractitionerRole: "PractitionerRole";
132
134
  readonly Procedure: "Procedure";
133
135
  readonly RelatedPerson: "RelatedPerson";
136
+ readonly ResearchStudy: "ResearchStudy";
134
137
  readonly ResearchSubject: "ResearchSubject";
135
138
  readonly Specimen: "Specimen";
136
139
  }>;
@@ -41,6 +41,7 @@ export const ResourceTypesFhirR4 = Object.freeze({
41
41
  PractitionerRole: 'PractitionerRole',
42
42
  Procedure: 'Procedure',
43
43
  RelatedPerson: 'RelatedPerson',
44
+ ResearchStudy: 'ResearchStudy',
44
45
  ResearchSubject: 'ResearchSubject',
45
46
  Specimen: 'Specimen',
46
47
  });
@@ -1,3 +1,9 @@
1
1
  import type { FhirResource, FlatClaims } from './convert-shared';
2
+ /** Converts native FHIR R4 Consent into the canonical flat claims contract. */
2
3
  export declare function consentFhirR4ToFlat(resource: FhirResource, context?: string): FlatClaims;
4
+ /** Projects canonical Consent claims to native FHIR R4. ODRL stays in sourceAttachment. */
3
5
  export declare function consentFlatToFhirR4(claims: FlatClaims): FhirResource;
6
+ /** Converts native FHIR R5 Consent into the same version-neutral flat claims contract. */
7
+ export declare function consentFhirR5ToFlat(resource: FhirResource, context?: string): FlatClaims;
8
+ /** Projects canonical Consent claims to native FHIR R5. ODRL stays in sourceAttachment. */
9
+ export declare function consentFlatToFhirR5(claims: FlatClaims): FhirResource;
@@ -1,78 +1,174 @@
1
1
  // Copyright 2026 Conéctate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
2
2
  import { ClaimConsent } from '../models/consent-rule.js';
3
- import { codingFromValue, codingToValue, referenceToValue, } from './convert-shared.js';
3
+ import { ResourceTypesFhirR4 } from '../constants/fhir-resource-types.js';
4
+ import { codingFromValue, codingToValue, referenceToValue } from './convert-shared.js';
5
+ const firstReference = (value) => referenceToValue(value?.[0]);
6
+ const conceptFromClaim = (value, display) => {
7
+ const coding = codingFromValue(value)?.map((item) => ({ ...item, ...(display ? { display } : {}) }));
8
+ return coding ? [{ coding }] : undefined;
9
+ };
10
+ const referencesFromClaim = (value) => value ? value.split(',').filter(Boolean).map((reference) => ({ reference })) : undefined;
11
+ const codingsFromClaim = (value) => {
12
+ const codings = value?.split(',').filter(Boolean).flatMap((token) => codingFromValue(token) || []);
13
+ return codings?.length ? codings : undefined;
14
+ };
15
+ const codingsToClaim = (value) => {
16
+ const tokens = (value || []).map((coding) => codingToValue(coding)).filter(Boolean);
17
+ return tokens.length ? tokens.join(',') : undefined;
18
+ };
19
+ const conceptsToClaim = (value) => {
20
+ const tokens = (value || []).flatMap((concept) => concept.coding || []).map((coding) => codingToValue(coding)).filter(Boolean);
21
+ return tokens.length ? tokens.join(',') : undefined;
22
+ };
23
+ const conceptsFromClaim = (value, display) => codingsFromClaim(value)?.map((coding) => ({ coding: [{ ...coding, ...(display ? { display } : {}) }] }));
24
+ const conceptWithCodingsFromClaim = (value) => {
25
+ const coding = codingsFromClaim(value);
26
+ return coding ? { coding } : undefined;
27
+ };
28
+ const attachmentFromClaims = (claims) => claims[ClaimConsent.attachmentContentType] || claims[ClaimConsent.attachmentData]
29
+ ? { contentType: claims[ClaimConsent.attachmentContentType], data: claims[ClaimConsent.attachmentData] }
30
+ : undefined;
31
+ const periodFromClaims = (claims, start, end) => claims[start] || claims[end] ? { start: claims[start], end: claims[end] } : undefined;
32
+ /** Converts native FHIR R4 Consent into the canonical flat claims contract. */
4
33
  export function consentFhirR4ToFlat(resource, context = 'org.hl7.fhir.api') {
5
- const concept = resource.category?.[0];
34
+ const category = resource.category?.[0];
6
35
  const provision = resource.provision;
36
+ const attachment = resource.sourceAttachment;
7
37
  const scope = resource.scope;
8
38
  const policyRule = resource.policyRule;
9
39
  return {
10
40
  '@context': context,
11
- [ClaimConsent.identifier]: resource.identifier?.[0]?.value
12
- || resource.id,
41
+ [ClaimConsent.identifier]: resource.identifier?.[0]?.value || resource.id,
13
42
  [ClaimConsent.status]: resource.status,
14
43
  [ClaimConsent.subject]: referenceToValue(resource.patient),
15
44
  [ClaimConsent.patient]: referenceToValue(resource.patient),
16
45
  [ClaimConsent.date]: resource.dateTime,
17
46
  [ClaimConsent.decision]: provision?.type,
18
- [ClaimConsent.action]: codingToValue(provision?.action?.[0]?.coding?.[0]),
19
47
  [ClaimConsent.periodStart]: provision?.period?.start,
20
48
  [ClaimConsent.periodEnd]: provision?.period?.end,
21
- [ClaimConsent.purpose]: codingToValue(provision?.purpose?.[0]),
49
+ [ClaimConsent.dataPeriodStart]: provision?.dataPeriod?.start,
50
+ [ClaimConsent.dataPeriodEnd]: provision?.dataPeriod?.end,
51
+ [ClaimConsent.grantor]: firstReference(resource.performer),
52
+ [ClaimConsent.grantee]: referenceToValue(provision?.actor?.[0]?.reference),
53
+ [ClaimConsent.manager]: firstReference(resource.organization),
54
+ [ClaimConsent.actorIdentifier]: referenceToValue(provision?.actor?.[0]?.reference),
55
+ [ClaimConsent.actorRole]: codingsToClaim(provision?.actor?.[0]?.role?.coding),
56
+ [ClaimConsent.action]: conceptsToClaim(provision?.action),
57
+ [ClaimConsent.purpose]: codingsToClaim(provision?.purpose),
58
+ [ClaimConsent.securityLabel]: codingsToClaim(provision?.securityLabel),
59
+ [ClaimConsent.resourceType]: codingsToClaim(provision?.class),
60
+ [ClaimConsent.provisionCode]: conceptsToClaim(provision?.code),
61
+ [ClaimConsent.provisionCodeDisplay]: provision?.code?.[0]?.coding?.[0]?.display,
22
62
  [ClaimConsent.sourceReference]: referenceToValue(resource.sourceReference),
23
- [ClaimConsent.category]: codingToValue(concept?.coding?.[0]),
24
- [ClaimConsent.categoryText]: concept?.text,
25
- [ClaimConsent.categoryDisplay]: concept?.coding?.[0]?.display,
63
+ [ClaimConsent.attachmentContentType]: attachment?.contentType,
64
+ [ClaimConsent.attachmentData]: attachment?.data,
65
+ [ClaimConsent.category]: codingToValue(category?.coding?.[0]),
66
+ [ClaimConsent.categoryText]: category?.text,
67
+ [ClaimConsent.categoryDisplay]: category?.coding?.[0]?.display,
26
68
  [ClaimConsent.scope]: codingToValue(scope?.coding?.[0]),
27
69
  [ClaimConsent.scopeDisplay]: scope?.coding?.[0]?.display,
28
70
  [ClaimConsent.policyRule]: codingToValue(policyRule?.coding?.[0]),
29
- [ClaimConsent.provisionCode]: codingToValue(provision?.code?.[0]?.coding?.[0]),
30
- [ClaimConsent.provisionCodeDisplay]: provision?.code?.[0]?.coding?.[0]?.display,
31
71
  };
32
72
  }
73
+ /** Projects canonical Consent claims to native FHIR R4. ODRL stays in sourceAttachment. */
33
74
  export function consentFlatToFhirR4(claims) {
34
- const coding = codingFromValue(claims[ClaimConsent.category])?.map((item) => ({
35
- ...item,
36
- ...(claims[ClaimConsent.categoryDisplay]
37
- ? { display: claims[ClaimConsent.categoryDisplay] }
38
- : {}),
39
- }));
40
- const actionCoding = codingFromValue(claims[ClaimConsent.action]);
41
- const purposeCoding = codingFromValue(claims[ClaimConsent.purpose]);
42
- const scopeCoding = codingFromValue(claims[ClaimConsent.scope])?.map((item) => ({ ...item, ...(claims[ClaimConsent.scopeDisplay] ? { display: claims[ClaimConsent.scopeDisplay] } : {}) }));
43
- const provisionCodeCoding = codingFromValue(claims[ClaimConsent.provisionCode])?.map((item) => ({ ...item, ...(claims[ClaimConsent.provisionCodeDisplay] ? { display: claims[ClaimConsent.provisionCodeDisplay] } : {}) }));
75
+ const actorRole = conceptWithCodingsFromClaim(claims[ClaimConsent.actorRole]);
76
+ const actorReference = claims[ClaimConsent.grantee] || claims[ClaimConsent.actorIdentifier];
77
+ const provisionCode = conceptsFromClaim(claims[ClaimConsent.provisionCode], claims[ClaimConsent.provisionCodeDisplay]);
78
+ const attachment = attachmentFromClaims(claims);
44
79
  return {
45
- resourceType: 'Consent',
80
+ resourceType: ResourceTypesFhirR4.Consent,
46
81
  identifier: claims[ClaimConsent.identifier] ? [{ value: claims[ClaimConsent.identifier] }] : undefined,
47
82
  status: claims[ClaimConsent.status],
48
- scope: scopeCoding ? { coding: scopeCoding } : undefined,
49
- policyRule: claims[ClaimConsent.policyRule] ? { coding: codingFromValue(claims[ClaimConsent.policyRule]) } : undefined,
50
- patient: claims[ClaimConsent.subject] || claims[ClaimConsent.patient]
51
- ? { reference: claims[ClaimConsent.subject] || claims[ClaimConsent.patient] }
52
- : undefined,
83
+ scope: conceptFromClaim(claims[ClaimConsent.scope], claims[ClaimConsent.scopeDisplay])?.[0],
84
+ category: conceptFromClaim(claims[ClaimConsent.category], claims[ClaimConsent.categoryDisplay]),
85
+ patient: claims[ClaimConsent.subject] || claims[ClaimConsent.patient] ? { reference: claims[ClaimConsent.subject] || claims[ClaimConsent.patient] } : undefined,
53
86
  dateTime: claims[ClaimConsent.date],
54
- sourceReference: claims[ClaimConsent.sourceReference]
55
- ? { reference: claims[ClaimConsent.sourceReference] }
56
- : undefined,
57
- category: coding || claims[ClaimConsent.categoryText] ? [{
58
- ...(coding ? { coding } : {}),
59
- ...(claims[ClaimConsent.categoryText] ? { text: claims[ClaimConsent.categoryText] } : {}),
60
- }] : undefined,
61
- provision: claims[ClaimConsent.decision]
62
- || claims[ClaimConsent.periodStart]
63
- || claims[ClaimConsent.periodEnd]
64
- || actionCoding
65
- || purposeCoding
66
- || provisionCodeCoding
67
- ? {
68
- type: claims[ClaimConsent.decision],
69
- period: claims[ClaimConsent.periodStart] || claims[ClaimConsent.periodEnd]
70
- ? { start: claims[ClaimConsent.periodStart], end: claims[ClaimConsent.periodEnd] }
71
- : undefined,
72
- action: actionCoding ? [{ coding: actionCoding }] : undefined,
73
- purpose: purposeCoding,
74
- code: provisionCodeCoding ? [{ coding: provisionCodeCoding }] : undefined,
75
- }
76
- : undefined,
87
+ performer: referencesFromClaim(claims[ClaimConsent.grantor]),
88
+ organization: referencesFromClaim(claims[ClaimConsent.manager]),
89
+ sourceAttachment: attachment,
90
+ sourceReference: claims[ClaimConsent.sourceReference] ? { reference: claims[ClaimConsent.sourceReference] } : undefined,
91
+ policyRule: conceptFromClaim(claims[ClaimConsent.policyRule])?.[0],
92
+ provision: {
93
+ type: claims[ClaimConsent.decision],
94
+ period: periodFromClaims(claims, ClaimConsent.periodStart, ClaimConsent.periodEnd),
95
+ actor: actorReference ? [{ role: actorRole, reference: { reference: actorReference } }] : undefined,
96
+ action: conceptsFromClaim(claims[ClaimConsent.action]),
97
+ purpose: codingsFromClaim(claims[ClaimConsent.purpose]),
98
+ securityLabel: codingsFromClaim(claims[ClaimConsent.securityLabel]),
99
+ class: codingsFromClaim(claims[ClaimConsent.resourceType]),
100
+ code: provisionCode,
101
+ dataPeriod: periodFromClaims(claims, ClaimConsent.dataPeriodStart, ClaimConsent.dataPeriodEnd),
102
+ },
103
+ };
104
+ }
105
+ /** Converts native FHIR R5 Consent into the same version-neutral flat claims contract. */
106
+ export function consentFhirR5ToFlat(resource, context = 'org.hl7.fhir.api') {
107
+ const categories = resource.category;
108
+ const scopeConcept = categories?.find((item) => item.coding?.[0]?.system?.includes('consentscope'));
109
+ const categoryConcept = categories?.find((item) => item !== scopeConcept);
110
+ const provision = resource.provision?.[0];
111
+ const attachment = resource.sourceAttachment?.[0];
112
+ return {
113
+ '@context': context,
114
+ [ClaimConsent.identifier]: resource.identifier?.[0]?.value || resource.id,
115
+ [ClaimConsent.status]: resource.status,
116
+ [ClaimConsent.subject]: referenceToValue(resource.subject),
117
+ [ClaimConsent.date]: resource.date,
118
+ [ClaimConsent.decision]: resource.decision,
119
+ [ClaimConsent.periodStart]: resource.period?.start,
120
+ [ClaimConsent.periodEnd]: resource.period?.end,
121
+ [ClaimConsent.grantor]: firstReference(resource.grantor),
122
+ [ClaimConsent.grantee]: firstReference(resource.grantee),
123
+ [ClaimConsent.manager]: firstReference(resource.manager),
124
+ [ClaimConsent.controller]: firstReference(resource.controller),
125
+ [ClaimConsent.actorIdentifier]: referenceToValue(provision?.actor?.[0]?.reference) || firstReference(resource.grantee),
126
+ [ClaimConsent.actorRole]: codingsToClaim(provision?.actor?.[0]?.role?.coding),
127
+ [ClaimConsent.action]: conceptsToClaim(provision?.action),
128
+ [ClaimConsent.purpose]: codingsToClaim(provision?.purpose),
129
+ [ClaimConsent.securityLabel]: codingsToClaim(provision?.securityLabel),
130
+ [ClaimConsent.documentType]: codingsToClaim(provision?.documentType),
131
+ [ClaimConsent.resourceType]: codingsToClaim(provision?.resourceType),
132
+ [ClaimConsent.provisionCode]: conceptsToClaim(provision?.code),
133
+ [ClaimConsent.dataPeriodStart]: provision?.dataPeriod?.start,
134
+ [ClaimConsent.dataPeriodEnd]: provision?.dataPeriod?.end,
135
+ [ClaimConsent.attachmentContentType]: attachment?.contentType,
136
+ [ClaimConsent.attachmentData]: attachment?.data,
137
+ [ClaimConsent.sourceReference]: firstReference(resource.sourceReference),
138
+ [ClaimConsent.scope]: codingToValue(scopeConcept?.coding?.[0]),
139
+ [ClaimConsent.category]: codingToValue(categoryConcept?.coding?.[0] || categories?.[0]?.coding?.[0]),
140
+ };
141
+ }
142
+ /** Projects canonical Consent claims to native FHIR R5. ODRL stays in sourceAttachment. */
143
+ export function consentFlatToFhirR5(claims) {
144
+ const category = conceptFromClaim(claims[ClaimConsent.category], claims[ClaimConsent.categoryDisplay]);
145
+ const scope = conceptFromClaim(claims[ClaimConsent.scope], claims[ClaimConsent.scopeDisplay]);
146
+ const actorReference = claims[ClaimConsent.grantee] || claims[ClaimConsent.actorIdentifier];
147
+ const attachment = attachmentFromClaims(claims);
148
+ return {
149
+ resourceType: ResourceTypesFhirR4.Consent,
150
+ identifier: claims[ClaimConsent.identifier] ? [{ value: claims[ClaimConsent.identifier] }] : undefined,
151
+ status: claims[ClaimConsent.status],
152
+ category: [...(scope || []), ...(category || [])],
153
+ subject: claims[ClaimConsent.subject] ? { reference: claims[ClaimConsent.subject] } : undefined,
154
+ date: claims[ClaimConsent.date],
155
+ period: periodFromClaims(claims, ClaimConsent.periodStart, ClaimConsent.periodEnd),
156
+ grantor: referencesFromClaim(claims[ClaimConsent.grantor]),
157
+ grantee: referencesFromClaim(claims[ClaimConsent.grantee] || claims[ClaimConsent.actorIdentifier]),
158
+ manager: referencesFromClaim(claims[ClaimConsent.manager]),
159
+ controller: referencesFromClaim(claims[ClaimConsent.controller]),
160
+ sourceAttachment: attachment ? [attachment] : undefined,
161
+ sourceReference: referencesFromClaim(claims[ClaimConsent.sourceReference]),
162
+ decision: claims[ClaimConsent.decision],
163
+ provision: [{
164
+ actor: actorReference ? [{ role: conceptWithCodingsFromClaim(claims[ClaimConsent.actorRole]), reference: { reference: actorReference } }] : undefined,
165
+ action: conceptsFromClaim(claims[ClaimConsent.action]),
166
+ securityLabel: codingsFromClaim(claims[ClaimConsent.securityLabel]),
167
+ purpose: codingsFromClaim(claims[ClaimConsent.purpose]),
168
+ documentType: codingsFromClaim(claims[ClaimConsent.documentType]),
169
+ resourceType: codingsFromClaim(claims[ClaimConsent.resourceType]),
170
+ code: conceptsFromClaim(claims[ClaimConsent.provisionCode], claims[ClaimConsent.provisionCodeDisplay]),
171
+ dataPeriod: periodFromClaims(claims, ClaimConsent.dataPeriodStart, ClaimConsent.dataPeriodEnd),
172
+ }],
77
173
  };
78
174
  }
@@ -0,0 +1,28 @@
1
+ /** Reusable, non-production identifiers for Consent R4/R5 projection tests. */
2
+ export declare const CONSENT_PROJECTION_TEST_DATA: Readonly<{
3
+ identifier: "urn:uuid:9f4105a1-fca1-4b92-a38f-cd40f37da36e";
4
+ subject: "Patient/consent-subject-test";
5
+ grantor: "RelatedPerson/consent-grantor-test";
6
+ grantee: "Organization/consent-grantee-test";
7
+ manager: "Organization/consent-manager-test";
8
+ controller: "Organization/consent-enforcer-test";
9
+ createdAt: "2026-09-08";
10
+ consentPeriodStart: "2026-09-08T10:00:00Z";
11
+ consentPeriodEnd: "2026-10-08T10:00:00Z";
12
+ dataPeriodStart: "2025-09-08T00:00:00Z";
13
+ dataPeriodEnd: "2026-09-08T00:00:00Z";
14
+ actorRole: "http://terminology.hl7.org/CodeSystem/v3-RoleCode|PAYOR";
15
+ action: "http://terminology.hl7.org/CodeSystem/consentaction|access";
16
+ purpose: "http://terminology.hl7.org/CodeSystem/v3-ActReason|HPAYMT";
17
+ scope: "http://terminology.hl7.org/CodeSystem/consentscope|patient-privacy";
18
+ category: "http://loinc.org|59284-0";
19
+ resourceType: "http://hl7.org/fhir/fhir-types|Observation";
20
+ provisionCode: "http://loinc.org|85353-1";
21
+ secondProvisionCode: "http://loinc.org|8310-5";
22
+ provisionCodeList: "http://loinc.org|85353-1,http://loinc.org|8310-5";
23
+ securityLabel: "http://terminology.hl7.org/CodeSystem/v3-Confidentiality|R";
24
+ attachmentContentType: "application/odrl+json";
25
+ attachmentData: "eyJAdHlwZSI6IkFncmVlbWVudCJ9";
26
+ odrlOnlyConstraintName: "odrl.profile.max-query-count";
27
+ odrlOnlyConstraintValue: "1";
28
+ }>;
@@ -0,0 +1,29 @@
1
+ // Copyright 2026 Conéctate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
2
+ /** Reusable, non-production identifiers for Consent R4/R5 projection tests. */
3
+ export const CONSENT_PROJECTION_TEST_DATA = Object.freeze({
4
+ identifier: 'urn:uuid:9f4105a1-fca1-4b92-a38f-cd40f37da36e',
5
+ subject: 'Patient/consent-subject-test',
6
+ grantor: 'RelatedPerson/consent-grantor-test',
7
+ grantee: 'Organization/consent-grantee-test',
8
+ manager: 'Organization/consent-manager-test',
9
+ controller: 'Organization/consent-enforcer-test',
10
+ createdAt: '2026-09-08',
11
+ consentPeriodStart: '2026-09-08T10:00:00Z',
12
+ consentPeriodEnd: '2026-10-08T10:00:00Z',
13
+ dataPeriodStart: '2025-09-08T00:00:00Z',
14
+ dataPeriodEnd: '2026-09-08T00:00:00Z',
15
+ actorRole: 'http://terminology.hl7.org/CodeSystem/v3-RoleCode|PAYOR',
16
+ action: 'http://terminology.hl7.org/CodeSystem/consentaction|access',
17
+ purpose: 'http://terminology.hl7.org/CodeSystem/v3-ActReason|HPAYMT',
18
+ scope: 'http://terminology.hl7.org/CodeSystem/consentscope|patient-privacy',
19
+ category: 'http://loinc.org|59284-0',
20
+ resourceType: 'http://hl7.org/fhir/fhir-types|Observation',
21
+ provisionCode: 'http://loinc.org|85353-1',
22
+ secondProvisionCode: 'http://loinc.org|8310-5',
23
+ provisionCodeList: 'http://loinc.org|85353-1,http://loinc.org|8310-5',
24
+ securityLabel: 'http://terminology.hl7.org/CodeSystem/v3-Confidentiality|R',
25
+ attachmentContentType: 'application/odrl+json',
26
+ attachmentData: 'eyJAdHlwZSI6IkFncmVlbWVudCJ9',
27
+ odrlOnlyConstraintName: 'odrl.profile.max-query-count',
28
+ odrlOnlyConstraintValue: '1',
29
+ });
@@ -1,4 +1,5 @@
1
1
  export * from './shared';
2
+ export * from './consent-projection';
2
3
  export * from './ica-activation-proof';
3
4
  export * from './ica-verify-response';
4
5
  export * from './dataspace-discovery';
@@ -1,4 +1,5 @@
1
1
  export * from './shared.js';
2
+ export * from './consent-projection.js';
2
3
  export * from './ica-activation-proof.js';
3
4
  export * from './ica-verify-response.js';
4
5
  export * from './dataspace-discovery.js';
@@ -13,7 +13,12 @@ export declare enum ClaimConsent {
13
13
  'date' = "Consent.date",
14
14
  'periodStart' = "Consent.period-start",
15
15
  'periodEnd' = "Consent.period-end",
16
+ 'dataPeriodStart' = "Consent.data-period-start",
17
+ 'dataPeriodEnd' = "Consent.data-period-end",
18
+ 'grantor' = "Consent.grantor",
16
19
  'grantee' = "Consent.grantee",
20
+ 'manager' = "Consent.manager",
21
+ 'controller' = "Consent.controller",
17
22
  'verifiedBy' = "Consent.verified-by",
18
23
  'verifiedDate' = "Consent.verified-date",
19
24
  'purpose' = "Consent.purpose",
@@ -25,6 +30,8 @@ export declare enum ClaimConsent {
25
30
  'policyRule' = "Consent.policy-rule",
26
31
  'provisionCode' = "Consent.provision-code",
27
32
  'provisionCodeDisplay' = "Consent.provision-code-display",
33
+ 'securityLabel' = "Consent.security-label",
34
+ 'documentType' = "Consent.document-type",
28
35
  'containedReferenceList' = "Consent.contained-reference-list",
29
36
  /**
30
37
  * @deprecated Use `containedReferenceList`.
@@ -137,6 +144,14 @@ export interface ConsentRule {
137
144
  * Derived from the end of the `org.hl7.fhir.api.Consent.period` claim.
138
145
  */
139
146
  'Consent.period-end'?: string;
147
+ /** Timeframe of the governed data; distinct from Consent validity. */
148
+ 'Consent.data-period-start'?: string;
149
+ 'Consent.data-period-end'?: string;
150
+ /** R5 grantor, grantee, workflow manager and enforcement controller references. */
151
+ 'Consent.grantor'?: string;
152
+ 'Consent.grantee'?: string;
153
+ 'Consent.manager'?: string;
154
+ 'Consent.controller'?: string;
140
155
  /**
141
156
  * The party to whom the consent is granted.
142
157
  * Use both 'Consent.actor-identifier' and 'Consent.actor-role' instead.
@@ -163,6 +178,11 @@ export interface ConsentRule {
163
178
  * Derived from the `org.hl7.fhir.api.Consent.identifier` claim.
164
179
  */
165
180
  'Consent.identifier': string;
181
+ /** Governed FHIR resource types and security/content classifications. */
182
+ 'Consent.resource-type'?: string;
183
+ 'Consent.security-label'?: string;
184
+ 'Consent.document-type'?: string;
185
+ 'Consent.provision-code'?: string;
166
186
  'Consent.contained-reference-list'?: string;
167
187
  'Consent.contained-resource-list'?: string;
168
188
  'Consent.contained-documents'?: string;
@@ -15,7 +15,12 @@ export var ClaimConsent;
15
15
  ClaimConsent["date"] = "Consent.date";
16
16
  ClaimConsent["periodStart"] = "Consent.period-start";
17
17
  ClaimConsent["periodEnd"] = "Consent.period-end";
18
+ ClaimConsent["dataPeriodStart"] = "Consent.data-period-start";
19
+ ClaimConsent["dataPeriodEnd"] = "Consent.data-period-end";
20
+ ClaimConsent["grantor"] = "Consent.grantor";
18
21
  ClaimConsent["grantee"] = "Consent.grantee";
22
+ ClaimConsent["manager"] = "Consent.manager";
23
+ ClaimConsent["controller"] = "Consent.controller";
19
24
  ClaimConsent["verifiedBy"] = "Consent.verified-by";
20
25
  ClaimConsent["verifiedDate"] = "Consent.verified-date";
21
26
  ClaimConsent["purpose"] = "Consent.purpose";
@@ -27,6 +32,8 @@ export var ClaimConsent;
27
32
  ClaimConsent["policyRule"] = "Consent.policy-rule";
28
33
  ClaimConsent["provisionCode"] = "Consent.provision-code";
29
34
  ClaimConsent["provisionCodeDisplay"] = "Consent.provision-code-display";
35
+ ClaimConsent["securityLabel"] = "Consent.security-label";
36
+ ClaimConsent["documentType"] = "Consent.document-type";
30
37
  ClaimConsent["containedReferenceList"] = "Consent.contained-reference-list";
31
38
  /**
32
39
  * @deprecated Use `containedReferenceList`.
@@ -12,7 +12,11 @@ export type FhirIpsCreatorBundleEntry = Readonly<{
12
12
  type FhirIpsCreatorBase = Readonly<{
13
13
  /** Stable imported or locally generated actor identifier. */
14
14
  actorIdentifier: string;
15
- /** Stable identifier referenced by Composition.author for this role/relationship. */
15
+ /**
16
+ * Historical API name for the stable role/relationship assignment identifier.
17
+ * Provenance uses this assignment as `Composition.attester.party`; it is not
18
+ * automatically the `Composition.author`.
19
+ */
16
20
  authorIdentifier: string;
17
21
  }>;
18
22
  export type FhirIpsCreatorAuthorInput = (FhirIpsCreatorBase & Readonly<{
@@ -93,8 +97,11 @@ type ClinicalCreatorChannelAliases = Readonly<{
93
97
  }>;
94
98
  /**
95
99
  * Durable clinical-creator identity stored behind the authenticated profile.
96
- * `actorIdentifier` identifies the person/member. `authorIdentifier` identifies
97
- * the concrete role or relationship assignment used as the FHIR author.
100
+ * `actorIdentifier` identifies the person/member. The historical wire property
101
+ * `authorIdentifier` identifies the concrete PractitionerRole/RelatedPerson
102
+ * assignment used as `Composition.attester.party`; it does not decide
103
+ * `Composition.author`. `ownerIdentifier` identifies the legal organization or
104
+ * individual organization/subject that owns that assignment.
98
105
  * Contacts, DCR clients, keys and operational DIDs are aliases only.
99
106
  */
100
107
  export type ClinicalCreatorBinding = ClinicalCreatorChannelAliases & Readonly<{
@@ -117,7 +124,10 @@ export type ClinicalCreatorBinding = ClinicalCreatorChannelAliases & Readonly<{
117
124
  export type ClinicalCreatorBindingInput = ClinicalCreatorChannelAliases & Readonly<{
118
125
  kind: FhirIpsCreatorKind;
119
126
  actorIdentifier: string;
120
- authorIdentifier: string;
127
+ /** Preferred high-level name for the PractitionerRole/RelatedPerson UUID. */
128
+ assignmentIdentifier?: string;
129
+ /** @deprecated Historical DCR/profile wire name; use `assignmentIdentifier`. */
130
+ authorIdentifier?: string;
121
131
  ownerIdentifier: string;
122
132
  role: string;
123
133
  }>;
@@ -129,13 +129,26 @@ export function buildFhirIpsCreatorAuthor(input) {
129
129
  */
130
130
  export function normalizeClinicalCreatorBinding(input) {
131
131
  const actorIdentifier = canonicalUuidUrn(input.actorIdentifier, 'actorIdentifier');
132
- const authorIdentifier = canonicalUuidUrn(input.authorIdentifier, 'authorIdentifier');
132
+ const preferredAssignment = input.assignmentIdentifier
133
+ ? canonicalUuidUrn(input.assignmentIdentifier, 'assignmentIdentifier')
134
+ : undefined;
135
+ const legacyAssignment = input.authorIdentifier
136
+ ? canonicalUuidUrn(input.authorIdentifier, 'authorIdentifier')
137
+ : undefined;
138
+ if (preferredAssignment && legacyAssignment && preferredAssignment !== legacyAssignment) {
139
+ throw new TypeError('assignmentIdentifier and deprecated authorIdentifier must identify the same assignment.');
140
+ }
141
+ const authorIdentifier = preferredAssignment || legacyAssignment;
142
+ if (!authorIdentifier) {
143
+ throw new TypeError('assignmentIdentifier is required.');
144
+ }
133
145
  const ownerIdentifier = input.kind === FhirIpsCreatorKinds.Professional
134
146
  ? requireReference(input.ownerIdentifier, 'ownerIdentifier')
135
147
  : canonicalUuidUrn(input.ownerIdentifier, 'ownerIdentifier');
136
148
  const role = canonicalRoleClaim(input.role);
149
+ const { assignmentIdentifier: _assignmentIdentifier, ...wireInput } = input;
137
150
  return {
138
- ...input,
151
+ ...wireInput,
139
152
  actorIdentifier,
140
153
  authorIdentifier,
141
154
  ownerIdentifier,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdc-common-utils-ts",
3
- "version": "2.9.9",
3
+ "version": "2.9.11",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },