gdc-sdk-node-ts 2.4.37 → 2.4.39

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
@@ -72,40 +72,43 @@ If you are integrating this package for the first time, open these in order:
72
72
  3. [docs/101-PROFESSIONAL-CONSENT-SMART.md](./docs/101-PROFESSIONAL-CONSENT-SMART.md)
73
73
  Canonical professional DID, consent, VP and SMART flow without literal
74
74
  sections or caller-built audience URLs.
75
- 4. [docs/101-SDK_INTEGRATION.md](./docs/101-SDK_INTEGRATION.md)
75
+ 4. [docs/101-AUTHORIZED_SUBJECT_DIRECTORY.md](./docs/101-AUTHORIZED_SUBJECT_DIRECTORY.md)
76
+ Signed OpenID account discovery of already-authorized subjects without
77
+ treating the account token as VP, SMART or wallet proof.
78
+ 5. [docs/101-SDK_INTEGRATION.md](./docs/101-SDK_INTEGRATION.md)
76
79
  Real backend setup plus the public runtime entrypoints:
77
80
  `HostOnboardingSdk`, `OrganizationControllerSdk`,
78
81
  `IndividualControllerSdk`, `ProfessionalSdk`, route-context usage, and the
79
82
  canonical `ProfileRuntime -> loadProfile(...) -> workspace/session -> actor facade -> submit/poll` shape.
80
- 5. [tests/101-live-full-cycle-bff-runtime.e2e.test.mjs](./tests/101-live-full-cycle-bff-runtime.e2e.test.mjs)
83
+ 6. [tests/101-live-full-cycle-bff-runtime.e2e.test.mjs](./tests/101-live-full-cycle-bff-runtime.e2e.test.mjs)
81
84
  Canonical live backend/BFF walkthrough on a fresh local GW lifecycle:
82
85
  host/tenant activation, employee provisioning, individual bootstrap,
83
86
  consent grant, professional SMART token, clinical read, and final cleanup.
84
- 6. [docs/101-LIVE_GW_LOCAL.md](./docs/101-LIVE_GW_LOCAL.md)
87
+ 7. [docs/101-LIVE_GW_LOCAL.md](./docs/101-LIVE_GW_LOCAL.md)
85
88
  Exact TTY/local/Docker commands for running the SDK against a real local GW
86
89
  CORE, including tenant bootstrap and employee-seat setup.
87
- 7. [docs/101-DISCOVERY.md](./docs/101-DISCOVERY.md)
90
+ 8. [docs/101-DISCOVERY.md](./docs/101-DISCOVERY.md)
88
91
  Node/BFF dataspace discovery, hosting-operator resolution, provider
89
92
  resolution, and the correct integration boundary for fallback and cache.
90
- 8. [gdc-sdk-core-ts/docs/101-SDK_FLOWS.md](https://github.com/Global-DataCare/gdc-sdk-core-ts/blob/main/docs/101-SDK_FLOWS.md)
93
+ 9. [gdc-sdk-core-ts/docs/101-SDK_FLOWS.md](https://github.com/Global-DataCare/gdc-sdk-core-ts/blob/main/docs/101-SDK_FLOWS.md)
91
94
  Actor split and business-flow map across organization, individual,
92
95
  permissions, invitation, import, and SMART flows.
93
- 9. [gwtemplate-node-ts/docs/PORTAL_API_TO_GW_CORE.md](https://github.com/Global-DataCare/gwtemplate-node-ts/blob/main/docs/PORTAL_API_TO_GW_CORE.md)
96
+ 10. [gwtemplate-node-ts/docs/PORTAL_API_TO_GW_CORE.md](https://github.com/Global-DataCare/gwtemplate-node-ts/blob/main/docs/PORTAL_API_TO_GW_CORE.md)
94
97
  Canonical portal/BFF functional map over GW CORE, including the domain
95
98
  split between `employees`, `related persons`, `members`, and `consents`.
96
- 10. [gdc-common-utils-ts/src/examples/](https://github.com/Global-DataCare/gdc-common-utils-ts/tree/main/src/examples)
99
+ 11. [gdc-common-utils-ts/src/examples/](https://github.com/Global-DataCare/gdc-common-utils-ts/tree/main/src/examples)
97
100
  Shared payload values used by the docs and tests.
98
- 11. [gdc-common-utils-ts/docs/101-LIFECYCLE.md](https://github.com/Global-DataCare/gdc-common-utils-ts/blob/main/docs/101-LIFECYCLE.md)
101
+ 12. [gdc-common-utils-ts/docs/101-LIFECYCLE.md](https://github.com/Global-DataCare/gdc-common-utils-ts/blob/main/docs/101-LIFECYCLE.md)
99
102
  Canonical `enable/disable/delete` semantics and copy/paste placeholders.
100
- 12. [tests/101-backend-profile-runtime.test.mjs](./tests/101-backend-profile-runtime.test.mjs)
103
+ 13. [tests/101-backend-profile-runtime.test.mjs](./tests/101-backend-profile-runtime.test.mjs)
101
104
  Technical runtime slice for profile/device/session orchestration internals.
102
105
  Read this after the public actor SDK guides, not before them.
103
- 13. [tests/101-individual-controller-backend-runtime.test.mjs](./tests/101-individual-controller-backend-runtime.test.mjs)
106
+ 14. [tests/101-individual-controller-backend-runtime.test.mjs](./tests/101-individual-controller-backend-runtime.test.mjs)
104
107
  Technical wrapper slice around the generic profile runtime. This is not the
105
108
  main onboarding path for new integrators.
106
- 14. [docs/V2_INDIVIDUAL_REGISTRATION_RECONCILIATION.md](./docs/V2_INDIVIDUAL_REGISTRATION_RECONCILIATION.md)
109
+ 15. [docs/V2_INDIVIDUAL_REGISTRATION_RECONCILIATION.md](./docs/V2_INDIVIDUAL_REGISTRATION_RECONCILIATION.md)
107
110
  Historical reconciliation note for the current CORE registration baseline.
108
- 15. [docs/NEXT_STEPS.md](./docs/NEXT_STEPS.md)
111
+ 16. [docs/NEXT_STEPS.md](./docs/NEXT_STEPS.md)
109
112
  Follow-up scope after GW CORE live validation, including the future user job
110
113
  manager boundary.
111
114
 
@@ -0,0 +1,51 @@
1
+ import type { SubmitAndPollResult } from 'gdc-sdk-core-ts';
2
+ import type { RouteContext } from './individual-onboarding.js';
3
+ /** Verified contact extracted by the BFF from a signed OpenID `id_token`. */
4
+ export type AuthorizedSubjectVerifiedContact = Readonly<{
5
+ email?: string;
6
+ telephone?: string;
7
+ }>;
8
+ /**
9
+ * High-level input for listing subject grants already accepted by an
10
+ * individual index. The contact locates grants; it is not role or action proof.
11
+ */
12
+ export type AuthorizedIndividualSubjectDirectoryInput = Readonly<{
13
+ verifiedContact: AuthorizedSubjectVerifiedContact;
14
+ requestThid?: string;
15
+ pollOptions?: Readonly<{
16
+ timeoutMs?: number;
17
+ intervalMs?: number;
18
+ }>;
19
+ }>;
20
+ /** One exact subject projection paired with its accepted grant metadata. */
21
+ export type AuthorizedIndividualSubject = Readonly<{
22
+ subjectDid: string;
23
+ role?: string;
24
+ /** Accepted provider grant claims; descriptive metadata, never action authority. */
25
+ grantClaims: Readonly<Record<string, unknown>>;
26
+ authorizationEvidenceId?: string;
27
+ issuerDid?: string;
28
+ subjectClaims: Readonly<Record<string, unknown>>;
29
+ }>;
30
+ type DirectoryDeps = Readonly<{
31
+ individualLicenseSearchPath: (ctx: RouteContext) => string;
32
+ individualLicenseSearchPollPath: (ctx: RouteContext) => string;
33
+ individualOrganizationSearchPath: (ctx: RouteContext) => string;
34
+ individualOrganizationSearchPollPath: (ctx: RouteContext) => string;
35
+ submitAndPoll: (submitPath: string, pollPath: string, payload: {
36
+ thid?: string;
37
+ } & Record<string, unknown>, pollOptions?: {
38
+ timeoutMs?: number;
39
+ intervalMs?: number;
40
+ }) => Promise<SubmitAndPollResult>;
41
+ }>;
42
+ /**
43
+ * Lists already-authorized individual subjects without exposing GW plumbing.
44
+ *
45
+ * The caller supplies a verified contact taken from a signed OpenID token.
46
+ * That account/contact proof is intentionally insufficient for protected
47
+ * actions: professional role proof remains a separate `vp_token`, and SMART
48
+ * access remains bound to a registered wallet/device session.
49
+ */
50
+ export declare function listAuthorizedIndividualSubjectsWithDeps(routeContext: RouteContext, input: AuthorizedIndividualSubjectDirectoryInput, deps: DirectoryDeps): Promise<AuthorizedIndividualSubject[]>;
51
+ export {};
@@ -0,0 +1,116 @@
1
+ // Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
2
+ const SCHEMA_CONTEXT = 'org.schema';
3
+ const INDIVIDUAL_AUDIENCE_TYPE = 'Individual';
4
+ const ACCEPTED_ITEM_CONDITION = 'https://schema.org/UsedCondition';
5
+ const CLAIM_AUDIENCE_TYPE = 'org.schema.IndividualProduct.audience.audienceType';
6
+ const CLAIM_ITEM_CONDITION = 'org.schema.IndividualProduct.itemCondition';
7
+ const CLAIM_PERSON_EMAIL = 'org.schema.Person.email';
8
+ const CLAIM_PERSON_TELEPHONE = 'org.schema.Person.telephone';
9
+ const CLAIM_ORGANIZATION_SAME_AS = 'org.schema.Organization.sameAs';
10
+ const CLAIM_RELATED_PERSON_ROLE = 'RelatedPerson.role';
11
+ const CLAIM_OCCUPATION_IDENTIFIER = 'org.schema.Person.hasOccupation.identifier.value';
12
+ /**
13
+ * Lists already-authorized individual subjects without exposing GW plumbing.
14
+ *
15
+ * The caller supplies a verified contact taken from a signed OpenID token.
16
+ * That account/contact proof is intentionally insufficient for protected
17
+ * actions: professional role proof remains a separate `vp_token`, and SMART
18
+ * access remains bound to a registered wallet/device session.
19
+ */
20
+ export async function listAuthorizedIndividualSubjectsWithDeps(routeContext, input, deps) {
21
+ const email = String(input.verifiedContact?.email || '').trim().toLowerCase();
22
+ const telephone = String(input.verifiedContact?.telephone || '').trim();
23
+ if (!email && !telephone) {
24
+ throw new Error('A verified email or telephone is required to list authorized subjects.');
25
+ }
26
+ const licenseClaims = {
27
+ '@context': SCHEMA_CONTEXT,
28
+ [CLAIM_AUDIENCE_TYPE]: INDIVIDUAL_AUDIENCE_TYPE,
29
+ [CLAIM_ITEM_CONDITION]: ACCEPTED_ITEM_CONDITION,
30
+ ...(email ? { [CLAIM_PERSON_EMAIL]: email } : {}),
31
+ ...(telephone ? { [CLAIM_PERSON_TELEPHONE]: telephone } : {}),
32
+ };
33
+ const licenseResult = await deps.submitAndPoll(deps.individualLicenseSearchPath(routeContext), deps.individualLicenseSearchPollPath(routeContext), {
34
+ thid: input.requestThid || createThreadId('authorized-subject-license'),
35
+ body: {
36
+ resourceType: 'Bundle',
37
+ type: 'batch',
38
+ data: [{
39
+ request: { method: 'POST' },
40
+ meta: { status: 'active', claims: licenseClaims },
41
+ }],
42
+ },
43
+ }, input.pollOptions);
44
+ const licenses = readAcceptedLicenses(licenseResult);
45
+ const resolved = await Promise.all(licenses.map(async (license) => {
46
+ const organizationClaims = {
47
+ '@context': SCHEMA_CONTEXT,
48
+ [CLAIM_ORGANIZATION_SAME_AS]: license.subjectDid,
49
+ };
50
+ const organizationResult = await deps.submitAndPoll(deps.individualOrganizationSearchPath(routeContext), deps.individualOrganizationSearchPollPath(routeContext), {
51
+ thid: createThreadId('authorized-subject-record'),
52
+ body: {
53
+ resourceType: 'Bundle',
54
+ type: 'batch',
55
+ data: [{
56
+ type: 'Family-search-v1.0',
57
+ request: { method: 'POST' },
58
+ meta: { claims: organizationClaims },
59
+ }],
60
+ },
61
+ }, input.pollOptions);
62
+ const subjectClaims = readFirstClaims(organizationResult);
63
+ if (String(subjectClaims[CLAIM_ORGANIZATION_SAME_AS] || '').trim() !== license.subjectDid) {
64
+ return undefined;
65
+ }
66
+ return {
67
+ subjectDid: license.subjectDid,
68
+ ...(license.role ? { role: license.role } : {}),
69
+ grantClaims: license.grantClaims,
70
+ ...(license.authorizationEvidenceId
71
+ ? { authorizationEvidenceId: license.authorizationEvidenceId }
72
+ : {}),
73
+ ...(license.issuerDid ? { issuerDid: license.issuerDid } : {}),
74
+ subjectClaims,
75
+ };
76
+ }));
77
+ return resolved.filter((value) => Boolean(value));
78
+ }
79
+ function readAcceptedLicenses(result) {
80
+ const rows = readData(result)[0]?.resource?.data;
81
+ if (!Array.isArray(rows))
82
+ return [];
83
+ return rows.flatMap((row) => {
84
+ const meta = row?.meta || row?.resource?.meta || {};
85
+ const subjectDid = String(meta.authorizedSubjectDid || '').trim();
86
+ if (!subjectDid)
87
+ return [];
88
+ const claims = (meta.claims || {});
89
+ const relatedRoles = String(claims[CLAIM_RELATED_PERSON_ROLE] || '')
90
+ .split(',')
91
+ .map((value) => value.trim().toUpperCase())
92
+ .filter(Boolean);
93
+ const occupation = String(claims[CLAIM_OCCUPATION_IDENTIFIER] || '').trim();
94
+ const role = relatedRoles.includes('CAREGIVER') ? 'CAREGIVER' : occupation;
95
+ return [{
96
+ subjectDid,
97
+ ...(role ? { role } : {}),
98
+ grantClaims: claims,
99
+ ...(meta.identifier ? { authorizationEvidenceId: String(meta.identifier) } : {}),
100
+ ...(meta.issuerDid ? { issuerDid: String(meta.issuerDid) } : {}),
101
+ }];
102
+ });
103
+ }
104
+ function readFirstClaims(result) {
105
+ const entry = readData(result)[0];
106
+ const claims = entry?.meta?.claims || entry?.resource?.meta?.claims;
107
+ return claims && typeof claims === 'object' ? claims : {};
108
+ }
109
+ function readData(result) {
110
+ const pollBody = (result?.poll?.body || {});
111
+ const root = pollBody?.body || pollBody;
112
+ return Array.isArray(root?.data) ? root.data : [];
113
+ }
114
+ function createThreadId(prefix) {
115
+ return `${prefix}-${globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`}`;
116
+ }
package/dist/index.d.ts CHANGED
@@ -24,6 +24,7 @@ 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 './authorized-subject-directory.js';
27
28
  export * from './digital-twin.js';
28
29
  export * from './operational-actor-did.js';
29
30
  export * from './profile-workspace.js';
package/dist/index.js CHANGED
@@ -25,6 +25,7 @@ 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 './authorized-subject-directory.js';
28
29
  export * from './digital-twin.js';
29
30
  export * from './operational-actor-did.js';
30
31
  export * from './profile-workspace.js';
@@ -21,6 +21,7 @@ import type { NodeRuntimeClient, PollOptions, PollResult, SubmitAndPollResult, S
21
21
  import { type FhirR5SubscriptionBatchInput } from './fhir-r5-subscription-runtime.js';
22
22
  import type { FhirR5SubscriptionTopic } from 'gdc-common-utils-ts/models/fhir-r5-subscription';
23
23
  import { type DigitalTwinMaterializationInput, type DigitalTwinSearchInput, type DigitalTwinSelectionInput } from './digital-twin.js';
24
+ import { type AuthorizedIndividualSubject, type AuthorizedIndividualSubjectDirectoryInput } from './authorized-subject-directory.js';
24
25
  export type HttpRuntimeClientOptions = {
25
26
  baseUrl: string;
26
27
  bearerToken?: string;
@@ -414,6 +415,12 @@ export declare class HttpRuntimeClient implements NodeRuntimeClient {
414
415
  * with optional filters.
415
416
  */
416
417
  listIndividualLicenses(ctx: RouteContext, input?: LicenseListRuntimeSearchInput): Promise<SubmitAndPollResult>;
418
+ /**
419
+ * Resolves the complete high-level directory of subjects already authorized
420
+ * for a verified OpenID contact. The SDK owns both GW searches and polling;
421
+ * the result itself grants no professional role or SMART capability.
422
+ */
423
+ listAuthorizedIndividualSubjects(ctx: RouteContext, input: AuthorizedIndividualSubjectDirectoryInput): Promise<AuthorizedIndividualSubject[]>;
417
424
  /** Adds zero-cost seats to one individual organization's member pool. */
418
425
  addFreeIndividualMemberLicenses(ctx: RouteContext, input: IndividualMemberLicenseAddInput): Promise<SubmitAndPollResult>;
419
426
  /** Reserves one individual seat for an existing FHIR v3-role contact. */
@@ -19,6 +19,7 @@ import { RuntimeClientPaths } from './runtime-client-paths.js';
19
19
  import { runtimeUuid, wrapBundleAsGatewayTransactionMessage } from './runtime-message.js';
20
20
  import { submitFhirR5SubscriptionBatchWithDeps, submitFhirR5SubscriptionTopicBatchWithDeps, } from './fhir-r5-subscription-runtime.js';
21
21
  import { materializeDigitalTwinWithDeps, saveDigitalTwinSelectionWithDeps, searchDigitalTwinsWithDeps, } from './digital-twin.js';
22
+ import { listAuthorizedIndividualSubjectsWithDeps, } from './authorized-subject-directory.js';
22
23
  const bootstrapFacade = createBootstrapFacade();
23
24
  /**
24
25
  * Runtime-oriented HTTP client for Node backends, BFFs, and workers that need
@@ -751,6 +752,20 @@ export class HttpRuntimeClient {
751
752
  submitAndPoll: this.submitAndPoll.bind(this),
752
753
  });
753
754
  }
755
+ /**
756
+ * Resolves the complete high-level directory of subjects already authorized
757
+ * for a verified OpenID contact. The SDK owns both GW searches and polling;
758
+ * the result itself grants no professional role or SMART capability.
759
+ */
760
+ async listAuthorizedIndividualSubjects(ctx, input) {
761
+ return listAuthorizedIndividualSubjectsWithDeps(ctx, input, {
762
+ individualLicenseSearchPath: this.paths.individualLicenseSearchPath.bind(this.paths),
763
+ individualLicenseSearchPollPath: this.paths.individualLicenseSearchPollPath.bind(this.paths),
764
+ individualOrganizationSearchPath: this.paths.individualFamilyOrganizationSearchPath.bind(this.paths),
765
+ individualOrganizationSearchPollPath: this.paths.individualFamilyOrganizationSearchPollPath.bind(this.paths),
766
+ submitAndPoll: this.submitAndPoll.bind(this),
767
+ });
768
+ }
754
769
  /** Adds zero-cost seats to one individual organization's member pool. */
755
770
  async addFreeIndividualMemberLicenses(ctx, input) {
756
771
  return addFreeIndividualMemberLicensesWithDeps(ctx, input, {
@@ -18,6 +18,7 @@ import type { EnsureFamilyOrganizationRegistrationInput, EnsureFamilyOrganizatio
18
18
  import type { OrganizationLicenseOrderConfirmInput } from '../organization-license-order.js';
19
19
  import type { SmartTokenExchangeResult, SmartTokenRequestInput } from '../smart-token.js';
20
20
  import type { DigitalTwinMaterializationInput, DigitalTwinSearchInput, DigitalTwinSelectionInput } from '../digital-twin.js';
21
+ import type { AuthorizedIndividualSubject, AuthorizedIndividualSubjectDirectoryInput } from '../authorized-subject-directory.js';
21
22
  import type { CommunicationIngestionInput, BlockchainArtifactRegistrationInput, CommunicationParticipantRuntimeSearchInput, ClinicalBundleSearchInput, ClinicalSectionUpdateInput, ClinicalSummaryReadResult, ClinicalSummaryRequestInput, ClinicalSummaryUpdateInput, VitalSignBatchCommunicationFromSearchResponseInput, DigitalTwinGenerationInput, DigitalTwinSecondaryUseConsentInput, DigitalTwinSecondaryUseConsentResult, DigitalTwinSubjectLinkPurgeInput, DigitalTwinSubjectLinkPurgeResult, GrantProfessionalAccessInput, GrantProfessionalAccessResult, IndividualMemberLifecycleInput, IndividualMemberLicenseAddInput, IndividualMemberLicenseInvitationInput, IndividualMemberLicenseTransitionInput, IpsOrFhirImportInput, OrganizationEmployeeCreationInput, OrganizationEmployeeLicenseAddInput, OrganizationEmployeeLicenseOfferInput, OrganizationEmployeeLicenseInvitationInput, OrganizationEmployeeLifecycleInput, OrganizationEmployeeSearchInput, ProfessionalAccessRequestInput, ProfessionalAccessRequestResult, RevokeProfessionalAccessInput, RevokeProfessionalAccessResult, RelatedPersonUpsertInput } from '../resource-operations.js';
22
23
  /**
23
24
  * Shared node-runtime activation input.
@@ -124,6 +125,8 @@ export type RuntimeClient = {
124
125
  revokeProfessionalAccess?: (ctx: RouteContext, input: RevokeProfessionalAccessInput) => Promise<RevokeProfessionalAccessResult>;
125
126
  searchIndividualLicenses?: (ctx: RouteContext, input: LicenseListRuntimeSearchInput) => Promise<SubmitAndPollResult>;
126
127
  listIndividualLicenses?: (ctx: RouteContext, input?: LicenseListRuntimeSearchInput) => Promise<SubmitAndPollResult>;
128
+ /** Lists exact subjects already authorized for one verified OpenID contact. */
129
+ listAuthorizedIndividualSubjects?: (ctx: RouteContext, input: AuthorizedIndividualSubjectDirectoryInput) => Promise<AuthorizedIndividualSubject[]>;
127
130
  addFreeIndividualMemberLicenses?: (ctx: RouteContext, input: IndividualMemberLicenseAddInput) => Promise<SubmitAndPollResult>;
128
131
  issueIndividualMemberLicense?: (ctx: RouteContext, input: IndividualMemberLicenseInvitationInput) => Promise<SubmitAndPollResult>;
129
132
  transitionIndividualMemberLicense?: (ctx: RouteContext, action: '_accept' | '_deactivate' | '_release', input: IndividualMemberLicenseTransitionInput) => Promise<SubmitAndPollResult>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdc-sdk-node-ts",
3
- "version": "2.4.37",
3
+ "version": "2.4.39",
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.",