gdc-sdk-node-ts 2.1.4 → 2.3.0

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.
Files changed (56) hide show
  1. package/README.md +11 -5
  2. package/dist/backend-profile-runtime.d.ts +23 -0
  3. package/dist/backend-profile-runtime.js +23 -0
  4. package/dist/backend-profile-workspace.d.ts +48 -0
  5. package/dist/backend-profile-workspace.js +91 -0
  6. package/dist/index.d.ts +3 -0
  7. package/dist/index.js +3 -0
  8. package/dist/individual-controller-backend-runtime.d.ts +6 -1
  9. package/dist/individual-controller-backend-runtime.js +7 -0
  10. package/dist/node-runtime-client.d.ts +51 -4
  11. package/dist/node-runtime-client.js +148 -4
  12. package/dist/orchestration/client-port.d.ts +6 -1
  13. package/dist/orchestration/individual-controller-sdk.d.ts +46 -1
  14. package/dist/orchestration/individual-controller-sdk.js +59 -0
  15. package/dist/orchestration/individual-member-sdk.d.ts +35 -1
  16. package/dist/orchestration/individual-member-sdk.js +44 -0
  17. package/dist/orchestration/personal-sdk.d.ts +3 -1
  18. package/dist/orchestration/personal-sdk.js +4 -0
  19. package/dist/orchestration/professional-sdk.d.ts +11 -1
  20. package/dist/orchestration/professional-sdk.js +14 -0
  21. package/dist/professional-backend-runtime.d.ts +6 -1
  22. package/dist/professional-backend-runtime.js +7 -0
  23. package/dist/profile-workspace.d.ts +12 -0
  24. package/dist/profile-workspace.js +15 -0
  25. package/dist/resource-operations.d.ts +185 -4
  26. package/dist/resource-operations.js +267 -3
  27. package/dist/runtime-client-paths.d.ts +8 -2
  28. package/dist/runtime-client-paths.js +6 -0
  29. package/dist/runtime-route-context.js +18 -1
  30. package/dist/runtime-transport.d.ts +13 -1
  31. package/dist/runtime-transport.js +18 -3
  32. package/dist/server-profile-protection.d.ts +64 -0
  33. package/dist/server-profile-protection.js +110 -0
  34. package/dist/server-profile-session.d.ts +115 -0
  35. package/dist/server-profile-session.js +259 -0
  36. package/dist/session.d.ts +7 -0
  37. package/dist/session.js +10 -0
  38. package/package.json +3 -3
  39. package/dist/legal-organization-onboarding-facade.d.ts +0 -70
  40. package/dist/legal-organization-onboarding-facade.js +0 -169
  41. package/dist/lifecycle-examples.d.ts +0 -7
  42. package/dist/lifecycle-examples.js +0 -8
  43. package/dist/runtime-software-proof.d.ts +0 -51
  44. package/dist/runtime-software-proof.js +0 -34
  45. package/dist/simple-device-activation.d.ts +0 -44
  46. package/dist/simple-device-activation.js +0 -44
  47. package/dist/simple-host-onboarding.d.ts +0 -27
  48. package/dist/simple-host-onboarding.js +0 -39
  49. package/dist/simple-individual-onboarding.d.ts +0 -27
  50. package/dist/simple-individual-onboarding.js +0 -38
  51. package/dist/simple-individual-start.d.ts +0 -45
  52. package/dist/simple-individual-start.js +0 -59
  53. package/dist/simple-poll-options.d.ts +0 -5
  54. package/dist/simple-poll-options.js +0 -17
  55. package/dist/simple-smart-token.d.ts +0 -58
  56. package/dist/simple-smart-token.js +0 -85
@@ -38,4 +38,11 @@ export class ProfessionalBackendRuntime {
38
38
  getLatestIps(profile, ctx, input) {
39
39
  return profile.sdk.getLatestIps(ctx, input);
40
40
  }
41
+ /**
42
+ * Registers one FHIR resource or raw artifact on blockchain before it is
43
+ * attached to a communication.
44
+ */
45
+ registerBlockchainArtifactAndUpdateIndex(profile, ctx, input) {
46
+ return profile.sdk.registerBlockchainArtifactAndUpdateIndex(ctx, input);
47
+ }
41
48
  }
@@ -1,4 +1,5 @@
1
1
  import { CommunicationAttachedBundleSession, IndividualBundleVault, buildVitalSignObservationClaims, createSummaryOperationRequestParameters, createSummaryOperationRequestParametersResource, summarizeClinicalBundle, toClinicalResourceExpandedViews, type BuildVitalSignObservationClaimsInput } from 'gdc-common-utils-ts';
2
+ import type { BundleEntry, BundleJsonApi } from 'gdc-common-utils-ts/models/bundle';
2
3
  import type { BackendOrganizationControllerProfile, BackendProfessionalProfile } from './backend-profile-runtime.js';
3
4
  export type IpsRequestDraft = Readonly<{
4
5
  subjectId: string;
@@ -29,6 +30,17 @@ declare class ProfessionalSubjectIpsWorkspace {
29
30
  processResponseBundleIps(body: unknown): ProcessedClinicalBundleResponse;
30
31
  rememberResponseBundleIps(body: unknown): Promise<IndividualBundleVault>;
31
32
  addVitalSign(input: BuildVitalSignObservationClaimsInput): Promise<IndividualBundleVault>;
33
+ buildVitalSignBatchCommunicationFromSearchResponse(searchResponse: unknown, selectedResourceIds?: readonly string[]): import("./resource-operations.js").CommunicationIngestionInput;
34
+ buildBlockchainArtifactBundleFromSearchResponse(searchResponse: unknown, selectedResourceIds?: readonly string[]): Readonly<{
35
+ availableResourceIds: readonly string[];
36
+ anchoredResourceIds: readonly string[];
37
+ unanchoredResourceIds: readonly string[];
38
+ selectedResourceIds: readonly string[];
39
+ missingResourceIds: readonly string[];
40
+ returnedCount: number;
41
+ totalCount: number;
42
+ bundle: BundleJsonApi<BundleEntry>;
43
+ }>;
32
44
  get cache(): IndividualBundleVault;
33
45
  }
34
46
  declare class ProfessionalSubjectWorkspace {
@@ -1,5 +1,6 @@
1
1
  // Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
2
2
  import { CommunicationAttachedBundleSession, IndividualBundleVault, VaultMemRepository, buildVitalSignObservationClaims, createConsentAccessEditor, createInvoiceBundleEditor, createSummaryOperationRequestParameters, createSummaryOperationRequestParametersResource, createSummaryOperationRequestReferencePath, readEmployeeSearchResults, summarizeClinicalBundle, summarizeLicenseListRecords, toClinicalResourceExpandedViews, } from 'gdc-common-utils-ts';
3
+ import { buildBlockchainArtifactBundleFromSearchResponse, buildVitalSignBatchCommunicationFromSearchResponse } from './resource-operations.js';
3
4
  function normalizeSectionList(sectionList) {
4
5
  return Array.from(new Set((sectionList || []).map((item) => String(item || '').trim()).filter(Boolean)));
5
6
  }
@@ -69,6 +70,20 @@ class ProfessionalSubjectIpsWorkspace {
69
70
  await this.bundleVault.upsertVitalSign(input);
70
71
  return this.bundleVault;
71
72
  }
73
+ buildVitalSignBatchCommunicationFromSearchResponse(searchResponse, selectedResourceIds) {
74
+ return buildVitalSignBatchCommunicationFromSearchResponse({
75
+ subject: this.subjectId,
76
+ searchResponse,
77
+ selectedResourceIds,
78
+ });
79
+ }
80
+ buildBlockchainArtifactBundleFromSearchResponse(searchResponse, selectedResourceIds) {
81
+ return buildBlockchainArtifactBundleFromSearchResponse({
82
+ subject: this.subjectId,
83
+ searchResponse,
84
+ selectedResourceIds,
85
+ });
86
+ }
72
87
  get cache() {
73
88
  return this.bundleVault;
74
89
  }
@@ -1,6 +1,8 @@
1
+ import type { DeviceAppType } from 'gdc-common-utils-ts/constants/device';
1
2
  import type { LicenseOfferSearchState, LicenseOrderSearchState } from 'gdc-common-utils-ts/utils/license-commercial-search';
2
3
  import type { LicenseListSearchState } from 'gdc-common-utils-ts/utils/license-list-search';
3
- import type { BundleSearchQuery, CommunicationInput, DateRange, EmployeeSearchValue, IndividualOrganizationLifecycleInput } from 'gdc-sdk-core-ts';
4
+ import type { BundleSearchQuery, CommMsgExtendedCommunicationOutboxJob, CommunicationOutboxJob, CommunicationInput, DateRange, EmployeeSearchValue, IndividualOrganizationLifecycleInput, TransportProfile } from 'gdc-sdk-core-ts';
5
+ import type { BundleEntry, BundleJsonApi } from 'gdc-common-utils-ts/models/bundle';
4
6
  import type { SubmitAndPollResult } from './orchestration/client-port.js';
5
7
  import type { RouteContext } from './individual-onboarding.js';
6
8
  export type OrganizationEmployeeCreationInput = {
@@ -84,6 +86,52 @@ export type LicenseListRuntimeSearchInput = {
84
86
  intervalMs?: number;
85
87
  };
86
88
  };
89
+ /**
90
+ * Reserves one individual-organization seat for an existing RelatedPerson.
91
+ *
92
+ * Only FHIR v3 RoleCode members belong here. ISCO professionals receive
93
+ * Consent/Communication access but keep using the license paid by their
94
+ * professional organization.
95
+ */
96
+ export type IndividualMemberLicenseInvitationInput = {
97
+ ownerOrganizationId: string;
98
+ /** Exact card/subject DID granted after the invitation is accepted. */
99
+ subjectDid: string;
100
+ relatedPersonId: string;
101
+ invitationId: string;
102
+ role: string;
103
+ email?: string;
104
+ telephone?: string;
105
+ type?: DeviceAppType;
106
+ requestThid?: string;
107
+ pollOptions?: {
108
+ timeoutMs?: number;
109
+ intervalMs?: number;
110
+ };
111
+ };
112
+ /** Adds zero-cost member seats to one individual organization. */
113
+ export type IndividualMemberLicenseAddInput = {
114
+ ownerOrganizationId: string;
115
+ quantity: number;
116
+ requestThid?: string;
117
+ pollOptions?: {
118
+ timeoutMs?: number;
119
+ intervalMs?: number;
120
+ };
121
+ };
122
+ /** Input shared by invitation acceptance, deactivation and release. */
123
+ export type IndividualMemberLicenseTransitionInput = {
124
+ /** Required for controller deactivation/release; acceptance resolves by code. */
125
+ ownerOrganizationId?: string;
126
+ activationCode: string;
127
+ subjectId?: string;
128
+ verifiedActorIdentifier?: string;
129
+ requestThid?: string;
130
+ pollOptions?: {
131
+ timeoutMs?: number;
132
+ intervalMs?: number;
133
+ };
134
+ };
87
135
  /**
88
136
  * Runtime search/list input for commercial offer read-models exposed through
89
137
  * actor facades.
@@ -143,6 +191,13 @@ export type IpsOrFhirImportInput = {
143
191
  };
144
192
  };
145
193
  export type RelatedPersonUpsertInput = {
194
+ /**
195
+ * Canonical RelatedPerson bundle. Keep kinship/legal relationship in
196
+ * `RelatedPerson.relationship`; the optional GDC `RelatedPerson.role`
197
+ * extension may carry comma-separated functions such as
198
+ * `CAREGIVER,ECON,DEPEN,BILL`.
199
+ * Access decisions such as PERMITTED do not belong in either claim.
200
+ */
146
201
  relatedPersonPayload: {
147
202
  thid?: string;
148
203
  } & Record<string, unknown>;
@@ -152,7 +207,22 @@ export type RelatedPersonUpsertInput = {
152
207
  };
153
208
  };
154
209
  export type CommunicationIngestionInput = {
155
- communicationPayload: CommunicationInput & Record<string, unknown>;
210
+ /**
211
+ * Preferred claims-first job created by
212
+ * `createCommunicationOutboxJobFromCommMsgExtendedDraft(...)`. Legacy
213
+ * `createOutboxJobFromDraft(...)` jobs remain accepted temporarily.
214
+ */
215
+ communicationJob?: CommunicationOutboxJob | CommMsgExtendedCommunicationOutboxJob;
216
+ /**
217
+ * Compatibility input for callers that already own one GW envelope.
218
+ * New application code should pass `communicationJob` instead.
219
+ */
220
+ communicationPayload?: CommunicationInput & Record<string, unknown>;
221
+ /** Overrides the client default only for this clinical submission. */
222
+ transportProfile?: TransportProfile;
223
+ /** Clinical representation rendered from canonical claims: built-in `api`/`r4` or an extension format. */
224
+ clinicalFormat?: string;
225
+ /** @deprecated Use `clinicalFormat`. This value selects representation and route, not transport. */
156
226
  pathFormatSegment?: 'org.hl7.fhir.api' | 'org.hl7.fhir.r4' | 'api' | 'r4' | 'fhir.r4';
157
227
  autoConvertClaimsToFhirR4?: boolean;
158
228
  pollOptions?: {
@@ -160,6 +230,53 @@ export type CommunicationIngestionInput = {
160
230
  intervalMs?: number;
161
231
  };
162
232
  };
233
+ export type BlockchainArtifactRegistrationInput = {
234
+ subject: string;
235
+ resource?: Record<string, unknown>;
236
+ contentDataBase64?: string;
237
+ contentType?: string;
238
+ identifier?: string;
239
+ title?: string;
240
+ description?: string;
241
+ date?: string;
242
+ location?: string;
243
+ language?: string;
244
+ requestThid?: string;
245
+ pollOptions?: {
246
+ timeoutMs?: number;
247
+ intervalMs?: number;
248
+ };
249
+ };
250
+ export type BlockchainArtifactSearchSelectionInput = Readonly<{
251
+ subject: string;
252
+ searchResponse: unknown;
253
+ selectedResourceIds?: readonly string[];
254
+ }>;
255
+ export type BlockchainArtifactSearchSelectionResult = Readonly<{
256
+ availableResourceIds: readonly string[];
257
+ anchoredResourceIds: readonly string[];
258
+ unanchoredResourceIds: readonly string[];
259
+ selectedResourceIds: readonly string[];
260
+ missingResourceIds: readonly string[];
261
+ returnedCount: number;
262
+ totalCount: number;
263
+ bundle: BundleJsonApi<BundleEntry>;
264
+ }>;
265
+ export type VitalSignBatchCommunicationFromSearchResponseInput = Readonly<{
266
+ subject: string;
267
+ searchResponse: unknown;
268
+ selectedResourceIds?: readonly string[];
269
+ sender?: string;
270
+ recipient?: string | string[];
271
+ sent?: string;
272
+ status?: string;
273
+ noteText?: string;
274
+ requestThid?: string;
275
+ pollOptions?: {
276
+ timeoutMs?: number;
277
+ intervalMs?: number;
278
+ };
279
+ }>;
163
280
  /**
164
281
  * Runtime participant query for `Communication/_search`.
165
282
  *
@@ -202,6 +319,8 @@ export type ClinicalBundleSearchInput = Omit<BundleSearchQuery, 'section' | 'sea
202
319
  timeoutMs?: number;
203
320
  intervalMs?: number;
204
321
  };
322
+ /** Overrides the client default for this subject-scoped clinical read. */
323
+ transportProfile?: TransportProfile;
205
324
  };
206
325
  export type ConsentActorTargetInput = string | string[] | {
207
326
  identifier?: string;
@@ -433,6 +552,40 @@ export declare function searchIndividualLicensesWithDeps(routeCtx: RouteContext,
433
552
  * search route without mandatory filters.
434
553
  */
435
554
  export declare function listIndividualLicensesWithDeps(routeCtx: RouteContext, input: LicenseListRuntimeSearchInput | undefined, deps: Parameters<typeof searchIndividualLicensesWithDeps>[2]): Promise<SubmitAndPollResult>;
555
+ type IndividualLicenseMutationDeps = {
556
+ individualLicenseActionPath: (ctx: RouteContext, action: string) => string;
557
+ individualLicenseActionPollPath: (ctx: RouteContext, action: string) => string;
558
+ submitAndPoll: (submitPath: string, pollPath: string, payload: {
559
+ thid?: string;
560
+ } & Record<string, unknown>, pollOptions?: {
561
+ timeoutMs?: number;
562
+ intervalMs?: number;
563
+ }) => Promise<SubmitAndPollResult>;
564
+ };
565
+ /**
566
+ * Reserves one available household/member seat for a selected contact.
567
+ *
568
+ * Ordered application flow:
569
+ * 1. Create/select RelatedPerson.
570
+ * 2. Author Consent rules.
571
+ * 3. Call this operation only for a FHIR v3 RoleCode member.
572
+ * 4. Put the returned activation code in the invitation Communication.
573
+ */
574
+ export declare function issueIndividualMemberLicenseWithDeps(routeCtx: RouteContext, input: IndividualMemberLicenseInvitationInput, deps: IndividualLicenseMutationDeps): Promise<SubmitAndPollResult>;
575
+ /**
576
+ * Adds free seats without creating contacts, permissions or invitations.
577
+ * Price zero is explicit so GW never routes this MVP operation through a
578
+ * payment-proof branch.
579
+ */
580
+ export declare function addFreeIndividualMemberLicensesWithDeps(routeCtx: RouteContext, input: IndividualMemberLicenseAddInput, deps: IndividualLicenseMutationDeps): Promise<SubmitAndPollResult>;
581
+ /**
582
+ * Accepts, deactivates or releases one member seat.
583
+ *
584
+ * `_accept` needs the Firebase/BFF verified recipient plus the authenticated
585
+ * subject id. `_release` remains backend-guarded and cannot free an active
586
+ * member directly.
587
+ */
588
+ export declare function transitionIndividualMemberLicenseWithDeps(routeCtx: RouteContext, action: '_accept' | '_deactivate' | '_release', input: IndividualMemberLicenseTransitionInput, deps: IndividualLicenseMutationDeps): Promise<SubmitAndPollResult>;
436
589
  export declare function searchIndividualLicenseOffersWithDeps(routeCtx: RouteContext, input: LicenseOfferRuntimeSearchInput, deps: {
437
590
  individualLicenseOfferSearchPath: (ctx: RouteContext) => string;
438
591
  individualLicenseOfferSearchPollPath: (ctx: RouteContext) => string;
@@ -502,8 +655,8 @@ export declare function upsertRelatedPersonAndPollWithDeps(routeCtx: RouteContex
502
655
  }) => Promise<SubmitAndPollResult>;
503
656
  }): Promise<SubmitAndPollResult>;
504
657
  export declare function ingestCommunicationAndUpdateIndexWithDeps(routeCtx: RouteContext, input: CommunicationIngestionInput, deps: {
505
- individualCommunicationBatchPath: (ctx: RouteContext, pathFormatSegment: 'org.hl7.fhir.api' | 'org.hl7.fhir.r4') => string;
506
- individualCommunicationPollPath: (ctx: RouteContext, pathFormatSegment: 'org.hl7.fhir.api' | 'org.hl7.fhir.r4') => string;
658
+ individualCommunicationBatchPath: (ctx: RouteContext, pathFormatSegment: string) => string;
659
+ individualCommunicationPollPath: (ctx: RouteContext, pathFormatSegment: string) => string;
507
660
  transformPayloadForFhirR4?: (payload: Record<string, unknown>, enabled: boolean) => Record<string, unknown>;
508
661
  submitAndPoll: (submitPath: string, pollPath: string, payload: {
509
662
  thid?: string;
@@ -532,11 +685,37 @@ export declare function searchClinicalBundleWithDeps(routeCtx: RouteContext, inp
532
685
  intervalMs?: number;
533
686
  }) => Promise<SubmitAndPollResult>;
534
687
  }): Promise<SubmitAndPollResult>;
688
+ /**
689
+ * Builds one Communication-ready ingestion payload from a paginated search
690
+ * response that already selected the day batches to be anchored.
691
+ *
692
+ * The search response is normalized into a `Bundle.type=batch` attachment,
693
+ * and the resulting `Communication` is ready for `ingestCommunication...`.
694
+ */
695
+ export declare function buildVitalSignBatchCommunicationFromSearchResponse(input: VitalSignBatchCommunicationFromSearchResponseInput): CommunicationIngestionInput;
696
+ /**
697
+ * Builds one blockchain-ready batch bundle from a paginated search response.
698
+ *
699
+ * The helper keeps the original search result as the source of truth and only
700
+ * projects the selected `resource.id` values into a `Bundle.type=batch`
701
+ * payload for subsequent communication/registration.
702
+ */
703
+ export declare function buildBlockchainArtifactBundleFromSearchResponse(input: BlockchainArtifactSearchSelectionInput): BlockchainArtifactSearchSelectionResult;
535
704
  export declare function searchLatestIpsWithDeps(routeCtx: RouteContext, input: Omit<ClinicalBundleSearchInput, 'includedTypes' | 'section'> & {
536
705
  section?: string | string[];
537
706
  }, deps: {
538
707
  searchClinicalBundle: (routeCtx: RouteContext, input: ClinicalBundleSearchInput) => Promise<SubmitAndPollResult>;
539
708
  }): Promise<SubmitAndPollResult>;
709
+ export declare function registerBlockchainArtifactAndUpdateIndexWithDeps(routeCtx: RouteContext, input: BlockchainArtifactRegistrationInput, deps: {
710
+ individualDocumentReferenceBatchPath: (ctx: RouteContext) => string;
711
+ individualDocumentReferencePollPath: (ctx: RouteContext) => string;
712
+ submitAndPoll: (submitPath: string, pollPath: string, payload: {
713
+ thid?: string;
714
+ } & Record<string, unknown>, pollOptions?: {
715
+ timeoutMs?: number;
716
+ intervalMs?: number;
717
+ }) => Promise<SubmitAndPollResult>;
718
+ }): Promise<SubmitAndPollResult>;
540
719
  export declare function grantProfessionalAccessWithDeps(routeCtx: RouteContext, input: GrantProfessionalAccessInput, deps: {
541
720
  buildConsentClaimsWithCid: (input: GrantProfessionalAccessInput, options?: {
542
721
  consentIdentifierFactory: () => string;
@@ -577,3 +756,5 @@ export declare function generateDigitalTwinFromSubjectDataWithDeps(routeCtx: Rou
577
756
  intervalMs?: number;
578
757
  }) => Promise<SubmitAndPollResult>;
579
758
  }): Promise<SubmitAndPollResult>;
759
+ export declare function normalizeCommunicationPathFormatSegment(raw?: string): string;
760
+ export {};
@@ -2,7 +2,8 @@
2
2
  import { HealthcareBasicSections, ResourceTypesFhirR4 } from 'gdc-common-utils-ts/constants';
3
3
  import { Format } from 'gdc-common-utils-ts/constants/Schemas';
4
4
  import { RelatedPersonClaim } from 'gdc-common-utils-ts/models/interoperable-claims/related-person-claims';
5
- import { buildCommunicationParticipantSearchBundle, createInteroperableResourceOperationEditor, IndividualOrganizationLifecycleEditor, LicenseOfferSearchEditor, LicenseOrderSearchEditor, InteroperableLifecycleStatuses, LicenseListSearchEditor, } from 'gdc-common-utils-ts';
5
+ import { buildCommunicationParticipantSearchBundle, buildBlockchainArtifactDocumentReference, BundleQuery, createInteroperableResourceOperationEditor, IndividualOrganizationLifecycleEditor, LicenseOfferSearchEditor, LicenseOrderSearchEditor, InteroperableLifecycleStatuses, LicenseListSearchEditor, buildLicenseIssueEntry, buildLicensePurchaseEntry, } from 'gdc-common-utils-ts';
6
+ import { addFhirResourceToCommunication, createCommunicationResource, } from 'gdc-sdk-core-ts';
6
7
  import { GwCoreLifecycleRequestMethod, GwCoreLifecycleRequestType, GwCoreLifecycleTodo, } from './constants/lifecycle.js';
7
8
  import { buildEmployeeBatchEntry, buildEmployeeSearchBundle, ConsentClaims, } from 'gdc-sdk-core-ts';
8
9
  export async function createOrganizationEmployeeWithDeps(routeCtx, input, options, deps) {
@@ -168,6 +169,81 @@ export async function searchIndividualLicensesWithDeps(routeCtx, input, deps) {
168
169
  export async function listIndividualLicensesWithDeps(routeCtx, input, deps) {
169
170
  return searchIndividualLicensesWithDeps(routeCtx, input || {}, deps);
170
171
  }
172
+ /**
173
+ * Reserves one available household/member seat for a selected contact.
174
+ *
175
+ * Ordered application flow:
176
+ * 1. Create/select RelatedPerson.
177
+ * 2. Author Consent rules.
178
+ * 3. Call this operation only for a FHIR v3 RoleCode member.
179
+ * 4. Put the returned activation code in the invitation Communication.
180
+ */
181
+ export async function issueIndividualMemberLicenseWithDeps(routeCtx, input, deps) {
182
+ const entry = buildLicenseIssueEntry({
183
+ email: input.email,
184
+ telephone: input.telephone,
185
+ role: input.role,
186
+ userClass: 'individual',
187
+ type: input.type || 'web',
188
+ ownerOrganizationId: input.ownerOrganizationId,
189
+ relatedPersonId: input.relatedPersonId,
190
+ invitationId: input.invitationId,
191
+ });
192
+ entry.meta.subjectDid = input.subjectDid;
193
+ return deps.submitAndPoll(deps.individualLicenseActionPath(routeCtx, '_issue'), deps.individualLicenseActionPollPath(routeCtx, '_issue'), {
194
+ thid: input.requestThid || `individual-license-issue-${createRuntimeUuid()}`,
195
+ body: { resourceType: 'Bundle', type: 'batch', data: [entry] },
196
+ }, input.pollOptions);
197
+ }
198
+ /**
199
+ * Adds free seats without creating contacts, permissions or invitations.
200
+ * Price zero is explicit so GW never routes this MVP operation through a
201
+ * payment-proof branch.
202
+ */
203
+ export async function addFreeIndividualMemberLicensesWithDeps(routeCtx, input, deps) {
204
+ const entry = buildLicensePurchaseEntry({
205
+ quantity: input.quantity,
206
+ userClass: 'individual',
207
+ type: 'web',
208
+ price: 0,
209
+ priceCurrency: 'EUR',
210
+ ownerOrganizationId: input.ownerOrganizationId,
211
+ });
212
+ return deps.submitAndPoll(deps.individualLicenseActionPath(routeCtx, '_add'), deps.individualLicenseActionPollPath(routeCtx, '_add'), {
213
+ thid: input.requestThid || `individual-license-add-${createRuntimeUuid()}`,
214
+ body: { resourceType: 'Bundle', type: 'batch', data: [entry] },
215
+ }, input.pollOptions);
216
+ }
217
+ /**
218
+ * Accepts, deactivates or releases one member seat.
219
+ *
220
+ * `_accept` needs the Firebase/BFF verified recipient plus the authenticated
221
+ * subject id. `_release` remains backend-guarded and cannot free an active
222
+ * member directly.
223
+ */
224
+ export async function transitionIndividualMemberLicenseWithDeps(routeCtx, action, input, deps) {
225
+ return deps.submitAndPoll(deps.individualLicenseActionPath(routeCtx, action), deps.individualLicenseActionPollPath(routeCtx, action), {
226
+ thid: input.requestThid || `individual-license-${action.slice(1)}-${createRuntimeUuid()}`,
227
+ body: {
228
+ resourceType: 'Bundle',
229
+ type: 'batch',
230
+ data: [{
231
+ id: input.activationCode,
232
+ type: `IndividualMemberLicense${action}`,
233
+ meta: {
234
+ ...(input.ownerOrganizationId ? { ownerOrganizationId: input.ownerOrganizationId } : {}),
235
+ ...(input.subjectId ? { subjectId: input.subjectId } : {}),
236
+ ...(input.verifiedActorIdentifier ? { verifiedActorIdentifier: input.verifiedActorIdentifier } : {}),
237
+ claims: {
238
+ '@context': 'org.schema',
239
+ 'org.schema.IndividualProduct.serialNumber': input.activationCode,
240
+ },
241
+ },
242
+ request: { method: 'POST' },
243
+ }],
244
+ },
245
+ }, input.pollOptions);
246
+ }
171
247
  export async function searchIndividualLicenseOffersWithDeps(routeCtx, input, deps) {
172
248
  return deps.submitAndPoll(deps.individualLicenseOfferSearchPath(routeCtx), deps.individualLicenseOfferSearchPollPath(routeCtx), {
173
249
  thid: input.requestThid || `individual-license-offer-search-${createRuntimeUuid()}`,
@@ -280,6 +356,9 @@ export async function upsertRelatedPersonAndPollWithDeps(routeCtx, input, deps)
280
356
  return deps.submitAndPoll(deps.individualRelatedPersonBatchPath(routeCtx), deps.individualRelatedPersonPollPath(routeCtx), payload, input.pollOptions);
281
357
  }
282
358
  export async function ingestCommunicationAndUpdateIndexWithDeps(routeCtx, input, deps) {
359
+ if (!input.communicationPayload) {
360
+ throw new Error('Legacy communication ingestion requires communicationPayload.');
361
+ }
283
362
  const payload = {
284
363
  thid: input.communicationPayload.thid || `communication-${createRuntimeUuid()}`,
285
364
  ...input.communicationPayload,
@@ -323,6 +402,117 @@ export async function searchClinicalBundleWithDeps(routeCtx, input, deps) {
323
402
  };
324
403
  return deps.submitAndPoll(deps.bundleSearchPath(routeCtx), deps.bundleSearchPollPath(routeCtx), payload, input.pollOptions);
325
404
  }
405
+ /**
406
+ * Builds one Communication-ready ingestion payload from a paginated search
407
+ * response that already selected the day batches to be anchored.
408
+ *
409
+ * The search response is normalized into a `Bundle.type=batch` attachment,
410
+ * and the resulting `Communication` is ready for `ingestCommunication...`.
411
+ */
412
+ export function buildVitalSignBatchCommunicationFromSearchResponse(input) {
413
+ const selection = buildBlockchainArtifactBundleFromSearchResponse({
414
+ subject: input.subject,
415
+ searchResponse: input.searchResponse,
416
+ selectedResourceIds: input.selectedResourceIds,
417
+ });
418
+ const communication = addFhirResourceToCommunication(createCommunicationResource({
419
+ subject: input.subject,
420
+ sender: input.sender,
421
+ recipient: input.recipient,
422
+ sent: input.sent,
423
+ status: input.status || 'completed',
424
+ noteText: input.noteText || `Selected ${selection.selectedResourceIds.length} vital-sign batch item(s) for blockchain anchoring.`,
425
+ claims: {
426
+ 'Communication.vital-sign-batch-selection.subject': input.subject,
427
+ 'Communication.vital-sign-batch-selection.selected-count': selection.selectedResourceIds.length,
428
+ 'Communication.vital-sign-batch-selection.unanchored-count': selection.unanchoredResourceIds.length,
429
+ 'Communication.vital-sign-batch-selection.selected-ids': selection.selectedResourceIds.join(','),
430
+ 'Communication.vital-sign-batch-selection.missing-ids': selection.missingResourceIds.join(','),
431
+ },
432
+ }), selection.bundle, {
433
+ attachmentContentType: 'application/fhir+json',
434
+ attachmentTitle: 'vital-sign-batch-selection.json',
435
+ });
436
+ return {
437
+ communicationPayload: communication,
438
+ pathFormatSegment: 'org.hl7.fhir.r4',
439
+ pollOptions: input.pollOptions,
440
+ autoConvertClaimsToFhirR4: false,
441
+ };
442
+ }
443
+ /**
444
+ * Builds one blockchain-ready batch bundle from a paginated search response.
445
+ *
446
+ * The helper keeps the original search result as the source of truth and only
447
+ * projects the selected `resource.id` values into a `Bundle.type=batch`
448
+ * payload for subsequent communication/registration.
449
+ */
450
+ export function buildBlockchainArtifactBundleFromSearchResponse(input) {
451
+ const bundle = normalizeBundleLike(input.searchResponse);
452
+ const query = new BundleQuery(bundle);
453
+ const availableResourceIds = query.getResourceIds();
454
+ const availableEntries = query.getResourceEntriesByIds(availableResourceIds);
455
+ const anchoredResourceIds = availableEntries
456
+ .filter((entry) => hasAuditTxId(entry))
457
+ .map((entry, index) => resolveBundleEntryStableId(entry, index));
458
+ const unanchoredResourceIds = availableResourceIds.filter((resourceId) => !anchoredResourceIds.includes(resourceId));
459
+ const requestedResourceIds = normalizeIdList(input.selectedResourceIds);
460
+ const selectedResourceIds = requestedResourceIds.length > 0
461
+ ? requestedResourceIds.filter((resourceId) => availableResourceIds.includes(resourceId))
462
+ : unanchoredResourceIds;
463
+ const missingResourceIds = requestedResourceIds.filter((resourceId) => !availableResourceIds.includes(resourceId));
464
+ if (requestedResourceIds.length > 0 && selectedResourceIds.length === 0) {
465
+ throw new Error('buildBlockchainArtifactBundleFromSearchResponse could not match any selected resource ids.');
466
+ }
467
+ const selectedEntries = query.getResourceEntriesByIds(selectedResourceIds);
468
+ const artifactEntries = selectedEntries.map((entry, index) => {
469
+ const resource = entry.resource && typeof entry.resource === 'object' ? entry.resource : undefined;
470
+ if (!resource) {
471
+ throw new Error(`buildBlockchainArtifactBundleFromSearchResponse requires resource data for selected entry at index ${index}.`);
472
+ }
473
+ const logicalIdentifier = asTrimmedString(resource.id || entry.id || entry.fullUrl || selectedResourceIds[index]);
474
+ const artifact = buildBlockchainArtifactDocumentReference({
475
+ subject: input.subject,
476
+ resource,
477
+ identifier: logicalIdentifier || undefined,
478
+ title: `${asTrimmedString(resource.resourceType) || 'resource'}.json`,
479
+ });
480
+ return {
481
+ type: 'DocumentReference',
482
+ meta: { claims: artifact.documentReference.meta?.claims || {} },
483
+ resource: artifact.documentReference,
484
+ request: {
485
+ method: 'POST',
486
+ url: 'individual/org.hl7.fhir.r4/DocumentReference/_batch',
487
+ },
488
+ };
489
+ });
490
+ return {
491
+ availableResourceIds,
492
+ anchoredResourceIds,
493
+ unanchoredResourceIds,
494
+ selectedResourceIds,
495
+ missingResourceIds,
496
+ returnedCount: query.getResourceIds().length,
497
+ totalCount: typeof bundle.total === 'number'
498
+ ? Number(bundle.total)
499
+ : query.getResourceIds().length,
500
+ bundle: {
501
+ resourceType: 'Bundle',
502
+ type: 'batch',
503
+ meta: {
504
+ claims: {
505
+ 'BlockchainArtifactSelection.subject': input.subject,
506
+ 'BlockchainArtifactSelection.availableCount': availableResourceIds.length,
507
+ 'BlockchainArtifactSelection.selectedCount': selectedResourceIds.length,
508
+ 'BlockchainArtifactSelection.missingCount': missingResourceIds.length,
509
+ 'BlockchainArtifactSelection.selectedResourceIds': selectedResourceIds.join(','),
510
+ },
511
+ },
512
+ data: artifactEntries,
513
+ },
514
+ };
515
+ }
326
516
  export async function searchLatestIpsWithDeps(routeCtx, input, deps) {
327
517
  return deps.searchClinicalBundle(routeCtx, {
328
518
  ...input,
@@ -330,6 +520,78 @@ export async function searchLatestIpsWithDeps(routeCtx, input, deps) {
330
520
  includedTypes: ['Composition', 'DocumentReference'],
331
521
  });
332
522
  }
523
+ export async function registerBlockchainArtifactAndUpdateIndexWithDeps(routeCtx, input, deps) {
524
+ const artifact = buildBlockchainArtifactDocumentReference({
525
+ subject: input.subject,
526
+ resource: input.resource,
527
+ contentDataBase64: input.contentDataBase64,
528
+ contentType: input.contentType,
529
+ identifier: input.identifier,
530
+ title: input.title,
531
+ description: input.description,
532
+ date: input.date,
533
+ location: input.location,
534
+ language: input.language,
535
+ });
536
+ const payload = {
537
+ thid: input.requestThid || `blockchain-artifact-${createRuntimeUuid()}`,
538
+ body: {
539
+ resourceType: 'Bundle',
540
+ type: 'batch',
541
+ data: [{
542
+ type: 'DocumentReference',
543
+ meta: { claims: artifact.documentReference.meta?.claims || {} },
544
+ resource: artifact.documentReference,
545
+ request: {
546
+ method: 'POST',
547
+ url: 'individual/org.hl7.fhir.r4/DocumentReference/_batch',
548
+ },
549
+ }],
550
+ },
551
+ };
552
+ return deps.submitAndPoll(deps.individualDocumentReferenceBatchPath(routeCtx), deps.individualDocumentReferencePollPath(routeCtx), payload, input.pollOptions);
553
+ }
554
+ function normalizeBundleLike(value) {
555
+ const root = value && typeof value === 'object' ? value : {};
556
+ const body = root.body && typeof root.body === 'object' ? root.body : root;
557
+ const resourceType = asTrimmedString(body.resourceType) || 'Bundle';
558
+ const type = asTrimmedString(body.type) || 'batch';
559
+ const data = Array.isArray(body.data)
560
+ ? body.data
561
+ : (Array.isArray(body.entry) ? body.entry : []);
562
+ return {
563
+ ...(typeof body.id === 'string' ? { id: asTrimmedString(body.id) || undefined } : {}),
564
+ resourceType: resourceType,
565
+ type,
566
+ total: typeof body.total === 'number' ? body.total : undefined,
567
+ meta: body.meta && typeof body.meta === 'object' ? body.meta : undefined,
568
+ data: data,
569
+ };
570
+ }
571
+ function normalizeIdList(value) {
572
+ return [...(value || [])]
573
+ .map((item) => asTrimmedString(item))
574
+ .filter(Boolean);
575
+ }
576
+ function asTrimmedString(value) {
577
+ if (value === undefined || value === null) {
578
+ return '';
579
+ }
580
+ return String(value).trim();
581
+ }
582
+ function resolveBundleEntryStableId(entry, index) {
583
+ const resource = entry.resource && typeof entry.resource === 'object' ? entry.resource : {};
584
+ return asTrimmedString(resource.id || entry.id || entry.fullUrl || `entry-${index}`);
585
+ }
586
+ function hasAuditTxId(entry) {
587
+ const entryAudit = entry && typeof entry === 'object' ? entry.audit : undefined;
588
+ const entryMeta = entry && typeof entry === 'object' ? entry.meta : undefined;
589
+ const resource = entry.resource && typeof entry.resource === 'object' ? entry.resource : {};
590
+ const resourceMeta = resource.meta && typeof resource.meta === 'object' ? resource.meta : {};
591
+ const audit = [entryAudit, entryMeta && typeof entryMeta === 'object' ? entryMeta.audit : undefined, resourceMeta.audit]
592
+ .find((candidate) => candidate && typeof candidate === 'object');
593
+ return Boolean(asTrimmedString(audit?.txId));
594
+ }
333
595
  export async function grantProfessionalAccessWithDeps(routeCtx, input, deps) {
334
596
  const built = deps.buildConsentClaimsWithCid({
335
597
  subjectDid: input.subjectDid,
@@ -404,13 +666,15 @@ export async function generateDigitalTwinFromSubjectDataWithDeps(routeCtx, input
404
666
  : deps.digitalTwinCompositionR4PollPath(routeCtx);
405
667
  return deps.submitAndPoll(submitPath, pollPath, payload, input.pollOptions);
406
668
  }
407
- function normalizeCommunicationPathFormatSegment(raw) {
669
+ export function normalizeCommunicationPathFormatSegment(raw) {
408
670
  const value = String(raw || '').trim().toLowerCase();
409
671
  if (!value || value === 'api' || value === 'org.hl7.fhir.api')
410
672
  return 'org.hl7.fhir.api';
411
673
  if (value === 'r4' || value === 'fhir.r4' || value === 'org.hl7.fhir.r4')
412
674
  return 'org.hl7.fhir.r4';
413
- return 'org.hl7.fhir.api';
675
+ if (/^org\.hl7\.fhir\.[a-z0-9.-]+$/.test(value))
676
+ return value;
677
+ throw new Error(`Unsupported Communication clinical format '${String(raw || '')}'.`);
414
678
  }
415
679
  function createRuntimeUuid() {
416
680
  const fromCrypto = globalThis.crypto?.randomUUID?.();
@@ -52,6 +52,10 @@ export declare class RuntimeClientPaths {
52
52
  individualFamilyOrganizationPurgePollPath(ctx?: RouteContext): string;
53
53
  individualLicenseSearchPath(ctx?: RouteContext): string;
54
54
  individualLicenseSearchPollPath(ctx?: RouteContext): string;
55
+ /** Builds one individual-member license mutation path. */
56
+ individualLicenseActionPath(ctx: RouteContext | undefined, action: string): string;
57
+ /** Builds the poll path paired with one individual-member license mutation. */
58
+ individualLicenseActionPollPath(ctx: RouteContext | undefined, action: string): string;
55
59
  individualLicenseOfferSearchPath(ctx?: RouteContext): string;
56
60
  individualLicenseOfferSearchPollPath(ctx?: RouteContext): string;
57
61
  individualLicenseOrderSearchPath(ctx?: RouteContext): string;
@@ -64,10 +68,12 @@ export declare class RuntimeClientPaths {
64
68
  individualRelatedPersonPurgePollPath(ctx?: RouteContext): string;
65
69
  individualConsentR4BatchPath(ctx: RouteContext): string;
66
70
  individualConsentR4PollPath(ctx: RouteContext): string;
67
- individualCommunicationBatchPath(ctx: RouteContext, format: 'org.hl7.fhir.api' | 'org.hl7.fhir.r4'): string;
68
- individualCommunicationPollPath(ctx: RouteContext, format: 'org.hl7.fhir.api' | 'org.hl7.fhir.r4'): string;
71
+ individualCommunicationBatchPath(ctx: RouteContext, format: string): string;
72
+ individualCommunicationPollPath(ctx: RouteContext, format: string): string;
69
73
  individualCommunicationSearchPath(ctx: RouteContext): string;
70
74
  individualCommunicationSearchPollPath(ctx: RouteContext): string;
75
+ individualDocumentReferenceBatchPath(ctx: RouteContext): string;
76
+ individualDocumentReferencePollPath(ctx: RouteContext): string;
71
77
  individualBundleSearchPath(ctx: RouteContext): string;
72
78
  individualBundleSearchPollPath(ctx: RouteContext): string;
73
79
  identityTokenExchangePath(ctx: RouteContext): string;