gdc-sdk-front-ts
v2.0.8
Published
Next-generation frontend runtime package for the GDC SDK family
Readme
gdc-sdk-front-ts
See ARCHITECTURE.md and CONTRIBUTING.md before adding frontend facades, profile/session runtime logic, or orchestration tests.
Short rule:
101tests must stay didactic and step by step- reusable fixtures/types must come from
gdc-common-utils-tsinstead of being repeated as frontend-local literals
Frontend runtime package for consuming the shared GDC SDK contracts in web or mobile apps.
Use this package when your frontend needs to:
- build UI flows on top of GDC shared contracts
- manage session/profile state
- prepare consent-aware requests
- consume the shared invitation, OTP, and relationship-PIN flows
- work with communication drafts and local outbox patterns
This package is frontend-facing. It should explain app flows, not gateway route details.
Architectural rule:
gdc-sdk-front-tsshould converge on the same actor-scoped facade boundaries asgdc-sdk-node-ts- transport and adapter details may differ
- business ownership must not differ by runtime
Start Here
If you are integrating this package for the first time, open these in order:
- gdc-sdk-core-ts/docs/101-SDK_PACKAGE_BOUNDARIES.md
Why
core,node, andfrontare separate packages, what each one owns, and why frontend facades should mirror backend actor boundaries. - tests/101-frontend-profile-runtime.test.mjs Minimal frontend-generic walkthrough for loading one actor profile, registering one trusted device/runtime context, connecting to one subject index, and reading one subject index composition.
- tests/101-individual-controller-frontend-runtime.test.mjs First pragmatic frontend wrapper over the generic profile runtime for the current individual-controller baseline.
- docs/101-SDK_INTEGRATION.md
Real frontend/native setup, imports,
new ClientSDK(...),initializeCommunicationIdentity(...), provider discovery, andinitializeSession(...). - docs/DATASPACE_DISCOVERY_FRONTEND_TODO.md Frontend discovery guide for BFF-first provider/operator discovery, UI card mapping, and copy/paste backend DTO consumption.
- gdc-sdk-core-ts/docs/101-SDK_FLOWS.md Shared business-flow map by actor family.
- gdc-sdk-node-ts/docs/101-LIVE_GW_LOCAL.md Canonical local/TTY/Docker GW reference when the frontend team also needs a real local GW running for end-to-end checks.
- gwtemplate-node-ts/docs/PORTAL_API_TO_GW_CORE.md
Canonical portal/BFF functional map over GW CORE, including the separation
between
related persons, invitedmembers, and effectiveconsents. - gdc-common-utils-ts/src/examples/frontend-session.ts Shared profile/session payload source of truth.
- gdc-common-utils-ts/docs/101-LIFECYCLE.md Canonical lifecycle semantics and reusable placeholders for UI and portal flows.
If you need the shortest path:
- app identity required by GW CORE:
appIdmandatory,appVersionoptional with defaultv1.0 - frontend technical identity:
initializeCommunicationIdentity(...)for the technical device/channel profile identity, not the legal organization id - main runtime class:
ClientSDK - profile/session bootstrap:
initializeSession(...)
Frontend Runtime Modes
There are two frontend integration modes. New developers should choose the mode first, before choosing classes or helpers.
1. Portal web / non confidential app
Use this mode when:
- the frontend is a Vite/web portal
- the portal backend holds controller/professional keys in AWS KMS
- the frontend does not send DIDComm directly to GW CORE
Recommended entry point:
- start from the shared editors/builders in
gdc-sdk-core-ts - send the resulting payload or bundle to the portal backend
- let the backend handle KMS, DIDComm, submit, and poll
Main references:
- gdc-common-utils-ts/docs/101-EMPLOYEE_ENTRY_EDITOR.md
- gdc-sdk-core-ts/docs/101-EMPLOYEES.md
- gdc-sdk-core-ts/docs/101-CONSENT_COMMUNICATION.md
- gdc-common-utils-ts/docs/101-CONSENT_ACCESS.md
- gdc-sdk-core-ts/tests/101-employees.test.mjs
- gdc-common-utils-ts/tests/101-consent-bundle-editor.test.ts
Use:
BundleEditorplusEmployeeEntryEditorfor employee create/search/disable/purge payloadsCommunicationAttachedBundleSessionforCommunication-carried bundlescreateConsentAccessEditor(...)for consent editing inside a communication bundle
Controller-only employee flow in a portal web app:
Use this example only when the current frontend screen belongs to the organization-controller/admin side.
Do not reuse this employee example for:
- professional screens
- individual/family screens
- generic end-user screens
Those actor families should start from their own business flow:
- professionals
- consent-aware access
- SMART token
- communication/index flows
- individuals / family
- consent editing
- related-person flows
- IPS/FHIR import or read flows
Create
Use BundleEditor plus one employee entry editor to prepare one employee create bundle. The browser
does not send it directly to GW CORE.
The portal backend wraps it into its own request/envelope, then applies KMS,
DIDComm, submit, and poll.
import { BundleEditor } from 'gdc-sdk-core-ts';
import {
EXAMPLE_EMPLOYEE_DOCTOR_ACTIVE,
EXAMPLE_PROVIDER_ORGANIZATION_DID,
} from 'gdc-common-utils-ts/examples';
import { ClaimsPersonSchemaorg } from 'gdc-common-utils-ts/constants/schemaorg';
import {
EmployeeBundleOperations,
EmployeeResourceTypes,
} from 'gdc-common-utils-ts/utils/employee';
// This editor lives only in frontend memory.
// It helps the UI build the canonical employee payload before sending it to
// the portal backend.
const bundle = new BundleEditor()
.setBundleOperation(EmployeeBundleOperations.create)
.setAllowedResourceType(EmployeeResourceTypes.employee);
const employeeEntry = bundle
.newEntry()
.asEmployee()
.setEmail(EXAMPLE_EMPLOYEE_DOCTOR_ACTIVE.email)
.setRole(EXAMPLE_EMPLOYEE_DOCTOR_ACTIVE.role)
.addClaim(ClaimsPersonSchemaorg.memberOf, EXAMPLE_PROVIDER_ORGANIZATION_DID);
// `employeeCreateBatchBundle` is the canonical one-entry employee `_batch` bundle.
// Your Vite frontend normally sends this bundle to its own backend, not
// directly to GW CORE.
const generatedEmployeeIdentifier = employeeEntry.getIdentifier();
employeeEntry.doneEntry();
const employeeCreateBatchBundle = bundle.build();
console.log(employeeCreateBatchBundle);If the frontend does not provide an employee identifier up front, the create flow can generate one and keep it in the same editor:
import { EmployeeBundleOperations } from 'gdc-common-utils-ts/utils/employee';
const bundle = new BundleEditor()
.setBundleOperation(EmployeeBundleOperations.create)
.setAllowedResourceType(EmployeeResourceTypes.employee);
const employeeEntry = bundle
.newEntry()
.asEmployee()
.setEmail(EXAMPLE_EMPLOYEE_DOCTOR_ACTIVE.email)
.setRole(EXAMPLE_EMPLOYEE_DOCTOR_ACTIVE.role);
const generatedEmployeeIdentifier = employeeEntry.getIdentifier();
employeeEntry.doneEntry();
const employeeCreateBatchBundle = bundle.build();If a frontend needs explicit claim-level control instead of only setEmail() /
setRole(), the same editor also exposes generic claim methods:
import { BundleEditor } from 'gdc-sdk-core-ts';
import {
EXAMPLE_EMPLOYEE_DOCTOR_ACTIVE,
EXAMPLE_PROVIDER_ORGANIZATION_DID,
} from 'gdc-common-utils-ts/examples';
import { ClaimsPersonSchemaorg } from 'gdc-common-utils-ts/constants/schemaorg';
import {
EmployeeBundleOperations,
EmployeeResourceTypes,
} from 'gdc-common-utils-ts/utils/employee';
const bundle = new BundleEditor()
.setBundleOperation(EmployeeBundleOperations.create)
.setAllowedResourceType(EmployeeResourceTypes.employee);
const employeeEntry = bundle
.newEntry(EXAMPLE_EMPLOYEE_DOCTOR_ACTIVE.identifier)
.asEmployee()
.setClaim(ClaimsPersonSchemaorg.email, EXAMPLE_EMPLOYEE_DOCTOR_ACTIVE.email)
.setClaim(ClaimsPersonSchemaorg.hasOccupationalRoleValue, EXAMPLE_EMPLOYEE_DOCTOR_ACTIVE.role)
.addClaim(ClaimsPersonSchemaorg.memberOf, EXAMPLE_PROVIDER_ORGANIZATION_DID);
console.log(employeeEntry.getClaim(ClaimsPersonSchemaorg.email));
employeeEntry.doneEntry();
const employeeCreateBatchBundle = bundle.build();Search
Search is a different operation and should be built separately.
email + role is the recommended exact operational lookup.
import { BundleEditor } from 'gdc-sdk-core-ts';
import { EXAMPLE_EMPLOYEE_DOCTOR_ACTIVE } from 'gdc-common-utils-ts/examples';
import {
EmployeeBundleOperations,
EmployeeResourceTypes,
} from 'gdc-common-utils-ts/utils/employee';
const bundle = new BundleEditor()
.setBundleOperation(EmployeeBundleOperations.search)
.setAllowedResourceType(EmployeeResourceTypes.employee);
bundle
.newEntry()
.asEmployee()
.setEmail(EXAMPLE_EMPLOYEE_DOCTOR_ACTIVE.email)
.setRole(EXAMPLE_EMPLOYEE_DOCTOR_ACTIVE.role)
.doneEntry();
const employeeSearchBundle = bundle.build();
console.log(employeeSearchBundle);Other valid search shapes:
- by
emailonly: all active profiles for that mailbox - by
roleonly: all active employees for that role - with no filters: all employees
- by
identifier: one exact technical or historical profile
Disable
Disable is a lifecycle operation. Today the shared employee editor still
produces the canonical _batch bundle with inner request.method = DELETE.
import { BundleEditor } from 'gdc-sdk-core-ts';
import { EXAMPLE_EMPLOYEE_DOCTOR_ACTIVE } from 'gdc-common-utils-ts/examples';
import {
EmployeeBundleOperations,
EmployeeResourceTypes,
} from 'gdc-common-utils-ts/utils/employee';
const bundle = new BundleEditor()
.setBundleOperation(EmployeeBundleOperations.disable)
.setAllowedResourceType(EmployeeResourceTypes.employee);
bundle
.newEntry(EXAMPLE_EMPLOYEE_DOCTOR_ACTIVE.identifier)
.asEmployee()
.doneEntry();
const employeeDisableBatchBundle = bundle.build();
console.log(employeeDisableBatchBundle);Current GW CORE contract vs preferred target:
- current live contract
- disable =
_batch+ innerrequest.method = DELETE - purge =
POST .../Employee/_purge
- disable =
- preferred target contract
- disable/enable = semantic state change via
PATCH - purge = final removal operation kept separate from state changes
- disable/enable = semantic state change via
Conceptual PATCH example for state change:
import { EmployeeBundleOperations } from 'gdc-common-utils-ts/utils/employee';
const employeeDisablePatchBatchBundle = new BundleEditor()
.setBundleOperation(EmployeeBundleOperations.disable)
.setAllowedResourceType(EmployeeResourceTypes.employee)
.newEntry(EXAMPLE_EMPLOYEE_DOCTOR_ACTIVE.identifier)
.asEmployee();Business meaning:
PATCHshould mean state transition such as enable/disableDELETEshould be reserved for final removal semantics such as purge- future operations like merge/split/destroy should be modeled first as named business operations, then mapped to transport
Purge
Purge is not the same route as create/disable, but the frontend should still
prepare a Bundle for it. The portal backend or runtime later submits that
bundle to the explicit Employee/_purge flow. The canonical purge selector is
the employee identifier.
import { BundleEditor } from 'gdc-sdk-core-ts';
import { EXAMPLE_EMPLOYEE_DOCTOR_ACTIVE } from 'gdc-common-utils-ts/examples';
import {
EmployeeBundleOperations,
EmployeeResourceTypes,
} from 'gdc-common-utils-ts/utils/employee';
const employeePurgeBundle = new BundleEditor()
.setBundleOperation(EmployeeBundleOperations.purge)
.setAllowedResourceType(EmployeeResourceTypes.employee)
.newEntry(EXAMPLE_EMPLOYEE_DOCTOR_ACTIVE.identifier)
.asEmployee()
.doneEntry()
.build();
console.log(employeePurgeBundle);Primary references for those employee flows:
Non-controller frontend references:
- professional / individual flow map:
- consent editing:
- consent + communication handoff:
2. Confidential app / direct client runtime
Use this mode when:
- the app stores transport keys locally
- the app can talk directly to GW CORE
- there is no portal backend doing KMS and DIDComm on behalf of the app
Recommended entry point:
- start from
ClientSDK - initialize runtime/session state
- use the same shared editors/builders from
sdk-corewhen a flow needs them
Main references:
Decision rule:
- no local key custody: start from
sdk-coreeditors and send to your backend - local key custody: start from
ClientSDKruntime/session and usesdk-coreeditors as shared modeling helpers
Minimal confidential-app example:
import { ClientSDK } from 'gdc-sdk-front-ts';
import { BundleEditor } from 'gdc-sdk-core-ts';
import {
EXAMPLE_EMPLOYEE_DOCTOR_ACTIVE,
EXAMPLE_PROFILE_SESSION_INPUT,
} from 'gdc-common-utils-ts/examples';
import {
EmployeeBundleOperations,
EmployeeResourceTypes,
} from 'gdc-common-utils-ts/utils/employee';
const appId = frontendAppConfig.appId;
const client = new ClientSDK({ appId });
// `initializeSession(...)` creates the authenticated frontend runtime session.
// In a confidential app this session owns the actor-facing runtime surface and
// can later expose organization-controller/professional capabilities.
const session = await client.initializeSession(EXAMPLE_PROFILE_SESSION_INPUT);
// The shared bundle editor from sdk-core is still used to model the employee bundle.
const employeeSearchBundle = new BundleEditor()
.setBundleOperation(EmployeeBundleOperations.search)
.setAllowedResourceType(EmployeeResourceTypes.employee)
.newEntry()
.asEmployee()
.setEmail(EXAMPLE_EMPLOYEE_DOCTOR_ACTIVE.email)
.setRole(EXAMPLE_EMPLOYEE_DOCTOR_ACTIVE.role)
.doneEntry()
.build();
console.log(session, employeeSearchBundle);Runtime note:
- a confidential app still must choose the correct actor surface before using an employee flow
- employee management belongs only to the organization-controller/admin side
- professional or individual apps should start from their own actor flow, not from the employee examples above
Primary references for that flow:
Executable Usage Examples
Open these tests when you want to see exact frontend calls and exact inputs:
- tests/client-sdk.test.mjs
ClientSDKbootstrap, provider metadata, and session-facing behavior. - tests/actor-session.test.mjs Actor-session descriptors and facade expansion.
- tests/individual-service.test.mjs Individual-facing service composition.
- tests/profile-registry.test.mjs Profile registry and persistence behavior.
- tests/session-descriptor.test.mjs Session descriptor shaping for UI/runtime code.
- tests/dataspace-discovery-client.test.mjs BFF-first dataspace discovery mapping, capability filtering, and DTO-to-card behavior.
Frontend Discovery Quick Map
Frontend apps should not crawl host catalogs directly by default.
Preferred model:
- backend/BFF resolves host discovery from the contextualized hosting-operator
/.well-known/dspace-version - backend/BFF derives
/dsp/catalog/dcat.json - frontend consumes normalized DTOs and maps them into cards
Primary references:
Copy/paste starting point:
import { HttpDataspaceDiscoveryClient } from 'gdc-sdk-front-ts';
import { ServiceCapabilityToken } from 'gdc-common-utils-ts/constants';
// Frontend talks to its own backend/BFF.
// The frontend should not need to know `networkType` or host bootstrap rules.
const discoveryClient = new HttpDataspaceDiscoveryClient({
endpointUrl: '/api/dataspace-discovery/providers',
});
// Ask for providers for one business sector in one jurisdiction.
const result = await discoveryClient.listPublishedProviders({
sector: 'animal-care',
jurisdiction: 'ES',
coverageScope: 'EU',
providerCapability: ServiceCapabilityToken.IndexProvider,
});
// Use the returned cards directly in the UI.
for (const provider of result.providers) {
console.log(provider.did, provider.title, provider.endpointUrl);
}What happens behind that frontend call:
- frontend sends
sector + jurisdiction + capabilityto its backend - backend uses
gdc-sdk-node-tswithdefault-first - backend resolves hosts and providers
- frontend receives normalized cards and renders them
Actor Split And UI Scope
The frontend package must also start from actor families, because screens and permissions differ:
- organization controller
- organization employee / professional member
- individual controller
- individual member / self
- related person
- professional with consented access
Frontend concerns include:
- actor/session bootstrap
- invitation and acceptance UX
- permission management UX
- notification / permission-request UX
- communication draft and submission UX
- clinical read/write UX constrained by evaluated permissions
Flow Families
- organization onboarding and employee invitation screens
- individual onboarding and order/offer confirmation screens
- permission creation, edit, deactivation, and grouped views
- invitation acceptance and relationship activation
- permission-request notification review
- document/resource import into the subject index
- subject-scoped communication and search flows
Main Flows
1. Controller invites another actor to connect with a subject
Typical frontend sequence:
- collect invitee data
- build invitation payload using shared helpers
- send it through your frontend/backend integration
- show invitation state to the user
The frontend should only care about the shared contract:
- actor kind
- delivery channel
- delivery target
- subject id
- purpose
It should not care about gateway path families.
2. Invitee accepts the invitation
Typical frontend sequence:
- enter invitation or activation details
- start OTP challenge
- confirm OTP
- set relationship PIN if required
- activate the relationship locally in UI/session state
Shared builders for this flow come from gdc-sdk-core-ts.
3. Consent-aware communication UI
Use this package when the frontend needs to:
- evaluate whether access is already covered
- show missing permissions
- prepare a permission-request
Communication - build local communication drafts before sending
What This Package Owns
- frontend runtime config
- session/profile-facing helpers
- app-facing composition over shared SDK contracts
What This Package Does Not Own
- canonical shared invitation/consent contract definitions
- Node GW runtime execution
- UNID-specific reminder/task runtime
Those belong to:
gdc-sdk-core-tsgdc-sdk-node-ts- product/runtime extension layers
Minimal Examples
Build invitation and OTP payloads in frontend code
import {
createRelationshipChannelInvitationInput,
createRelationshipChannelOtpStartInput,
RelationshipAccessActorKinds,
RelationshipEnrollmentChannels,
RelationshipOtpDeliveryChannels,
RelationshipSubjectKinds,
type RelationshipChannelInvitationInput,
type RelationshipChannelOtpStartInput,
} from 'gdc-sdk-core-ts';
import {
buildIndividualDidWeb,
HealthcareConsentPurposes,
} from 'gdc-common-utils-ts';
const tenantId = 'acme-id';
const jurisdiction = 'ES';
const sector = 'health-care';
const providerOrganizationDid = activeSubjectProfile.organizationDid;
const subjectLocalId = activeSubjectProfile.subjectId;
const subjectId = buildIndividualDidWeb({
organizationDidWeb: providerOrganizationDid,
subjectId: subjectLocalId,
});
const actorIdentifier = relatedPersonForm.email;
const deliveryTarget = actorIdentifier;
const invitationInput: RelationshipChannelInvitationInput = {
tenantId,
jurisdiction,
sector,
subjectId,
subjectKind: RelationshipSubjectKinds.Person,
actorKind: RelationshipAccessActorKinds.RelatedPerson,
actorIdentifier,
relationshipLabel: 'daughter',
deliveryChannel: RelationshipEnrollmentChannels.Email,
deliveryTarget,
purpose: HealthcareConsentPurposes.CareManagement,
phonePinOptional: true,
};
const invitation = createRelationshipChannelInvitationInput(invitationInput);
const invitationId = 'rel-invite-001';
const otpStartInput: RelationshipChannelOtpStartInput = {
invitationId,
deliveryChannel: RelationshipOtpDeliveryChannels.Email,
locale: 'es-ES',
};
const otpStart = createRelationshipChannelOtpStartInput(otpStartInput);Route naming rule:
- the example above is tenant-scoped, so
jurisdiction + sectorbelongs to the tenant route context - host onboarding is different: host routes use
host coverage jurisdiction + hostNetwork - if you later call a host route like
/host/cds-EU/v1/test/..., thattestis the host runtime/network segment, not the tenant sector
Build permission-request communication
import {
buildPermissionRequestCommunication,
getMissingPermissions,
} from 'gdc-sdk-core-ts';
import {
EXAMPLE_EMAIL_PROFESSIONAL,
EXAMPLE_INDIVIDUAL_DID_WEB,
} from 'gdc-common-utils-ts/examples/consent-access';
import { HealthcareActorRoles } from 'gdc-common-utils-ts/constants/healthcare';
const subjectDid = EXAMPLE_INDIVIDUAL_DID_WEB;
const emailProfessional = EXAMPLE_EMAIL_PROFESSIONAL;
const missing = getMissingPermissions(evaluation);
const communication = buildPermissionRequestCommunication({
subject: subjectDid,
requester: { actorKind: 'professional', email: emailProfessional },
requesterRole: HealthcareActorRoles.Physician,
missing,
});Shared Contract Sources
Reusable payload examples:
gdc-common-utils-ts/examples/frontend-sessiongdc-common-utils-ts/examples/professionalgdc-common-utils-ts/examples/lifecyclegdc-common-utils-ts/examples/api-flow-examples
API Index
Full Public Surface
This package exports the full gdc-sdk-core-ts surface plus the frontend
runtime modules below.
src/runtime-contracts.ts- types/constants:
LegacyFrontSourcePackage,FrontRuntimeKind,FrontFetchLike,FrontRuntimeConfig,FrontPackageStatus,GDC_SDK_FRONT_STATUS
- types/constants:
src/actor-session.ts- re-exports actor-session descriptor helpers for frontend consumption
src/session-descriptor.ts- types:
FrontActorFlags,FrontSessionDescriptorInput - functions:
describeFrontActorSession(...),describeFrontActorFacades(...)
- types:
src/types.ts- types:
SdkConfig,FrontDateRange,FrontBundleSearchQuery,FrontCommunicationInput - re-exported shared types:
AppInfo,InitializeSessionParams,Profile,ProfileRegistryEntry,VaultQueryCondition,VaultQuery,IVaultRepository,IApiConfig,INetwork,IVerifier
- types:
src/services.ts- classes:
CommonAuthService,OrgAdminService,FamilyAdminService,IndividualService,PhysicianService,ParamedicService
- classes:
src/roleRegistry.ts- interfaces:
OrgAdminServices,FamilyAdminServices,IndividualServices,ProfessionalServices,CommonServices
- interfaces:
src/capabilityMapper.ts- function:
mapCapabilitiesToServices(...)
- function:
src/VerifierService.ts- class:
VerifierService
- class:
src/ProfileManager.ts- class:
ProfileManager - compatibility export:
ActorSession
- class:
src/ProfileRegistry.ts- class:
ProfileRegistry
- class:
src/ClientSDK.ts- class:
ClientSDK - re-exported session/profile types from shared contracts
- class:
The runtime-facing meaning of these exports is:
ClientSDK,ProfileManager,ProfileRegistry,VerifierService- app/session orchestration
services.ts- app-facing domain services
session-descriptor.tsandactor-session.ts- actor-role expansion for frontend use
capabilityMapper.tsandroleRegistry.ts- capability-to-service wiring
Re-exported shared helpers from gdc-sdk-core-ts
- consent access helpers
- relationship invitation/acceptance builders
- communication draft helpers
- document facade helpers
- vital-sign helpers
Frontend runtime configuration
Frontend runtime services
ClientSDKProfileManagerProfileRegistryVerifierServicedescribeFrontActorSession(...)describeFrontActorFacades(...)
Documentation Rule
- README should explain app flows first.
- Shared contract definitions should stay in
gdc-sdk-core-ts. - Frontend consumers should not need UNID runtime knowledge to understand this package.
