gdc-sdk-node-ts 2.4.32 → 2.4.33
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 +1 -1
- package/dist/digital-twin.d.ts +0 -8
- package/dist/digital-twin.js +0 -10
- package/dist/gateway-active-consent-provider.js +16 -2
- package/dist/orchestration/individual-controller-sdk.d.ts +4 -6
- package/dist/orchestration/individual-controller-sdk.js +11 -10
- package/dist/resource-operations.d.ts +4 -4
- package/dist/resource-operations.js +6 -3
- package/package.json +1 -1
- package/dist/organization-registration-authorization.d.ts +0 -16
- package/dist/organization-registration-authorization.js +0 -34
package/README.md
CHANGED
|
@@ -649,7 +649,7 @@ modules below.
|
|
|
649
649
|
- functions: `createOrganizationEmployeeWithDeps(...)`, `importIpsOrFhirAndUpdateIndexWithDeps(...)`, `upsertRelatedPersonAndPollWithDeps(...)`, `ingestCommunicationAndUpdateIndexWithDeps(...)`, `searchClinicalBundleWithDeps(...)`, `searchLatestIpsWithDeps(...)`, `grantProfessionalAccessWithDeps(...)`, `setDigitalTwinSecondaryUseConsentWithDeps(...)`, `purgeDigitalTwinSubjectLinkWithDeps(...)`
|
|
650
650
|
- [`src/digital-twin.ts`](src/digital-twin.ts)
|
|
651
651
|
- types: `DigitalTwinSearchInput`, `DigitalTwinSelectionInput`, `DigitalTwinResearchTag`, `DigitalTwinMaterializationInput`
|
|
652
|
-
- functions: `
|
|
652
|
+
- functions: `searchDigitalTwinsWithDeps(...)`, `saveDigitalTwinSelectionWithDeps(...)`, `materializeDigitalTwinWithDeps(...)`
|
|
653
653
|
- [`src/orchestration/digital-twin-sdk.ts`](src/orchestration/digital-twin-sdk.ts)
|
|
654
654
|
- class: `DigitalTwinSdk` (`requestSmartToken`, `search`, `saveSelection`, `materialize`)
|
|
655
655
|
- [`src/session.ts`](src/session.ts)
|
package/dist/digital-twin.d.ts
CHANGED
|
@@ -3,14 +3,6 @@ import type { MetaTagCoding } from 'gdc-common-utils-ts/models/confidential-stor
|
|
|
3
3
|
import type { RouteContext } from './individual-onboarding.js';
|
|
4
4
|
import type { PollOptions, SubmitAndPollResult } from './orchestration/client-port.js';
|
|
5
5
|
export type DigitalTwinFhirFormat = 'org.hl7.fhir.r4' | 'org.hl7.fhir.api';
|
|
6
|
-
/**
|
|
7
|
-
* Creates the opaque FHIR identifier for the provider-level secondary-use
|
|
8
|
-
* Consent. Call this exactly once when the server creates the subject's index
|
|
9
|
-
* enrollment, persist the result there, and reuse it for every permit/deny.
|
|
10
|
-
* It is intentionally random and must not be derived from a subject or tenant
|
|
11
|
-
* identifier.
|
|
12
|
-
*/
|
|
13
|
-
export declare function createDigitalTwinSecondaryUseConsentIdentifier(): string;
|
|
14
6
|
/** Returns whether a value can be used as a pseudonymous digital-twin subject. */
|
|
15
7
|
export declare function isDigitalTwinSubjectId(value: unknown): value is string;
|
|
16
8
|
/** Rejects operational DIDs and malformed or caller-invented subject shapes. */
|
package/dist/digital-twin.js
CHANGED
|
@@ -3,16 +3,6 @@ import { randomUUID } from 'node:crypto';
|
|
|
3
3
|
import { HealthcareDocumentTypes } from 'gdc-common-utils-ts/constants';
|
|
4
4
|
import { CompositionClaim } from 'gdc-common-utils-ts/models';
|
|
5
5
|
const DIGITAL_TWIN_SUBJECT_URN_UUID = /^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
6
|
-
/**
|
|
7
|
-
* Creates the opaque FHIR identifier for the provider-level secondary-use
|
|
8
|
-
* Consent. Call this exactly once when the server creates the subject's index
|
|
9
|
-
* enrollment, persist the result there, and reuse it for every permit/deny.
|
|
10
|
-
* It is intentionally random and must not be derived from a subject or tenant
|
|
11
|
-
* identifier.
|
|
12
|
-
*/
|
|
13
|
-
export function createDigitalTwinSecondaryUseConsentIdentifier() {
|
|
14
|
-
return `urn:uuid:${randomUUID()}`;
|
|
15
|
-
}
|
|
16
6
|
/** Returns whether a value can be used as a pseudonymous digital-twin subject. */
|
|
17
7
|
export function isDigitalTwinSubjectId(value) {
|
|
18
8
|
return DIGITAL_TWIN_SUBJECT_URN_UUID.test(String(value || '').trim());
|
|
@@ -48,9 +48,11 @@ export function extractConsentRules(value) {
|
|
|
48
48
|
? record.claims
|
|
49
49
|
: undefined;
|
|
50
50
|
for (const candidate of [record, claims]) {
|
|
51
|
-
if (!candidate
|
|
51
|
+
if (!candidate)
|
|
52
|
+
continue;
|
|
53
|
+
const rule = normalizeConsentRule(candidate);
|
|
54
|
+
if (!looksLikeConsentRule(rule))
|
|
52
55
|
continue;
|
|
53
|
-
const rule = candidate;
|
|
54
56
|
const key = String(rule[ClaimConsent.identifier] || JSON.stringify(rule));
|
|
55
57
|
if (!seenRules.has(key)) {
|
|
56
58
|
seenRules.add(key);
|
|
@@ -62,6 +64,18 @@ export function extractConsentRules(value) {
|
|
|
62
64
|
visit(value);
|
|
63
65
|
return candidates;
|
|
64
66
|
}
|
|
67
|
+
function normalizeConsentRule(value) {
|
|
68
|
+
const normalized = { ...value };
|
|
69
|
+
const context = String(value['@context'] || '').trim();
|
|
70
|
+
if (context) {
|
|
71
|
+
const prefix = context.endsWith('.') ? context : `${context}.`;
|
|
72
|
+
for (const [key, claimValue] of Object.entries(value)) {
|
|
73
|
+
if (key.startsWith(prefix))
|
|
74
|
+
normalized[key.slice(prefix.length)] = claimValue;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return normalized;
|
|
78
|
+
}
|
|
65
79
|
function looksLikeConsentRule(value) {
|
|
66
80
|
return Boolean(String(value[ClaimConsent.subject] || '').trim()
|
|
67
81
|
&& String(value[ClaimConsent.actorIdentifier] || '').trim()
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { type ConsentRule } from 'gdc-common-utils-ts/models/consent-rule';
|
|
2
1
|
import { type IndividualControllerCredentialInput, type IndividualControllerVpPayloadInput, type IndividualSubjectCredentialInput } from 'gdc-common-utils-ts';
|
|
3
2
|
import { type NodeRuntimeClient, type PollOptions, type SubmitAndPollResult, type SubmitPayload } from './client-port.js';
|
|
4
3
|
import type { FamilyOrganizationSummary } from 'gdc-common-utils-ts/utils/family-organization-summary';
|
|
@@ -129,8 +128,8 @@ export declare class IndividualControllerSdk {
|
|
|
129
128
|
* Enables or disables the subject's secondary-use digital-twin projection.
|
|
130
129
|
* This is the canonical patient-side operation; application code must not
|
|
131
130
|
* publish a canonical Composition directly into the research index. The
|
|
132
|
-
* caller
|
|
133
|
-
*
|
|
131
|
+
* caller supplies only the portal/software/study reference. GW owns and
|
|
132
|
+
* reuses the underlying FHIR Consent identifier.
|
|
134
133
|
*/
|
|
135
134
|
setDigitalTwinSecondaryUseConsent(ctx: RouteContext, input: DigitalTwinSecondaryUseConsentInput): Promise<DigitalTwinSecondaryUseConsentResult>;
|
|
136
135
|
/**
|
|
@@ -139,15 +138,14 @@ export declare class IndividualControllerSdk {
|
|
|
139
138
|
* data already shared for research.
|
|
140
139
|
*/
|
|
141
140
|
purgeDigitalTwinSubjectLink(ctx: RouteContext, input: DigitalTwinSubjectLinkPurgeInput): Promise<DigitalTwinSubjectLinkPurgeResult>;
|
|
142
|
-
/** Returns
|
|
141
|
+
/** Returns the current decision for one portal, software or research study. */
|
|
143
142
|
getDigitalTwinSecondaryUseConsentStatus(ctx: RouteContext, input: Readonly<{
|
|
144
143
|
subjectDid: string;
|
|
145
144
|
indexProviderOrganizationDid: string;
|
|
146
|
-
|
|
145
|
+
researchUseReference: string;
|
|
147
146
|
}>): Promise<Readonly<{
|
|
148
147
|
exists: boolean;
|
|
149
148
|
enabled: boolean;
|
|
150
|
-
consent?: ConsentRule;
|
|
151
149
|
}>>;
|
|
152
150
|
/**
|
|
153
151
|
* Searches indexed clinical bundles for the current subject/controller context.
|
|
@@ -192,8 +192,8 @@ export class IndividualControllerSdk {
|
|
|
192
192
|
* Enables or disables the subject's secondary-use digital-twin projection.
|
|
193
193
|
* This is the canonical patient-side operation; application code must not
|
|
194
194
|
* publish a canonical Composition directly into the research index. The
|
|
195
|
-
* caller
|
|
196
|
-
*
|
|
195
|
+
* caller supplies only the portal/software/study reference. GW owns and
|
|
196
|
+
* reuses the underlying FHIR Consent identifier.
|
|
197
197
|
*/
|
|
198
198
|
setDigitalTwinSecondaryUseConsent(ctx, input) {
|
|
199
199
|
assertFacadeCapability(this.capabilities, ActorCapabilities.IndividualGenerateDigitalTwin, ActorKinds.IndividualController, 'setDigitalTwinSecondaryUseConsent');
|
|
@@ -208,26 +208,27 @@ export class IndividualControllerSdk {
|
|
|
208
208
|
assertFacadeCapability(this.capabilities, ActorCapabilities.IndividualGenerateDigitalTwin, ActorKinds.IndividualController, 'purgeDigitalTwinSubjectLink');
|
|
209
209
|
return requireClientMethod(this.client, 'purgeDigitalTwinSubjectLink')(ctx, input);
|
|
210
210
|
}
|
|
211
|
-
/** Returns
|
|
211
|
+
/** Returns the current decision for one portal, software or research study. */
|
|
212
212
|
async getDigitalTwinSecondaryUseConsentStatus(ctx, input) {
|
|
213
213
|
assertFacadeCapability(this.capabilities, ActorCapabilities.IndividualGenerateDigitalTwin, ActorKinds.IndividualController, 'getDigitalTwinSecondaryUseConsentStatus');
|
|
214
|
-
const
|
|
214
|
+
const consents = await new GatewayActiveConsentProvider(this.client, ctx)
|
|
215
215
|
.getActiveConsentsForSubject(input.subjectDid);
|
|
216
|
-
const
|
|
217
|
-
if (!
|
|
218
|
-
throw new Error('
|
|
216
|
+
const researchUseReference = String(input.researchUseReference || '').trim();
|
|
217
|
+
if (!researchUseReference)
|
|
218
|
+
throw new Error('researchUseReference is required to identify the portal, software or study consent.');
|
|
219
219
|
const indexProviderOrganizationDid = String(input.indexProviderOrganizationDid || '').trim();
|
|
220
220
|
if (!indexProviderOrganizationDid)
|
|
221
221
|
throw new Error('indexProviderOrganizationDid is required.');
|
|
222
|
-
const consent =
|
|
222
|
+
const consent = consents.find((rule) => {
|
|
223
|
+
const claims = rule;
|
|
223
224
|
const actions = String(rule[ClaimConsent.action] || '').split(',').map((value) => value.trim());
|
|
224
|
-
return String(
|
|
225
|
+
return String(claims[ClaimConsent.sourceReference] || '').trim() === researchUseReference
|
|
225
226
|
&& String(rule[ClaimConsent.actorIdentifier] || '').trim() === indexProviderOrganizationDid
|
|
226
227
|
&& String(rule[ClaimConsent.purpose] || '').trim().toUpperCase() === String(HealthcareConsentPurposes.Research).toUpperCase()
|
|
227
228
|
&& actions.includes(ServiceCapability.DigitalTwinReader);
|
|
228
229
|
});
|
|
229
230
|
return consent
|
|
230
|
-
? { exists: true, enabled: String(consent[ClaimConsent.decision] || '').trim().toLowerCase() === 'permit'
|
|
231
|
+
? { exists: true, enabled: String(consent[ClaimConsent.decision] || '').trim().toLowerCase() === 'permit' }
|
|
231
232
|
: { exists: false, enabled: false };
|
|
232
233
|
}
|
|
233
234
|
/**
|
|
@@ -508,11 +508,11 @@ export type DigitalTwinSecondaryUseConsentInput = {
|
|
|
508
508
|
indexProviderOrganizationDid: string;
|
|
509
509
|
decision: 'permit' | 'deny';
|
|
510
510
|
/**
|
|
511
|
-
*
|
|
512
|
-
*
|
|
513
|
-
*
|
|
511
|
+
* Stable URL or URI identifying the portal, software or research study whose
|
|
512
|
+
* permission is being changed. GW uses it as `Consent.source-reference` to
|
|
513
|
+
* find or create the rule; callers never manage `Consent.identifier`.
|
|
514
514
|
*/
|
|
515
|
-
|
|
515
|
+
researchUseReference: string;
|
|
516
516
|
consentDate?: string;
|
|
517
517
|
dataType?: string;
|
|
518
518
|
pollOptions?: {
|
|
@@ -811,8 +811,9 @@ export async function grantProfessionalAccessWithDeps(routeCtx, input, deps) {
|
|
|
811
811
|
* tenant-private subject alias.
|
|
812
812
|
*/
|
|
813
813
|
export async function setDigitalTwinSecondaryUseConsentWithDeps(routeCtx, input, deps) {
|
|
814
|
-
|
|
815
|
-
|
|
814
|
+
const researchUseReference = String(input.researchUseReference || '').trim();
|
|
815
|
+
if (!researchUseReference) {
|
|
816
|
+
throw new Error('researchUseReference is required to identify the portal, software or study consent.');
|
|
816
817
|
}
|
|
817
818
|
const indexProviderOrganizationDid = String(input.indexProviderOrganizationDid || '').trim();
|
|
818
819
|
if (!indexProviderOrganizationDid) {
|
|
@@ -827,7 +828,9 @@ export async function setDigitalTwinSecondaryUseConsentWithDeps(routeCtx, input,
|
|
|
827
828
|
if (context) {
|
|
828
829
|
delete claims[`${context}.${ClaimConsent.attachmentContentType}`];
|
|
829
830
|
delete claims[`${context}.${ClaimConsent.attachmentData}`];
|
|
831
|
+
delete claims[`${context}.${ClaimConsent.identifier}`];
|
|
830
832
|
}
|
|
833
|
+
delete claims[ClaimConsent.identifier];
|
|
831
834
|
const assigned = assignCidToClaimsId(claims);
|
|
832
835
|
return { ...built, consentClaims: assigned.claims, claimsCid: assigned.cid };
|
|
833
836
|
};
|
|
@@ -838,7 +841,7 @@ export async function setDigitalTwinSecondaryUseConsentWithDeps(routeCtx, input,
|
|
|
838
841
|
purpose: HealthcareConsentPurposes.Research,
|
|
839
842
|
actions: [ServiceCapability.DigitalTwinReader],
|
|
840
843
|
decision: input.decision,
|
|
841
|
-
|
|
844
|
+
sourceReference: researchUseReference,
|
|
842
845
|
consentDate: input.consentDate,
|
|
843
846
|
dataType: input.dataType,
|
|
844
847
|
pollOptions: input.pollOptions,
|
package/package.json
CHANGED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { VerifiableCredentialV2 } from 'gdc-common-utils-ts/models/verifiable-credential';
|
|
2
|
-
import type { IWallet, WalletExecutionContext, WalletKeySelection } from 'gdc-sdk-core-ts';
|
|
3
|
-
/** Input required to add one human-authorizer proof to the out-of-band VC. */
|
|
4
|
-
export type SignOrganizationRegistrationAuthorizationInput = Readonly<{
|
|
5
|
-
credential: VerifiableCredentialV2;
|
|
6
|
-
wallet: IWallet;
|
|
7
|
-
context: WalletExecutionContext;
|
|
8
|
-
key: WalletKeySelection;
|
|
9
|
-
verificationMethod: string;
|
|
10
|
-
createdAt?: string;
|
|
11
|
-
}>;
|
|
12
|
-
/**
|
|
13
|
-
* Adds one detached JWS proof made by the already-unlocked professional
|
|
14
|
-
* profile. The browser never receives the private key or clear wallet seed.
|
|
15
|
-
*/
|
|
16
|
-
export declare function signOrganizationRegistrationAuthorizationCredential(input: SignOrganizationRegistrationAuthorizationInput): Promise<VerifiableCredentialV2>;
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { canonicalizeOrganizationRegistrationAuthorizationCredential, } from 'gdc-common-utils-ts/utils/organization-registration-authorization';
|
|
2
|
-
/**
|
|
3
|
-
* Adds one detached JWS proof made by the already-unlocked professional
|
|
4
|
-
* profile. The browser never receives the private key or clear wallet seed.
|
|
5
|
-
*/
|
|
6
|
-
export async function signOrganizationRegistrationAuthorizationCredential(input) {
|
|
7
|
-
if (!input.wallet.signDetachedJws) {
|
|
8
|
-
throw new Error('Wallet does not support detached organization authorization proofs.');
|
|
9
|
-
}
|
|
10
|
-
const verificationMethod = String(input.verificationMethod || '').trim();
|
|
11
|
-
if (!verificationMethod)
|
|
12
|
-
throw new Error('Organization authorization proof requires verificationMethod.');
|
|
13
|
-
const payload = canonicalizeOrganizationRegistrationAuthorizationCredential(input.credential);
|
|
14
|
-
const jws = await input.wallet.signDetachedJws(input.context, {
|
|
15
|
-
payload,
|
|
16
|
-
header: { typ: 'application/vc+ld+json' },
|
|
17
|
-
key: input.key,
|
|
18
|
-
});
|
|
19
|
-
const existingProofs = Array.isArray(input.credential.proof)
|
|
20
|
-
? input.credential.proof
|
|
21
|
-
: input.credential.proof
|
|
22
|
-
? [input.credential.proof]
|
|
23
|
-
: [];
|
|
24
|
-
return {
|
|
25
|
-
...input.credential,
|
|
26
|
-
proof: [...existingProofs, {
|
|
27
|
-
type: 'JsonWebSignature2020',
|
|
28
|
-
created: input.createdAt || new Date().toISOString(),
|
|
29
|
-
proofPurpose: 'contractAgreement',
|
|
30
|
-
verificationMethod,
|
|
31
|
-
jws,
|
|
32
|
-
}],
|
|
33
|
-
};
|
|
34
|
-
}
|