npm.io
2.9.31 • Published 5d ago

gdc-sdk-node-ts

Licence
Apache-2.0
Version
2.9.31
Deps
2
Size
929 kB
Vulns
0
Weekly
0

gdc-sdk-node-ts

Development and releases follow the mandatory local-first TDD and release contract.

See ARCHITECTURE.md and CONTRIBUTING.md before adding node runtime facades, execution adapters, or orchestration tests.

Short rule:

  • 101 tests must read like executable tutorials
  • gdc-common-utils-ts owns the canonical step-by-step editors/readers, shared fixtures, and reusable payload examples
  • gdc-sdk-node-ts starts after that shared authoring step and at ProfileRuntime -> loadProfile(...) -> workspace/session -> actor facade in node runtime tests

Node runtime package for consuming the shared GDC SDK contracts against real gateway backends.

Use this package when your backend needs to:

  • call GW APIs
  • submit/poll async operations
  • orchestrate onboarding, consent, communication, and search flows
  • consume the shared relationship invitation/acceptance contracts from gdc-sdk-core-ts

This package is for runtime execution. It is not the place where the canonical business contract is defined.

Important test-harness boundary:

  • gdc-sdk-node-ts is not the product BFF
  • the live E2E suite simulates a controlled virtual API with a BFF-like role only to validate GW CORE lifecycles end to end
  • the current live suites run with the future user job manager queue disabled, so every high-level call goes directly through that controlled virtual API
  • app-side job queues, offline retry, local vault/read models, and the future user job manager are separate follow-up concerns

Important live-run rule:

  • the canonical live E2E result must come from the user's real terminal/TTY
  • do not assume an AI agent sandbox has equivalent localhost, Docker, DNS, or GCP connectivity
  • if sandboxed runs disagree with the user's terminal, trust the user's terminal for live GW validation

Required live validation order:

  1. local process E2E from real TTY
  2. local Docker image/container E2E
  3. staging E2E
  4. production image/deploy only after staging is green

Architectural rule:

  • shared contracts and actor boundaries come from gdc-sdk-core-ts
  • this package executes those flows against GW
  • this package should not widen an actor facade just because the runtime client happens to expose the underlying method

Start Here

If you are integrating this package for the first time, open these in order:

  1. gdc-sdk-core-ts/docs/101-SDK_PACKAGE_BOUNDARIES.md Why core, node, and front are separate packages, what belongs in each one, and why actor-scoped facades must stay aligned across runtimes.
  2. docs/101-SDK_END_TO_END.md Ordered onboarding guide with end-to-end journeys, copy/paste snippets, and the recommended reading path for new backend integrators.
  3. docs/101-PDQM_PATIENT_MATCH.md Resolve only indexProviderDid in Fabric, then use the high-level Node SDK to call IHE PDQm POST Patient/$match with one FHIR Parameters request.
  4. docs/101-HIGH_LEVEL_CLINICAL_PROFILE_WRITES.md Focused subset of the end-to-end guide: load a professional or individual member/controller profile, create or import clinical data in the selected index provider, and verify it through an authoritative summary readback.
  5. docs/101-PROFESSIONAL-CONSENT-SMART.md Canonical professional DID, consent, VP and SMART flow without literal sections or caller-built audience URLs.
  6. docs/101-AUTHORIZED_SUBJECT_DIRECTORY.md Signed OpenID account discovery of already-authorized subjects without treating the account token as VP, SMART or wallet proof.
  7. docs/101-SDK_INTEGRATION.md Real backend setup plus the public runtime entrypoints: HostOnboardingSdk, OrganizationControllerSdk, IndividualControllerSdk, ProfessionalSdk, route-context usage, and the canonical ProfileRuntime -> loadProfile(...) -> workspace/session -> actor facade -> submit/poll shape.
  8. tests/101-live-full-cycle-bff-runtime.e2e.test.mjs Canonical live backend/BFF walkthrough on a fresh local GW lifecycle: host/tenant activation, employee provisioning, individual bootstrap, consent grant, professional SMART token, clinical read, and final cleanup.
  9. docs/101-LIVE_GW_LOCAL.md Exact TTY/local/Docker commands for running the SDK against a real local GW CORE, including tenant bootstrap and employee-seat setup.
  10. docs/101-DISCOVERY.md Node/BFF dataspace discovery, hosting-operator resolution, provider resolution, and the correct integration boundary for fallback and cache.
  11. gdc-sdk-core-ts/docs/101-SDK_FLOWS.md Actor split and business-flow map across organization, individual, permissions, invitation, import, and SMART flows.
  12. gwtemplate-node-ts/docs/PORTAL_API_TO_GW_CORE.md Canonical portal/BFF functional map over GW CORE, including the domain split between employees, related persons, members, and consents.
  13. gdc-common-utils-ts/src/examples/ Shared payload values used by the docs and tests.
  14. gdc-common-utils-ts/docs/101-LIFECYCLE.md Canonical enable/disable/delete semantics and copy/paste placeholders.
  15. tests/101-backend-profile-runtime.test.mjs Technical runtime slice for profile/device/session orchestration internals. Read this after the public actor SDK guides, not before them.
  16. tests/101-individual-controller-backend-runtime.test.mjs Technical wrapper slice around the generic profile runtime. This is not the main onboarding path for new integrators.
  17. docs/V2_INDIVIDUAL_REGISTRATION_RECONCILIATION.md Historical reconciliation note for the current CORE registration baseline.
  18. docs/NEXT_STEPS.md Follow-up scope after GW CORE live validation, including the future user job manager boundary.

If you need the shortest path:

Current live teaching target:

Executable Usage Examples

Open these tests when you want to see exact method calls and exact inputs:

Live GW CORE Flow

Use tests/101-live-full-cycle-bff-runtime.e2e.test.mjs as the canonical live 101 for backend/BFF integrators.

Use tests/live-gw-node-runtime.e2e.test.mjs as the broader runtime-regression suite.

Before running that suite, read:

Execution requirement:

  • run the live suite from a real user terminal/TTY
  • if an AI agent is assisting, it should prefer a long-lived TTY process and avoid treating sandbox-local connectivity failures as product failures

Teaching rule:

  • defaults come from gdc-common-utils-ts/examples
  • override with env vars only when your tenant, bearer, or route is different
  • local GW default is http://127.0.0.1:3000
  • Docker-exposed GW can be overridden with BASE_URL=http://127.0.0.1:8000
  • LIVE_GW_E2E_EXECUTION_MODE=direct is the current and only supported mode for live validation; queued app-side job management is a later phase

Current live 101 flow covered by the test suite:

  1. activate one hosted tenant / legal organization
  2. provision one professional employee through the organization controller
  3. load the individual-controller profile and bootstrap one hosted individual
  4. confirm the returned order and verify the invoice bundle projection
  5. ingest one IPS/clinical Communication whose attached payload is the canonical document Bundle with Composition first entry
  6. grant professional consent for one patient-summary section
  7. load the professional profile and request one SMART token
  8. read the allowed IPS bundle as the professional actor
  9. clean up consent, individual, employee, tenant, and host state

Run the main live 101:

npm run test:e2e:live-full-cycle

What is still not fully covered as one single root lifecycle:

  • initial organization license listing
  • extra-seat activation after the portal-side fictitious payment confirmation
  • relisting licenses after seat activation
  • one employee bundle with employee A and employee B
  • selective disable/purge validation across both employees
  • consent escalation from partial IPS access to broader IPS access
  • final cleanup of consent, individual, remaining employees, and tenant

Current invoice/readback behavior:

  • both host and individual Order/_batch-response flows now return the flat compatibility claims and an embedded invoice Bundle
  • the invoice bundle contains one FHIR Invoice, one PDF DocumentReference, and one structured JSON/XML DocumentReference
  • live suites can read that bundle back through the same high-level response body that the virtual API exposes to the simulated front

Current runtime boundary:

  • OrganizationControllerSdk.confirmOrganizationLicenseOrder(...) now uses the public host Order/_batch route used by GW CORE for portal-managed post-payment seat activation
  • that route is commercial routing only: iss remains the exact controller DID registered by tenant DCR, aud remains the tenant id, and GW resolves its JWS/JWE key identifiers from the tenant rather than the host
  • applications use the high-level SDK method and configured transport profile; they do not populate meta.jws, move public keys, or construct DIDComm by hand
  • the long root lifecycle is still not fully closed because the suite does not yet orchestrate the whole license list -> pay -> confirm -> relist -> two employees -> selective purge -> cleanup dialogue as one single test

The exact pending release-readiness checklist lives in:

Optional live lifecycle extension:

  • set RUN_LIVE_GW_E2E_INDIVIDUAL_LIFECYCLE=1 to extend the same suite with disableIndividual(...) + purgeIndividual(...) against the real gwtemplate-node-ts runtime contract
  • this extra block is intentionally separate from the default happy path because it changes lifecycle state and should only run when that tenant/test subject is disposable

Shared example source of truth:

The two medication defaults used by the live test are intentionally reusable:

  • Ibuprofen 400 mg
  • Paracetamol 600 mg
  • both every 8 hours
  • both PRN / dosage-asneeded = true
  • note text keeps the 4 hour gap in English

Run the full live runtime baseline:

npm run test:e2e:live-gw

Select one live transport profile from the same Node entrypoint:

npm run test:e2e:live-gw:didcomm-plain
npm run test:e2e:live-gw:legacy-fhir
npm run test:e2e:live-gw:all

Profile note:

  • didcomm-plain is the explicit demo-only live profile
  • legacy-fhir exercises raw application/fhir+json async batch submission for org.hl7.fhir.*
  • all runs every implemented profile from the same suite file
  • each NodeHttpClient selects one profile at construction; every facade operation and poll uses it and cannot override or downgrade it

Run the IPS ingestion/search branch as well:

RUN_LIVE_GW_E2E_IPS_INGESTION=1 \
npm run test:e2e:live-gw

Implementation note:

  • the beginner search story stays on FHIR parameters such as Composition.section
  • gdc-sdk-node-ts submits the search request through the runtime facade
  • GW CORE resolves it internally from indexed subject sections and returns the consolidated IPS bundle document

Common overrides:

BASE_URL=http://127.0.0.1:3000 \
AUTH_BEARER=... \
TENANT_ID=VATES-B00112233 \
TENANT_ROUTE_ID=acme-live \
JURISDICTION=ES \
SECTOR=health-care \
SUBJECT_DID=did:web:api.acme.org:individual:123 \
RUN_LIVE_GW_E2E_IPS_INGESTION=1 \
LIVE_GW_NODE_E2E_DEBUG=1 \
npm run test:e2e:live-gw

Docker-exposed GW example:

BASE_URL=http://127.0.0.1:8000 \
RUN_LIVE_GW_E2E_IPS_INGESTION=1 \
npm run test:e2e:live-gw

Documentation consistency rule for this repo family:

  • scripts, README examples, Swagger examples, and internal tests must reuse the same example data and flow order
  • if a new request/response example is added, add it first to gdc-common-utils-ts/examples and consume it from there instead of re-hardcoding values locally

Dataspace Discovery Quick Map

Use the Node discovery layer when your backend or BFF needs to:

  • start from preloaded hosting-operator semantics
  • fetch the canonical host-scoped /<hosting-operator-network-context>/.well-known/dspace-version entrypoint
  • derive the participant-scoped /dsp/catalog/dcat.json artifact
  • return normalized provider/operator matches to portal or app backends

Primary references:

Architecture note:

  • the reusable discovery resolver logic lives in gdc-sdk-core-ts
  • gdc-sdk-node-ts re-exports it and supplies the Node runtime surface

Copy/paste starting point:

import { createDefaultFirstDataspaceDiscovery } from 'gdc-sdk-node-ts';
import { DataspaceSectors } from 'gdc-common-utils-ts';
import { HostNetworkTypes } from 'gdc-common-utils-ts/constants/network';

const discovery = createDefaultFirstDataspaceDiscovery({
  version: 'v1',
  networkType: HostNetworkTypes.Test,
  defaults,
});

const providers = await discovery.getIndexProviders({
  sector: DataspaceSectors.AnimalCare,
  jurisdiction: 'ES',
});

Actor Split And Runtime Scope

This package must be understandable from the same actor split used by the shared contracts:

  • organization controller
  • organization employee / professional member
  • individual controller
  • individual member / self
  • related person
  • professional with consented access

The Node runtime layer is where those shared flows are executed against GW. That includes organization onboarding, employee creation, individual bootstrap, permission grants, RelatedPerson upserts, SMART token requests, and clinical data ingestion/search.

Flow Families

  • organization activation and order/offer confirmation
  • employee creation and employee activation
  • individual organization start and order confirmation
  • related person upsert
  • professional access grant
  • invitation / OTP / relationship PIN runtime wiring
  • permission-request Communication
  • composed professional request submission and subject decision correlation
  • communication ingestion and search
  • SMART token retrieval

ProfessionalSdk.requestProfessionalAccess(...) is the bootstrap operation for the inverse flow: it persists a subject-scoped permission-request Communication with an attached batch Bundle of normal Consent.status = draft resources, using the configured HTTP bearer and transport profile without requiring SMART. A draft is inbox evidence and cannot authorize access. The subject later answers through respondToProfessionalAccessRequest(...); that facade uses the normal Consent grant/deny operation and retains the original Communication identifier/thread. listProfessionalAccessRequests(...) provides the subject/requester inbox view, and GatewayActiveConsentProvider supplies evaluateRequestedAccess(...) from the GW Consent source of truth rather than a parallel application table. There is no AccessRequest FHIR resource or AccessRequest.* claim family. The Communication itself uses the standard FHIR notification category; permission-request is workflow meaning carried by the attached draft Consent, not a custom category. See the HL7 category value set, HL7 v3-ActReason BTG code system, and the IHE compound normal/break-glass Consent example.

Main Flows

Typical backend sequence:

  1. build shared invitation payload with gdc-sdk-core-ts
  2. send it through the node runtime client or your backend adapter
  3. persist or return the invitation state to portal/app

What matters here:

  • this package executes the runtime call
  • gdc-sdk-core-ts defines the payload shape
  • callers should not hardcode route families in app code
2. Invitee accepts the relationship

Typical backend sequence:

  1. start OTP challenge
  2. confirm OTP
  3. set relationship PIN if required
  4. activate the relationship channel

Shared contract builders come from gdc-sdk-core-ts; this package is where a Node backend wires them to real runtime operations.

Use this package when your backend needs to:

  • ingest Communication
  • search clinical bundles
  • request SMART tokens
  • grant access
  • create or update RelatedPerson

What This Package Owns

  • Node runtime client
  • submit/poll orchestration
  • actor-scoped node sessions
  • backend-facing orchestration helpers

What This Package Does Not Own

  • the canonical invitation/OTP/PIN contract
  • application-specific reminder runtime semantics
  • frontend session UX

Those belong to:

  • gdc-sdk-core-ts for shared contracts
  • runtime extensions for product-specific behavior
  • gdc-sdk-front-ts for frontend-facing consumption

Minimal Examples

Build the individual-controller DCR actor DID

The controller consumes the licensed member seat with role RESPRSN. A phone or email stored on the Individual Organization remains contact data and does not consume another seat. The role keeps its full type and value in protected data; only the value is appended to the DID.

import { buildIndividualMemberDidWebFromPrivateIdentifiers } from 'gdc-sdk-node-ts';

const individualControllerDid = buildIndividualMemberDidWebFromPrivateIdentifiers({
  providerDidWeb: 'did:web:host.example.org:health-care:organization:taxid:VATES-B00112233',
  secureIdTypeIndividual: 'UUID',
  privateIdValueIndividual: 'a87e5b15-aea4-4475-9c7c-40aa88354b6f',
  secureIdTypeMember: 'EMAIL',
  privateIdValueMember: 'controller@example.org',
  roleType: 'http://terminology.hl7.org/CodeSystem/v3-RoleCode',
  roleValue: 'RESPRSN',
});

// did:web:host.example.org:health-care:organization:taxid:VATES-B00112233:individual:multibase:zG9H82pae9SCXvec3D4YKqhX8bj8F1mRgzxMEdwXXonT7BWsvsUiP2u52sWQTeESpoMee:member:zG9DrMLpQW8eoCc9Ay9AFxuMGiswgJePpbUMz9svJCZ8tKjUd4xoExgCPA5jmHc6hPATJ:RESPRSN
Use shared invitation contract from Node
import {
  createRelationshipChannelInvitationInput,
  RelationshipAccessActorKinds,
  RelationshipEnrollmentChannels,
  type RelationshipChannelInvitationInput,
} from 'gdc-sdk-core-ts';
import {
  buildIndividualDidWeb,
  HealthcareActorRoles,
  HealthcareConsentPurposes,
} from 'gdc-common-utils-ts';

const tenantId = 'acme-id';
const jurisdiction = 'ES';
const sector = 'health-care';
const providerOrganizationDid = subjectProfile.organizationDid;
const subjectLocalId = subjectProfile.subjectId;
const subjectId = buildIndividualDidWeb({
  organizationDidWeb: providerOrganizationDid,
  subjectId: subjectLocalId,
});
const professionalEmail = invitedProfessional.email;

const invitationInput: RelationshipChannelInvitationInput = {
  tenantId,
  jurisdiction,
  sector,
  subjectId,
  subjectKind: 'person',
  actorKind: RelationshipAccessActorKinds.Professional,
  actorIdentifier: professionalEmail,
  actorRole: HealthcareActorRoles.Physician,
  deliveryChannel: RelationshipEnrollmentChannels.Email,
  deliveryTarget: professionalEmail,
  purpose: HealthcareConsentPurposes.Treatment,
  relationshipLabel: 'primary-physician',
  phonePinOptional: false,
};

const invitation = createRelationshipChannelInvitationInput(invitationInput);

The backend should obtain those variables from:

  • tenant route selection
  • target subject identifier
  • invited actor identity
  • selected enrollment channel
  • intended purpose/relationship label
Request SMART token and search bundle
import { NodeHttpClient } from 'gdc-sdk-node-ts';
import {
  EXAMPLE_LATEST_IPS_SEARCH_INPUT,
} from 'gdc-common-utils-ts/examples/individual-controller';
import { HealthcareBasicSections } from 'gdc-common-utils-ts/constants/healthcare';
import { buildSmartCompositionReadScope } from 'gdc-common-utils-ts/utils/smart-scope';

const client = new NodeHttpClient({ baseUrl: process.env.BASE_URL! });

const subjectDid = EXAMPLE_LATEST_IPS_SEARCH_INPUT.subject;

const token = await client.requestSmartToken({
  ctx,
  actorDid: 'did:web:doctor.example.org:employee:001',
  subjectDid,
  scopes: [
    buildSmartCompositionReadScope({
      subjectDid,
      sections: HealthcareBasicSections.PatientSummaryDocument.claim,
    }),
  ],
  idToken: '...',
});

const result = await client.searchClinicalBundle(ctx, {
  subject: subjectDid,
});

Teaching rule:

  • start with the composition read scope when the actor only needs subject-scoped read access
  • add SmartGatewayScopesFhirR4.ConsentCruds only if the backend also needs consent management operations

Shared Contract Sources

Reusable payload examples:

Local terminology route for Next.js

LocalTerminologyBffService keeps complete JSON catalogs in the application BFF and returns only matching options:

const terminology = new LocalTerminologyBffService(catalogs);

export async function GET(request: Request) {
  const url = new URL(request.url);
  return Response.json(terminology.search({
    text: url.searchParams.get('text') || '',
    language: url.searchParams.get('language') || 'en',
    jurisdiction: url.searchParams.get('jurisdiction') || undefined,
    systems: url.searchParams.getAll('system'),
    limit: 20,
  }));
}

The application validates which systems are allowed for the specific coded form field. This MVP service performs no external terminology request.

Typed clinical detail editors

Applications can import the clinical editors from gdc-sdk-node-ts; they do not need setClaim(...), private claim names or deep package imports for these FHIR fields:

  • AllergyIntoleranceEntryEditor: setReactionManifestation(system, code), setReactionSeverity(...) and their matching getters. Reaction severity is distinct from future-risk criticality.
  • MedicationStatementEntryEditor: setEffectivePeriodStart(...), setEffectivePeriodEnd(...) and their matching getters.
  • ImmunizationEntryEditor: setRoute(system, code), setSite(system, code) and their component/token getters.
  • ObservationEntryEditor: setReferenceRangeText(...) and getReferenceRangeText().

BundleEditor returns those typed editors through asAllergy(), asMedicationStatement(), asImmunization() and asObservation().

All registered clinical editor families are exported from the SDK root. A received Bundle stays read-only in BundleReader; call reader.toBundleEditor() to create a detached clone before using typed resource getters or setters. See Clinical Bundle Getters And Setters 101 for the complete resource table and examples.

Governed ValueSet options through the terminology service

For creation controls, the authenticated BFF can resolve the ValueSet bound to a canonical flat claim without exposing its service token or terminology server to the browser:

import {
  AllergyIntoleranceClaim,
  MemoryTerminologyValueSetCache,
  TerminologyServiceClient,
} from 'gdc-sdk-node-ts';

const terminology = new TerminologyServiceClient({
  baseUrl: process.env.TERMINOLOGY_SERVICE_URL!,
  serviceToken: process.env.TERMINOLOGY_SERVICE_TOKEN!,
  cache: new MemoryTerminologyValueSetCache(),
});

const { document, cacheStatus } = await terminology.expandValueSetForClaim({
  sector: 'health-care',
  resourceType: 'AllergyIntolerance',
  claim: AllergyIntoleranceClaim.Manifestation,
  // Optional: language, jurisdiction, version, fhirVersion, offset and count.
});

Omission means English, international/default terminology, the latest installed terminology version and FHIR R4. Jurisdiction is a SNOMED edition selector; it does not become part of LOINC or WHO ATC identity. The cache key still separates language, SNOMED edition/version, claim and page.

MemoryTerminologyValueSetCache.prime(...) accepts immutable startup snapshots. This permits an application to ship or load the English IPS page from private storage and render it without a live terminology call. A local language page is preferred when cached; if refresh fails, an English cached page is returned with cacheStatus: 'english-fallback-cache'. Complete catalogs and the service bearer credential remain server-side. A BFF may replace the memory implementation with a GCS, Firestore or PostgreSQL adapter implementing TerminologyValueSetCache; large complete catalogs should not be stored in one Firestore document.

API Index

Full Public Surface

This package exports the full gdc-sdk-core-ts surface plus the Node runtime modules below.

  • src/runtime-contracts.ts
    • types/constants: LegacyNodeSourcePackage, NodeRuntimeMode, NodeInteropMode, TenantContext, NodeOperatorContext, NodeFetchLike, NodeRuntimeConfig, NodePackageStatus, GDC_SDK_NODE_STATUS
  • src/identity-bootstrap.ts
    • function: initializeCommunicationIdentity(...)
  • src/async-polling.ts
    • types: AcceptedPollResponse
    • function: pollUntilCompleteWithMethod(...)
  • src/poll-options.ts
    • re-export: resolvePollOptionsFromSeconds(...)
  • src/host-onboarding.ts
    • types: HostRouteContext, LegalOrganizationOrderInput
    • function: confirmLegalOrganizationOrderWithDeps(...)
  • src/individual-start.ts
    • types: IndividualOrganizationBootstrapInput, OfferPreview, IndividualOrganizationStartResult
    • function: registerIndividualOrganizationWithDeps(...)
  • src/individual-onboarding.ts
    • types: RouteContext, IndividualOrganizationConfirmOrderInput, IndividualOrganizationOrderResult
    • functions: confirmIndividualOrganizationOrderWithDeps(...), readIndividualOrganizationControllerAssignmentIdentifier(...)
  • src/device-activation.ts
    • types: EmployeeDeviceActivationInput, EmployeeDeviceActivationRequestInput, EmployeeDeviceActivationResult
    • functions: activateEmployeeDeviceWithActivationCodeWithDeps(...), activateEmployeeDeviceWithActivationRequestWithDeps(...)
  • src/smart-token.ts
    • types: SmartTokenRequestInput, SmartTokenExchangeResult
    • function: requestSmartTokenWithDeps(...)
  • src/resource-operations.ts
    • types: OrganizationEmployeeCreationInput, IpsOrFhirImportInput, RelatedPersonUpsertInput, CommunicationIngestionInput, ClinicalDateRange, ClinicalBundleSearchInput, ConsentActorTargetInput, GrantProfessionalAccessInput, GrantProfessionalAccessResult, DigitalTwinSecondaryUseConsentInput, DigitalTwinSubjectLinkPurgeInput, SubjectConsentSearchInput
    • functions: createOrganizationEmployeeWithDeps(...), importIpsOrFhirAndUpdateIndexWithDeps(...), upsertRelatedPersonAndPollWithDeps(...), ingestCommunicationAndUpdateIndexWithDeps(...), searchClinicalBundleWithDeps(...), searchSubjectConsentsWithDeps(...), searchLatestIpsWithDeps(...), grantProfessionalAccessWithDeps(...), setDigitalTwinSecondaryUseConsentWithDeps(...), purgeDigitalTwinSubjectLinkWithDeps(...)
  • src/digital-twin.ts
    • types: DigitalTwinSearchInput, DigitalTwinSelectionInput, DigitalTwinResearchTag, DigitalTwinMaterializationInput
    • functions: searchDigitalTwinsWithDeps(...), saveDigitalTwinSelectionWithDeps(...), materializeDigitalTwinWithDeps(...)
  • src/orchestration/digital-twin-sdk.ts
    • class: DigitalTwinSdk (requestSmartToken, search, saveSelection, searchSelections, materialize); both searches expose ResearchSubject through FHIR Parameters, with its canonical internal Composition available as composition
  • src/session.ts
    • types: NodeCapability, NodeActorSessionContext, ActorSessionContext
    • classes: ActorSession, NodeActorSession
  • src/node-runtime-client.ts
    • types: HttpRuntimeClientOptions, NodeHttpClientOptions
    • classes: HttpRuntimeClient, NodeHttpClient
  • src/gdc-session-bridge.ts
    • functions: createNodeActorSessionsFromFacades(...), createNodeActorSessionFromFacade(...), createNodeActorSessionsFromDescriptor(...), createNodeActorSessionFromDescriptor(...), createActorSessionsFromFacades(...), createActorSessionFromFacade(...), createActorSessionsFromDescriptor(...), createActorSessionFromDescriptor(...)
  • src/orchestration/client-port.ts
    • types: RuntimeClient, NodeRuntimeClient
    • functions: requireClientMethod(...), submitAndPollWithMethods(...), canClientSubmitAndPoll(...), submitAndPollWithClient(...)
  • src/orchestration/host-onboarding-sdk.ts
    • class: HostOnboardingSdk
  • src/orchestration/organization-controller-sdk.ts
    • class: OrganizationControllerSdk
  • src/orchestration/organization-employee-sdk.ts
    • class: OrganizationEmployeeSdk
  • src/orchestration/individual-controller-sdk.ts
    • class: IndividualControllerSdk
  • src/orchestration/individual-member-sdk.ts
    • class: IndividualMemberSdk
  • src/orchestration/personal-sdk.ts
    • class: PersonalSdk
  • src/orchestration/professional-sdk.ts
    • class: ProfessionalSdk
  • src/legacy-compat.ts
    • compatibility aliases for simplified helpers, runtime classes, and legacy names such as GdcNodeActorSession and GdcNodeHttpClient
Re-exported shared helpers from gdc-sdk-core-ts
  • consent access helpers
  • relationship invitation/acceptance builders
  • communication/document builders
  • draft/outbox helpers
  • document facade helpers
  • cloneImportedClinicalDocumentForDemo(...) for an explicit editable demo copy whose stable FHIR author/attester come from the protected clinicalCreator export; direct summary updates use profile actorDid only as sender and the real hosted provider-tenant DID as recipient, never a portal alias
  • vital-sign helpers
Author and attester provenance in subject sections and documents

The canonical integration boundary is Subject-section author and profile-attester contract. It keeps technical enrollment free of per-write authorship, preserves the write-specific data author and resolves the attester from the unlocked profile.

A document Bundle keeps Composition.author (the organization, EHR/patient portal or individual that supplies the document) separate from Composition.attester (the registered personal or professional assignment that attested its content). A RelatedPerson or PractitionerRole is an attester, never a shortcut for replacing the source author. For personal content, the BFF selects Owner when the individual originated/dictated it or Creator when the registered member originated it. Use assignmentIdentifier for that RelatedPerson/PractitionerRole UUID; the old wire name authorIdentifier is deprecated and does not choose the author. The protected BFF profile resolves both from its registered binding. DIDComm sender and signing keys remain audit/transport identities. The BFF never configures ledger routing or supplies channel/smart-contract names. GW owns that decision and returns one transaction receipt with per-resource CID/version evidence for a successful clinical Bundle. See Clinical writes from a Node BFF, multi-actor IPS export, and the gateway authorship contract.

Node runtime client
Runtime configuration
Low-level orchestration helpers

Documentation Rule

  • README explains backend-facing flows first.
  • Shared contract shapes must be documented in gdc-sdk-core-ts, not duplicated here.
  • Route details and GW-specific behavior belong in runtime docs and JSDoc, not in app-facing examples.

Host Onboarding Runtime Flow

For legal-organization onboarding from a Node BFF/runtime, keep the host steps separate:

  1. new flow: Organization/_transaction
  2. legacy compatibility flow: ICA _verify -> Organization/_activate
  3. downstream business continuation: Order/_batch

Use OrganizationControllerSdk.submitLegalOrganizationVerificationTransaction(...) or NodeHttpClient.submitLegalOrganizationVerificationTransaction(...) for step 1.

Existing-tenant recovery/reverification:

  • use OrganizationControllerSdk.submitLegalOrganizationCredentialReissuance(...) or NodeHttpClient.submitLegalOrganizationCredentialReissuance(...) to refresh the ICA-backed verification for an already registered tenant without creating a new Offer
  • inside the terminal decoded GW Bundle, the canonical polled response keeps all deduplicated ICA-issued credentials in body.data[0].vc[] and the complete ICA payload in body.data[0].resource.icaResponse; SDK callers receive that terminal HTTP payload as SubmitAndPollResult.poll.body
  • separately, GW returns the controller License activation code in body.data[0].meta.claims['org.schema.IndividualProduct.serialNumber']
  • Organization/_issue is not License/_issue: the activation code is not a VC and License:Issued is not the canonical response entry type for this route
  • then continue with the existing helper chain: recoverOrganizationControllerWithCredentialReissuanceWithDeps(...) or directly _exchange -> _dcr
  • submitLegalOrganizationIssue(...) remains only as a deprecated alias named after the underlying Organization/_issue route
  • for a reproducible local/staging runtime check, use: npm run test:e2e:live-gw:issue-recovery
  • for the deterministic controller-lifecycle contract that also proves post-registration seat preservation and final tenant teardown, use: docs/101-ORGANIZATION_CONTROLLER_LIFECYCLE.md

Rules:

  • _transaction and _activate are different flows
  • transport/runtime communication keys stay outside the business payload
  • controller binding key stays in body.data[].resource.controller.*
  • do not mix this path with requestIcaEnrollment or Fabric

Legacy compatibility coverage in the live suite:

  • the canonical test is LIVE professional lifecycle on GW
  • set RUN_LIVE_GW_E2E_HOST_VERIFICATION_TRANSACTION=1 to exercise:
    • Organization/_transaction -> Order/_batch
  • set RUN_LIVE_GW_E2E_HOST_VERIFICATION_TRANSACTION=0 to exercise the older:
    • ICA _verify -> Organization/_activate -> Order/_batch

Dedicated legacy live command:

cd /Users/fernando/GITS/gdc-workspace/gdc-sdk-node-ts
RUN_LIVE_GW_E2E=1 \
RUN_LIVE_GW_E2E_ACTOR_CHAIN=1 \
RUN_LIVE_GW_E2E_HOST_VERIFICATION_TRANSACTION=0 \
LIVE_GW_E2E_SUITE=professional \
node --test tests/live-gw-node-runtime.e2e.test.mjs

Live E2E legal PDF source:

  • local file: set LIVE_GW_HOST_VERIFICATION_PDF_PATH=/abs/path/file.pdf
  • public URL: set LIVE_GW_HOST_VERIFICATION_PDF_URL=https://.../file.pdf
  • if both are present, the live suite prefers LIVE_GW_HOST_VERIFICATION_PDF_URL
  • Dropbox-style links are normalized to dl=1 direct-download mode automatically

Recovery-specific live rule:

  • Organization/_issue can succeed and still be followed by _exchange failure if the controller id_token is invalid, expired or does not prove the expected actor/contact
  • the current GW _exchange path expects a valid IdP token but takes tenant authority from the already validated request route
  • a custom tenant_id claim is optional; when present it must match the route and cannot select another tenant
  • a failed poll OperationOutcome is surfaced before the helper checks for initial_access_token, preserving the real GW diagnostic
  • every asynchronous success test uses BundleReader.getResponseAnalysis(); HTTP 200/201 alone is transport completion, not proof that the inner Bundle entries succeeded
  • the bundled recovery runner generates a syntactically valid demo JWT if CONTROLLER_ID_TOKEN is not provided, but production/staging should use a real IdP-issued token

Minimal live recovery command:

PDF_PATH=/Users/fernando/GITS/gdc-workspace/examples/TEST-A4-Antifraud.pdf \
BASE_URL=http://127.0.0.1:3000 \
TENANT_ID=acme-id \
npm run test:e2e:live-gw:issue-recovery