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.
@@ -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
+ }
@@ -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 `did:web:${hostDomain}:${sector}:organization:taxid:${providerTaxId}`;
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 = requireUuidUrn(input.subjectReference, 'subjectReference');
70
+ const subjectReference = requireReference(input.subjectReference, 'subjectReference');
33
71
  return {
34
72
  authorReference: authorIdentifier,
35
73
  entries: [{
@@ -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';
@@ -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';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdc-common-utils-ts",
3
- "version": "2.8.5",
3
+ "version": "2.8.6",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },