gdc-sdk-node-ts 2.4.31 → 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/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 +5 -5
- package/dist/orchestration/individual-controller-sdk.js +12 -9
- package/dist/resource-operations.d.ts +6 -2
- 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/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';
|
|
@@ -128,7 +127,9 @@ export declare class IndividualControllerSdk {
|
|
|
128
127
|
/**
|
|
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
|
-
* publish a canonical Composition directly into the research index.
|
|
130
|
+
* publish a canonical Composition directly into the research index. The
|
|
131
|
+
* caller supplies only the portal/software/study reference. GW owns and
|
|
132
|
+
* reuses the underlying FHIR Consent identifier.
|
|
132
133
|
*/
|
|
133
134
|
setDigitalTwinSecondaryUseConsent(ctx: RouteContext, input: DigitalTwinSecondaryUseConsentInput): Promise<DigitalTwinSecondaryUseConsentResult>;
|
|
134
135
|
/**
|
|
@@ -137,15 +138,14 @@ export declare class IndividualControllerSdk {
|
|
|
137
138
|
* data already shared for research.
|
|
138
139
|
*/
|
|
139
140
|
purgeDigitalTwinSubjectLink(ctx: RouteContext, input: DigitalTwinSubjectLinkPurgeInput): Promise<DigitalTwinSubjectLinkPurgeResult>;
|
|
140
|
-
/** Returns
|
|
141
|
+
/** Returns the current decision for one portal, software or research study. */
|
|
141
142
|
getDigitalTwinSecondaryUseConsentStatus(ctx: RouteContext, input: Readonly<{
|
|
142
143
|
subjectDid: string;
|
|
143
144
|
indexProviderOrganizationDid: string;
|
|
144
|
-
|
|
145
|
+
researchUseReference: string;
|
|
145
146
|
}>): Promise<Readonly<{
|
|
146
147
|
exists: boolean;
|
|
147
148
|
enabled: boolean;
|
|
148
|
-
consent?: ConsentRule;
|
|
149
149
|
}>>;
|
|
150
150
|
/**
|
|
151
151
|
* Searches indexed clinical bundles for the current subject/controller context.
|
|
@@ -191,7 +191,9 @@ export class IndividualControllerSdk {
|
|
|
191
191
|
/**
|
|
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
|
-
* publish a canonical Composition directly into the research index.
|
|
194
|
+
* publish a canonical Composition directly into the research index. The
|
|
195
|
+
* caller supplies only the portal/software/study reference. GW owns and
|
|
196
|
+
* reuses the underlying FHIR Consent identifier.
|
|
195
197
|
*/
|
|
196
198
|
setDigitalTwinSecondaryUseConsent(ctx, input) {
|
|
197
199
|
assertFacadeCapability(this.capabilities, ActorCapabilities.IndividualGenerateDigitalTwin, ActorKinds.IndividualController, 'setDigitalTwinSecondaryUseConsent');
|
|
@@ -206,26 +208,27 @@ export class IndividualControllerSdk {
|
|
|
206
208
|
assertFacadeCapability(this.capabilities, ActorCapabilities.IndividualGenerateDigitalTwin, ActorKinds.IndividualController, 'purgeDigitalTwinSubjectLink');
|
|
207
209
|
return requireClientMethod(this.client, 'purgeDigitalTwinSubjectLink')(ctx, input);
|
|
208
210
|
}
|
|
209
|
-
/** Returns
|
|
211
|
+
/** Returns the current decision for one portal, software or research study. */
|
|
210
212
|
async getDigitalTwinSecondaryUseConsentStatus(ctx, input) {
|
|
211
213
|
assertFacadeCapability(this.capabilities, ActorCapabilities.IndividualGenerateDigitalTwin, ActorKinds.IndividualController, 'getDigitalTwinSecondaryUseConsentStatus');
|
|
212
|
-
const
|
|
214
|
+
const consents = await new GatewayActiveConsentProvider(this.client, ctx)
|
|
213
215
|
.getActiveConsentsForSubject(input.subjectDid);
|
|
214
|
-
const
|
|
215
|
-
if (!
|
|
216
|
-
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.');
|
|
217
219
|
const indexProviderOrganizationDid = String(input.indexProviderOrganizationDid || '').trim();
|
|
218
220
|
if (!indexProviderOrganizationDid)
|
|
219
221
|
throw new Error('indexProviderOrganizationDid is required.');
|
|
220
|
-
const consent =
|
|
222
|
+
const consent = consents.find((rule) => {
|
|
223
|
+
const claims = rule;
|
|
221
224
|
const actions = String(rule[ClaimConsent.action] || '').split(',').map((value) => value.trim());
|
|
222
|
-
return String(
|
|
225
|
+
return String(claims[ClaimConsent.sourceReference] || '').trim() === researchUseReference
|
|
223
226
|
&& String(rule[ClaimConsent.actorIdentifier] || '').trim() === indexProviderOrganizationDid
|
|
224
227
|
&& String(rule[ClaimConsent.purpose] || '').trim().toUpperCase() === String(HealthcareConsentPurposes.Research).toUpperCase()
|
|
225
228
|
&& actions.includes(ServiceCapability.DigitalTwinReader);
|
|
226
229
|
});
|
|
227
230
|
return consent
|
|
228
|
-
? { exists: true, enabled: String(consent[ClaimConsent.decision] || '').trim().toLowerCase() === 'permit'
|
|
231
|
+
? { exists: true, enabled: String(consent[ClaimConsent.decision] || '').trim().toLowerCase() === 'permit' }
|
|
229
232
|
: { exists: false, enabled: false };
|
|
230
233
|
}
|
|
231
234
|
/**
|
|
@@ -507,8 +507,12 @@ export type DigitalTwinSecondaryUseConsentInput = {
|
|
|
507
507
|
/** Organization/tenant that provides and stores the subject's index. */
|
|
508
508
|
indexProviderOrganizationDid: string;
|
|
509
509
|
decision: 'permit' | 'deny';
|
|
510
|
-
/**
|
|
511
|
-
|
|
510
|
+
/**
|
|
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
|
+
*/
|
|
515
|
+
researchUseReference: string;
|
|
512
516
|
consentDate?: string;
|
|
513
517
|
dataType?: string;
|
|
514
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
|
-
}
|