gdc-common-utils-ts 2.0.18 → 2.2.2

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 (185) hide show
  1. package/README.md +18 -1
  2. package/dist/claims/claims-helpers-allergy-intolerance.d.ts +8 -0
  3. package/dist/claims/claims-helpers-allergy-intolerance.js +30 -12
  4. package/dist/claims/claims-helpers-condition.d.ts +8 -0
  5. package/dist/claims/claims-helpers-condition.js +30 -12
  6. package/dist/claims/claims-helpers-consent.d.ts +4 -0
  7. package/dist/claims/claims-helpers-consent.js +15 -1
  8. package/dist/claims/claims-helpers-diagnostic-report.d.ts +4 -0
  9. package/dist/claims/claims-helpers-diagnostic-report.js +17 -2
  10. package/dist/claims/claims-helpers-medication-statement.d.ts +10 -2
  11. package/dist/claims/claims-helpers-medication-statement.js +32 -14
  12. package/dist/constants/didcomm.d.ts +22 -0
  13. package/dist/constants/didcomm.js +22 -0
  14. package/dist/constants/fhir-resource-types.d.ts +2 -0
  15. package/dist/constants/fhir-resource-types.js +1 -0
  16. package/dist/constants/index.d.ts +1 -0
  17. package/dist/constants/index.js +1 -0
  18. package/dist/constants/lifecycle.d.ts +2 -0
  19. package/dist/constants/lifecycle.js +2 -0
  20. package/dist/constants/verifiable-credentials.d.ts +9 -0
  21. package/dist/constants/verifiable-credentials.js +9 -0
  22. package/dist/examples/bundle-didcomm-payload.d.ts +11 -0
  23. package/dist/examples/bundle-didcomm-payload.js +15 -0
  24. package/dist/examples/communication-attached-bundle-session.js +28 -27
  25. package/dist/examples/communication-didcomm-payload.d.ts +39 -0
  26. package/dist/examples/communication-didcomm-payload.js +44 -0
  27. package/dist/examples/consent-access.js +2 -1
  28. package/dist/examples/employee.d.ts +21 -0
  29. package/dist/examples/employee.js +21 -0
  30. package/dist/examples/index.d.ts +4 -0
  31. package/dist/examples/index.js +4 -0
  32. package/dist/examples/individual-controller.d.ts +27 -0
  33. package/dist/examples/individual-controller.js +28 -1
  34. package/dist/examples/inter-tenant-access-contract.js +3 -2
  35. package/dist/examples/ips-bundle.js +17 -16
  36. package/dist/examples/lifecycle.js +13 -11
  37. package/dist/examples/profile-manager-mem.d.ts +45 -0
  38. package/dist/examples/profile-manager-mem.js +30 -0
  39. package/dist/examples/related-person.d.ts +14 -0
  40. package/dist/examples/related-person.js +18 -4
  41. package/dist/examples/shared.d.ts +13 -3
  42. package/dist/examples/shared.js +14 -4
  43. package/dist/examples/vital-signs.js +1 -1
  44. package/dist/examples/wallet-mem.d.ts +38 -0
  45. package/dist/examples/wallet-mem.js +42 -0
  46. package/dist/index.d.ts +1 -0
  47. package/dist/index.js +1 -0
  48. package/dist/interfaces/IProfileOutboxRepository.d.ts +36 -0
  49. package/dist/interfaces/IProfileOutboxRepository.js +2 -0
  50. package/dist/interfaces/IWallet.d.ts +74 -0
  51. package/dist/interfaces/IWallet.js +2 -0
  52. package/dist/interfaces/IWalletQueue.d.ts +30 -0
  53. package/dist/interfaces/IWalletQueue.js +2 -0
  54. package/dist/interfaces/index.d.ts +8 -0
  55. package/dist/interfaces/index.js +8 -0
  56. package/dist/models/authority-resolution.d.ts +56 -0
  57. package/dist/models/authority-resolution.js +2 -0
  58. package/dist/models/bundle-editor-types.d.ts +89 -0
  59. package/dist/models/bundle-editor-types.js +45 -0
  60. package/dist/models/bundle.d.ts +5 -0
  61. package/dist/models/communication-attached-bundle-session.d.ts +195 -0
  62. package/dist/models/communication-attached-bundle-session.js +36 -0
  63. package/dist/models/confidential-storage.d.ts +2 -1
  64. package/dist/models/consent-rule.d.ts +11 -1
  65. package/dist/models/consent-rule.js +9 -1
  66. package/dist/models/index.d.ts +5 -0
  67. package/dist/models/index.js +5 -0
  68. package/dist/models/individual-onboarding.d.ts +18 -0
  69. package/dist/models/interoperable-claims/allergy-intolerance-claims.d.ts +12 -1
  70. package/dist/models/interoperable-claims/allergy-intolerance-claims.js +12 -1
  71. package/dist/models/interoperable-claims/condition-claims.d.ts +12 -1
  72. package/dist/models/interoperable-claims/condition-claims.js +12 -1
  73. package/dist/models/interoperable-claims/diagnostic-report-claims.d.ts +14 -4
  74. package/dist/models/interoperable-claims/diagnostic-report-claims.js +13 -3
  75. package/dist/models/interoperable-claims/document-reference-claims.d.ts +1 -0
  76. package/dist/models/interoperable-claims/document-reference-claims.js +3 -0
  77. package/dist/models/interoperable-claims/medication-statement-claims.d.ts +17 -0
  78. package/dist/models/interoperable-claims/medication-statement-claims.js +17 -0
  79. package/dist/models/profile-manager.d.ts +85 -0
  80. package/dist/models/profile-manager.js +2 -0
  81. package/dist/models/wallet.d.ts +59 -0
  82. package/dist/models/wallet.js +22 -0
  83. package/dist/utils/allergy-intolerance-entry-editor.d.ts +51 -0
  84. package/dist/utils/allergy-intolerance-entry-editor.js +55 -0
  85. package/dist/utils/authority-resolution.d.ts +37 -0
  86. package/dist/utils/authority-resolution.js +112 -0
  87. package/dist/utils/backend-message-manager-mem.d.ts +78 -0
  88. package/dist/utils/backend-message-manager-mem.js +238 -0
  89. package/dist/utils/bundle-didcomm-payload.d.ts +29 -0
  90. package/dist/utils/bundle-didcomm-payload.js +49 -0
  91. package/dist/utils/bundle-document-builder.d.ts +1 -0
  92. package/dist/utils/bundle-document-builder.js +134 -14
  93. package/dist/utils/bundle-editor-core.d.ts +181 -0
  94. package/dist/utils/bundle-editor-core.js +467 -0
  95. package/dist/utils/bundle-editor-helpers.d.ts +53 -0
  96. package/dist/utils/bundle-editor-helpers.js +146 -0
  97. package/dist/utils/bundle-editor-registry.d.ts +6 -0
  98. package/dist/utils/bundle-editor-registry.js +14 -0
  99. package/dist/utils/bundle-editor.d.ts +21 -722
  100. package/dist/utils/bundle-editor.js +21 -1601
  101. package/dist/utils/bundle-entry-editor.d.ts +96 -0
  102. package/dist/utils/bundle-entry-editor.js +305 -0
  103. package/dist/utils/bundle-query.d.ts +1 -0
  104. package/dist/utils/bundle-query.js +12 -3
  105. package/dist/utils/bundle-reader.d.ts +48 -0
  106. package/dist/utils/bundle-reader.js +175 -1
  107. package/dist/utils/care-plan-entry-editor.d.ts +43 -0
  108. package/dist/utils/care-plan-entry-editor.js +47 -0
  109. package/dist/utils/client-assertion.d.ts +38 -0
  110. package/dist/utils/client-assertion.js +113 -0
  111. package/dist/utils/clinical-impression-entry-editor.d.ts +43 -0
  112. package/dist/utils/clinical-impression-entry-editor.js +47 -0
  113. package/dist/utils/clinical-resource-entry-editor.d.ts +123 -0
  114. package/dist/utils/clinical-resource-entry-editor.js +296 -0
  115. package/dist/utils/communication-attached-bundle-session-helpers.d.ts +54 -0
  116. package/dist/utils/communication-attached-bundle-session-helpers.js +519 -0
  117. package/dist/utils/communication-attached-bundle-session.d.ts +87 -304
  118. package/dist/utils/communication-attached-bundle-session.js +175 -841
  119. package/dist/utils/communication-consent-access-editor.d.ts +85 -0
  120. package/dist/utils/communication-consent-access-editor.js +244 -0
  121. package/dist/utils/communication-didcomm-payload.d.ts +38 -0
  122. package/dist/utils/communication-didcomm-payload.js +85 -0
  123. package/dist/utils/communication-document-reference.d.ts +23 -0
  124. package/dist/utils/communication-document-reference.js +89 -0
  125. package/dist/utils/communication-editor.d.ts +53 -0
  126. package/dist/utils/communication-editor.js +97 -0
  127. package/dist/utils/condition-entry-editor.d.ts +30 -0
  128. package/dist/utils/condition-entry-editor.js +34 -0
  129. package/dist/utils/consent-claim-helpers.d.ts +4 -0
  130. package/dist/utils/consent-claim-helpers.js +15 -1
  131. package/dist/utils/coverage-entry-editor.d.ts +51 -0
  132. package/dist/utils/coverage-entry-editor.js +55 -0
  133. package/dist/utils/device-entry-editor.d.ts +59 -0
  134. package/dist/utils/device-entry-editor.js +63 -0
  135. package/dist/utils/device-use-statement-entry-editor.d.ts +43 -0
  136. package/dist/utils/device-use-statement-entry-editor.js +47 -0
  137. package/dist/utils/diagnostic-report-entry-editor.d.ts +74 -0
  138. package/dist/utils/diagnostic-report-entry-editor.js +136 -0
  139. package/dist/utils/did.d.ts +8 -0
  140. package/dist/utils/did.js +22 -0
  141. package/dist/utils/document-reference-entry-editor.d.ts +62 -0
  142. package/dist/utils/document-reference-entry-editor.js +66 -0
  143. package/dist/utils/employee-entry-editor.d.ts +36 -0
  144. package/dist/utils/employee-entry-editor.js +88 -0
  145. package/dist/utils/employee.d.ts +1 -0
  146. package/dist/utils/employee.js +2 -1
  147. package/dist/utils/encounter-entry-editor.d.ts +51 -0
  148. package/dist/utils/encounter-entry-editor.js +55 -0
  149. package/dist/utils/flag-entry-editor.d.ts +47 -0
  150. package/dist/utils/flag-entry-editor.js +51 -0
  151. package/dist/utils/immunization-entry-editor.d.ts +87 -0
  152. package/dist/utils/immunization-entry-editor.js +169 -0
  153. package/dist/utils/index.d.ts +15 -0
  154. package/dist/utils/index.js +15 -0
  155. package/dist/utils/individual-onboarding-acceptance-credential.d.ts +35 -0
  156. package/dist/utils/individual-onboarding-acceptance-credential.js +55 -0
  157. package/dist/utils/individual-smart.d.ts +59 -0
  158. package/dist/utils/individual-smart.js +135 -0
  159. package/dist/utils/medication-statement-entry-editor.d.ts +90 -0
  160. package/dist/utils/medication-statement-entry-editor.js +94 -0
  161. package/dist/utils/observation-component-entry-editor.d.ts +57 -0
  162. package/dist/utils/observation-component-entry-editor.js +105 -0
  163. package/dist/utils/observation-entry-editor.d.ts +29 -0
  164. package/dist/utils/observation-entry-editor.js +68 -0
  165. package/dist/utils/procedure-entry-editor.d.ts +71 -0
  166. package/dist/utils/procedure-entry-editor.js +75 -0
  167. package/dist/utils/professional-smart.d.ts +87 -0
  168. package/dist/utils/professional-smart.js +118 -4
  169. package/dist/utils/profile-manager-mem.d.ts +69 -0
  170. package/dist/utils/profile-manager-mem.js +79 -0
  171. package/dist/utils/profile-outbox-memory-repository.d.ts +34 -0
  172. package/dist/utils/profile-outbox-memory-repository.js +57 -0
  173. package/dist/utils/same-as.d.ts +40 -0
  174. package/dist/utils/same-as.js +72 -0
  175. package/dist/utils/unified-health-id.d.ts +51 -0
  176. package/dist/utils/unified-health-id.js +92 -0
  177. package/dist/utils/vital-sign-day-batch.d.ts +25 -0
  178. package/dist/utils/vital-sign-day-batch.js +115 -0
  179. package/dist/utils/vital-sign-entry-editor.d.ts +61 -0
  180. package/dist/utils/vital-sign-entry-editor.js +152 -0
  181. package/dist/utils/wallet-mem.d.ts +93 -0
  182. package/dist/utils/wallet-mem.js +277 -0
  183. package/dist/utils/wallet-memory-queue.d.ts +32 -0
  184. package/dist/utils/wallet-memory-queue.js +82 -0
  185. package/package.json +2 -1
@@ -0,0 +1,78 @@
1
+ import type { IWallet } from '../interfaces/IWallet';
2
+ import type { IProfileOutboxRepository } from '../interfaces/IProfileOutboxRepository';
3
+ import type { BackendMessageResponseRecord, BackendMessageTransport, ProfileOutboxMessageRecord, QueueProfileMessageInput } from '../models/profile-manager';
4
+ import { type WalletQueuedMessage } from '../models/wallet';
5
+ export type BackendMessageManagerMemOptions = Readonly<{
6
+ wallet: IWallet;
7
+ entityId: string;
8
+ transport: BackendMessageTransport;
9
+ outboxRepository: IProfileOutboxRepository;
10
+ }>;
11
+ /**
12
+ * Memory-backed backend message manager that:
13
+ * - queues outbound payloads through `WalletMem`
14
+ * - packs the next pending message as DIDComm-like JWS+JWE
15
+ * - submits it through an injected transport
16
+ * - unwraps the response and indexes it by `thid`
17
+ *
18
+ * This is the lowest reusable BFF/proxy slice before actor-specific SDK
19
+ * facades appear.
20
+ *
21
+ * Normal deployment story:
22
+ * - the injected wallet is the unlocked wallet of the user profile runtime
23
+ * - that same profile wallet usually encrypts outbound messages and decrypts
24
+ * replies
25
+ *
26
+ * Optional deployment story:
27
+ * - one BFF/proxy may also own a separate service wallet for its own
28
+ * envelopes, signatures, confidential storage, or tenant-level messaging
29
+ * - that service wallet is a different actor and must not be confused with
30
+ * the user-profile wallet handled here
31
+ */
32
+ export declare class BackendMessageManagerMem {
33
+ private readonly wallet;
34
+ private readonly entityId;
35
+ private readonly transport;
36
+ private readonly outboxRepository;
37
+ private readonly responsesByThread;
38
+ constructor(options: BackendMessageManagerMemOptions);
39
+ /**
40
+ * Queues one outbound business payload for later submission.
41
+ */
42
+ queueMessage(input: QueueProfileMessageInput): Promise<WalletQueuedMessage>;
43
+ /**
44
+ * Returns queued messages in current priority/FIFO processing order.
45
+ */
46
+ listQueuedMessages(): Promise<WalletQueuedMessage[]>;
47
+ /**
48
+ * Submits the next pending message through the injected transport and stores
49
+ * the decoded response by `thid` when one response envelope is returned.
50
+ */
51
+ submitNextPendingMessage(recipientEncryptionJwk: Record<string, unknown>): Promise<{
52
+ message: WalletQueuedMessage;
53
+ response?: BackendMessageResponseRecord;
54
+ pending?: boolean;
55
+ }>;
56
+ /**
57
+ * Returns the latest decoded response for one DIDComm thread.
58
+ */
59
+ getResponseByThreadId(thid: string): BackendMessageResponseRecord | undefined;
60
+ /**
61
+ * Lists submitted message history records preserved by the outbox repository.
62
+ */
63
+ listOutboxHistory(): Promise<ProfileOutboxMessageRecord[]>;
64
+ /**
65
+ * Polls one previously submitted asynchronous message by id.
66
+ */
67
+ pollSubmittedMessage(messageId: string): Promise<{
68
+ pending: boolean;
69
+ response?: BackendMessageResponseRecord;
70
+ record: ProfileOutboxMessageRecord;
71
+ }>;
72
+ /**
73
+ * Reads one decoded response by thread id, first from memory and then from the repository.
74
+ */
75
+ readResponseByThreadId(thid: string): Promise<BackendMessageResponseRecord | undefined>;
76
+ private persistMessageRecord;
77
+ private requireOutboxRecord;
78
+ }
@@ -0,0 +1,238 @@
1
+ // Copyright 2026 Conectate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
2
+ import { WalletQueueStatuses } from '../models/wallet.js';
3
+ function clone(value) {
4
+ return JSON.parse(JSON.stringify(value));
5
+ }
6
+ /**
7
+ * Memory-backed backend message manager that:
8
+ * - queues outbound payloads through `WalletMem`
9
+ * - packs the next pending message as DIDComm-like JWS+JWE
10
+ * - submits it through an injected transport
11
+ * - unwraps the response and indexes it by `thid`
12
+ *
13
+ * This is the lowest reusable BFF/proxy slice before actor-specific SDK
14
+ * facades appear.
15
+ *
16
+ * Normal deployment story:
17
+ * - the injected wallet is the unlocked wallet of the user profile runtime
18
+ * - that same profile wallet usually encrypts outbound messages and decrypts
19
+ * replies
20
+ *
21
+ * Optional deployment story:
22
+ * - one BFF/proxy may also own a separate service wallet for its own
23
+ * envelopes, signatures, confidential storage, or tenant-level messaging
24
+ * - that service wallet is a different actor and must not be confused with
25
+ * the user-profile wallet handled here
26
+ */
27
+ export class BackendMessageManagerMem {
28
+ wallet;
29
+ entityId;
30
+ transport;
31
+ outboxRepository;
32
+ responsesByThread = new Map();
33
+ constructor(options) {
34
+ this.wallet = options.wallet;
35
+ this.entityId = String(options.entityId || '').trim();
36
+ this.transport = options.transport;
37
+ this.outboxRepository = options.outboxRepository;
38
+ }
39
+ /**
40
+ * Queues one outbound business payload for later submission.
41
+ */
42
+ async queueMessage(input) {
43
+ if (!this.wallet.enqueueMessage) {
44
+ throw new Error('BackendMessageManagerMem requires wallet.enqueueMessage().');
45
+ }
46
+ const thid = typeof input.payload['thid'] === 'string' ? String(input.payload['thid']) : undefined;
47
+ const messageType = typeof input.payload['type'] === 'string' ? String(input.payload['type']) : undefined;
48
+ return this.wallet.enqueueMessage({
49
+ payload: input.payload,
50
+ priority: input.priority,
51
+ thid,
52
+ messageType,
53
+ });
54
+ }
55
+ /**
56
+ * Returns queued messages in current priority/FIFO processing order.
57
+ */
58
+ async listQueuedMessages() {
59
+ if (!this.wallet.listMessages) {
60
+ throw new Error('BackendMessageManagerMem requires wallet.listMessages().');
61
+ }
62
+ return this.wallet.listMessages();
63
+ }
64
+ /**
65
+ * Submits the next pending message through the injected transport and stores
66
+ * the decoded response by `thid` when one response envelope is returned.
67
+ */
68
+ async submitNextPendingMessage(recipientEncryptionJwk) {
69
+ if (!this.wallet.peekNextMessage) {
70
+ throw new Error('BackendMessageManagerMem requires wallet.peekNextMessage().');
71
+ }
72
+ if (!this.wallet.pack) {
73
+ throw new Error('BackendMessageManagerMem requires wallet.pack().');
74
+ }
75
+ if (!this.wallet.markMessageDelivered || !this.wallet.markMessageFailed) {
76
+ throw new Error('BackendMessageManagerMem requires wallet.markMessageDelivered() and wallet.markMessageFailed().');
77
+ }
78
+ const next = await this.wallet.peekNextMessage();
79
+ if (!next || next.status !== WalletQueueStatuses.Pending) {
80
+ throw new Error('BackendMessageManagerMem found no pending queued message.');
81
+ }
82
+ const envelope = await this.wallet.pack(next.payload, recipientEncryptionJwk, this.entityId);
83
+ const transportResult = await this.transport.submit({
84
+ message: next,
85
+ envelope,
86
+ });
87
+ if (!transportResult.accepted) {
88
+ const failed = await this.wallet.markMessageFailed(next.id, String(transportResult.errorMessage || 'transport rejected queued message'));
89
+ await this.persistMessageRecord(failed, envelope, undefined, {
90
+ transportStatus: 'failed',
91
+ });
92
+ return { message: failed };
93
+ }
94
+ const delivered = await this.wallet.markMessageDelivered(next.id);
95
+ if (!transportResult.responseEnvelope) {
96
+ await this.persistMessageRecord(delivered, envelope, undefined, {
97
+ transportStatus: 'submitted',
98
+ locationUrl: transportResult.locationUrl,
99
+ pollCount: 0,
100
+ });
101
+ return { message: delivered, pending: true };
102
+ }
103
+ if (!this.wallet.unpack) {
104
+ throw new Error('BackendMessageManagerMem requires wallet.unpack() to decode response envelopes.');
105
+ }
106
+ const unpacked = await this.wallet.unpack(transportResult.responseEnvelope, this.entityId);
107
+ const responseContent = clone(unpacked.content);
108
+ const thid = String(responseContent['thid'] || delivered.thid || delivered.id).trim();
109
+ const response = {
110
+ thid,
111
+ receivedAt: new Date().toISOString(),
112
+ content: responseContent,
113
+ meta: clone(unpacked.meta),
114
+ };
115
+ this.responsesByThread.set(thid, response);
116
+ await this.outboxRepository.putResponse(response);
117
+ await this.persistMessageRecord(delivered, envelope, response.thid, {
118
+ transportStatus: 'completed',
119
+ });
120
+ return {
121
+ message: delivered,
122
+ response,
123
+ };
124
+ }
125
+ /**
126
+ * Returns the latest decoded response for one DIDComm thread.
127
+ */
128
+ getResponseByThreadId(thid) {
129
+ const normalizedThreadId = String(thid || '').trim();
130
+ const stored = this.responsesByThread.get(normalizedThreadId);
131
+ return stored ? clone(stored) : undefined;
132
+ }
133
+ /**
134
+ * Lists submitted message history records preserved by the outbox repository.
135
+ */
136
+ async listOutboxHistory() {
137
+ return this.outboxRepository.listMessages();
138
+ }
139
+ /**
140
+ * Polls one previously submitted asynchronous message by id.
141
+ */
142
+ async pollSubmittedMessage(messageId) {
143
+ if (!this.transport.poll) {
144
+ throw new Error('BackendMessageManagerMem requires transport.poll() for asynchronous polling.');
145
+ }
146
+ const currentRecord = await this.requireOutboxRecord(messageId);
147
+ const pollResult = await this.transport.poll({
148
+ message: currentRecord,
149
+ });
150
+ if (pollResult.pending) {
151
+ const pendingRecord = await this.outboxRepository.putMessage({
152
+ ...currentRecord,
153
+ transportStatus: 'submitted',
154
+ pollCount: (currentRecord.pollCount || 0) + 1,
155
+ recordedAt: new Date().toISOString(),
156
+ });
157
+ return {
158
+ pending: true,
159
+ record: pendingRecord,
160
+ };
161
+ }
162
+ if (!pollResult.completed || !pollResult.responseEnvelope) {
163
+ const failedRecord = await this.outboxRepository.putMessage({
164
+ ...currentRecord,
165
+ transportStatus: pollResult.retryable ? 'submitted' : 'failed',
166
+ pollCount: (currentRecord.pollCount || 0) + 1,
167
+ recordedAt: new Date().toISOString(),
168
+ });
169
+ return {
170
+ pending: Boolean(pollResult.retryable),
171
+ record: failedRecord,
172
+ };
173
+ }
174
+ if (!this.wallet.unpack) {
175
+ throw new Error('BackendMessageManagerMem requires wallet.unpack() to decode polled response envelopes.');
176
+ }
177
+ const unpacked = await this.wallet.unpack(pollResult.responseEnvelope, this.entityId);
178
+ const responseContent = clone(unpacked.content);
179
+ const thid = String(responseContent['thid'] || currentRecord.thid || currentRecord.id).trim();
180
+ const response = {
181
+ thid,
182
+ receivedAt: new Date().toISOString(),
183
+ content: responseContent,
184
+ meta: clone(unpacked.meta),
185
+ };
186
+ this.responsesByThread.set(thid, response);
187
+ await this.outboxRepository.putResponse(response);
188
+ const completedRecord = await this.outboxRepository.putMessage({
189
+ ...currentRecord,
190
+ transportStatus: 'completed',
191
+ responseThid: response.thid,
192
+ pollCount: (currentRecord.pollCount || 0) + 1,
193
+ recordedAt: new Date().toISOString(),
194
+ });
195
+ return {
196
+ pending: false,
197
+ response,
198
+ record: completedRecord,
199
+ };
200
+ }
201
+ /**
202
+ * Reads one decoded response by thread id, first from memory and then from the repository.
203
+ */
204
+ async readResponseByThreadId(thid) {
205
+ const inMemory = this.getResponseByThreadId(thid);
206
+ if (inMemory)
207
+ return inMemory;
208
+ const stored = await this.outboxRepository.getLatestResponseByThreadId(thid);
209
+ if (stored) {
210
+ this.responsesByThread.set(String(stored.thid || '').trim(), clone(stored));
211
+ }
212
+ return stored;
213
+ }
214
+ async persistMessageRecord(message, envelope, responseThid, options = {}) {
215
+ await this.outboxRepository.putMessage({
216
+ id: message.id,
217
+ thid: message.thid,
218
+ messageType: message.messageType,
219
+ priority: message.priority,
220
+ status: message.status,
221
+ transportStatus: options.transportStatus,
222
+ recordedAt: new Date().toISOString(),
223
+ payload: clone(message.payload),
224
+ envelope,
225
+ ...(responseThid ? { responseThid } : {}),
226
+ ...(options.locationUrl ? { locationUrl: options.locationUrl } : {}),
227
+ ...(typeof options.pollCount === 'number' ? { pollCount: options.pollCount } : {}),
228
+ });
229
+ }
230
+ async requireOutboxRecord(messageId) {
231
+ const normalizedMessageId = String(messageId || '').trim();
232
+ const stored = await this.outboxRepository.getMessage(normalizedMessageId);
233
+ if (!stored) {
234
+ throw new Error(`BackendMessageManagerMem outbox record not found: ${normalizedMessageId}`);
235
+ }
236
+ return stored;
237
+ }
238
+ }
@@ -0,0 +1,29 @@
1
+ import type { IDecodedDidcommPayload } from '../models/confidential-message';
2
+ export declare const BundleDidcommEntryTypes: Readonly<{
3
+ readonly Batch: "Bundle-batch-request-v1.0";
4
+ readonly Document: "Bundle-document-request-v1.0";
5
+ }>;
6
+ /**
7
+ * Input contract for wrapping one bundle-like resource as one low-level
8
+ * DIDComm-style batch payload.
9
+ */
10
+ export type BuildDidcommPayloadFromBundleInput = Readonly<{
11
+ bundle: Record<string, unknown>;
12
+ iss: string;
13
+ aud: string;
14
+ jti: string;
15
+ thid: string;
16
+ entryType?: string;
17
+ }>;
18
+ /**
19
+ * Builds one low-level DIDComm-style payload carrying one direct `Bundle`
20
+ * resource in `body.data[0].resource`.
21
+ *
22
+ * This path is meant for operational bundles such as employee/search/purge
23
+ * flows that travel as a bundle itself, not nested inside a `Communication`.
24
+ */
25
+ export declare function buildDidcommPayloadFromBundle(input: BuildDidcommPayloadFromBundleInput): IDecodedDidcommPayload;
26
+ /**
27
+ * Reads the first `Bundle` resource carried under `body.data[0].resource`.
28
+ */
29
+ export declare function getFirstBundleResourceFromDidcommPayload(payload: IDecodedDidcommPayload): Record<string, unknown>;
@@ -0,0 +1,49 @@
1
+ // Copyright 2026 Conectate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
2
+ import { DidcommMessageTypes } from '../constants/didcomm.js';
3
+ export const BundleDidcommEntryTypes = Object.freeze({
4
+ Batch: DidcommMessageTypes.BundleBatchRequest,
5
+ Document: DidcommMessageTypes.BundleDocumentRequest,
6
+ });
7
+ function clone(value) {
8
+ return JSON.parse(JSON.stringify(value));
9
+ }
10
+ /**
11
+ * Builds one low-level DIDComm-style payload carrying one direct `Bundle`
12
+ * resource in `body.data[0].resource`.
13
+ *
14
+ * This path is meant for operational bundles such as employee/search/purge
15
+ * flows that travel as a bundle itself, not nested inside a `Communication`.
16
+ */
17
+ export function buildDidcommPayloadFromBundle(input) {
18
+ return {
19
+ iss: String(input.iss || '').trim(),
20
+ aud: String(input.aud || '').trim(),
21
+ jti: String(input.jti || '').trim(),
22
+ thid: String(input.thid || '').trim(),
23
+ type: String(input.entryType || BundleDidcommEntryTypes.Batch).trim(),
24
+ body: {
25
+ data: [
26
+ {
27
+ id: String(input.jti || '').trim(),
28
+ type: String(input.entryType || BundleDidcommEntryTypes.Batch).trim(),
29
+ resource: clone(input.bundle),
30
+ },
31
+ ],
32
+ },
33
+ };
34
+ }
35
+ /**
36
+ * Reads the first `Bundle` resource carried under `body.data[0].resource`.
37
+ */
38
+ export function getFirstBundleResourceFromDidcommPayload(payload) {
39
+ const first = Array.isArray(payload?.body?.data)
40
+ ? payload.body.data[0]
41
+ : undefined;
42
+ const resource = first?.resource && typeof first.resource === 'object'
43
+ ? clone(first.resource)
44
+ : {};
45
+ if (String(resource['resourceType'] || '').trim() !== 'Bundle') {
46
+ throw new Error('getFirstBundleResourceFromDidcommPayload requires body.data[0].resource.resourceType = Bundle.');
47
+ }
48
+ return resource;
49
+ }
@@ -4,6 +4,7 @@ export type BuildBundleDocumentFromClaimsInput = Readonly<{
4
4
  claimsList: readonly BundleDocumentClaims[];
5
5
  subjectDid?: string;
6
6
  compositionType?: string;
7
+ compositionClaims?: BundleDocumentClaims;
7
8
  }>;
8
9
  export type ValidateBundleDocumentResult = Readonly<{
9
10
  ok: boolean;
@@ -1,4 +1,10 @@
1
1
  import { ResourceTypesFhirR4 } from '../constants/fhir-resource-types.js';
2
+ import { AllergyIntoleranceClaim } from '../models/interoperable-claims/allergy-intolerance-claims.js';
3
+ import { CompositionClaim } from '../models/interoperable-claims/composition-claims.js';
4
+ import { ConditionClaim } from '../models/interoperable-claims/condition-claims.js';
5
+ import { DiagnosticReportClaim } from '../models/interoperable-claims/diagnostic-report-claims.js';
6
+ import { MedicationStatementClaim } from '../models/interoperable-claims/medication-statement-claims.js';
7
+ import { ClaimConsent } from '../models/consent-rule.js';
2
8
  import { allergyIntoleranceFlatToFhirR4, appointmentFlatToFhirR4, carePlanFlatToFhirR4, clinicalImpressionFlatToFhirR4, compositionFlatToFhirR4, conditionFlatToFhirR4, convertFhirResourceToClaims, coverageFlatToFhirR4, deviceFlatToFhirR4, documentReferenceFlatToFhirR4, diagnosticReportFlatToFhirR4, encounterFlatToFhirR4, flagFlatToFhirR4, flatClaimsToFhirResource, immunizationFlatToFhirR4, locationFlatToFhirR4, medicationStatementFlatToFhirR4, observationFromFlatToFhirR4, organizationFlatToFhirR4, procedureFlatToFhirR4, relatedPersonFlatToFhirR4, } from './clinical-resource-converters.js';
3
9
  function asTrimmedString(value) {
4
10
  if (value === undefined || value === null)
@@ -93,6 +99,43 @@ function normalizeSectionCode(value) {
93
99
  return { code: system };
94
100
  return { system, code };
95
101
  }
102
+ function resolveContainedReferenceListClaimKey(resourceType) {
103
+ switch (resourceType) {
104
+ case ResourceTypesFhirR4.MedicationStatement:
105
+ return MedicationStatementClaim.ContainedReferenceList;
106
+ case ResourceTypesFhirR4.AllergyIntolerance:
107
+ return AllergyIntoleranceClaim.ContainedReferenceList;
108
+ case ResourceTypesFhirR4.Condition:
109
+ return ConditionClaim.ContainedReferenceList;
110
+ case ResourceTypesFhirR4.DiagnosticReport:
111
+ return DiagnosticReportClaim.ContainedReferenceList;
112
+ case ResourceTypesFhirR4.Consent:
113
+ return ClaimConsent.containedReferenceList;
114
+ default:
115
+ return undefined;
116
+ }
117
+ }
118
+ function resolveContainedFlagClaimKey(resourceType) {
119
+ const normalized = asTrimmedString(resourceType);
120
+ return normalized ? `${normalized}.is-contained` : undefined;
121
+ }
122
+ function resolveContainedParentReferenceClaimKey(resourceType) {
123
+ const normalized = asTrimmedString(resourceType);
124
+ return normalized ? `${normalized}.contained-parent-reference` : undefined;
125
+ }
126
+ function mergeContainedResourceReferenceList(claims, references) {
127
+ const resourceType = detectClaimsResourceType(claims);
128
+ const claimKey = resourceType ? resolveContainedReferenceListClaimKey(resourceType) : undefined;
129
+ if (!claimKey || references.length === 0) {
130
+ return claims;
131
+ }
132
+ const current = splitCsv(claims[claimKey]);
133
+ const next = Array.from(new Set([...current, ...references].map((item) => asTrimmedString(item)).filter(Boolean)));
134
+ return {
135
+ ...claims,
136
+ [claimKey]: next.join(','),
137
+ };
138
+ }
96
139
  export function detectClaimsResourceType(claims) {
97
140
  const keys = Object.keys(claims || {});
98
141
  if (keys.some((key) => key.startsWith('MedicationStatement.')))
@@ -181,12 +224,47 @@ export function extractBundleDocumentClaimsList(bundle, context = 'org.hl7.fhir.
181
224
  .map((entry) => entry?.resource)
182
225
  .filter((resource) => resource && typeof resource === 'object')
183
226
  .filter((resource) => !ignoredResourceTypes.has(String(resource.resourceType || '')))
184
- .map((resource) => {
185
- const metaClaims = resource?.meta?.claims;
186
- if (metaClaims && typeof metaClaims === 'object' && !Array.isArray(metaClaims)) {
187
- return ensureClaimsIdentifier({ ...metaClaims }, resource);
188
- }
189
- return ensureClaimsIdentifier(convertFhirResourceToClaims(resource, context), resource);
227
+ .flatMap((resource) => {
228
+ const resourceRecord = resource;
229
+ const resourceReference = `${asTrimmedString(resourceRecord.resourceType)}/${asTrimmedString(resourceRecord.id)}`;
230
+ const metaClaims = resourceRecord?.meta?.claims;
231
+ const baseClaims = metaClaims && typeof metaClaims === 'object' && !Array.isArray(metaClaims)
232
+ ? ensureClaimsIdentifier({ ...metaClaims }, resourceRecord)
233
+ : ensureClaimsIdentifier(convertFhirResourceToClaims(resourceRecord, context), resourceRecord);
234
+ const containedResources = Array.isArray(resourceRecord.contained)
235
+ ? resourceRecord.contained
236
+ : [];
237
+ const containedClaimsList = containedResources.map((containedResource, index) => {
238
+ const containedMetaClaims = (containedResource?.meta?.claims);
239
+ const containedClaims = containedMetaClaims && typeof containedMetaClaims === 'object' && !Array.isArray(containedMetaClaims)
240
+ ? ensureClaimsIdentifier({ ...containedMetaClaims }, containedResource)
241
+ : ensureClaimsIdentifier(convertFhirResourceToClaims(containedResource, context), containedResource);
242
+ const containedResourceType = asTrimmedString(containedResource.resourceType);
243
+ const containedFlagClaimKey = resolveContainedFlagClaimKey(containedResourceType);
244
+ const containedParentReferenceClaimKey = resolveContainedParentReferenceClaimKey(containedResourceType);
245
+ return {
246
+ ...containedClaims,
247
+ ...(containedFlagClaimKey ? { [containedFlagClaimKey]: true } : {}),
248
+ ...(containedParentReferenceClaimKey ? { [containedParentReferenceClaimKey]: resourceReference } : {}),
249
+ ...(asTrimmedString(containedResource.id) ? {} : {
250
+ [`${containedResource.resourceType}.identifier`]: `${resourceReference}#contained-${index + 1}`,
251
+ }),
252
+ };
253
+ });
254
+ const containedReferences = containedResources
255
+ .map((containedResource, index) => {
256
+ const containedType = asTrimmedString(containedResource.resourceType);
257
+ const containedId = asTrimmedString(containedResource.id) || `${resourceReference}#contained-${index + 1}`;
258
+ if (!containedType || !containedId) {
259
+ return '';
260
+ }
261
+ return `${containedType}/${containedId}`;
262
+ })
263
+ .filter(Boolean);
264
+ return [
265
+ mergeContainedResourceReferenceList(baseClaims, containedReferences),
266
+ ...containedClaimsList,
267
+ ];
190
268
  });
191
269
  }
192
270
  export function resolveClaimsSectionList(claims) {
@@ -202,9 +280,17 @@ export function resolveClaimsSectionList(claims) {
202
280
  return Array.from(new Set(values));
203
281
  }
204
282
  export function buildBundleDocumentFromClaims(input) {
205
- const compositionType = asTrimmedString(input.compositionType) || 'http://loinc.org|60591-5';
283
+ const compositionClaims = { ...(input.compositionClaims || {}) };
284
+ const compositionType = asTrimmedString(input.compositionType || compositionClaims[CompositionClaim.Type]) || 'http://loinc.org|60591-5';
285
+ const compositionSubject = asTrimmedString(input.subjectDid || compositionClaims[CompositionClaim.Subject]);
286
+ const compositionIdentifier = asTrimmedString(compositionClaims[CompositionClaim.Identifier]);
287
+ const compositionTitle = asTrimmedString(compositionClaims[CompositionClaim.Title]);
288
+ const compositionDate = asTrimmedString(compositionClaims[CompositionClaim.Date]);
289
+ const compositionAuthorList = splitCsv(compositionClaims[CompositionClaim.Author]);
206
290
  const compositionSections = new Map();
207
- const entries = [];
291
+ const visibleEntries = [];
292
+ const resourceByReference = new Map();
293
+ const containedChildrenByParentReference = new Map();
208
294
  input.claimsList.forEach((claims, index) => {
209
295
  const resource = convertClaimsToFhirResource(claims);
210
296
  resource.meta = {
@@ -212,8 +298,23 @@ export function buildBundleDocumentFromClaims(input) {
212
298
  claims: { ...claims },
213
299
  };
214
300
  ensureResourceIdentifier(resource, claims, index);
215
- entries.push({ resource });
216
- const resourceRef = `${resource.resourceType}/${resource.id}`;
301
+ const resourceReference = `${resource.resourceType}/${asTrimmedString(resource.id)}`;
302
+ const containedFlagClaimKey = resolveContainedFlagClaimKey(resource.resourceType);
303
+ const containedParentReferenceClaimKey = resolveContainedParentReferenceClaimKey(resource.resourceType);
304
+ const isContained = containedFlagClaimKey
305
+ ? (claims[containedFlagClaimKey] === true || claims[containedFlagClaimKey] === 'true')
306
+ : false;
307
+ const containedParentReference = containedParentReferenceClaimKey
308
+ ? asTrimmedString(claims[containedParentReferenceClaimKey])
309
+ : '';
310
+ if (isContained && containedParentReference) {
311
+ const currentChildren = containedChildrenByParentReference.get(containedParentReference) || [];
312
+ containedChildrenByParentReference.set(containedParentReference, [...currentChildren, resource]);
313
+ return;
314
+ }
315
+ visibleEntries.push({ resource });
316
+ resourceByReference.set(resourceReference, resource);
317
+ const resourceRef = resourceReference;
217
318
  resolveClaimsSectionList(claims).forEach((sectionValue) => {
218
319
  const normalizedSection = asTrimmedString(sectionValue);
219
320
  if (!normalizedSection)
@@ -230,6 +331,13 @@ export function buildBundleDocumentFromClaims(input) {
230
331
  compositionSections.set(normalizedSection, section);
231
332
  });
232
333
  });
334
+ containedChildrenByParentReference.forEach((children, parentReference) => {
335
+ const parent = resourceByReference.get(parentReference);
336
+ if (!parent) {
337
+ return;
338
+ }
339
+ parent.contained = [...(Array.isArray(parent.contained) ? parent.contained : []), ...children];
340
+ });
233
341
  const [compositionSystem, compositionCode] = compositionType.split('|');
234
342
  return {
235
343
  resourceType: ResourceTypesFhirR4.Bundle,
@@ -238,8 +346,14 @@ export function buildBundleDocumentFromClaims(input) {
238
346
  {
239
347
  resource: {
240
348
  resourceType: ResourceTypesFhirR4.Composition,
349
+ ...(compositionIdentifier ? { id: compositionIdentifier } : {}),
241
350
  status: 'final',
242
- subject: input.subjectDid ? { reference: input.subjectDid } : undefined,
351
+ ...(compositionIdentifier ? {
352
+ identifier: [{ value: compositionIdentifier }],
353
+ } : {}),
354
+ ...(compositionSubject ? {
355
+ subject: { reference: compositionSubject },
356
+ } : {}),
243
357
  type: {
244
358
  coding: [
245
359
  compositionCode
@@ -247,18 +361,24 @@ export function buildBundleDocumentFromClaims(input) {
247
361
  : { code: compositionSystem },
248
362
  ],
249
363
  },
364
+ ...(compositionTitle ? { title: compositionTitle } : {}),
365
+ ...(compositionDate ? { date: compositionDate } : {}),
366
+ ...(compositionAuthorList.length > 0 ? {
367
+ author: compositionAuthorList.map((reference) => ({ reference })),
368
+ } : {}),
369
+ meta: { claims: compositionClaims },
250
370
  section: Array.from(compositionSections.values()),
251
371
  },
252
372
  },
253
- ...(input.subjectDid
373
+ ...(compositionSubject
254
374
  ? [{
255
375
  resource: {
256
376
  resourceType: 'Patient',
257
- id: input.subjectDid,
377
+ id: compositionSubject,
258
378
  },
259
379
  }]
260
380
  : []),
261
- ...entries,
381
+ ...visibleEntries,
262
382
  ],
263
383
  };
264
384
  }