gdc-sdk-node-ts 2.6.2 → 2.6.4
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 +8 -2
- package/dist/node-runtime-client.d.ts +11 -2
- package/dist/node-runtime-client.js +11 -2
- package/dist/orchestration/professional-sdk.d.ts +2 -1
- package/dist/orchestration/professional-sdk.js +2 -1
- package/dist/organization-employee-lifecycle.d.ts +2 -0
- package/dist/organization-employee-lifecycle.js +21 -1
- package/dist/resource-operations.d.ts +8 -3
- package/dist/resource-operations.js +35 -9
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -427,13 +427,16 @@ data ingestion/search.
|
|
|
427
427
|
|
|
428
428
|
`ProfessionalSdk.requestProfessionalAccess(...)` is the bootstrap operation
|
|
429
429
|
for the inverse flow: it persists a subject-scoped permission-request
|
|
430
|
-
`Communication`
|
|
431
|
-
|
|
430
|
+
`Communication` with an attached batch Bundle of normal
|
|
431
|
+
`Consent.status = draft` resources, using the configured HTTP bearer and
|
|
432
|
+
transport profile without requiring SMART. A draft is inbox evidence and
|
|
433
|
+
cannot authorize access. The subject later answers through
|
|
432
434
|
`respondToProfessionalAccessRequest(...)`; that facade uses the normal Consent
|
|
433
435
|
grant/deny operation and retains the original Communication identifier/thread.
|
|
434
436
|
`listProfessionalAccessRequests(...)` provides the subject/requester inbox view,
|
|
435
437
|
and `GatewayActiveConsentProvider` supplies `evaluateRequestedAccess(...)` from
|
|
436
438
|
the GW Consent source of truth rather than a parallel application table.
|
|
439
|
+
There is no `AccessRequest` FHIR resource or `AccessRequest.*` claim family.
|
|
437
440
|
|
|
438
441
|
## Main Flows
|
|
439
442
|
|
|
@@ -813,6 +816,9 @@ Recovery-specific live rule:
|
|
|
813
816
|
and cannot select another tenant
|
|
814
817
|
- a failed poll `OperationOutcome` is surfaced before the helper checks for
|
|
815
818
|
`initial_access_token`, preserving the real GW diagnostic
|
|
819
|
+
- every asynchronous success test uses `BundleReader.getResponseAnalysis()`;
|
|
820
|
+
HTTP 200/201 alone is transport completion, not proof that the inner Bundle
|
|
821
|
+
entries succeeded
|
|
816
822
|
- the bundled recovery runner generates a syntactically valid demo JWT if
|
|
817
823
|
`CONTROLLER_ID_TOKEN` is not provided, but production/staging should use a
|
|
818
824
|
real IdP-issued token
|
|
@@ -143,6 +143,13 @@ export declare class HttpRuntimeClient implements NodeRuntimeClient {
|
|
|
143
143
|
}, pollOptions?: PollOptions): Promise<PollResult>;
|
|
144
144
|
/**
|
|
145
145
|
* Convenience wrapper that performs submit and poll in sequence.
|
|
146
|
+
*
|
|
147
|
+
* A terminal HTTP 200/201 means polling completed; it does not guarantee the
|
|
148
|
+
* business operation succeeded. Consumers and E2E tests must inspect
|
|
149
|
+
* `result.poll.body` with `BundleReader.getResponseAnalysis()` and reject
|
|
150
|
+
* error OperationOutcomes. High-level product code must not manually traverse
|
|
151
|
+
* DIDComm `body`, JSON-API `data[]`, FHIR `entry[]` or `issue[]` to decide
|
|
152
|
+
* success.
|
|
146
153
|
*/
|
|
147
154
|
submitAndPoll(submitPath: string, pollPath: string, payload: SubmitPayload, pollOptions?: PollOptions): Promise<SubmitAndPollResult>;
|
|
148
155
|
/** Registers one neutral FHIR R5 topic in the tenant-owned CORE catalog. */
|
|
@@ -496,8 +503,10 @@ export declare class HttpRuntimeClient implements NodeRuntimeClient {
|
|
|
496
503
|
ingestCommunicationAndUpdateIndex(ctx: RouteContext, input: CommunicationIngestionInput): Promise<SubmitAndPollResult>;
|
|
497
504
|
/**
|
|
498
505
|
* Records a subject-scoped permission-request Communication before SMART
|
|
499
|
-
* authorization exists. The
|
|
500
|
-
*
|
|
506
|
+
* authorization exists. The Communication carries a batch Bundle of
|
|
507
|
+
* `Consent.status = draft`; that draft grants nothing. The configured bearer
|
|
508
|
+
* authenticates HTTP and the configured secure transport adapter
|
|
509
|
+
* signs/encrypts DIDComm when enabled.
|
|
501
510
|
*/
|
|
502
511
|
requestProfessionalAccess(ctx: RouteContext, input: ProfessionalAccessRequestInput): Promise<ProfessionalAccessRequestResult>;
|
|
503
512
|
/**
|
|
@@ -129,6 +129,13 @@ export class HttpRuntimeClient {
|
|
|
129
129
|
}
|
|
130
130
|
/**
|
|
131
131
|
* Convenience wrapper that performs submit and poll in sequence.
|
|
132
|
+
*
|
|
133
|
+
* A terminal HTTP 200/201 means polling completed; it does not guarantee the
|
|
134
|
+
* business operation succeeded. Consumers and E2E tests must inspect
|
|
135
|
+
* `result.poll.body` with `BundleReader.getResponseAnalysis()` and reject
|
|
136
|
+
* error OperationOutcomes. High-level product code must not manually traverse
|
|
137
|
+
* DIDComm `body`, JSON-API `data[]`, FHIR `entry[]` or `issue[]` to decide
|
|
138
|
+
* success.
|
|
132
139
|
*/
|
|
133
140
|
async submitAndPoll(submitPath, pollPath, payload, pollOptions) {
|
|
134
141
|
return this.submitClinicalMessageAndPoll(submitPath, pollPath, payload, this.transportProfile, pollOptions);
|
|
@@ -984,8 +991,10 @@ export class HttpRuntimeClient {
|
|
|
984
991
|
}
|
|
985
992
|
/**
|
|
986
993
|
* Records a subject-scoped permission-request Communication before SMART
|
|
987
|
-
* authorization exists. The
|
|
988
|
-
*
|
|
994
|
+
* authorization exists. The Communication carries a batch Bundle of
|
|
995
|
+
* `Consent.status = draft`; that draft grants nothing. The configured bearer
|
|
996
|
+
* authenticates HTTP and the configured secure transport adapter
|
|
997
|
+
* signs/encrypts DIDComm when enabled.
|
|
989
998
|
*/
|
|
990
999
|
async requestProfessionalAccess(ctx, input) {
|
|
991
1000
|
this.requireConfiguredTransportProfile(input.transportProfile);
|
|
@@ -77,7 +77,8 @@ export declare class ProfessionalSdk {
|
|
|
77
77
|
ingestCommunicationAndUpdateIndex(ctx: RouteContext, input: CommunicationIngestionInput): Promise<SubmitAndPollResult>;
|
|
78
78
|
/**
|
|
79
79
|
* Requests subject-controlled access without requiring a SMART token. The
|
|
80
|
-
* runtime persists one canonical permission-request Communication
|
|
80
|
+
* runtime persists one canonical permission-request Communication carrying
|
|
81
|
+
* a draft Consent Bundle. The draft is auditable but never authorizes access.
|
|
81
82
|
*/
|
|
82
83
|
requestProfessionalAccess(ctx: RouteContext, input: ProfessionalAccessRequestInput): Promise<ProfessionalAccessRequestResult>;
|
|
83
84
|
/** Lists permission requests sent by or involving this professional. */
|
|
@@ -92,7 +92,8 @@ export class ProfessionalSdk {
|
|
|
92
92
|
}
|
|
93
93
|
/**
|
|
94
94
|
* Requests subject-controlled access without requiring a SMART token. The
|
|
95
|
-
* runtime persists one canonical permission-request Communication
|
|
95
|
+
* runtime persists one canonical permission-request Communication carrying
|
|
96
|
+
* a draft Consent Bundle. The draft is auditable but never authorizes access.
|
|
96
97
|
*/
|
|
97
98
|
requestProfessionalAccess(ctx, input) {
|
|
98
99
|
return requireClientMethod(this.client, 'requestProfessionalAccess')(ctx, input);
|
|
@@ -36,6 +36,8 @@ export type OrganizationEmployeeProvisioningDeps = Readonly<{
|
|
|
36
36
|
* returns the exact GW activation credential.
|
|
37
37
|
*/
|
|
38
38
|
export declare function provisionOrganizationEmployeeWithDeps(routeContext: RouteContext, input: OrganizationEmployeeProvisioningInput, deps: OrganizationEmployeeProvisioningDeps): Promise<OrganizationEmployeeProvisioningResult>;
|
|
39
|
+
/** Reads the technical employee id that must own the subsequently issued seat. */
|
|
40
|
+
export declare function readEmployeeResourceId(value: unknown): string | undefined;
|
|
39
41
|
/** Reads the effective positive installation allowance returned by `License/_issue`. */
|
|
40
42
|
export declare function readEmployeeLicenseMaxDevices(value: unknown): number | undefined;
|
|
41
43
|
/**
|
|
@@ -29,7 +29,14 @@ export async function provisionOrganizationEmployeeWithDeps(routeContext, input,
|
|
|
29
29
|
throw new Error('provisionOrganizationEmployee: employee creation still requires a licence after Order confirmation.');
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
-
const
|
|
32
|
+
const employeeResourceId = readEmployeeResourceId(employee.poll.body);
|
|
33
|
+
if (!employeeResourceId) {
|
|
34
|
+
throw new Error('provisionOrganizationEmployee: GW response did not contain the created employee resource id.');
|
|
35
|
+
}
|
|
36
|
+
const license = await deps.issueLicense(routeContext, {
|
|
37
|
+
...input.invitation,
|
|
38
|
+
subjectId: employeeResourceId,
|
|
39
|
+
});
|
|
33
40
|
assertSuccessfulEmployeeOperation('licence issue', license);
|
|
34
41
|
const activationCode = readEmployeeActivationCode(license.poll.body);
|
|
35
42
|
if (!activationCode) {
|
|
@@ -44,6 +51,19 @@ export async function provisionOrganizationEmployeeWithDeps(routeContext, input,
|
|
|
44
51
|
...(maxDevices ? { maxDevices } : {}),
|
|
45
52
|
};
|
|
46
53
|
}
|
|
54
|
+
/** Reads the technical employee id that must own the subsequently issued seat. */
|
|
55
|
+
export function readEmployeeResourceId(value) {
|
|
56
|
+
for (const candidate of nestedRecords(value)) {
|
|
57
|
+
const response = record(candidate.response);
|
|
58
|
+
const status = parseHttpStatus(response?.status);
|
|
59
|
+
if (status !== undefined && (status < 200 || status >= 300))
|
|
60
|
+
continue;
|
|
61
|
+
const resourceId = String(record(candidate.resource)?.id || '').trim();
|
|
62
|
+
if (resourceId)
|
|
63
|
+
return resourceId;
|
|
64
|
+
}
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
47
67
|
/** Reads the effective positive installation allowance returned by `License/_issue`. */
|
|
48
68
|
export function readEmployeeLicenseMaxDevices(value) {
|
|
49
69
|
for (const candidate of nestedRecords(value)) {
|
|
@@ -33,6 +33,8 @@ export type OrganizationEmployeeLicenseInvitationInput = {
|
|
|
33
33
|
email: string;
|
|
34
34
|
role: string;
|
|
35
35
|
subjectDid: string;
|
|
36
|
+
/** Technical employee resource id returned by Employee creation. */
|
|
37
|
+
subjectId?: string;
|
|
36
38
|
type?: DeviceAppType;
|
|
37
39
|
requestThid?: string;
|
|
38
40
|
pollOptions?: {
|
|
@@ -268,8 +270,9 @@ export type CommunicationIngestionInput = {
|
|
|
268
270
|
/**
|
|
269
271
|
* Canonical professional-to-subject permission request.
|
|
270
272
|
*
|
|
271
|
-
* This operation records
|
|
272
|
-
*
|
|
273
|
+
* This operation records one `Communication` carrying a normal
|
|
274
|
+
* `Consent.status = draft`; it never creates an active grant and does not
|
|
275
|
+
* require a SMART token. HTTP authentication and optional secure DIDComm
|
|
273
276
|
* transport remain concerns of the configured runtime client.
|
|
274
277
|
*/
|
|
275
278
|
export type ProfessionalAccessRequestInput = Omit<PermissionRequestCommunicationInput, 'missing'> & Readonly<{
|
|
@@ -288,6 +291,7 @@ export type ProfessionalAccessRequestInput = Omit<PermissionRequestCommunication
|
|
|
288
291
|
export type ProfessionalAccessRequestResult = Readonly<{
|
|
289
292
|
thid: string;
|
|
290
293
|
communicationIdentifier: string;
|
|
294
|
+
consentIdentifier: string;
|
|
291
295
|
communication: CommunicationInput;
|
|
292
296
|
delivery: SubmitAndPollResult;
|
|
293
297
|
}>;
|
|
@@ -846,7 +850,8 @@ export declare function ingestCommunicationAndUpdateIndexWithDeps(routeCtx: Rout
|
|
|
846
850
|
/**
|
|
847
851
|
* Builds and persists one professional access request against the subject's
|
|
848
852
|
* provider route. The request is deliberately independent from SMART because
|
|
849
|
-
* no subject consent exists yet.
|
|
853
|
+
* no active subject consent exists yet. The auditable Communication carries a
|
|
854
|
+
* draft Consent Bundle; the draft itself never authorizes data access.
|
|
850
855
|
*/
|
|
851
856
|
export declare function requestProfessionalAccessWithDeps(routeCtx: RouteContext, input: ProfessionalAccessRequestInput, deps: {
|
|
852
857
|
individualCommunicationBatchPath: (ctx: RouteContext, pathFormatSegment: string) => string;
|
|
@@ -70,19 +70,23 @@ export async function issueOrganizationEmployeeLicenseWithDeps(routeCtx, input,
|
|
|
70
70
|
const email = String(input.email || '').trim().toLowerCase();
|
|
71
71
|
const role = String(input.role || '').trim();
|
|
72
72
|
const subjectDid = String(input.subjectDid || '').trim();
|
|
73
|
+
const subjectId = String(input.subjectId || '').trim();
|
|
73
74
|
if (!email)
|
|
74
75
|
throw new Error('issueOrganizationEmployeeLicense: email is required.');
|
|
75
76
|
if (!role)
|
|
76
77
|
throw new Error('issueOrganizationEmployeeLicense: role is required.');
|
|
77
78
|
if (!subjectDid)
|
|
78
79
|
throw new Error('issueOrganizationEmployeeLicense: subjectDid is required.');
|
|
80
|
+
if (!subjectId)
|
|
81
|
+
throw new Error('issueOrganizationEmployeeLicense: subjectId is required.');
|
|
79
82
|
const entry = buildLicenseIssueEntry({
|
|
80
83
|
email,
|
|
81
84
|
role,
|
|
82
85
|
userClass: DeviceUserClasses.Employee,
|
|
83
86
|
type: input.type || DeviceAppTypes.Web,
|
|
87
|
+
subjectId,
|
|
88
|
+
subjectDid,
|
|
84
89
|
});
|
|
85
|
-
entry.meta.subjectDid = subjectDid;
|
|
86
90
|
return deps.submitAndPoll(deps.identityLicenseIssuePath(routeCtx), deps.identityLicenseIssuePollPath(routeCtx), {
|
|
87
91
|
thid: input.requestThid || `employee-license-issue-${createRuntimeUuid()}`,
|
|
88
92
|
body: {
|
|
@@ -476,7 +480,8 @@ export async function ingestCommunicationAndUpdateIndexWithDeps(routeCtx, input,
|
|
|
476
480
|
/**
|
|
477
481
|
* Builds and persists one professional access request against the subject's
|
|
478
482
|
* provider route. The request is deliberately independent from SMART because
|
|
479
|
-
* no subject consent exists yet.
|
|
483
|
+
* no active subject consent exists yet. The auditable Communication carries a
|
|
484
|
+
* draft Consent Bundle; the draft itself never authorizes data access.
|
|
480
485
|
*/
|
|
481
486
|
export async function requestProfessionalAccessWithDeps(routeCtx, input, deps) {
|
|
482
487
|
const subject = String(input.subject || '').trim();
|
|
@@ -498,11 +503,14 @@ export async function requestProfessionalAccessWithDeps(routeCtx, input, deps) {
|
|
|
498
503
|
const thid = String(input.thid || '').trim() || `permission-request-${createRuntimeUuid()}`;
|
|
499
504
|
const communicationIdentifier = String(input.communicationIdentifier || '').trim()
|
|
500
505
|
|| `urn:uuid:${createRuntimeUuid()}`;
|
|
506
|
+
const consentIdentifier = String(input.consentIdentifier || '').trim()
|
|
507
|
+
|| `urn:uuid:${createRuntimeUuid()}`;
|
|
501
508
|
const communication = buildPermissionRequestCommunication({
|
|
502
509
|
...input,
|
|
503
510
|
subject,
|
|
504
511
|
thid,
|
|
505
512
|
communicationIdentifier,
|
|
513
|
+
consentIdentifier,
|
|
506
514
|
missing: {
|
|
507
515
|
sections: missingSections,
|
|
508
516
|
resourceTypes: missingResourceTypes,
|
|
@@ -518,6 +526,23 @@ export async function requestProfessionalAccessWithDeps(routeCtx, input, deps) {
|
|
|
518
526
|
[CommunicationClaim.NoteText]: communication.text,
|
|
519
527
|
};
|
|
520
528
|
const persistedCommunication = { ...communication, claims: communicationClaims };
|
|
529
|
+
if (!communication.payload) {
|
|
530
|
+
throw new Error('Permission request requires one draft Consent Bundle.');
|
|
531
|
+
}
|
|
532
|
+
const communicationResource = addFhirResourceToCommunication(createCommunicationResource({
|
|
533
|
+
subject,
|
|
534
|
+
sender: communication.sender,
|
|
535
|
+
recipient: communication.recipient,
|
|
536
|
+
category: Array.isArray(communication.category)
|
|
537
|
+
? communication.category
|
|
538
|
+
: communication.category ? [communication.category] : undefined,
|
|
539
|
+
noteText: communication.text,
|
|
540
|
+
claims: communicationClaims,
|
|
541
|
+
}), communication.payload, {
|
|
542
|
+
attachmentTitle: 'draft-consent-request.json',
|
|
543
|
+
attachmentContentType: 'application/fhir+json',
|
|
544
|
+
});
|
|
545
|
+
communicationResource.id = communicationIdentifier;
|
|
521
546
|
const communicationPayload = {
|
|
522
547
|
thid,
|
|
523
548
|
body: {
|
|
@@ -526,12 +551,7 @@ export async function requestProfessionalAccessWithDeps(routeCtx, input, deps) {
|
|
|
526
551
|
data: [{
|
|
527
552
|
type: 'CommMsgExtended',
|
|
528
553
|
meta: { claims: communicationClaims },
|
|
529
|
-
resource:
|
|
530
|
-
resourceType: ResourceTypesFhirR4.Communication,
|
|
531
|
-
id: communicationIdentifier,
|
|
532
|
-
status: 'completed',
|
|
533
|
-
meta: { claims: communicationClaims },
|
|
534
|
-
},
|
|
554
|
+
resource: communicationResource,
|
|
535
555
|
}],
|
|
536
556
|
},
|
|
537
557
|
};
|
|
@@ -543,7 +563,13 @@ export async function requestProfessionalAccessWithDeps(routeCtx, input, deps) {
|
|
|
543
563
|
pathFormatSegment: 'r4',
|
|
544
564
|
pollOptions: input.pollOptions,
|
|
545
565
|
}, deps);
|
|
546
|
-
return {
|
|
566
|
+
return {
|
|
567
|
+
thid,
|
|
568
|
+
communicationIdentifier,
|
|
569
|
+
consentIdentifier,
|
|
570
|
+
communication: persistedCommunication,
|
|
571
|
+
delivery,
|
|
572
|
+
};
|
|
547
573
|
}
|
|
548
574
|
export async function searchCommunicationParticipantsWithDeps(routeCtx, input, deps) {
|
|
549
575
|
const payload = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdc-sdk-node-ts",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.4",
|
|
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.",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"test:e2e:live-gw:clean": "bash ./scripts/run-live-gw-clean.sh"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"gdc-common-utils-ts": "2.7.
|
|
42
|
-
"gdc-sdk-core-ts": "2.4.
|
|
41
|
+
"gdc-common-utils-ts": "2.7.3",
|
|
42
|
+
"gdc-sdk-core-ts": "2.4.3"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/node": "^20.14.10",
|