gdc-common-utils-ts 2.8.5 → 2.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/models/confidential-storage.d.ts +12 -0
- package/dist/models/interoperable-claims/composition-claims.d.ts +8 -0
- package/dist/models/interoperable-claims/composition-claims.js +7 -0
- package/dist/utils/confidential-document-provenance.d.ts +26 -0
- package/dist/utils/confidential-document-provenance.js +68 -0
- package/dist/utils/did.d.ts +15 -0
- package/dist/utils/did.js +32 -1
- package/dist/utils/fhir-ips-creator-identity.d.ts +33 -0
- package/dist/utils/fhir-ips-creator-identity.js +39 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/package.json +1 -1
|
@@ -50,6 +50,18 @@ export interface AuditInfo {
|
|
|
50
50
|
txId?: string;
|
|
51
51
|
/** Transaction timestamp (ISO 8601). */
|
|
52
52
|
txTime?: string;
|
|
53
|
+
/** Stable semantic creator resolved from authenticated clinical identity. */
|
|
54
|
+
creatorDid?: string;
|
|
55
|
+
/** Authenticated transport actor when different from the source author. */
|
|
56
|
+
submitterDid?: string;
|
|
57
|
+
/** Verified DCR/JWS signing key used by the submitting channel. */
|
|
58
|
+
signingKeyId?: string;
|
|
59
|
+
/** Organization or subject that owns the source author's role/relationship. */
|
|
60
|
+
authorOwnerIdentifier?: string;
|
|
61
|
+
/** Source Composition.date used for strictly-later successor decisions. */
|
|
62
|
+
documentDate?: string;
|
|
63
|
+
/** Source record linked to a derived confidential projection. */
|
|
64
|
+
sourceRecordId?: string;
|
|
53
65
|
}
|
|
54
66
|
/**
|
|
55
67
|
* Public runtime projection kept outside encrypted `content`.
|
|
@@ -16,4 +16,12 @@ export declare const CompositionClaim: {
|
|
|
16
16
|
readonly Identifier: "Composition.identifier";
|
|
17
17
|
readonly Title: "Composition.title";
|
|
18
18
|
};
|
|
19
|
+
/** Canonical FHIR R4 Composition.attester.mode codes. */
|
|
20
|
+
export declare const CompositionAttesterModes: Readonly<{
|
|
21
|
+
readonly Personal: "personal";
|
|
22
|
+
readonly Professional: "professional";
|
|
23
|
+
readonly Legal: "legal";
|
|
24
|
+
readonly Official: "official";
|
|
25
|
+
}>;
|
|
26
|
+
export type CompositionAttesterMode = typeof CompositionAttesterModes[keyof typeof CompositionAttesterModes];
|
|
19
27
|
export type CompositionClaimKey = typeof CompositionClaim[keyof typeof CompositionClaim];
|
|
@@ -18,3 +18,10 @@ export const CompositionClaim = {
|
|
|
18
18
|
Identifier: 'Composition.identifier',
|
|
19
19
|
Title: 'Composition.title',
|
|
20
20
|
};
|
|
21
|
+
/** Canonical FHIR R4 Composition.attester.mode codes. */
|
|
22
|
+
export const CompositionAttesterModes = Object.freeze({
|
|
23
|
+
Personal: 'personal',
|
|
24
|
+
Professional: 'professional',
|
|
25
|
+
Legal: 'legal',
|
|
26
|
+
Official: 'official',
|
|
27
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AuditInfo, IndexedAttribute } from '../models/confidential-storage.js';
|
|
2
|
+
export declare const ConfidentialDocumentIndex: Readonly<{
|
|
3
|
+
readonly Sector: "sector";
|
|
4
|
+
readonly CreatorDid: "audit.creatorDid";
|
|
5
|
+
readonly SubmitterDid: "audit.submitterDid";
|
|
6
|
+
readonly SigningKeyId: "audit.signingKeyId";
|
|
7
|
+
}>;
|
|
8
|
+
/** Flat Composition claims whose string value is a positional or set-like CSV projection. */
|
|
9
|
+
export declare const CompositionCsvClaimKeys: readonly ["Composition.author", "Composition.attester", "Composition.attester-mode", "Composition.attester-time", "Composition.section", "Composition.entry"];
|
|
10
|
+
export type ConfidentialDocumentProvenanceAudit = Pick<AuditInfo, 'creatorDid' | 'submitterDid' | 'signingKeyId'>;
|
|
11
|
+
export type ConfidentialDocumentIndexedAttributesInput = Readonly<{
|
|
12
|
+
claims: Readonly<Record<string, unknown>>;
|
|
13
|
+
sector?: string;
|
|
14
|
+
audit?: ConfidentialDocumentProvenanceAudit;
|
|
15
|
+
/**
|
|
16
|
+
* Temporarily retain the former whole-CSV attribute so exact legacy queries
|
|
17
|
+
* continue to match while consumers move to one attribute per value.
|
|
18
|
+
*/
|
|
19
|
+
includeDeprecatedCsvAggregate?: boolean;
|
|
20
|
+
}>;
|
|
21
|
+
/**
|
|
22
|
+
* Projects searchable metadata without changing the protected canonical
|
|
23
|
+
* claims. CSV multiplicity is governed by `CompositionCsvClaimKeys`; arbitrary
|
|
24
|
+
* text containing commas is never split.
|
|
25
|
+
*/
|
|
26
|
+
export declare function buildConfidentialDocumentIndexedAttributes(input: ConfidentialDocumentIndexedAttributesInput): IndexedAttribute[];
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Copyright 2026 Conéctate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
|
|
2
|
+
import { CompositionClaim } from '../models/interoperable-claims/composition-claims.js';
|
|
3
|
+
export const ConfidentialDocumentIndex = Object.freeze({
|
|
4
|
+
Sector: 'sector',
|
|
5
|
+
CreatorDid: 'audit.creatorDid',
|
|
6
|
+
SubmitterDid: 'audit.submitterDid',
|
|
7
|
+
SigningKeyId: 'audit.signingKeyId',
|
|
8
|
+
});
|
|
9
|
+
/** Flat Composition claims whose string value is a positional or set-like CSV projection. */
|
|
10
|
+
export const CompositionCsvClaimKeys = Object.freeze([
|
|
11
|
+
CompositionClaim.Author,
|
|
12
|
+
CompositionClaim.Attester,
|
|
13
|
+
CompositionClaim.AttesterMode,
|
|
14
|
+
CompositionClaim.AttesterTime,
|
|
15
|
+
CompositionClaim.Section,
|
|
16
|
+
CompositionClaim.Entry,
|
|
17
|
+
]);
|
|
18
|
+
const CSV_CLAIMS = new Set(CompositionCsvClaimKeys);
|
|
19
|
+
const COMPOSITION_CLAIMS = new Set(Object.values(CompositionClaim));
|
|
20
|
+
function canonicalCompositionClaimName(name) {
|
|
21
|
+
return [...COMPOSITION_CLAIMS].find((claimName) => name === claimName || name.endsWith(`.${claimName}`));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Projects searchable metadata without changing the protected canonical
|
|
25
|
+
* claims. CSV multiplicity is governed by `CompositionCsvClaimKeys`; arbitrary
|
|
26
|
+
* text containing commas is never split.
|
|
27
|
+
*/
|
|
28
|
+
export function buildConfidentialDocumentIndexedAttributes(input) {
|
|
29
|
+
const attributes = [];
|
|
30
|
+
const includeDeprecatedAggregate = input.includeDeprecatedCsvAggregate !== false;
|
|
31
|
+
const append = (name, value, unique) => {
|
|
32
|
+
const normalized = String(value ?? '').trim();
|
|
33
|
+
if (!normalized)
|
|
34
|
+
return;
|
|
35
|
+
if (attributes.some((attribute) => attribute.name === name && attribute.value === normalized))
|
|
36
|
+
return;
|
|
37
|
+
attributes.push({ name, value: normalized, ...(unique ? { unique: true } : {}) });
|
|
38
|
+
};
|
|
39
|
+
append(ConfidentialDocumentIndex.Sector, input.sector);
|
|
40
|
+
append(ConfidentialDocumentIndex.CreatorDid, input.audit?.creatorDid);
|
|
41
|
+
append(ConfidentialDocumentIndex.SubmitterDid, input.audit?.submitterDid);
|
|
42
|
+
append(ConfidentialDocumentIndex.SigningKeyId, input.audit?.signingKeyId);
|
|
43
|
+
for (const [name, rawValue] of Object.entries(input.claims)) {
|
|
44
|
+
if (name === '@context' || name === '@type' || rawValue === undefined || rawValue === null || Array.isArray(rawValue)) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const value = String(rawValue).trim();
|
|
48
|
+
if (!value)
|
|
49
|
+
continue;
|
|
50
|
+
const canonicalCompositionName = canonicalCompositionClaimName(name);
|
|
51
|
+
const indexedName = canonicalCompositionName || name;
|
|
52
|
+
if (CSV_CLAIMS.has(indexedName)) {
|
|
53
|
+
const values = value.split(',').map((item) => item.trim()).filter(Boolean);
|
|
54
|
+
values.forEach((item) => append(indexedName, item));
|
|
55
|
+
if (includeDeprecatedAggregate && values.length > 1)
|
|
56
|
+
append(indexedName, value);
|
|
57
|
+
if (name !== indexedName)
|
|
58
|
+
append(name, value);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const isUniqueResourceIdentifier = !canonicalCompositionName
|
|
62
|
+
&& (name.endsWith('.identifier') || name.endsWith('.identifier.value'));
|
|
63
|
+
append(indexedName, value, isUniqueResourceIdentifier);
|
|
64
|
+
if (name !== indexedName)
|
|
65
|
+
append(name, value);
|
|
66
|
+
}
|
|
67
|
+
return attributes;
|
|
68
|
+
}
|
package/dist/utils/did.d.ts
CHANGED
|
@@ -70,6 +70,21 @@ export declare function createHostedDidWeb(hostDidWeb: string, tenantId: string,
|
|
|
70
70
|
* used by the hosted organization form.
|
|
71
71
|
*/
|
|
72
72
|
export declare function extractTenantIdFromHostedDidWeb(did: string): string | undefined;
|
|
73
|
+
/**
|
|
74
|
+
* Builds the canonical hosted organization DID from the official identifier
|
|
75
|
+
* vocabulary governed by the selected data space.
|
|
76
|
+
*
|
|
77
|
+
* The identifier type is a path token such as `taxID`, `VAT`, or `BN`; this
|
|
78
|
+
* helper normalizes only that token and never guesses a type by parsing the
|
|
79
|
+
* identifier value. The corresponding FHIR Organization must retain the same
|
|
80
|
+
* type and value in `Organization.identifier`.
|
|
81
|
+
*/
|
|
82
|
+
export declare function buildHostedOrganizationDidWeb(input: {
|
|
83
|
+
hostDomain: string;
|
|
84
|
+
sector: string;
|
|
85
|
+
officialIdentifierType: string;
|
|
86
|
+
officialIdentifierValue: string;
|
|
87
|
+
}): string;
|
|
73
88
|
/**
|
|
74
89
|
* Builds the canonical hosted provider DID root used by hosted tenant services.
|
|
75
90
|
*
|
package/dist/utils/did.js
CHANGED
|
@@ -150,6 +150,32 @@ export function extractTenantIdFromHostedDidWeb(did) {
|
|
|
150
150
|
}
|
|
151
151
|
return decodeURIComponent(tenantId);
|
|
152
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Builds the canonical hosted organization DID from the official identifier
|
|
155
|
+
* vocabulary governed by the selected data space.
|
|
156
|
+
*
|
|
157
|
+
* The identifier type is a path token such as `taxID`, `VAT`, or `BN`; this
|
|
158
|
+
* helper normalizes only that token and never guesses a type by parsing the
|
|
159
|
+
* identifier value. The corresponding FHIR Organization must retain the same
|
|
160
|
+
* type and value in `Organization.identifier`.
|
|
161
|
+
*/
|
|
162
|
+
export function buildHostedOrganizationDidWeb(input) {
|
|
163
|
+
const hostDomain = String(input.hostDomain || '').trim().toLowerCase();
|
|
164
|
+
const sector = String(input.sector || '').trim().toLowerCase();
|
|
165
|
+
const identifierType = String(input.officialIdentifierType || '').trim().toLowerCase();
|
|
166
|
+
const identifierValue = String(input.officialIdentifierValue || '').trim();
|
|
167
|
+
if (!hostDomain)
|
|
168
|
+
throw new Error('buildHostedOrganizationDidWeb requires hostDomain.');
|
|
169
|
+
if (!sector)
|
|
170
|
+
throw new Error('buildHostedOrganizationDidWeb requires sector.');
|
|
171
|
+
if (!/^[a-z][a-z0-9._-]*$/.test(identifierType)) {
|
|
172
|
+
throw new Error('buildHostedOrganizationDidWeb requires a canonical officialIdentifierType path token.');
|
|
173
|
+
}
|
|
174
|
+
if (!identifierValue || /[:/\s]/.test(identifierValue)) {
|
|
175
|
+
throw new Error('buildHostedOrganizationDidWeb requires one DID-path-safe officialIdentifierValue.');
|
|
176
|
+
}
|
|
177
|
+
return `did:web:${hostDomain}:${sector}:organization:${identifierType}:${identifierValue}`;
|
|
178
|
+
}
|
|
153
179
|
/**
|
|
154
180
|
* Builds the canonical hosted provider DID root used by hosted tenant services.
|
|
155
181
|
*
|
|
@@ -170,7 +196,12 @@ export function buildHostedProviderDidWeb(input) {
|
|
|
170
196
|
throw new Error('buildHostedProviderDidWeb requires sector.');
|
|
171
197
|
if (!providerTaxId)
|
|
172
198
|
throw new Error('buildHostedProviderDidWeb requires providerTaxId.');
|
|
173
|
-
return
|
|
199
|
+
return buildHostedOrganizationDidWeb({
|
|
200
|
+
hostDomain,
|
|
201
|
+
sector,
|
|
202
|
+
officialIdentifierType: 'taxid',
|
|
203
|
+
officialIdentifierValue: providerTaxId,
|
|
204
|
+
});
|
|
174
205
|
}
|
|
175
206
|
/**
|
|
176
207
|
* Builds the canonical public provider DID root used by an external provider domain.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type CompositionAttesterMode } from '../models/interoperable-claims/composition-claims.js';
|
|
1
2
|
export declare const FhirIpsCreatorKinds: Readonly<{
|
|
2
3
|
readonly IndividualSubject: "individual-subject";
|
|
3
4
|
readonly IndividualMember: "individual-member";
|
|
@@ -30,6 +31,34 @@ export type FhirIpsCreatorAuthor = Readonly<{
|
|
|
30
31
|
authorReference: string;
|
|
31
32
|
entries: readonly FhirIpsCreatorBundleEntry[];
|
|
32
33
|
}>;
|
|
34
|
+
export type FhirIpsCreatorProvenanceInput = FhirIpsCreatorAuthorInput & Readonly<{
|
|
35
|
+
/** Explicit source originator; defaults to organization, subject, or oneself by creator kind. */
|
|
36
|
+
compositionAuthorReference?: string;
|
|
37
|
+
/** Defaults to professional for employees and personal for individual members. */
|
|
38
|
+
attesterMode?: CompositionAttesterMode;
|
|
39
|
+
/** Optional FHIR instant copied to Composition.attester.time. */
|
|
40
|
+
attestedAt?: string;
|
|
41
|
+
}>;
|
|
42
|
+
export type FhirIpsCreatorProvenance = Readonly<{
|
|
43
|
+
authorReference: string;
|
|
44
|
+
attesters: readonly Readonly<{
|
|
45
|
+
mode: CompositionAttesterMode;
|
|
46
|
+
party: Readonly<{
|
|
47
|
+
reference: string;
|
|
48
|
+
}>;
|
|
49
|
+
time?: string;
|
|
50
|
+
}>[];
|
|
51
|
+
entries: readonly FhirIpsCreatorBundleEntry[];
|
|
52
|
+
}>;
|
|
53
|
+
/**
|
|
54
|
+
* Builds source authorship separately from the person or role that attests it.
|
|
55
|
+
*
|
|
56
|
+
* Defaults model the ordinary delegated flows: an organization's professional
|
|
57
|
+
* attests an organization-authored record, while a member/caregiver attests a
|
|
58
|
+
* subject-authored record. `compositionAuthorReference` remains explicit for
|
|
59
|
+
* imports and cases where the actual originator differs from those defaults.
|
|
60
|
+
*/
|
|
61
|
+
export declare function buildFhirIpsCreatorProvenance(input: FhirIpsCreatorProvenanceInput): FhirIpsCreatorProvenance;
|
|
33
62
|
/**
|
|
34
63
|
* Builds the ordinary FHIR R4 resources referenced by an IPS
|
|
35
64
|
* `Composition.author`.
|
|
@@ -39,6 +68,10 @@ export type FhirIpsCreatorAuthor = Readonly<{
|
|
|
39
68
|
* ONESELF references the existing Patient/subject entry. A professional
|
|
40
69
|
* references PractitionerRole while retaining the underlying Practitioner.
|
|
41
70
|
* An individual member references RelatedPerson.
|
|
71
|
+
*
|
|
72
|
+
* @deprecated Use `buildFhirIpsCreatorProvenance` so Composition.author and
|
|
73
|
+
* Composition.attester remain separate. This compatibility helper preserves
|
|
74
|
+
* its historical role-as-author result.
|
|
42
75
|
*/
|
|
43
76
|
export declare function buildFhirIpsCreatorAuthor(input: FhirIpsCreatorAuthorInput): FhirIpsCreatorAuthor;
|
|
44
77
|
type ClinicalCreatorChannelAliases = Readonly<{
|
|
@@ -2,11 +2,45 @@
|
|
|
2
2
|
import { getHealthcareRoleByClaim } from '../constants/healthcare.js';
|
|
3
3
|
import { isStableActorIdentifier } from './actor-identifier.js';
|
|
4
4
|
import { normalizeUuid } from './normalize-uuid.js';
|
|
5
|
+
import { CompositionAttesterModes, } from '../models/interoperable-claims/composition-claims.js';
|
|
5
6
|
export const FhirIpsCreatorKinds = Object.freeze({
|
|
6
7
|
IndividualSubject: 'individual-subject',
|
|
7
8
|
IndividualMember: 'individual-member',
|
|
8
9
|
Professional: 'professional',
|
|
9
10
|
});
|
|
11
|
+
/**
|
|
12
|
+
* Builds source authorship separately from the person or role that attests it.
|
|
13
|
+
*
|
|
14
|
+
* Defaults model the ordinary delegated flows: an organization's professional
|
|
15
|
+
* attests an organization-authored record, while a member/caregiver attests a
|
|
16
|
+
* subject-authored record. `compositionAuthorReference` remains explicit for
|
|
17
|
+
* imports and cases where the actual originator differs from those defaults.
|
|
18
|
+
*/
|
|
19
|
+
export function buildFhirIpsCreatorProvenance(input) {
|
|
20
|
+
if (input.kind === FhirIpsCreatorKinds.IndividualSubject) {
|
|
21
|
+
return {
|
|
22
|
+
authorReference: requireReference(input.compositionAuthorReference || input.subjectReference, 'compositionAuthorReference'),
|
|
23
|
+
attesters: [],
|
|
24
|
+
entries: [],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const creator = buildFhirIpsCreatorAuthor(input);
|
|
28
|
+
const defaultAuthor = input.kind === FhirIpsCreatorKinds.Professional
|
|
29
|
+
? input.organizationReference
|
|
30
|
+
: input.subjectReference;
|
|
31
|
+
const mode = input.attesterMode || (input.kind === FhirIpsCreatorKinds.Professional
|
|
32
|
+
? CompositionAttesterModes.Professional
|
|
33
|
+
: CompositionAttesterModes.Personal);
|
|
34
|
+
return {
|
|
35
|
+
authorReference: requireReference(input.compositionAuthorReference || defaultAuthor, 'compositionAuthorReference'),
|
|
36
|
+
attesters: [{
|
|
37
|
+
mode,
|
|
38
|
+
party: { reference: creator.authorReference },
|
|
39
|
+
...(input.attestedAt ? { time: input.attestedAt } : {}),
|
|
40
|
+
}],
|
|
41
|
+
entries: creator.entries,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
10
44
|
/**
|
|
11
45
|
* Builds the ordinary FHIR R4 resources referenced by an IPS
|
|
12
46
|
* `Composition.author`.
|
|
@@ -16,6 +50,10 @@ export const FhirIpsCreatorKinds = Object.freeze({
|
|
|
16
50
|
* ONESELF references the existing Patient/subject entry. A professional
|
|
17
51
|
* references PractitionerRole while retaining the underlying Practitioner.
|
|
18
52
|
* An individual member references RelatedPerson.
|
|
53
|
+
*
|
|
54
|
+
* @deprecated Use `buildFhirIpsCreatorProvenance` so Composition.author and
|
|
55
|
+
* Composition.attester remain separate. This compatibility helper preserves
|
|
56
|
+
* its historical role-as-author result.
|
|
19
57
|
*/
|
|
20
58
|
export function buildFhirIpsCreatorAuthor(input) {
|
|
21
59
|
const actorIdentifier = requireUuidUrn(input.actorIdentifier, 'actorIdentifier');
|
|
@@ -29,7 +67,7 @@ export function buildFhirIpsCreatorAuthor(input) {
|
|
|
29
67
|
}
|
|
30
68
|
const role = requireRoleCoding(input.role);
|
|
31
69
|
if (input.kind === FhirIpsCreatorKinds.IndividualMember) {
|
|
32
|
-
const subjectReference =
|
|
70
|
+
const subjectReference = requireReference(input.subjectReference, 'subjectReference');
|
|
33
71
|
return {
|
|
34
72
|
authorReference: authorIdentifier,
|
|
35
73
|
entries: [{
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -52,6 +52,7 @@ export * from './bundle-didcomm-payload';
|
|
|
52
52
|
export * from './communication-didcomm-payload';
|
|
53
53
|
export * from './confidential-storage-persistence';
|
|
54
54
|
export * from './confidential-storage-test-data';
|
|
55
|
+
export * from './confidential-document-provenance';
|
|
55
56
|
export * from './permission-templates';
|
|
56
57
|
export * from './client-assertion';
|
|
57
58
|
export * from './professional-smart';
|
package/dist/utils/index.js
CHANGED
|
@@ -52,6 +52,7 @@ export * from './bundle-didcomm-payload.js';
|
|
|
52
52
|
export * from './communication-didcomm-payload.js';
|
|
53
53
|
export * from './confidential-storage-persistence.js';
|
|
54
54
|
export * from './confidential-storage-test-data.js';
|
|
55
|
+
export * from './confidential-document-provenance.js';
|
|
55
56
|
export * from './permission-templates.js';
|
|
56
57
|
export * from './client-assertion.js';
|
|
57
58
|
export * from './professional-smart.js';
|