gdc-sdk-node-ts 2.4.4 → 2.4.8

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 CHANGED
@@ -640,6 +640,11 @@ modules below.
640
640
  - [`src/resource-operations.ts`](src/resource-operations.ts)
641
641
  - types: `OrganizationEmployeeCreationInput`, `IpsOrFhirImportInput`, `RelatedPersonUpsertInput`, `CommunicationIngestionInput`, `ClinicalDateRange`, `ClinicalBundleSearchInput`, `ConsentActorTargetInput`, `GrantProfessionalAccessInput`, `GrantProfessionalAccessResult`, `DigitalTwinGenerationInput`
642
642
  - functions: `createOrganizationEmployeeWithDeps(...)`, `importIpsOrFhirAndUpdateIndexWithDeps(...)`, `upsertRelatedPersonAndPollWithDeps(...)`, `ingestCommunicationAndUpdateIndexWithDeps(...)`, `searchClinicalBundleWithDeps(...)`, `searchLatestIpsWithDeps(...)`, `grantProfessionalAccessWithDeps(...)`, `generateDigitalTwinFromSubjectDataWithDeps(...)`
643
+ - [`src/digital-twin.ts`](src/digital-twin.ts)
644
+ - types: `DigitalTwinSearchInput`, `DigitalTwinSelectionInput`, `DigitalTwinResearchTag`, `DigitalTwinMaterializationInput`
645
+ - functions: `searchDigitalTwinsWithDeps(...)`, `saveDigitalTwinSelectionWithDeps(...)`, `materializeDigitalTwinWithDeps(...)`
646
+ - [`src/orchestration/digital-twin-sdk.ts`](src/orchestration/digital-twin-sdk.ts)
647
+ - class: `DigitalTwinSdk` (`requestSmartToken`, `search`, `saveSelection`, `materialize`)
643
648
  - [`src/session.ts`](src/session.ts)
644
649
  - types: `NodeCapability`, `NodeActorSessionContext`, `ActorSessionContext`
645
650
  - classes: `ActorSession`, `NodeActorSession`
@@ -0,0 +1,109 @@
1
+ import { CompositionClaim } from 'gdc-common-utils-ts/models';
2
+ import type { MetaTagCoding } from 'gdc-common-utils-ts/models/confidential-storage';
3
+ import type { RouteContext } from './individual-onboarding.js';
4
+ import type { PollOptions, SubmitAndPollResult } from './orchestration/client-port.js';
5
+ export type DigitalTwinFhirFormat = 'org.hl7.fhir.r4' | 'org.hl7.fhir.api';
6
+ /** Search parameters added by the digital-twin Composition profile. */
7
+ export declare const DigitalTwinSearchParameter: Readonly<{
8
+ Section: "section";
9
+ MetaTag: "Composition.meta-tag";
10
+ }>;
11
+ export type DigitalTwinSearchInput = {
12
+ /** SMART bearer used for this research operation. */
13
+ accessToken?: string;
14
+ thid?: string;
15
+ format?: DigitalTwinFhirFormat;
16
+ resourceType?: string;
17
+ filters?: Readonly<Record<string, string | readonly string[] | undefined>>;
18
+ pollOptions?: PollOptions;
19
+ };
20
+ /** One pseudonymous Composition returned by a digital-twin search. */
21
+ export type DigitalTwinSearchMatch = Record<string, unknown> & {
22
+ id?: string;
23
+ [CompositionClaim.Subject]: string;
24
+ [CompositionClaim.Section]?: string;
25
+ meta?: {
26
+ tag?: DigitalTwinResearchTag[];
27
+ };
28
+ };
29
+ /** High-level search result; GW transport envelopes remain available for diagnostics. */
30
+ export type DigitalTwinSearchResult = {
31
+ total: number;
32
+ matches: DigitalTwinSearchMatch[];
33
+ operation: SubmitAndPollResult;
34
+ };
35
+ export type DigitalTwinMaterializationInput = {
36
+ /** SMART bearer used for this research operation. */
37
+ accessToken?: string;
38
+ twinSubjectId: string;
39
+ thid?: string;
40
+ format?: DigitalTwinFhirFormat;
41
+ sections?: readonly string[];
42
+ sent?: string;
43
+ pollOptions?: PollOptions;
44
+ };
45
+ /** Ledger-safe employee-defined marker attached to a research working copy. */
46
+ export type DigitalTwinResearchTag = Required<Pick<MetaTagCoding, 'system' | 'code'>> & Pick<MetaTagCoding, 'version' | 'userSelected'> & {
47
+ /** Stable position/name for the tag in the Composition metadata. */
48
+ id?: string;
49
+ };
50
+ /**
51
+ * Saves a researcher-owned Composition working selection for one twin.
52
+ *
53
+ * This does not mutate the canonical twin or copy clinical data. The record
54
+ * stores the pseudonymous subject, section, author and ledger-safe tags used
55
+ * to recover the researcher's working set later.
56
+ */
57
+ export type DigitalTwinSelectionInput = {
58
+ accessToken?: string;
59
+ twinSubjectId: string;
60
+ section: string;
61
+ tags: readonly DigitalTwinResearchTag[];
62
+ /**
63
+ * Operational hosted employee DID. High-level callers should omit this:
64
+ * `DigitalTwinSdk` binds it to the authenticated actor session.
65
+ */
66
+ authorDid?: string;
67
+ /** Optional FHIR logical id for this saved selection. A UUID is generated when omitted. */
68
+ selectionId?: string;
69
+ /** @deprecated Use `selectionId`; retained for low-level compatibility. */
70
+ compositionId?: string;
71
+ documentType?: string;
72
+ date?: string;
73
+ thid?: string;
74
+ format?: DigitalTwinFhirFormat;
75
+ pollOptions?: PollOptions;
76
+ };
77
+ /**
78
+ * Builds one opaque FHIR logical id for a saved working selection.
79
+ *
80
+ * Ownership remains in the standard `Composition.author` claim. Reopening a
81
+ * a saved collection uses `meta.tag` plus that author; no non-FHIR branch
82
+ * claims or employee hashes in tag systems exist.
83
+ */
84
+ export declare function buildDigitalTwinSelectionIdentifier(input: Readonly<{
85
+ selectionId?: string;
86
+ }>): Readonly<{
87
+ selectionId: string;
88
+ compositionId: string;
89
+ }>;
90
+ export type DigitalTwinRuntimeDeps = {
91
+ digitalTwinSearchPath: (ctx: RouteContext, format: DigitalTwinFhirFormat, resourceType: string) => string;
92
+ digitalTwinSearchPollPath: (ctx: RouteContext, format: DigitalTwinFhirFormat, resourceType: string) => string;
93
+ digitalTwinCommunicationBatchPath: (ctx: RouteContext, format: DigitalTwinFhirFormat) => string;
94
+ digitalTwinCommunicationPollPath: (ctx: RouteContext, format: DigitalTwinFhirFormat) => string;
95
+ digitalTwinCompositionBatchPath: (ctx: RouteContext, format: DigitalTwinFhirFormat) => string;
96
+ digitalTwinCompositionPollPath: (ctx: RouteContext, format: DigitalTwinFhirFormat) => string;
97
+ submitAndPoll: (submitPath: string, pollPath: string, payload: {
98
+ thid: string;
99
+ body: Record<string, unknown>;
100
+ }, pollOptions?: PollOptions) => Promise<SubmitAndPollResult>;
101
+ };
102
+ /** Opens the GW async response and exposes the matched Compositions directly. */
103
+ export declare function readDigitalTwinSearchResult(operation: SubmitAndPollResult): DigitalTwinSearchResult;
104
+ /** Persists one tagged researcher working copy through `digitaltwin/Composition/_batch`. */
105
+ export declare function saveDigitalTwinSelectionWithDeps(ctx: RouteContext, input: DigitalTwinSelectionInput, deps: DigitalTwinRuntimeDeps): Promise<SubmitAndPollResult>;
106
+ /** Searches the tenant digital-twin index through its public asynchronous route. */
107
+ export declare function searchDigitalTwinsWithDeps(ctx: RouteContext, input: DigitalTwinSearchInput, deps: DigitalTwinRuntimeDeps): Promise<SubmitAndPollResult>;
108
+ /** Materializes one selected pseudonymous twin as a research summary Bundle. */
109
+ export declare function materializeDigitalTwinWithDeps(ctx: RouteContext, input: DigitalTwinMaterializationInput, deps: DigitalTwinRuntimeDeps): Promise<SubmitAndPollResult>;
@@ -0,0 +1,181 @@
1
+ // Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
2
+ import { randomUUID } from 'node:crypto';
3
+ import { HealthcareDocumentTypes } from 'gdc-common-utils-ts/constants';
4
+ import { CompositionClaim } from 'gdc-common-utils-ts/models';
5
+ /** Search parameters added by the digital-twin Composition profile. */
6
+ export const DigitalTwinSearchParameter = Object.freeze({
7
+ Section: 'section',
8
+ MetaTag: 'Composition.meta-tag',
9
+ });
10
+ /**
11
+ * Builds one opaque FHIR logical id for a saved working selection.
12
+ *
13
+ * Ownership remains in the standard `Composition.author` claim. Reopening a
14
+ * a saved collection uses `meta.tag` plus that author; no non-FHIR branch
15
+ * claims or employee hashes in tag systems exist.
16
+ */
17
+ export function buildDigitalTwinSelectionIdentifier(input) {
18
+ const selectionId = String(input.selectionId || '').trim() || randomUUID();
19
+ if (!/^[A-Za-z0-9.-]{1,64}$/.test(selectionId)) {
20
+ throw new Error('Digital twin selectionId must be a valid FHIR logical id.');
21
+ }
22
+ return Object.freeze({
23
+ selectionId,
24
+ compositionId: selectionId,
25
+ });
26
+ }
27
+ function normalizeResearchTags(tags) {
28
+ if (!Array.isArray(tags) || tags.length === 0)
29
+ throw new Error('At least one research tag is required.');
30
+ const normalized = tags.map((tag, index) => {
31
+ const system = String(tag?.system || '').trim();
32
+ const code = String(tag?.code || '').trim();
33
+ if (!system || !code)
34
+ throw new Error('Each research tag requires system and code.');
35
+ return {
36
+ id: String(tag.id || `Composition.meta.tag[${index}]`).trim(),
37
+ system,
38
+ code,
39
+ ...(tag.version ? { version: String(tag.version) } : {}),
40
+ ...(typeof tag.userSelected === 'boolean' ? { userSelected: tag.userSelected } : {}),
41
+ };
42
+ });
43
+ if (new Set(normalized.map((tag) => tag.id)).size !== normalized.length) {
44
+ throw new Error('Research tag ids must be unique within one selection.');
45
+ }
46
+ return normalized;
47
+ }
48
+ /** Opens the GW async response and exposes the matched Compositions directly. */
49
+ export function readDigitalTwinSearchResult(operation) {
50
+ const body = operation?.poll?.body;
51
+ const responseEntries = Array.isArray(body?.data)
52
+ ? body.data
53
+ : Array.isArray(body?.entry)
54
+ ? body.entry
55
+ : [];
56
+ const responseEntry = responseEntries.find((entry) => {
57
+ const resource = entry?.resource;
58
+ return Boolean(resource && typeof resource === 'object' && Array.isArray(resource.data));
59
+ });
60
+ if (!responseEntry?.resource || !Array.isArray(responseEntry.resource.data)) {
61
+ throw new Error('Digital twin search did not return a Composition result set.');
62
+ }
63
+ const matches = responseEntry.resource.data;
64
+ const parsedTotal = Number(responseEntry.resource.total);
65
+ return {
66
+ total: Number.isFinite(parsedTotal) ? parsedTotal : matches.length,
67
+ matches,
68
+ operation,
69
+ };
70
+ }
71
+ /** Persists one tagged researcher working copy through `digitaltwin/Composition/_batch`. */
72
+ export async function saveDigitalTwinSelectionWithDeps(ctx, input, deps) {
73
+ const twinSubjectId = String(input.twinSubjectId || '').trim();
74
+ const section = String(input.section || '').trim();
75
+ const authorDid = String(input.authorDid || '').trim();
76
+ if (!twinSubjectId)
77
+ throw new Error('twinSubjectId is required.');
78
+ if (!section)
79
+ throw new Error('Digital twin selection section is required.');
80
+ if (!authorDid)
81
+ throw new Error('Digital twin selection authorDid is required.');
82
+ const format = input.format || 'org.hl7.fhir.r4';
83
+ const thid = String(input.thid || randomUUID());
84
+ const selectionIdentifier = buildDigitalTwinSelectionIdentifier({
85
+ selectionId: input.selectionId || input.compositionId,
86
+ });
87
+ const compositionId = selectionIdentifier.compositionId;
88
+ const tags = normalizeResearchTags(input.tags);
89
+ const claims = {
90
+ '@context': format,
91
+ '@type': 'Composition:ResearcherWorkingSelection',
92
+ [CompositionClaim.Identifier]: compositionId,
93
+ [CompositionClaim.Subject]: twinSubjectId,
94
+ [CompositionClaim.Section]: section,
95
+ [CompositionClaim.Type]: input.documentType || HealthcareDocumentTypes.IPS.attributeValue,
96
+ [CompositionClaim.Author]: authorDid,
97
+ [CompositionClaim.Date]: input.date || new Date().toISOString(),
98
+ };
99
+ const resource = {
100
+ resourceType: 'Composition',
101
+ id: compositionId,
102
+ meta: { claims, tag: tags },
103
+ };
104
+ const entry = {
105
+ type: 'Composition',
106
+ resource,
107
+ request: { method: 'POST', url: `digitaltwin/${format}/Composition` },
108
+ };
109
+ return deps.submitAndPoll(deps.digitalTwinCompositionBatchPath(ctx, format), deps.digitalTwinCompositionPollPath(ctx, format), {
110
+ thid,
111
+ body: format === 'org.hl7.fhir.api'
112
+ ? { resourceType: 'Bundle', type: 'batch', data: [entry] }
113
+ : { resourceType: 'Bundle', type: 'batch', entry: [entry] },
114
+ }, input.pollOptions);
115
+ }
116
+ /** Searches the tenant digital-twin index through its public asynchronous route. */
117
+ export async function searchDigitalTwinsWithDeps(ctx, input, deps) {
118
+ const format = input.format || 'org.hl7.fhir.r4';
119
+ const resourceType = String(input.resourceType || 'Composition').trim();
120
+ if (!resourceType)
121
+ throw new Error('Digital twin resourceType is required.');
122
+ const parameters = Object.entries(input.filters || {}).flatMap(([name, value]) => {
123
+ const values = Array.isArray(value) ? value : value === undefined ? [] : [value];
124
+ return values.map((item) => ({ name, valueString: String(item) }));
125
+ });
126
+ const thid = String(input.thid || randomUUID());
127
+ const body = format === 'org.hl7.fhir.api'
128
+ ? {
129
+ data: [{
130
+ type: `${resourceType}-search-request-v1.0`,
131
+ resource: { resourceType: 'Parameters', parameter: parameters },
132
+ meta: {
133
+ claims: Object.fromEntries([
134
+ ['@context', format],
135
+ ...parameters.map((parameter) => [parameter.name, parameter.valueString]),
136
+ ]),
137
+ },
138
+ }],
139
+ }
140
+ : { resourceType: 'Parameters', parameter: parameters };
141
+ return deps.submitAndPoll(deps.digitalTwinSearchPath(ctx, format, resourceType), deps.digitalTwinSearchPollPath(ctx, format, resourceType), {
142
+ thid,
143
+ body,
144
+ }, input.pollOptions);
145
+ }
146
+ /** Materializes one selected pseudonymous twin as a research summary Bundle. */
147
+ export async function materializeDigitalTwinWithDeps(ctx, input, deps) {
148
+ const twinSubjectId = String(input.twinSubjectId || '').trim();
149
+ if (!twinSubjectId)
150
+ throw new Error('twinSubjectId is required.');
151
+ const format = input.format || 'org.hl7.fhir.r4';
152
+ const thid = String(input.thid || randomUUID());
153
+ const parameters = [
154
+ { name: 'subject', valueString: twinSubjectId },
155
+ ...(input.sections || []).map((section) => ({ name: 'section', valueString: String(section) })),
156
+ ];
157
+ const encodedParameters = Buffer.from(JSON.stringify({
158
+ resourceType: 'Parameters',
159
+ parameter: parameters,
160
+ }), 'utf8').toString('base64');
161
+ return deps.submitAndPoll(deps.digitalTwinCommunicationBatchPath(ctx, format), deps.digitalTwinCommunicationPollPath(ctx, format), {
162
+ thid,
163
+ body: {
164
+ resourceType: 'Bundle',
165
+ type: 'batch',
166
+ entry: [{
167
+ request: { method: 'POST', url: `digitaltwin/${format}/Communication` },
168
+ resource: {
169
+ resourceType: 'Communication',
170
+ status: 'completed',
171
+ subject: { reference: twinSubjectId },
172
+ sent: input.sent || new Date().toISOString(),
173
+ payload: [{
174
+ contentReference: { reference: `digitaltwin/${format}/ResearchSubject/$summary` },
175
+ contentAttachment: { contentType: 'application/fhir+json', data: encodedParameters },
176
+ }],
177
+ },
178
+ }],
179
+ },
180
+ }, input.pollOptions);
181
+ }
package/dist/index.d.ts CHANGED
@@ -24,6 +24,8 @@ export * from './fhir-r5-subscription-runtime.js';
24
24
  export * from './order-offer-summary.js';
25
25
  export * from './organization-license-order.js';
26
26
  export * from './resource-operations.js';
27
+ export * from './digital-twin.js';
28
+ export * from './operational-actor-did.js';
27
29
  export * from './profile-workspace.js';
28
30
  export * from './UserProfileIndexStore.js';
29
31
  export * from './wallet-backed-job-manager.js';
@@ -44,5 +46,6 @@ export * from './orchestration/individual-controller-sdk.js';
44
46
  export * from './orchestration/individual-member-sdk.js';
45
47
  export * from './orchestration/personal-sdk.js';
46
48
  export * from './orchestration/professional-sdk.js';
49
+ export * from './orchestration/digital-twin-sdk.js';
47
50
  export * from './legacy-compat.js';
48
51
  export * from './local-terminology-bff.js';
package/dist/index.js CHANGED
@@ -25,6 +25,8 @@ export * from './fhir-r5-subscription-runtime.js';
25
25
  export * from './order-offer-summary.js';
26
26
  export * from './organization-license-order.js';
27
27
  export * from './resource-operations.js';
28
+ export * from './digital-twin.js';
29
+ export * from './operational-actor-did.js';
28
30
  export * from './profile-workspace.js';
29
31
  export * from './UserProfileIndexStore.js';
30
32
  export * from './wallet-backed-job-manager.js';
@@ -45,5 +47,6 @@ export * from './orchestration/individual-controller-sdk.js';
45
47
  export * from './orchestration/individual-member-sdk.js';
46
48
  export * from './orchestration/personal-sdk.js';
47
49
  export * from './orchestration/professional-sdk.js';
50
+ export * from './orchestration/digital-twin-sdk.js';
48
51
  export * from './legacy-compat.js';
49
52
  export * from './local-terminology-bff.js';
@@ -19,6 +19,7 @@ import type { OrganizationEmployeeLifecycleRecord } from 'gdc-common-utils-ts/mo
19
19
  import type { NodeRuntimeClient, PollOptions, PollResult, SubmitAndPollResult, SubmitPayload, SubmitResponse } from './orchestration/client-port.js';
20
20
  import { type FhirR5SubscriptionBatchInput } from './fhir-r5-subscription-runtime.js';
21
21
  import type { FhirR5SubscriptionTopic } from 'gdc-common-utils-ts/models/fhir-r5-subscription';
22
+ import { type DigitalTwinMaterializationInput, type DigitalTwinSearchInput, type DigitalTwinSelectionInput } from './digital-twin.js';
22
23
  export type HttpRuntimeClientOptions = {
23
24
  baseUrl: string;
24
25
  bearerToken?: string;
@@ -512,6 +513,12 @@ export declare class HttpRuntimeClient implements NodeRuntimeClient {
512
513
  * @param input Search parameters such as subject, section, date and included resource types.
513
514
  */
514
515
  searchClinicalBundle(ctx: RouteContext, input: ClinicalBundleSearchInput): Promise<SubmitAndPollResult>;
516
+ /** Searches tenant digital twins using coded, pseudonymous research claims. */
517
+ searchDigitalTwins(ctx: RouteContext, input: DigitalTwinSearchInput): Promise<SubmitAndPollResult>;
518
+ /** Saves one tagged researcher working copy without mutating the canonical twin. */
519
+ saveDigitalTwinSelection(ctx: RouteContext, input: DigitalTwinSelectionInput): Promise<SubmitAndPollResult>;
520
+ /** Materializes one selected pseudonymous research subject as a summary Bundle. */
521
+ materializeDigitalTwin(ctx: RouteContext, input: DigitalTwinMaterializationInput): Promise<SubmitAndPollResult>;
515
522
  /**
516
523
  * Searches the latest IPS-oriented document view for a subject.
517
524
  *
@@ -21,6 +21,7 @@ import { buildGrantProfessionalAccessClaimsWithCid } from './runtime-consent.js'
21
21
  import { RuntimeClientPaths } from './runtime-client-paths.js';
22
22
  import { runtimeUuid, wrapBundleAsGatewayTransactionMessage } from './runtime-message.js';
23
23
  import { submitFhirR5SubscriptionBatchWithDeps, submitFhirR5SubscriptionTopicBatchWithDeps, } from './fhir-r5-subscription-runtime.js';
24
+ import { materializeDigitalTwinWithDeps, saveDigitalTwinSelectionWithDeps, searchDigitalTwinsWithDeps, } from './digital-twin.js';
24
25
  const bootstrapFacade = createBootstrapFacade();
25
26
  /**
26
27
  * Runtime-oriented HTTP client for Node backends, BFFs, and workers that need
@@ -908,12 +909,15 @@ export class HttpRuntimeClient {
908
909
  * Applies the configured wire profile to a direct subject-index search.
909
910
  * This is compatibility/specialized plumbing, not the primary summary read.
910
911
  */
911
- async submitClinicalMessageAndPoll(submitPath, pollPath, payload, profile, pollOptions) {
912
+ async submitClinicalMessageAndPoll(submitPath, pollPath, payload, profile, pollOptions, accessToken) {
913
+ const transportConfig = accessToken
914
+ ? { ...this.transportConfig, bearerToken: accessToken }
915
+ : this.transportConfig;
912
916
  const renderedSubmit = await renderGatewayMessageRequest(payload, profile, this.secureTransportAdapter);
913
- const submit = await postRenderedWithRuntimeConfig(this.transportConfig, submitPath, renderedSubmit);
917
+ const submit = await postRenderedWithRuntimeConfig(transportConfig, submitPath, renderedSubmit);
914
918
  const poll = await pollUntilCompleteWithMethod(async (path, request) => {
915
919
  const renderedPoll = await renderTransportPollRequest(request.thid, profile, this.secureTransportAdapter);
916
- const response = await postRenderedWithRuntimeConfig(this.transportConfig, path, renderedPoll);
920
+ const response = await postRenderedWithRuntimeConfig(transportConfig, path, renderedPoll);
917
921
  return {
918
922
  status: response.status,
919
923
  body: response.status === 202
@@ -986,6 +990,42 @@ export class HttpRuntimeClient {
986
990
  submitAndPoll: (submitPath, pollPath, payload, pollOptions) => this.submitClinicalMessageAndPoll(submitPath, pollPath, payload, input.transportProfile || this.transportProfile, pollOptions),
987
991
  });
988
992
  }
993
+ /** Searches tenant digital twins using coded, pseudonymous research claims. */
994
+ async searchDigitalTwins(ctx, input) {
995
+ return searchDigitalTwinsWithDeps(ctx, input, {
996
+ digitalTwinSearchPath: this.paths.digitalTwinSearchPath.bind(this.paths),
997
+ digitalTwinSearchPollPath: this.paths.digitalTwinSearchPollPath.bind(this.paths),
998
+ digitalTwinCompositionBatchPath: this.paths.digitalTwinCompositionBatchPath.bind(this.paths),
999
+ digitalTwinCompositionPollPath: this.paths.digitalTwinCompositionPollPath.bind(this.paths),
1000
+ digitalTwinCommunicationBatchPath: this.paths.digitalTwinCommunicationBatchPath.bind(this.paths),
1001
+ digitalTwinCommunicationPollPath: this.paths.digitalTwinCommunicationPollPath.bind(this.paths),
1002
+ submitAndPoll: (submitPath, pollPath, payload, pollOptions) => this.submitClinicalMessageAndPoll(submitPath, pollPath, payload, this.transportProfile, pollOptions, input.accessToken),
1003
+ });
1004
+ }
1005
+ /** Saves one tagged researcher working copy without mutating the canonical twin. */
1006
+ async saveDigitalTwinSelection(ctx, input) {
1007
+ return saveDigitalTwinSelectionWithDeps(ctx, input, {
1008
+ digitalTwinSearchPath: this.paths.digitalTwinSearchPath.bind(this.paths),
1009
+ digitalTwinSearchPollPath: this.paths.digitalTwinSearchPollPath.bind(this.paths),
1010
+ digitalTwinCompositionBatchPath: this.paths.digitalTwinCompositionBatchPath.bind(this.paths),
1011
+ digitalTwinCompositionPollPath: this.paths.digitalTwinCompositionPollPath.bind(this.paths),
1012
+ digitalTwinCommunicationBatchPath: this.paths.digitalTwinCommunicationBatchPath.bind(this.paths),
1013
+ digitalTwinCommunicationPollPath: this.paths.digitalTwinCommunicationPollPath.bind(this.paths),
1014
+ submitAndPoll: (submitPath, pollPath, payload, pollOptions) => this.submitClinicalMessageAndPoll(submitPath, pollPath, payload, this.transportProfile, pollOptions, input.accessToken),
1015
+ });
1016
+ }
1017
+ /** Materializes one selected pseudonymous research subject as a summary Bundle. */
1018
+ async materializeDigitalTwin(ctx, input) {
1019
+ return materializeDigitalTwinWithDeps(ctx, input, {
1020
+ digitalTwinSearchPath: this.paths.digitalTwinSearchPath.bind(this.paths),
1021
+ digitalTwinSearchPollPath: this.paths.digitalTwinSearchPollPath.bind(this.paths),
1022
+ digitalTwinCompositionBatchPath: this.paths.digitalTwinCompositionBatchPath.bind(this.paths),
1023
+ digitalTwinCompositionPollPath: this.paths.digitalTwinCompositionPollPath.bind(this.paths),
1024
+ digitalTwinCommunicationBatchPath: this.paths.digitalTwinCommunicationBatchPath.bind(this.paths),
1025
+ digitalTwinCommunicationPollPath: this.paths.digitalTwinCommunicationPollPath.bind(this.paths),
1026
+ submitAndPoll: (submitPath, pollPath, payload, pollOptions) => this.submitClinicalMessageAndPoll(submitPath, pollPath, payload, this.transportProfile, pollOptions, input.accessToken),
1027
+ });
1028
+ }
989
1029
  /**
990
1030
  * Searches the latest IPS-oriented document view for a subject.
991
1031
  *
@@ -0,0 +1,14 @@
1
+ /** Minimal DID document shape required for portal-alias resolution. */
2
+ export type OperationalActorDidDocument = Readonly<{
3
+ id: string;
4
+ alsoKnownAs?: readonly string[];
5
+ }>;
6
+ /**
7
+ * Resolves a public portal/vanity DID to the canonical hosted operational DID.
8
+ *
9
+ * `alsoKnownAs` proves only alias continuity. Authentication and authorization
10
+ * still come from the verified employee credential and SMART token. The
11
+ * returned primary `id` is the value used in ActorSession and internal GW
12
+ * requests.
13
+ */
14
+ export declare function resolveOperationalActorDid(publicActorDid: string, resolveDidDocument: (did: string) => Promise<OperationalActorDidDocument>): Promise<string>;
@@ -0,0 +1,26 @@
1
+ // Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
2
+ /**
3
+ * Resolves a public portal/vanity DID to the canonical hosted operational DID.
4
+ *
5
+ * `alsoKnownAs` proves only alias continuity. Authentication and authorization
6
+ * still come from the verified employee credential and SMART token. The
7
+ * returned primary `id` is the value used in ActorSession and internal GW
8
+ * requests.
9
+ */
10
+ export async function resolveOperationalActorDid(publicActorDid, resolveDidDocument) {
11
+ const requestedDid = String(publicActorDid || '').trim();
12
+ if (!requestedDid.startsWith('did:'))
13
+ throw new Error('A public actor DID is required.');
14
+ const document = await resolveDidDocument(requestedDid);
15
+ const operationalDid = String(document?.id || '').trim();
16
+ if (!operationalDid.startsWith('did:web:')) {
17
+ throw new Error('Resolved employee identity must have a hosted did:web primary id.');
18
+ }
19
+ if (operationalDid !== requestedDid) {
20
+ const aliases = (document.alsoKnownAs || []).map((value) => String(value || '').trim());
21
+ if (!aliases.includes(requestedDid)) {
22
+ throw new Error('Resolved DID document does not bind the requested public actor alias.');
23
+ }
24
+ }
25
+ return operationalDid;
26
+ }
@@ -17,6 +17,7 @@ import type { FhirR5SubscriptionBatchInput } from '../fhir-r5-subscription-runti
17
17
  import type { EnsureFamilyOrganizationRegistrationInput, EnsureFamilyOrganizationRegistrationResult } from '../family-organization-registration.js';
18
18
  import type { OrganizationLicenseOrderConfirmInput } from '../organization-license-order.js';
19
19
  import type { SmartTokenExchangeResult, SmartTokenRequestInput } from '../smart-token.js';
20
+ import type { DigitalTwinMaterializationInput, DigitalTwinSearchInput, DigitalTwinSelectionInput } from '../digital-twin.js';
20
21
  import type { CommunicationIngestionInput, BlockchainArtifactRegistrationInput, CommunicationParticipantRuntimeSearchInput, ClinicalBundleSearchInput, ClinicalSectionUpdateInput, ClinicalSummaryReadResult, ClinicalSummaryRequestInput, ClinicalSummaryUpdateInput, VitalSignBatchCommunicationFromSearchResponseInput, DigitalTwinGenerationInput, GrantProfessionalAccessInput, GrantProfessionalAccessResult, IndividualMemberLifecycleInput, IndividualMemberLicenseAddInput, IndividualMemberLicenseInvitationInput, IndividualMemberLicenseTransitionInput, IpsOrFhirImportInput, OrganizationEmployeeCreationInput, OrganizationEmployeeLicenseInvitationInput, OrganizationEmployeeLifecycleInput, OrganizationEmployeeSearchInput, ProfessionalAccessRequestInput, ProfessionalAccessRequestResult, RevokeProfessionalAccessInput, RevokeProfessionalAccessResult, RelatedPersonUpsertInput } from '../resource-operations.js';
21
22
  /**
22
23
  * Shared node-runtime activation input.
@@ -125,6 +126,9 @@ export type RuntimeClient = {
125
126
  upsertRelatedPersonAndPoll?: (ctx: RouteContext, input: RelatedPersonUpsertInput) => Promise<SubmitAndPollResult>;
126
127
  generateDigitalTwinFromSubjectData?: (ctx: RouteContext, input: DigitalTwinGenerationInput) => Promise<SubmitAndPollResult>;
127
128
  searchClinicalBundle?: (ctx: RouteContext, input: ClinicalBundleSearchInput) => Promise<SubmitAndPollResult>;
129
+ searchDigitalTwins?: (ctx: RouteContext, input: DigitalTwinSearchInput) => Promise<SubmitAndPollResult>;
130
+ saveDigitalTwinSelection?: (ctx: RouteContext, input: DigitalTwinSelectionInput) => Promise<SubmitAndPollResult>;
131
+ materializeDigitalTwin?: (ctx: RouteContext, input: DigitalTwinMaterializationInput) => Promise<SubmitAndPollResult>;
128
132
  getLatestIps?: (ctx: RouteContext, input: Omit<ClinicalBundleSearchInput, 'includedTypes'>) => Promise<SubmitAndPollResult>;
129
133
  submitBatch?: (submitPath: string, payload: SubmitPayload) => Promise<SubmitResponse>;
130
134
  pollUntilComplete?: (pollPath: string, request: AsyncPollRequest, pollOptions?: PollOptions) => Promise<PollResult>;
@@ -0,0 +1,30 @@
1
+ import type { RouteContext } from '../individual-onboarding.js';
2
+ import type { SmartTokenExchangeResult, SmartTokenRequestInput } from '../smart-token.js';
3
+ import type { DigitalTwinMaterializationInput, DigitalTwinSelectionInput, DigitalTwinSearchInput, DigitalTwinSearchResult } from '../digital-twin.js';
4
+ import { type NodeRuntimeClient, type SubmitAndPollResult } from './client-port.js';
5
+ /** Public research facade for licensed digital-twin access. */
6
+ export declare class DigitalTwinSdk {
7
+ private readonly client;
8
+ private smartAccessToken?;
9
+ private researcherDid?;
10
+ constructor(client: NodeRuntimeClient, actorDid?: string);
11
+ /** Requests the SMART token used by subsequent digital-twin operations. */
12
+ requestSmartToken(input: SmartTokenRequestInput): Promise<SmartTokenExchangeResult>;
13
+ /** Searches pseudonymous records and exposes matched Compositions directly. */
14
+ search(ctx: RouteContext, input: DigitalTwinSearchInput): Promise<DigitalTwinSearchResult>;
15
+ /**
16
+ * Saves a tagged researcher-owned working selection for one matching twin.
17
+ * The canonical clinical twin is never modified.
18
+ */
19
+ saveSelection(ctx: RouteContext, input: DigitalTwinSelectionInput): Promise<SubmitAndPollResult>;
20
+ /** Reopens only this employee's saved selections for one exact custom tag. */
21
+ searchSelections(ctx: RouteContext, input: Omit<DigitalTwinSearchInput, 'filters'> & {
22
+ section: string;
23
+ tag: Readonly<{
24
+ system: string;
25
+ code: string;
26
+ }>;
27
+ }): Promise<DigitalTwinSearchResult>;
28
+ /** Materializes one selected research subject through `ResearchSubject/$summary`. */
29
+ materialize(ctx: RouteContext, input: DigitalTwinMaterializationInput): Promise<SubmitAndPollResult>;
30
+ }
@@ -0,0 +1,100 @@
1
+ // Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
2
+ import { HealthcareConsentPurposes, ServiceCapability } from 'gdc-common-utils-ts/constants';
3
+ import { CompositionClaim } from 'gdc-common-utils-ts/models';
4
+ import { DigitalTwinSearchParameter, readDigitalTwinSearchResult } from '../digital-twin.js';
5
+ import { requireClientMethod } from './client-port.js';
6
+ /** Public research facade for licensed digital-twin access. */
7
+ export class DigitalTwinSdk {
8
+ constructor(client, actorDid) {
9
+ this.client = client;
10
+ this.researcherDid = String(actorDid || '').trim() || undefined;
11
+ }
12
+ /** Requests the SMART token used by subsequent digital-twin operations. */
13
+ async requestSmartToken(input) {
14
+ const requestedActorDid = String(input.actorDid || '').trim() || undefined;
15
+ if (this.researcherDid && requestedActorDid && requestedActorDid !== this.researcherDid) {
16
+ throw new Error('DigitalTwinSdk actorDid must match the actor session.');
17
+ }
18
+ const actorDid = this.researcherDid || requestedActorDid;
19
+ const result = await requireClientMethod(this.client, 'requestSmartToken')({
20
+ ...input,
21
+ actorDid,
22
+ purpose: input.purpose || HealthcareConsentPurposes.Research,
23
+ scopes: input.scopes?.length ? input.scopes : [ServiceCapability.DigitalTwinReader],
24
+ });
25
+ if (result.accessToken)
26
+ this.smartAccessToken = result.accessToken;
27
+ if (actorDid)
28
+ this.researcherDid = actorDid;
29
+ return result;
30
+ }
31
+ /** Searches pseudonymous records and exposes matched Compositions directly. */
32
+ async search(ctx, input) {
33
+ const filters = { ...(input.filters || {}) };
34
+ const isPrivateSelectionSearch = Object.keys(filters).some((name) => {
35
+ const normalized = String(name || '').trim().toLowerCase();
36
+ return normalized === 'composition.meta-tag' || normalized === 'composition.meta.tag';
37
+ });
38
+ if (isPrivateSelectionSearch) {
39
+ if (!this.researcherDid) {
40
+ throw new Error('Digital twin working-selection search requires an operational actor DID.');
41
+ }
42
+ const requestedAuthor = filters[CompositionClaim.Author];
43
+ const requestedAuthors = Array.isArray(requestedAuthor)
44
+ ? requestedAuthor.map(String)
45
+ : requestedAuthor === undefined
46
+ ? []
47
+ : [String(requestedAuthor)];
48
+ if (requestedAuthors.some((author) => author !== this.researcherDid)) {
49
+ throw new Error('Digital twin selection author filter must match the actor session.');
50
+ }
51
+ filters[CompositionClaim.Author] = this.researcherDid;
52
+ }
53
+ const operation = await requireClientMethod(this.client, 'searchDigitalTwins')(ctx, {
54
+ ...input,
55
+ filters,
56
+ accessToken: input.accessToken || this.smartAccessToken,
57
+ });
58
+ return readDigitalTwinSearchResult(operation);
59
+ }
60
+ /**
61
+ * Saves a tagged researcher-owned working selection for one matching twin.
62
+ * The canonical clinical twin is never modified.
63
+ */
64
+ saveSelection(ctx, input) {
65
+ const requestedAuthorDid = String(input.authorDid || '').trim() || undefined;
66
+ if (this.researcherDid && requestedAuthorDid && requestedAuthorDid !== this.researcherDid) {
67
+ throw new Error('Digital twin selection authorDid must match the actor session.');
68
+ }
69
+ return requireClientMethod(this.client, 'saveDigitalTwinSelection')(ctx, {
70
+ ...input,
71
+ authorDid: this.researcherDid || requestedAuthorDid,
72
+ accessToken: input.accessToken || this.smartAccessToken,
73
+ });
74
+ }
75
+ /** Reopens only this employee's saved selections for one exact custom tag. */
76
+ searchSelections(ctx, input) {
77
+ const section = String(input.section || '').trim();
78
+ const system = String(input.tag?.system || '').trim();
79
+ const code = String(input.tag?.code || '').trim();
80
+ if (!section)
81
+ throw new Error('Digital twin selection section is required.');
82
+ if (!system || !code)
83
+ throw new Error('Digital twin selection tag requires system and code.');
84
+ const { section: _section, tag: _tag, ...searchInput } = input;
85
+ return this.search(ctx, {
86
+ ...searchInput,
87
+ filters: {
88
+ [DigitalTwinSearchParameter.Section]: section,
89
+ [DigitalTwinSearchParameter.MetaTag]: `${system}|${code}`,
90
+ },
91
+ });
92
+ }
93
+ /** Materializes one selected research subject through `ResearchSubject/$summary`. */
94
+ materialize(ctx, input) {
95
+ return requireClientMethod(this.client, 'materializeDigitalTwin')(ctx, {
96
+ ...input,
97
+ accessToken: input.accessToken || this.smartAccessToken,
98
+ });
99
+ }
100
+ }
@@ -117,7 +117,8 @@ export declare class IndividualControllerSdk {
117
117
  */
118
118
  searchCommunicationParticipants(ctx: RouteContext, input: CommunicationParticipantRuntimeSearchInput): Promise<SubmitAndPollResult>;
119
119
  /**
120
- * Generates a digital twin projection from subject data.
120
+ * Explicitly packages subject data for a study/transfer flow.
121
+ * Normal clinical ingestion is projected into the tenant research index by GW.
121
122
  */
122
123
  generateDigitalTwinFromSubjectData(ctx: RouteContext, input: DigitalTwinGenerationInput): Promise<SubmitAndPollResult>;
123
124
  /**
@@ -175,7 +175,8 @@ export class IndividualControllerSdk {
175
175
  return requireClientMethod(this.client, 'searchCommunicationParticipants')(ctx, input);
176
176
  }
177
177
  /**
178
- * Generates a digital twin projection from subject data.
178
+ * Explicitly packages subject data for a study/transfer flow.
179
+ * Normal clinical ingestion is projected into the tenant research index by GW.
179
180
  */
180
181
  generateDigitalTwinFromSubjectData(ctx, input) {
181
182
  assertFacadeCapability(this.capabilities, ActorCapabilities.IndividualGenerateDigitalTwin, ActorKinds.IndividualController, 'generateDigitalTwinFromSubjectData');
@@ -28,7 +28,7 @@ export declare class PersonalSdk {
28
28
  registerBlockchainArtifactAndUpdateIndex(ctx: RouteContext, input: BlockchainArtifactRegistrationInput): Promise<SubmitAndPollResult>;
29
29
  /** Searches indexed communication channel records by subject and participant identifiers. */
30
30
  searchCommunicationParticipants(ctx: RouteContext, input: CommunicationParticipantRuntimeSearchInput): Promise<SubmitAndPollResult>;
31
- /** Triggers digital twin generation from subject data. */
31
+ /** Explicitly packages subject data for a study/transfer; normal ingestion is projected by GW. */
32
32
  generateDigitalTwinFromSubjectData(ctx: RouteContext, input: DigitalTwinGenerationInput): Promise<SubmitAndPollResult>;
33
33
  /** Searches indexed clinical bundles for the current subject/controller context. */
34
34
  searchClinicalBundle(ctx: RouteContext, input: ClinicalBundleSearchInput): Promise<SubmitAndPollResult>;
@@ -50,7 +50,7 @@ export class PersonalSdk {
50
50
  searchCommunicationParticipants(ctx, input) {
51
51
  return requireClientMethod(this.client, 'searchCommunicationParticipants')(ctx, input);
52
52
  }
53
- /** Triggers digital twin generation from subject data. */
53
+ /** Explicitly packages subject data for a study/transfer; normal ingestion is projected by GW. */
54
54
  generateDigitalTwinFromSubjectData(ctx, input) {
55
55
  return requireClientMethod(this.client, 'generateDigitalTwinFromSubjectData')(ctx, input);
56
56
  }
@@ -0,0 +1,16 @@
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>;
@@ -0,0 +1,34 @@
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
+ }
@@ -84,6 +84,12 @@ export declare class RuntimeClientPaths {
84
84
  fhirR5SubscriptionPollPath(ctx: RouteContext, section: 'entity' | 'individual'): string;
85
85
  individualBundleSearchPath(ctx: RouteContext): string;
86
86
  individualBundleSearchPollPath(ctx: RouteContext): string;
87
+ digitalTwinSearchPath(ctx: RouteContext, format: string, resourceType: string): string;
88
+ digitalTwinSearchPollPath(ctx: RouteContext, format: string, resourceType: string): string;
89
+ digitalTwinCompositionBatchPath(ctx: RouteContext, format: string): string;
90
+ digitalTwinCompositionPollPath(ctx: RouteContext, format: string): string;
91
+ digitalTwinCommunicationBatchPath(ctx: RouteContext, format: string): string;
92
+ digitalTwinCommunicationPollPath(ctx: RouteContext, format: string): string;
87
93
  identityTokenExchangePath(ctx: RouteContext): string;
88
94
  identityTokenExchangePollPath(ctx: RouteContext): string;
89
95
  identityDeviceDcrPath(ctx: RouteContext): string;
@@ -98,6 +98,12 @@ export class RuntimeClientPaths {
98
98
  fhirR5SubscriptionPollPath(ctx, section) { return this.v1Path(ctx, section, 'org.hl7.fhir.r5', 'Subscription', '_batch-response'); }
99
99
  individualBundleSearchPath(ctx) { return this.v1Path(ctx, 'individual', 'org.hl7.fhir.r4', 'Bundle', '_search'); }
100
100
  individualBundleSearchPollPath(ctx) { return this.v1Path(ctx, 'individual', 'org.hl7.fhir.r4', 'Bundle', '_search-response'); }
101
+ digitalTwinSearchPath(ctx, format, resourceType) { return this.v1Path(ctx, 'digitaltwin', format, resourceType, '_search'); }
102
+ digitalTwinSearchPollPath(ctx, format, resourceType) { return this.v1Path(ctx, 'digitaltwin', format, resourceType, '_batch-response'); }
103
+ digitalTwinCompositionBatchPath(ctx, format) { return this.v1Path(ctx, 'digitaltwin', format, 'Composition', '_batch'); }
104
+ digitalTwinCompositionPollPath(ctx, format) { return this.v1Path(ctx, 'digitaltwin', format, 'Composition', '_batch-response'); }
105
+ digitalTwinCommunicationBatchPath(ctx, format) { return this.v1Path(ctx, 'digitaltwin', format, 'Communication', '_batch'); }
106
+ digitalTwinCommunicationPollPath(ctx, format) { return this.v1Path(ctx, 'digitaltwin', format, 'Communication', '_batch-response'); }
101
107
  identityTokenExchangePath(ctx) { return buildIdentityTokenExchangePath(ctx); }
102
108
  identityTokenExchangePollPath(ctx) { return buildIdentityTokenExchangePollPath(ctx); }
103
109
  identityDeviceDcrPath(ctx) { return buildIdentityDeviceDcrPath(ctx); }
package/dist/session.d.ts CHANGED
@@ -6,6 +6,7 @@ import { PersonalSdk } from './orchestration/personal-sdk.js';
6
6
  import { OrganizationControllerSdk } from './orchestration/organization-controller-sdk.js';
7
7
  import { OrganizationEmployeeSdk } from './orchestration/organization-employee-sdk.js';
8
8
  import { ProfessionalSdk } from './orchestration/professional-sdk.js';
9
+ import { DigitalTwinSdk } from './orchestration/digital-twin-sdk.js';
9
10
  import type { RuntimeClient } from './orchestration/client-port.js';
10
11
  export type NodeCapability = Capability;
11
12
  export type NodeActorSessionContext = {
@@ -47,6 +48,8 @@ export declare class ActorSession {
47
48
  asPersonal(): PersonalSdk;
48
49
  asIndividualMember(): IndividualMemberSdk;
49
50
  asProfessional(): ProfessionalSdk;
51
+ /** Materializes the digital-twin facade for an organization employee or professional actor. */
52
+ asDigitalTwin(): DigitalTwinSdk;
50
53
  private requireClient;
51
54
  }
52
55
  /**
@@ -68,4 +71,5 @@ export declare class NodeActorSession {
68
71
  asPersonal(): PersonalSdk;
69
72
  asIndividualMember(): IndividualMemberSdk;
70
73
  asProfessional(): ProfessionalSdk;
74
+ asDigitalTwin(): DigitalTwinSdk;
71
75
  }
package/dist/session.js CHANGED
@@ -7,6 +7,7 @@ import { PersonalSdk } from './orchestration/personal-sdk.js';
7
7
  import { OrganizationControllerSdk } from './orchestration/organization-controller-sdk.js';
8
8
  import { OrganizationEmployeeSdk } from './orchestration/organization-employee-sdk.js';
9
9
  import { ProfessionalSdk } from './orchestration/professional-sdk.js';
10
+ import { DigitalTwinSdk } from './orchestration/digital-twin-sdk.js';
10
11
  /**
11
12
  * Preferred neutral actor session abstraction for runtime packages.
12
13
  *
@@ -61,6 +62,13 @@ export class ActorSession {
61
62
  this.assertActorKind(ActorKinds.Professional);
62
63
  return new ProfessionalSdk(this.requireClient());
63
64
  }
65
+ /** Materializes the digital-twin facade for an organization employee or professional actor. */
66
+ asDigitalTwin() {
67
+ if (this.actorKind !== ActorKinds.OrganizationEmployee && this.actorKind !== ActorKinds.Professional) {
68
+ throw new Error(`ActorSession is '${this.actorKind}' and cannot use digital-twin research operations.`);
69
+ }
70
+ return new DigitalTwinSdk(this.requireClient(), this.actorDid);
71
+ }
64
72
  requireClient() {
65
73
  if (!this.client) {
66
74
  throw new Error('ActorSession requires a runtime client to materialize actor facades.');
@@ -88,4 +96,5 @@ export class NodeActorSession {
88
96
  asPersonal() { return this.inner.asPersonal(); }
89
97
  asIndividualMember() { return this.inner.asIndividualMember(); }
90
98
  asProfessional() { return this.inner.asProfessional(); }
99
+ asDigitalTwin() { return this.inner.asDigitalTwin(); }
91
100
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdc-sdk-node-ts",
3
- "version": "2.4.4",
3
+ "version": "2.4.8",
4
4
  "description": "Next-generation Node runtime package for the GDC SDK family",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Antifraud Services Inc.",
@@ -37,7 +37,7 @@
37
37
  "test:e2e:live-gw:clean": "bash ./scripts/run-live-gw-clean.sh"
38
38
  },
39
39
  "dependencies": {
40
- "gdc-common-utils-ts": "2.5.6",
40
+ "gdc-common-utils-ts": "2.5.7",
41
41
  "gdc-sdk-core-ts": "~2.3.18"
42
42
  },
43
43
  "devDependencies": {