gdc-common-utils-ts 2.3.0 → 2.3.2
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 +22 -0
- package/dist/CryptographyService.d.ts +3 -0
- package/dist/CryptographyService.js +64 -35
- package/dist/constants/identity-identifiers.d.ts +60 -7
- package/dist/constants/identity-identifiers.js +58 -7
- package/dist/constants/schemaorg.d.ts +4 -0
- package/dist/constants/schemaorg.js +4 -0
- package/dist/constants/urn.d.ts +1 -0
- package/dist/constants/urn.js +1 -0
- package/dist/convert/index.d.ts +1 -0
- package/dist/convert/index.js +1 -0
- package/dist/convert/schemaorg-to-gaia-x.d.ts +116 -0
- package/dist/convert/schemaorg-to-gaia-x.js +211 -0
- package/dist/examples/shared.d.ts +1 -1
- package/dist/examples/shared.js +1 -1
- package/dist/models/bundle-editor-types.d.ts +13 -3
- package/dist/models/bundle-editor-types.js +9 -1
- package/dist/models/gaia-x.d.ts +132 -0
- package/dist/models/gaia-x.js +26 -0
- package/dist/models/index.d.ts +2 -0
- package/dist/models/index.js +2 -0
- package/dist/models/interoperable-claims/invoice-claims.d.ts +1 -1
- package/dist/models/interoperable-claims/invoice-claims.js +2 -2
- package/dist/models/interoperable-claims/observation-claims.d.ts +2 -2
- package/dist/models/interoperable-claims/observation-claims.js +2 -2
- package/dist/models/jwe.d.ts +25 -0
- package/dist/models/subject-identifier-ledger.d.ts +16 -0
- package/dist/models/subject-identifier-ledger.js +1 -0
- package/dist/utils/bundle-editor-core.d.ts +9 -0
- package/dist/utils/bundle-editor-core.js +46 -0
- package/dist/utils/bundle-editor.d.ts +2 -0
- package/dist/utils/bundle-editor.js +2 -0
- package/dist/utils/bundle-entry-editor.d.ts +6 -0
- package/dist/utils/bundle-entry-editor.js +20 -0
- package/dist/utils/communication-consent-access-editor.d.ts +14 -1
- package/dist/utils/communication-consent-access-editor.js +27 -0
- package/dist/utils/consent-entry-editor.d.ts +42 -0
- package/dist/utils/consent-entry-editor.js +80 -0
- package/dist/utils/did.d.ts +2 -2
- package/dist/utils/did.js +3 -3
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/individual-identifier.d.ts +14 -0
- package/dist/utils/individual-identifier.js +39 -0
- package/dist/utils/multibasehash.d.ts +19 -5
- package/dist/utils/multibasehash.js +35 -10
- package/dist/utils/multiformat-profile.d.ts +4 -2
- package/dist/utils/multiformat-profile.js +5 -3
- package/dist/utils/related-person-entry-editor.d.ts +32 -0
- package/dist/utils/related-person-entry-editor.js +97 -0
- package/dist/utils/same-as.d.ts +10 -0
- package/dist/utils/same-as.js +28 -3
- package/package.json +2 -1
|
@@ -77,6 +77,52 @@ export class BundleEditor {
|
|
|
77
77
|
getBundleType() {
|
|
78
78
|
return this.bundleType;
|
|
79
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Replaces staged entries with one previously materialized JSON-API-like
|
|
82
|
+
* Bundle snapshot so a later UI session can reopen, edit or append entries.
|
|
83
|
+
*
|
|
84
|
+
* This is an in-memory authoring operation only. It does not imply that the
|
|
85
|
+
* supplied Bundle came from authoritative GW readback and it performs no
|
|
86
|
+
* Communication wrapping, submission or polling.
|
|
87
|
+
*/
|
|
88
|
+
setBundle(bundle) {
|
|
89
|
+
if (!bundle || bundle.resourceType !== ResourceTypesFhirR4.Bundle || !Array.isArray(bundle.data)) {
|
|
90
|
+
throw new TypeError('BundleEditor.setBundle requires a Bundle with data[].');
|
|
91
|
+
}
|
|
92
|
+
if (!Object.values(BundleTypes).includes(bundle.type)) {
|
|
93
|
+
throw new TypeError(`BundleEditor.setBundle does not support Bundle.type: ${String(bundle.type || '')}`);
|
|
94
|
+
}
|
|
95
|
+
const operation = this.requireBundleOperation();
|
|
96
|
+
const nextEntries = bundle.data.map((source, index) => {
|
|
97
|
+
const resource = source?.resource;
|
|
98
|
+
const resourceType = normalizeOptionalIdentifier(resource?.resourceType);
|
|
99
|
+
if (!resource || !resourceType) {
|
|
100
|
+
throw new TypeError(`BundleEditor.setBundle requires data[${index}].resource.resourceType.`);
|
|
101
|
+
}
|
|
102
|
+
if (bundle.type !== BundleTypes.document
|
|
103
|
+
&& this.allowedResourceType
|
|
104
|
+
&& resourceType !== this.allowedResourceType) {
|
|
105
|
+
throw new Error(`BundleEditor cannot mix resource types in ${bundle.type} mode: ${this.allowedResourceType} vs ${resourceType}`);
|
|
106
|
+
}
|
|
107
|
+
const cloned = cloneEntry(source);
|
|
108
|
+
return {
|
|
109
|
+
type: normalizeOptionalIdentifier(cloned.type) || inferGenericEntryType(resourceType, operation),
|
|
110
|
+
request: cloned.request || { method: resolveRequestMethodForOperation(operation) },
|
|
111
|
+
...(cloned.fullUrl ? { fullUrl: cloned.fullUrl } : {}),
|
|
112
|
+
resource: {
|
|
113
|
+
...cloned.resource,
|
|
114
|
+
resourceType,
|
|
115
|
+
meta: {
|
|
116
|
+
...(cloned.resource?.meta || {}),
|
|
117
|
+
claims: { ...(cloned.resource?.meta?.claims || {}) },
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
this.bundleType = bundle.type;
|
|
123
|
+
this.entries.splice(0, this.entries.length, ...nextEntries);
|
|
124
|
+
return this;
|
|
125
|
+
}
|
|
80
126
|
setCompositionIdentifier(identifier) {
|
|
81
127
|
return this.setCompositionScalarClaim(CompositionClaim.Identifier, identifier);
|
|
82
128
|
}
|
|
@@ -19,3 +19,5 @@ export * from './immunization-entry-editor';
|
|
|
19
19
|
export * from './procedure-entry-editor';
|
|
20
20
|
export * from './diagnostic-report-entry-editor';
|
|
21
21
|
export * from './employee-entry-editor';
|
|
22
|
+
export * from './consent-entry-editor';
|
|
23
|
+
export * from './related-person-entry-editor';
|
|
@@ -19,3 +19,5 @@ export * from './immunization-entry-editor.js';
|
|
|
19
19
|
export * from './procedure-entry-editor.js';
|
|
20
20
|
export * from './diagnostic-report-entry-editor.js';
|
|
21
21
|
export * from './employee-entry-editor.js';
|
|
22
|
+
export * from './consent-entry-editor.js';
|
|
23
|
+
export * from './related-person-entry-editor.js';
|
|
@@ -17,6 +17,8 @@ import type { MedicationStatementEntryEditor } from './medication-statement-entr
|
|
|
17
17
|
import type { ObservationEntryEditor } from './observation-entry-editor';
|
|
18
18
|
import type { ProcedureEntryEditor } from './procedure-entry-editor';
|
|
19
19
|
import type { VitalSignEntryEditor } from './vital-sign-entry-editor';
|
|
20
|
+
import type { ConsentEntryEditor } from './consent-entry-editor';
|
|
21
|
+
import type { RelatedPersonEntryEditor } from './related-person-entry-editor';
|
|
20
22
|
import { type EmployeeClaims } from './employee';
|
|
21
23
|
export declare class BundleEntryEditor {
|
|
22
24
|
protected readonly bundleEditor: BundleEditor;
|
|
@@ -31,6 +33,10 @@ export declare class BundleEntryEditor {
|
|
|
31
33
|
asResourceType<T extends AllowedResourceType>(resourceType: T): ResourceTypeEntryEditor<T>;
|
|
32
34
|
/** Opens the current entry as one employee-specific resource editor. */
|
|
33
35
|
asEmployee(): EmployeeEntryEditor;
|
|
36
|
+
/** Opens the current entry as one Consent permission editor. */
|
|
37
|
+
asConsent(): ConsentEntryEditor;
|
|
38
|
+
/** Opens the current entry as one RelatedPerson/contact editor. */
|
|
39
|
+
asRelatedPerson(): RelatedPersonEntryEditor;
|
|
34
40
|
/** Opens the current entry as one vital-sign-specific Observation editor. */
|
|
35
41
|
asVitalSign(): VitalSignEntryEditor;
|
|
36
42
|
/** Opens the current entry as one general Observation editor. */
|
|
@@ -27,6 +27,10 @@ export class BundleEntryEditor {
|
|
|
27
27
|
switch (normalized) {
|
|
28
28
|
case BundleEditableResourceTypes.employee:
|
|
29
29
|
return this.asEmployee();
|
|
30
|
+
case BundleEditableResourceTypes.consent:
|
|
31
|
+
return this.asConsent();
|
|
32
|
+
case BundleEditableResourceTypes.relatedPerson:
|
|
33
|
+
return this.asRelatedPerson();
|
|
30
34
|
case BundleEditableResourceTypes.vitalSign:
|
|
31
35
|
return this.asVitalSign();
|
|
32
36
|
case BundleEditableResourceTypes.observation:
|
|
@@ -71,6 +75,22 @@ export class BundleEntryEditor {
|
|
|
71
75
|
}
|
|
72
76
|
return createRegisteredBundleEntryEditor(BundleEditableResourceTypes.employee, this.bundleEditor, this.entryIndex);
|
|
73
77
|
}
|
|
78
|
+
/** Opens the current entry as one Consent permission editor. */
|
|
79
|
+
asConsent() {
|
|
80
|
+
const entry = this.getMutableEntry();
|
|
81
|
+
if (entry.resource?.resourceType !== ResourceTypesFhirR4.Consent) {
|
|
82
|
+
throw new Error(`BundleEntryEditor cannot open this entry as Consent: ${String(entry.resource?.resourceType || '')}`);
|
|
83
|
+
}
|
|
84
|
+
return createRegisteredBundleEntryEditor(BundleEditableResourceTypes.consent, this.bundleEditor, this.entryIndex);
|
|
85
|
+
}
|
|
86
|
+
/** Opens the current entry as one RelatedPerson/contact editor. */
|
|
87
|
+
asRelatedPerson() {
|
|
88
|
+
const entry = this.getMutableEntry();
|
|
89
|
+
if (entry.resource?.resourceType !== ResourceTypesFhirR4.RelatedPerson) {
|
|
90
|
+
throw new Error(`BundleEntryEditor cannot open this entry as RelatedPerson: ${String(entry.resource?.resourceType || '')}`);
|
|
91
|
+
}
|
|
92
|
+
return createRegisteredBundleEntryEditor(BundleEditableResourceTypes.relatedPerson, this.bundleEditor, this.entryIndex);
|
|
93
|
+
}
|
|
74
94
|
/** Opens the current entry as one vital-sign-specific Observation editor. */
|
|
75
95
|
asVitalSign() {
|
|
76
96
|
const entry = this.getMutableEntry();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type HealthcareActorRoleDescriptor, type HealthcareSectionDescriptor } from '../constants/healthcare';
|
|
2
2
|
import type { DataspaceSector } from '../constants/sectors';
|
|
3
|
-
import type { CommunicationAttachedBundleSessionOptions, ConsentEditorClassifiedActors, ConsentEditorClassifiedPurpose, ConsentEditorClassifiedRoles, ConsentEditorClassifiedTarget, ConsentViewModel } from '../models/communication-attached-bundle-session';
|
|
3
|
+
import type { ActiveEntrySelection, CommunicationAttachedBundleSessionOptions, ConsentEditorClassifiedActors, ConsentEditorClassifiedPurpose, ConsentEditorClassifiedRoles, ConsentEditorClassifiedTarget, ConsentViewModel, UpsertClaimsResourceEntryInput } from '../models/communication-attached-bundle-session';
|
|
4
4
|
import type { ConsentDuplicateRuleConflict } from './consent-duplicate-rules';
|
|
5
5
|
import { CommunicationAttachedBundleSession } from './communication-attached-bundle-session';
|
|
6
6
|
/**
|
|
@@ -15,6 +15,19 @@ import { CommunicationAttachedBundleSession } from './communication-attached-bun
|
|
|
15
15
|
* by the existing access-rule contract.
|
|
16
16
|
*/
|
|
17
17
|
export declare class ConsentAccessEditor extends CommunicationAttachedBundleSession {
|
|
18
|
+
/**
|
|
19
|
+
* Adds one authored Consent to the in-memory permission Bundle and selects it.
|
|
20
|
+
*
|
|
21
|
+
* Prefer this semantic name in app code and tutorials. Saving, attaching the
|
|
22
|
+
* completed Bundle to a Communication and transporting it are later steps.
|
|
23
|
+
*/
|
|
24
|
+
addConsent(input: UpsertClaimsResourceEntryInput<Record<string, unknown>>): this;
|
|
25
|
+
/** Reopens one Consent already present in the in-memory permission Bundle. */
|
|
26
|
+
openConsent(selection: ActiveEntrySelection): this;
|
|
27
|
+
/** Saves the active Consent and closes its editing selection. */
|
|
28
|
+
saveConsent(): this;
|
|
29
|
+
/** @deprecated Internal compatibility plumbing. Use `addConsent(...)`. */
|
|
30
|
+
upsertActiveConsentEntry(input: UpsertClaimsResourceEntryInput<Record<string, unknown>>): this;
|
|
18
31
|
/** Returns duplicate atomic consent-rule conflicts across the current bundle. */
|
|
19
32
|
getConsentRuleDuplicateConflicts(): ConsentDuplicateRuleConflict[];
|
|
20
33
|
/** Returns duplicate atomic consent-rule conflicts affecting the active Consent entry. */
|
|
@@ -18,6 +18,33 @@ import { asTrimmedString, buildClassifiedConsentTarget, buildConsentViewModel, b
|
|
|
18
18
|
* by the existing access-rule contract.
|
|
19
19
|
*/
|
|
20
20
|
export class ConsentAccessEditor extends CommunicationAttachedBundleSession {
|
|
21
|
+
/**
|
|
22
|
+
* Adds one authored Consent to the in-memory permission Bundle and selects it.
|
|
23
|
+
*
|
|
24
|
+
* Prefer this semantic name in app code and tutorials. Saving, attaching the
|
|
25
|
+
* completed Bundle to a Communication and transporting it are later steps.
|
|
26
|
+
*/
|
|
27
|
+
addConsent(input) {
|
|
28
|
+
return super.upsertActiveConsentEntry(input);
|
|
29
|
+
}
|
|
30
|
+
/** Reopens one Consent already present in the in-memory permission Bundle. */
|
|
31
|
+
openConsent(selection) {
|
|
32
|
+
this.selectActiveEntry(selection);
|
|
33
|
+
const resourceType = this.getActiveEntry()?.resource?.resourceType;
|
|
34
|
+
if (resourceType !== ResourceTypesFhirR4.Consent) {
|
|
35
|
+
this.clearActiveEntry();
|
|
36
|
+
throw new Error(`ConsentAccessEditor cannot open resource type: ${String(resourceType || '')}`);
|
|
37
|
+
}
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
/** Saves the active Consent and closes its editing selection. */
|
|
41
|
+
saveConsent() {
|
|
42
|
+
return this.saveAndReleaseActiveEntry();
|
|
43
|
+
}
|
|
44
|
+
/** @deprecated Internal compatibility plumbing. Use `addConsent(...)`. */
|
|
45
|
+
upsertActiveConsentEntry(input) {
|
|
46
|
+
return this.addConsent(input);
|
|
47
|
+
}
|
|
21
48
|
/** Returns duplicate atomic consent-rule conflicts across the current bundle. */
|
|
22
49
|
getConsentRuleDuplicateConflicts() {
|
|
23
50
|
return detectDuplicateConsentRuleConflicts(this.getBundleInMemory().data);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File discipline note:
|
|
3
|
+
* - Read `ARCHITECTURE.md` and `CONTRIBUTING.md` before changing this module.
|
|
4
|
+
* - This file owns only the typed editor for one staged Consent entry.
|
|
5
|
+
*/
|
|
6
|
+
import { type ConsentDecision } from '../models/consent-rule';
|
|
7
|
+
import { BundleEntryEditor } from './bundle-entry-editor';
|
|
8
|
+
/**
|
|
9
|
+
* Typed editor for one Consent permission staged inside a Bundle.
|
|
10
|
+
*
|
|
11
|
+
* It edits semantic permission data only. It does not create a Communication,
|
|
12
|
+
* choose a clinical projection, pack DIDComm, authorize, submit or poll.
|
|
13
|
+
*/
|
|
14
|
+
export declare class ConsentEntryEditor extends BundleEntryEditor {
|
|
15
|
+
setIdentifier(value?: string | null): this;
|
|
16
|
+
getIdentifier(): string | undefined;
|
|
17
|
+
ensureIdentifier(): string;
|
|
18
|
+
setSubject(value?: string | null): this;
|
|
19
|
+
getSubject(): string | undefined;
|
|
20
|
+
setDecision(value?: ConsentDecision | null): this;
|
|
21
|
+
getDecision(): string | undefined;
|
|
22
|
+
setActorIdentifierList(values: readonly string[]): this;
|
|
23
|
+
getActorIdentifierList(): string[];
|
|
24
|
+
setActorRoleList(values: readonly string[]): this;
|
|
25
|
+
getActorRoleList(): string[];
|
|
26
|
+
setPurposeList(values: readonly string[]): this;
|
|
27
|
+
getPurposeList(): string[];
|
|
28
|
+
setSectionList(values: readonly string[]): this;
|
|
29
|
+
getSectionList(): string[];
|
|
30
|
+
setResourceTypeList(values: readonly string[]): this;
|
|
31
|
+
getResourceTypeList(): string[];
|
|
32
|
+
setDate(value?: string | null): this;
|
|
33
|
+
getDate(): string | undefined;
|
|
34
|
+
setPeriodStart(value?: string | null): this;
|
|
35
|
+
getPeriodStart(): string | undefined;
|
|
36
|
+
setPeriodEnd(value?: string | null): this;
|
|
37
|
+
getPeriodEnd(): string | undefined;
|
|
38
|
+
private setOptionalText;
|
|
39
|
+
private getOptionalText;
|
|
40
|
+
private setList;
|
|
41
|
+
private getList;
|
|
42
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File discipline note:
|
|
3
|
+
* - Read `ARCHITECTURE.md` and `CONTRIBUTING.md` before changing this module.
|
|
4
|
+
* - This file owns only the typed editor for one staged Consent entry.
|
|
5
|
+
*/
|
|
6
|
+
import { ClaimConsent } from '../models/consent-rule.js';
|
|
7
|
+
import { BundleEditableResourceTypes } from '../models/bundle-editor-types.js';
|
|
8
|
+
import { BundleEntryEditor } from './bundle-entry-editor.js';
|
|
9
|
+
import { registerBundleEntryEditor } from './bundle-editor-registry.js';
|
|
10
|
+
function normalizeText(value) {
|
|
11
|
+
const normalized = String(value || '').trim();
|
|
12
|
+
return normalized || undefined;
|
|
13
|
+
}
|
|
14
|
+
function normalizeList(values) {
|
|
15
|
+
return [...new Set(values.map((value) => String(value || '').trim()).filter(Boolean))];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Typed editor for one Consent permission staged inside a Bundle.
|
|
19
|
+
*
|
|
20
|
+
* It edits semantic permission data only. It does not create a Communication,
|
|
21
|
+
* choose a clinical projection, pack DIDComm, authorize, submit or poll.
|
|
22
|
+
*/
|
|
23
|
+
export class ConsentEntryEditor extends BundleEntryEditor {
|
|
24
|
+
setIdentifier(value) {
|
|
25
|
+
const normalized = normalizeText(value);
|
|
26
|
+
if (!normalized) {
|
|
27
|
+
this.removeClaim(ClaimConsent.identifier).setResourceId().setFullUrl();
|
|
28
|
+
return this;
|
|
29
|
+
}
|
|
30
|
+
return this.setClaim(ClaimConsent.identifier, normalized)
|
|
31
|
+
.setResourceId(normalized)
|
|
32
|
+
.setFullUrl(normalized);
|
|
33
|
+
}
|
|
34
|
+
getIdentifier() {
|
|
35
|
+
return normalizeText(String(this.getClaim(ClaimConsent.identifier) || this.getResourceId() || this.getFullUrl() || ''));
|
|
36
|
+
}
|
|
37
|
+
ensureIdentifier() {
|
|
38
|
+
const identifier = this.getIdentifier();
|
|
39
|
+
if (!identifier)
|
|
40
|
+
throw new Error('Consent entry requires a generated resource id or explicit identifier.');
|
|
41
|
+
this.setIdentifier(identifier);
|
|
42
|
+
return identifier;
|
|
43
|
+
}
|
|
44
|
+
setSubject(value) { return this.setOptionalText(ClaimConsent.subject, value); }
|
|
45
|
+
getSubject() { return this.getOptionalText(ClaimConsent.subject); }
|
|
46
|
+
setDecision(value) { return this.setOptionalText(ClaimConsent.decision, value); }
|
|
47
|
+
getDecision() { return this.getOptionalText(ClaimConsent.decision); }
|
|
48
|
+
setActorIdentifierList(values) { return this.setList(ClaimConsent.actorIdentifier, values); }
|
|
49
|
+
getActorIdentifierList() { return this.getList(ClaimConsent.actorIdentifier); }
|
|
50
|
+
setActorRoleList(values) { return this.setList(ClaimConsent.actorRole, values); }
|
|
51
|
+
getActorRoleList() { return this.getList(ClaimConsent.actorRole); }
|
|
52
|
+
setPurposeList(values) { return this.setList(ClaimConsent.purpose, values); }
|
|
53
|
+
getPurposeList() { return this.getList(ClaimConsent.purpose); }
|
|
54
|
+
setSectionList(values) { return this.setList(ClaimConsent.action, values); }
|
|
55
|
+
getSectionList() { return this.getList(ClaimConsent.action); }
|
|
56
|
+
setResourceTypeList(values) { return this.setList(ClaimConsent.resourceType, values); }
|
|
57
|
+
getResourceTypeList() { return this.getList(ClaimConsent.resourceType); }
|
|
58
|
+
setDate(value) { return this.setOptionalText(ClaimConsent.date, value); }
|
|
59
|
+
getDate() { return this.getOptionalText(ClaimConsent.date); }
|
|
60
|
+
setPeriodStart(value) { return this.setOptionalText(ClaimConsent.periodStart, value); }
|
|
61
|
+
getPeriodStart() { return this.getOptionalText(ClaimConsent.periodStart); }
|
|
62
|
+
setPeriodEnd(value) { return this.setOptionalText(ClaimConsent.periodEnd, value); }
|
|
63
|
+
getPeriodEnd() { return this.getOptionalText(ClaimConsent.periodEnd); }
|
|
64
|
+
setOptionalText(key, value) {
|
|
65
|
+
const normalized = normalizeText(value);
|
|
66
|
+
return normalized ? this.setClaim(key, normalized) : this.removeClaim(key);
|
|
67
|
+
}
|
|
68
|
+
getOptionalText(key) {
|
|
69
|
+
return normalizeText(String(this.getClaim(key) || ''));
|
|
70
|
+
}
|
|
71
|
+
setList(key, values) {
|
|
72
|
+
const normalized = normalizeList(values);
|
|
73
|
+
return normalized.length ? this.setClaim(key, normalized.join(',')) : this.removeClaim(key);
|
|
74
|
+
}
|
|
75
|
+
getList(key) {
|
|
76
|
+
const value = this.getClaim(key);
|
|
77
|
+
return normalizeList(Array.isArray(value) ? value.map(String) : String(value || '').split(','));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
registerBundleEntryEditor(BundleEditableResourceTypes.consent, ConsentEntryEditor);
|
package/dist/utils/did.d.ts
CHANGED
|
@@ -71,7 +71,7 @@ export declare function extractTenantIdFromHostedDidWeb(did: string): string | u
|
|
|
71
71
|
* Builds the canonical hosted provider DID root used by hosted tenant services.
|
|
72
72
|
*
|
|
73
73
|
* Canonical form:
|
|
74
|
-
* `did:web:<host.domain>:<sector
|
|
74
|
+
* `did:web:<host.domain>:<sector>:organization:taxid:<provider-tax-id>`
|
|
75
75
|
*
|
|
76
76
|
* This is the provider-scoped DID root under which hosted individual and member
|
|
77
77
|
* identities are published. Downstream individual DIDs extend this root with
|
|
@@ -167,7 +167,7 @@ export declare function buildProfessionalDidWeb(input: {
|
|
|
167
167
|
*
|
|
168
168
|
* Canonical supported forms:
|
|
169
169
|
* - hosted:
|
|
170
|
-
* `did:web:<host.domain>:<sector
|
|
170
|
+
* `did:web:<host.domain>:<sector>:organization:taxid:<provider-tax-id>:individual:multibase:<individualId>`
|
|
171
171
|
* - external/provider-domain:
|
|
172
172
|
* `did:web:<sector.provider.domain>:individual:multibase:<individualId>`
|
|
173
173
|
*
|
package/dist/utils/did.js
CHANGED
|
@@ -137,7 +137,7 @@ export function extractTenantIdFromHostedDidWeb(did) {
|
|
|
137
137
|
* Builds the canonical hosted provider DID root used by hosted tenant services.
|
|
138
138
|
*
|
|
139
139
|
* Canonical form:
|
|
140
|
-
* `did:web:<host.domain>:<sector
|
|
140
|
+
* `did:web:<host.domain>:<sector>:organization:taxid:<provider-tax-id>`
|
|
141
141
|
*
|
|
142
142
|
* This is the provider-scoped DID root under which hosted individual and member
|
|
143
143
|
* identities are published. Downstream individual DIDs extend this root with
|
|
@@ -153,7 +153,7 @@ export function buildHostedProviderDidWeb(input) {
|
|
|
153
153
|
throw new Error('buildHostedProviderDidWeb requires sector.');
|
|
154
154
|
if (!providerTaxId)
|
|
155
155
|
throw new Error('buildHostedProviderDidWeb requires providerTaxId.');
|
|
156
|
-
return `did:web:${hostDomain}:${sector}
|
|
156
|
+
return `did:web:${hostDomain}:${sector}:organization:taxid:${providerTaxId}`;
|
|
157
157
|
}
|
|
158
158
|
/**
|
|
159
159
|
* Builds the canonical public provider DID root used by an external provider domain.
|
|
@@ -269,7 +269,7 @@ export function buildProfessionalDidWeb(input) {
|
|
|
269
269
|
*
|
|
270
270
|
* Canonical supported forms:
|
|
271
271
|
* - hosted:
|
|
272
|
-
* `did:web:<host.domain>:<sector
|
|
272
|
+
* `did:web:<host.domain>:<sector>:organization:taxid:<provider-tax-id>:individual:multibase:<individualId>`
|
|
273
273
|
* - external/provider-domain:
|
|
274
274
|
* `did:web:<sector.provider.domain>:individual:multibase:<individualId>`
|
|
275
275
|
*
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -59,6 +59,7 @@ export * from './clinical-resource-view';
|
|
|
59
59
|
export * from './fhir-validator';
|
|
60
60
|
export * from './family-registration-test-data';
|
|
61
61
|
export * from './individual-form-pdf';
|
|
62
|
+
export * from './individual-identifier';
|
|
62
63
|
export * from './individual-smart';
|
|
63
64
|
export * from './individual-organization-claims';
|
|
64
65
|
export * from './inter-tenant-access-contract';
|
package/dist/utils/index.js
CHANGED
|
@@ -59,6 +59,7 @@ export * from './clinical-resource-view.js';
|
|
|
59
59
|
export * from './fhir-validator.js';
|
|
60
60
|
export * from './family-registration-test-data.js';
|
|
61
61
|
export * from './individual-form-pdf.js';
|
|
62
|
+
export * from './individual-identifier.js';
|
|
62
63
|
export * from './individual-smart.js';
|
|
63
64
|
export * from './individual-organization-claims.js';
|
|
64
65
|
export * from './inter-tenant-access-contract.js';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Hl7V20203IdentifierCode, type IdKindValue } from '../constants/identity-identifiers.js';
|
|
2
|
+
export type IndividualIdentifierInput = Readonly<{
|
|
3
|
+
type: Hl7V20203IdentifierCode | IdKindValue | string;
|
|
4
|
+
jurisdiction: string;
|
|
5
|
+
value: string;
|
|
6
|
+
}>;
|
|
7
|
+
/** Resolves a supported short or reverse-DNS HL7 identifier type to one canonical type. */
|
|
8
|
+
export declare function normalizeIndividualIdentifierType(type: IndividualIdentifierInput['type']): IdKindValue;
|
|
9
|
+
/** Builds the sole canonical input hashed for an external individual identifier alias. */
|
|
10
|
+
export declare function buildIndividualIdentifierToken(input: IndividualIdentifierInput): string;
|
|
11
|
+
/** Returns the deterministic CIDv1/SHA3-384 alias stored in `Organization.sameAs`. */
|
|
12
|
+
export declare function buildIndividualIdentifierCid(input: IndividualIdentifierInput): string;
|
|
13
|
+
/** Returns the SHA3-384 multihash URN used as a ledger lookup asset id. */
|
|
14
|
+
export declare function buildIndividualIdentifierLedgerAssetId(input: IndividualIdentifierInput): string;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { HL7_V2_0203_REVERSE_DNS_PREFIX, HL7_V2_0203_REVERSE_DNS_TYPES, } from '../constants/identity-identifiers.js';
|
|
2
|
+
import { buildRawCidV1FromUtf8String } from './multiformat-profile.js';
|
|
3
|
+
import { encodeMultibaseSha3 } from './multibasehash.js';
|
|
4
|
+
import { UrnPrefixes } from '../constants/urn.js';
|
|
5
|
+
const LEGACY_HL7_V2_0203_REVERSE_DNS_PREFIX = 'org.hl7.terminology.codesystem.v2-0203';
|
|
6
|
+
const ISO_3166_JURISDICTION_PATTERN = /^[A-Z]{2}(?:-[A-Z0-9]{1,3})?$/;
|
|
7
|
+
/** Resolves a supported short or reverse-DNS HL7 identifier type to one canonical type. */
|
|
8
|
+
export function normalizeIndividualIdentifierType(type) {
|
|
9
|
+
const candidate = String(type).trim();
|
|
10
|
+
const shortCode = candidate.startsWith(`${HL7_V2_0203_REVERSE_DNS_PREFIX}.`)
|
|
11
|
+
? candidate.slice(HL7_V2_0203_REVERSE_DNS_PREFIX.length + 1)
|
|
12
|
+
: candidate.startsWith(`${LEGACY_HL7_V2_0203_REVERSE_DNS_PREFIX}.`)
|
|
13
|
+
? candidate.slice(LEGACY_HL7_V2_0203_REVERSE_DNS_PREFIX.length + 1)
|
|
14
|
+
: candidate;
|
|
15
|
+
const canonical = HL7_V2_0203_REVERSE_DNS_TYPES[shortCode];
|
|
16
|
+
if (!canonical)
|
|
17
|
+
throw new Error(`Unsupported individual identifier type: ${candidate}`);
|
|
18
|
+
return canonical;
|
|
19
|
+
}
|
|
20
|
+
/** Builds the sole canonical input hashed for an external individual identifier alias. */
|
|
21
|
+
export function buildIndividualIdentifierToken(input) {
|
|
22
|
+
const type = normalizeIndividualIdentifierType(input.type);
|
|
23
|
+
const jurisdiction = input.jurisdiction.trim().toUpperCase();
|
|
24
|
+
const value = input.value.trim().normalize('NFKC').toUpperCase();
|
|
25
|
+
if (!ISO_3166_JURISDICTION_PATTERN.test(jurisdiction)) {
|
|
26
|
+
throw new Error(`Invalid ISO 3166 jurisdiction: ${input.jurisdiction}`);
|
|
27
|
+
}
|
|
28
|
+
if (!value)
|
|
29
|
+
throw new Error('Individual identifier value is required');
|
|
30
|
+
return `${type}|${jurisdiction}|${value}`;
|
|
31
|
+
}
|
|
32
|
+
/** Returns the deterministic CIDv1/SHA3-384 alias stored in `Organization.sameAs`. */
|
|
33
|
+
export function buildIndividualIdentifierCid(input) {
|
|
34
|
+
return buildRawCidV1FromUtf8String(buildIndividualIdentifierToken(input));
|
|
35
|
+
}
|
|
36
|
+
/** Returns the SHA3-384 multihash URN used as a ledger lookup asset id. */
|
|
37
|
+
export function buildIndividualIdentifierLedgerAssetId(input) {
|
|
38
|
+
return `${UrnPrefixes.Multibase}${encodeMultibaseSha3(buildIndividualIdentifierToken(input))}`;
|
|
39
|
+
}
|
|
@@ -1,8 +1,22 @@
|
|
|
1
|
+
export type Sha3DigestBits = 224 | 256 | 384 | 512;
|
|
1
2
|
/**
|
|
2
|
-
* Encodes
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* Encodes bytes as `multibase(base58btc(multihash(SHA3-n)))`.
|
|
4
|
+
*
|
|
5
|
+
* Contract:
|
|
6
|
+
* - input strings are hashed as their exact UTF-8 bytes; callers own any
|
|
7
|
+
* higher-level identifier or JSON canonicalization
|
|
8
|
+
* - SHA3-384 is the default profile
|
|
9
|
+
* - the multihash stores the canonical SHA3 multicodec code and digest length
|
|
10
|
+
* - the returned `z...` value is a multibase string, not a CID
|
|
11
|
+
*
|
|
12
|
+
* Use a CID builder when identifying a content-addressed record. Use this
|
|
13
|
+
* helper when the multihash itself is the stable identifier or index key.
|
|
14
|
+
*/
|
|
15
|
+
export declare function encodeMultibaseSha3(input: string | Uint8Array, digestBits?: Sha3DigestBits): string;
|
|
16
|
+
/**
|
|
17
|
+
* @deprecated Use `encodeMultibaseSha3(input)` or pass `384` explicitly.
|
|
18
|
+
*
|
|
19
|
+
* This compatibility name now follows its historically intended multihash
|
|
20
|
+
* profile: SHA3-384, not SHA-2 SHA-384 with a mismatched SHA3 prefix.
|
|
7
21
|
*/
|
|
8
22
|
export declare function encodeMultibaseSha384(input: string | Uint8Array): string;
|
|
@@ -1,23 +1,48 @@
|
|
|
1
1
|
// Copyright 2025 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
2
|
// Use explicit .js subpaths to satisfy package exports in Metro/Node ESM.
|
|
3
|
-
import {
|
|
3
|
+
import { sha3_224, sha3_256, sha3_384, sha3_512 } from '@noble/hashes/sha3.js';
|
|
4
4
|
import { utf8ToBytes } from '@noble/hashes/utils.js';
|
|
5
5
|
import baseX from 'base-x';
|
|
6
6
|
const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
|
7
7
|
const base58btc = baseX(BASE58_ALPHABET);
|
|
8
|
+
const SHA3_PROFILES = Object.freeze({
|
|
9
|
+
224: Object.freeze({ code: 0x17, digestLengthBytes: 28, digest: sha3_224 }),
|
|
10
|
+
256: Object.freeze({ code: 0x16, digestLengthBytes: 32, digest: sha3_256 }),
|
|
11
|
+
384: Object.freeze({ code: 0x15, digestLengthBytes: 48, digest: sha3_384 }),
|
|
12
|
+
512: Object.freeze({ code: 0x14, digestLengthBytes: 64, digest: sha3_512 }),
|
|
13
|
+
});
|
|
8
14
|
/**
|
|
9
|
-
* Encodes
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
15
|
+
* Encodes bytes as `multibase(base58btc(multihash(SHA3-n)))`.
|
|
16
|
+
*
|
|
17
|
+
* Contract:
|
|
18
|
+
* - input strings are hashed as their exact UTF-8 bytes; callers own any
|
|
19
|
+
* higher-level identifier or JSON canonicalization
|
|
20
|
+
* - SHA3-384 is the default profile
|
|
21
|
+
* - the multihash stores the canonical SHA3 multicodec code and digest length
|
|
22
|
+
* - the returned `z...` value is a multibase string, not a CID
|
|
23
|
+
*
|
|
24
|
+
* Use a CID builder when identifying a content-addressed record. Use this
|
|
25
|
+
* helper when the multihash itself is the stable identifier or index key.
|
|
14
26
|
*/
|
|
15
|
-
export function
|
|
27
|
+
export function encodeMultibaseSha3(input, digestBits = 384) {
|
|
28
|
+
const profile = SHA3_PROFILES[digestBits];
|
|
29
|
+
if (!profile) {
|
|
30
|
+
throw new Error(`Unsupported SHA3 digest size: ${String(digestBits)}`);
|
|
31
|
+
}
|
|
16
32
|
const bytes = typeof input === 'string' ? utf8ToBytes(input) : input;
|
|
17
|
-
const hashBytes =
|
|
33
|
+
const hashBytes = profile.digest(bytes);
|
|
18
34
|
const multihashBytes = new Uint8Array(2 + hashBytes.length);
|
|
19
|
-
multihashBytes[0] =
|
|
20
|
-
multihashBytes[1] =
|
|
35
|
+
multihashBytes[0] = profile.code;
|
|
36
|
+
multihashBytes[1] = profile.digestLengthBytes;
|
|
21
37
|
multihashBytes.set(hashBytes, 2);
|
|
22
38
|
return 'z' + base58btc.encode(multihashBytes);
|
|
23
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* @deprecated Use `encodeMultibaseSha3(input)` or pass `384` explicitly.
|
|
42
|
+
*
|
|
43
|
+
* This compatibility name now follows its historically intended multihash
|
|
44
|
+
* profile: SHA3-384, not SHA-2 SHA-384 with a mismatched SHA3 prefix.
|
|
45
|
+
*/
|
|
46
|
+
export function encodeMultibaseSha384(input) {
|
|
47
|
+
return encodeMultibaseSha3(input, 384);
|
|
48
|
+
}
|
|
@@ -38,9 +38,11 @@ export declare const SHA3_256_MULTIHASH_PROFILE: MultihashProfile;
|
|
|
38
38
|
*/
|
|
39
39
|
export declare const SHA3_384_MULTIHASH_PROFILE: MultihashProfile;
|
|
40
40
|
/**
|
|
41
|
-
* Builds a `CIDv1` over a UTF-8 string using
|
|
41
|
+
* Builds a `CIDv1` over a UTF-8 string using SHA3-384 by default.
|
|
42
42
|
*
|
|
43
43
|
* This helper intentionally does not canonicalize the input string for you.
|
|
44
44
|
* Callers must pass the final canonical logical identifier they want to anchor.
|
|
45
|
+
* A caller may supply another explicit multihash profile when an established
|
|
46
|
+
* external contract requires it.
|
|
45
47
|
*/
|
|
46
|
-
export declare function buildRawCidV1FromUtf8String(value: string, profile
|
|
48
|
+
export declare function buildRawCidV1FromUtf8String(value: string, profile?: MultihashProfile): string;
|
|
@@ -19,7 +19,7 @@ export const MULTICODEC_RAW_CODE = 0x55;
|
|
|
19
19
|
*/
|
|
20
20
|
export const SHA3_256_MULTIHASH_PROFILE = Object.freeze({
|
|
21
21
|
algorithm: 'sha3-256',
|
|
22
|
-
code:
|
|
22
|
+
code: 0x16,
|
|
23
23
|
digestLengthBytes: 32,
|
|
24
24
|
digest: sha3_256,
|
|
25
25
|
});
|
|
@@ -36,12 +36,14 @@ export const SHA3_384_MULTIHASH_PROFILE = Object.freeze({
|
|
|
36
36
|
digest: sha3_384,
|
|
37
37
|
});
|
|
38
38
|
/**
|
|
39
|
-
* Builds a `CIDv1` over a UTF-8 string using
|
|
39
|
+
* Builds a `CIDv1` over a UTF-8 string using SHA3-384 by default.
|
|
40
40
|
*
|
|
41
41
|
* This helper intentionally does not canonicalize the input string for you.
|
|
42
42
|
* Callers must pass the final canonical logical identifier they want to anchor.
|
|
43
|
+
* A caller may supply another explicit multihash profile when an established
|
|
44
|
+
* external contract requires it.
|
|
43
45
|
*/
|
|
44
|
-
export function buildRawCidV1FromUtf8String(value, profile) {
|
|
46
|
+
export function buildRawCidV1FromUtf8String(value, profile = SHA3_384_MULTIHASH_PROFILE) {
|
|
45
47
|
const digest = profile.digest(utf8ToBytes(String(value || '')));
|
|
46
48
|
const multihash = concatBytes(Uint8Array.from([profile.code, profile.digestLengthBytes]), digest);
|
|
47
49
|
const cidBytes = concatBytes(encodeVarint(MULTIFORMAT_CID_V1_CODE), encodeVarint(MULTICODEC_RAW_CODE), multihash);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { BundleEntryEditor } from './bundle-entry-editor';
|
|
2
|
+
/**
|
|
3
|
+
* Typed editor for one RelatedPerson/contact staged inside a Bundle.
|
|
4
|
+
*
|
|
5
|
+
* Relationship authoring is separate from access authorization. This editor
|
|
6
|
+
* does not grant Consent, build a Communication, submit or poll.
|
|
7
|
+
*/
|
|
8
|
+
export declare class RelatedPersonEntryEditor extends BundleEntryEditor {
|
|
9
|
+
setIdentifier(value?: string | null): this;
|
|
10
|
+
getIdentifier(): string | undefined;
|
|
11
|
+
ensureIdentifier(): string;
|
|
12
|
+
setActive(value?: boolean | null): this;
|
|
13
|
+
getActive(): boolean | undefined;
|
|
14
|
+
setSubject(value?: string | null): this;
|
|
15
|
+
getSubject(): string | undefined;
|
|
16
|
+
setRelationship(value?: string | null): this;
|
|
17
|
+
getRelationship(): string | undefined;
|
|
18
|
+
setRoleList(values: readonly string[]): this;
|
|
19
|
+
getRoleList(): string[];
|
|
20
|
+
setName(value?: string | null): this;
|
|
21
|
+
getName(): string | undefined;
|
|
22
|
+
setTelecom(value?: string | null): this;
|
|
23
|
+
getTelecom(): string | undefined;
|
|
24
|
+
setRelatedEntityType(value?: string | null): this;
|
|
25
|
+
getRelatedEntityType(): string | undefined;
|
|
26
|
+
setActorIdentifierList(values: readonly string[]): this;
|
|
27
|
+
getActorIdentifierList(): string[];
|
|
28
|
+
private setOptionalText;
|
|
29
|
+
private getOptionalText;
|
|
30
|
+
private setList;
|
|
31
|
+
private getList;
|
|
32
|
+
}
|