gdc-common-utils-ts 2.3.1 → 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/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/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/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
|
@@ -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/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
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
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 RelatedPerson entry.
|
|
5
|
+
*/
|
|
6
|
+
import { RelatedPersonClaim } from '../models/interoperable-claims/related-person-claims.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 RelatedPerson/contact staged inside a Bundle.
|
|
19
|
+
*
|
|
20
|
+
* Relationship authoring is separate from access authorization. This editor
|
|
21
|
+
* does not grant Consent, build a Communication, submit or poll.
|
|
22
|
+
*/
|
|
23
|
+
export class RelatedPersonEntryEditor extends BundleEntryEditor {
|
|
24
|
+
setIdentifier(value) {
|
|
25
|
+
const normalized = normalizeText(value);
|
|
26
|
+
if (!normalized) {
|
|
27
|
+
this.removeClaim(RelatedPersonClaim.IdentifierValue)
|
|
28
|
+
.removeClaim(RelatedPersonClaim.Identifier)
|
|
29
|
+
.setResourceId()
|
|
30
|
+
.setFullUrl();
|
|
31
|
+
return this;
|
|
32
|
+
}
|
|
33
|
+
return this.setClaim(RelatedPersonClaim.IdentifierValue, normalized)
|
|
34
|
+
.setClaim(RelatedPersonClaim.Identifier, normalized)
|
|
35
|
+
.setResourceId(normalized)
|
|
36
|
+
.setFullUrl(normalized);
|
|
37
|
+
}
|
|
38
|
+
getIdentifier() {
|
|
39
|
+
return normalizeText(String(this.getClaim(RelatedPersonClaim.IdentifierValue)
|
|
40
|
+
|| this.getClaim(RelatedPersonClaim.Identifier)
|
|
41
|
+
|| this.getResourceId()
|
|
42
|
+
|| this.getFullUrl()
|
|
43
|
+
|| ''));
|
|
44
|
+
}
|
|
45
|
+
ensureIdentifier() {
|
|
46
|
+
const identifier = this.getIdentifier();
|
|
47
|
+
if (!identifier)
|
|
48
|
+
throw new Error('RelatedPerson entry requires a generated resource id or explicit identifier.');
|
|
49
|
+
this.setIdentifier(identifier);
|
|
50
|
+
return identifier;
|
|
51
|
+
}
|
|
52
|
+
setActive(value) {
|
|
53
|
+
return value === undefined || value === null
|
|
54
|
+
? this.removeClaim(RelatedPersonClaim.Active)
|
|
55
|
+
: this.setClaim(RelatedPersonClaim.Active, value);
|
|
56
|
+
}
|
|
57
|
+
getActive() {
|
|
58
|
+
const value = this.getClaim(RelatedPersonClaim.Active);
|
|
59
|
+
if (typeof value === 'boolean')
|
|
60
|
+
return value;
|
|
61
|
+
if (String(value).toLowerCase() === 'true')
|
|
62
|
+
return true;
|
|
63
|
+
if (String(value).toLowerCase() === 'false')
|
|
64
|
+
return false;
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
setSubject(value) { return this.setOptionalText(RelatedPersonClaim.Patient, value); }
|
|
68
|
+
getSubject() { return this.getOptionalText(RelatedPersonClaim.Patient); }
|
|
69
|
+
setRelationship(value) { return this.setOptionalText(RelatedPersonClaim.Relationship, value); }
|
|
70
|
+
getRelationship() { return this.getOptionalText(RelatedPersonClaim.Relationship); }
|
|
71
|
+
setRoleList(values) { return this.setList(RelatedPersonClaim.Role, values); }
|
|
72
|
+
getRoleList() { return this.getList(RelatedPersonClaim.Role); }
|
|
73
|
+
setName(value) { return this.setOptionalText(RelatedPersonClaim.Name, value); }
|
|
74
|
+
getName() { return this.getOptionalText(RelatedPersonClaim.Name); }
|
|
75
|
+
setTelecom(value) { return this.setOptionalText(RelatedPersonClaim.Telecom, value); }
|
|
76
|
+
getTelecom() { return this.getOptionalText(RelatedPersonClaim.Telecom); }
|
|
77
|
+
setRelatedEntityType(value) { return this.setOptionalText(RelatedPersonClaim.RelatedEntityType, value); }
|
|
78
|
+
getRelatedEntityType() { return this.getOptionalText(RelatedPersonClaim.RelatedEntityType); }
|
|
79
|
+
setActorIdentifierList(values) { return this.setList(RelatedPersonClaim.ActorIdentifier, values); }
|
|
80
|
+
getActorIdentifierList() { return this.getList(RelatedPersonClaim.ActorIdentifier); }
|
|
81
|
+
setOptionalText(key, value) {
|
|
82
|
+
const normalized = normalizeText(value);
|
|
83
|
+
return normalized ? this.setClaim(key, normalized) : this.removeClaim(key);
|
|
84
|
+
}
|
|
85
|
+
getOptionalText(key) {
|
|
86
|
+
return normalizeText(String(this.getClaim(key) || ''));
|
|
87
|
+
}
|
|
88
|
+
setList(key, values) {
|
|
89
|
+
const normalized = normalizeList(values);
|
|
90
|
+
return normalized.length ? this.setClaim(key, normalized.join(',')) : this.removeClaim(key);
|
|
91
|
+
}
|
|
92
|
+
getList(key) {
|
|
93
|
+
const value = this.getClaim(key);
|
|
94
|
+
return normalizeList(Array.isArray(value) ? value.map(String) : String(value || '').split(','));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
registerBundleEntryEditor(BundleEditableResourceTypes.relatedPerson, RelatedPersonEntryEditor);
|
package/dist/utils/same-as.d.ts
CHANGED
|
@@ -49,6 +49,16 @@ export declare function normalizeSameAsHash(value: string): string;
|
|
|
49
49
|
* @param value Canonical sameAs source value(s).
|
|
50
50
|
*/
|
|
51
51
|
export declare function normalizeSameAsHashList(value: string | readonly string[] | undefined): string[];
|
|
52
|
+
/**
|
|
53
|
+
* Splits a public alias CSV without changing the aliases themselves.
|
|
54
|
+
*
|
|
55
|
+
* Use this for mixed DID/CID `sameAs` indexes. Unlike
|
|
56
|
+
* `normalizeSameAsHashList`, it deliberately does not interpret a CIDv1
|
|
57
|
+
* beginning with `z` as a bare `urn:multibase` value.
|
|
58
|
+
*/
|
|
59
|
+
export declare function normalizePublicAliasList(value: string | readonly string[] | undefined): string[];
|
|
60
|
+
/** Derives an opaque SHA3-384 multihash URN for a global-ledger alias key. */
|
|
61
|
+
export declare function buildPublicAliasLedgerAssetId(alias: string): string;
|
|
52
62
|
/**
|
|
53
63
|
* Joins one or more normalized `sameAs` aliases into the current flat CSV
|
|
54
64
|
* storage form used by some shared VC/profile helpers.
|
package/dist/utils/same-as.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { sha3_256 } from '@noble/hashes/sha3.js';
|
|
2
|
+
import { utf8ToBytes } from '@noble/hashes/utils.js';
|
|
2
3
|
import { normalizePhone } from './consent.js';
|
|
3
4
|
import { encodeMultibase58btc } from './multibase58.js';
|
|
5
|
+
import { encodeMultibaseSha3 } from './multibasehash.js';
|
|
6
|
+
import { UrnPrefixes } from '../constants/urn.js';
|
|
4
7
|
/**
|
|
5
8
|
* Builds the ICA-compatible multibase58(multihash(sha3-256)) payload for a
|
|
6
9
|
* public controller alias.
|
|
@@ -18,8 +21,8 @@ export function multibase58MultihashSha3_256(value) {
|
|
|
18
21
|
if (!normalized) {
|
|
19
22
|
throw new Error('Cannot create multihash from empty value.');
|
|
20
23
|
}
|
|
21
|
-
const digest =
|
|
22
|
-
const multihash =
|
|
24
|
+
const digest = sha3_256(utf8ToBytes(normalized));
|
|
25
|
+
const multihash = Uint8Array.from([0x16, 0x20, ...digest]);
|
|
23
26
|
return encodeMultibase58btc(multihash);
|
|
24
27
|
}
|
|
25
28
|
function looksLikeBase58Multibase(value) {
|
|
@@ -96,6 +99,28 @@ export function normalizeSameAsHashList(value) {
|
|
|
96
99
|
.filter(Boolean);
|
|
97
100
|
return [...new Set(normalized)];
|
|
98
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Splits a public alias CSV without changing the aliases themselves.
|
|
104
|
+
*
|
|
105
|
+
* Use this for mixed DID/CID `sameAs` indexes. Unlike
|
|
106
|
+
* `normalizeSameAsHashList`, it deliberately does not interpret a CIDv1
|
|
107
|
+
* beginning with `z` as a bare `urn:multibase` value.
|
|
108
|
+
*/
|
|
109
|
+
export function normalizePublicAliasList(value) {
|
|
110
|
+
const candidates = Array.isArray(value)
|
|
111
|
+
? value.flatMap((entry) => splitCommaSeparatedValues(entry))
|
|
112
|
+
: splitCommaSeparatedValues(String(value || ''));
|
|
113
|
+
return [...new Set(candidates.map((candidate) => candidate.trim()).filter(Boolean))];
|
|
114
|
+
}
|
|
115
|
+
/** Derives an opaque SHA3-384 multihash URN for a global-ledger alias key. */
|
|
116
|
+
export function buildPublicAliasLedgerAssetId(alias) {
|
|
117
|
+
const normalized = String(alias || '').trim().normalize('NFKC');
|
|
118
|
+
if (!normalized)
|
|
119
|
+
throw new Error('Public alias is required');
|
|
120
|
+
if (normalized.startsWith(UrnPrefixes.Multibase))
|
|
121
|
+
return normalized;
|
|
122
|
+
return `${UrnPrefixes.Multibase}${encodeMultibaseSha3(normalized)}`;
|
|
123
|
+
}
|
|
99
124
|
/**
|
|
100
125
|
* Joins one or more normalized `sameAs` aliases into the current flat CSV
|
|
101
126
|
* storage form used by some shared VC/profile helpers.
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdc-common-utils-ts",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.2",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
7
7
|
"type": "module",
|
|
8
8
|
"dependencies": {
|
|
9
|
+
"@noble/hashes": "^2.0.1",
|
|
9
10
|
"@noble/post-quantum": "^0.5.4",
|
|
10
11
|
"@stablelib/base64": "^1.0.1",
|
|
11
12
|
"@stablelib/utf8": "^1.0.2",
|