gdc-sdk-node-ts 2.4.32 → 2.4.34
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 +3 -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/node-managed-wallet.d.ts +6 -4
- package/dist/node-managed-wallet.js +6 -4
- 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/README.md
CHANGED
|
@@ -113,6 +113,8 @@ If you need the shortest path:
|
|
|
113
113
|
|
|
114
114
|
- main onboarding guide:
|
|
115
115
|
[docs/101-SDK_END_TO_END.md](./docs/101-SDK_END_TO_END.md)
|
|
116
|
+
- wallet `context`, stable `runtimeId` and KMS-encrypted seed custody:
|
|
117
|
+
[docs/101-WALLET_CONTEXT_AND_KEY_CUSTODY.md](./docs/101-WALLET_CONTEXT_AND_KEY_CUSTODY.md)
|
|
116
118
|
- first public actor surfaces:
|
|
117
119
|
`HostOnboardingSdk`, `OrganizationControllerSdk`,
|
|
118
120
|
`IndividualControllerSdk`, `ProfessionalSdk`
|
|
@@ -649,7 +651,7 @@ modules below.
|
|
|
649
651
|
- functions: `createOrganizationEmployeeWithDeps(...)`, `importIpsOrFhirAndUpdateIndexWithDeps(...)`, `upsertRelatedPersonAndPollWithDeps(...)`, `ingestCommunicationAndUpdateIndexWithDeps(...)`, `searchClinicalBundleWithDeps(...)`, `searchLatestIpsWithDeps(...)`, `grantProfessionalAccessWithDeps(...)`, `setDigitalTwinSecondaryUseConsentWithDeps(...)`, `purgeDigitalTwinSubjectLinkWithDeps(...)`
|
|
650
652
|
- [`src/digital-twin.ts`](src/digital-twin.ts)
|
|
651
653
|
- types: `DigitalTwinSearchInput`, `DigitalTwinSelectionInput`, `DigitalTwinResearchTag`, `DigitalTwinMaterializationInput`
|
|
652
|
-
- functions: `
|
|
654
|
+
- functions: `searchDigitalTwinsWithDeps(...)`, `saveDigitalTwinSelectionWithDeps(...)`, `materializeDigitalTwinWithDeps(...)`
|
|
653
655
|
- [`src/orchestration/digital-twin-sdk.ts`](src/orchestration/digital-twin-sdk.ts)
|
|
654
656
|
- class: `DigitalTwinSdk` (`requestSmartToken`, `search`, `saveSelection`, `materialize`)
|
|
655
657
|
- [`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()
|
|
@@ -76,10 +76,12 @@ export declare class NodeManagedWallet implements IWallet {
|
|
|
76
76
|
* representative; these runtime keys protect DIDComm communications.
|
|
77
77
|
*
|
|
78
78
|
* The caller owns durable wallet custody. When deterministic provisioning is
|
|
79
|
-
* used, protect `seedMaterial` in the portal wallet store (for example
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
79
|
+
* used, protect `seedMaterial` in the portal wallet store (for example with
|
|
80
|
+
* its KMS/KEK and optionally a user PIN) and persist the same non-secret
|
|
81
|
+
* `context.runtime.runtimeId`. A fresh `NodeManagedWallet` reconstructs the
|
|
82
|
+
* same private keys from that seed and context after restart, so private JWKs
|
|
83
|
+
* do not need separate persistence. ICA and GW receive only this returned
|
|
84
|
+
* public JWKS.
|
|
83
85
|
*/
|
|
84
86
|
initializeCommunicationJsonWebKeySet(context: WalletExecutionContext, options?: NodeCommunicationWalletInitialization): Promise<JwkSet>;
|
|
85
87
|
/**
|
|
@@ -143,10 +143,12 @@ export class NodeManagedWallet {
|
|
|
143
143
|
* representative; these runtime keys protect DIDComm communications.
|
|
144
144
|
*
|
|
145
145
|
* The caller owns durable wallet custody. When deterministic provisioning is
|
|
146
|
-
* used, protect `seedMaterial` in the portal wallet store (for example
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
146
|
+
* used, protect `seedMaterial` in the portal wallet store (for example with
|
|
147
|
+
* its KMS/KEK and optionally a user PIN) and persist the same non-secret
|
|
148
|
+
* `context.runtime.runtimeId`. A fresh `NodeManagedWallet` reconstructs the
|
|
149
|
+
* same private keys from that seed and context after restart, so private JWKs
|
|
150
|
+
* do not need separate persistence. ICA and GW receive only this returned
|
|
151
|
+
* public JWKS.
|
|
150
152
|
*/
|
|
151
153
|
async initializeCommunicationJsonWebKeySet(context, options = {}) {
|
|
152
154
|
if (!context.runtime?.runtimeId) {
|
|
@@ -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,
|