gdc-common-utils-ts 2.0.18 → 2.1.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 (57) hide show
  1. package/README.md +0 -1
  2. package/dist/examples/bundle-didcomm-payload.d.ts +11 -0
  3. package/dist/examples/bundle-didcomm-payload.js +13 -0
  4. package/dist/examples/communication-attached-bundle-session.js +2 -1
  5. package/dist/examples/communication-didcomm-payload.d.ts +35 -0
  6. package/dist/examples/communication-didcomm-payload.js +38 -0
  7. package/dist/examples/index.d.ts +4 -0
  8. package/dist/examples/index.js +4 -0
  9. package/dist/examples/profile-manager-mem.d.ts +45 -0
  10. package/dist/examples/profile-manager-mem.js +29 -0
  11. package/dist/examples/shared.d.ts +1 -0
  12. package/dist/examples/shared.js +1 -0
  13. package/dist/examples/wallet-mem.d.ts +38 -0
  14. package/dist/examples/wallet-mem.js +40 -0
  15. package/dist/index.d.ts +1 -0
  16. package/dist/index.js +1 -0
  17. package/dist/interfaces/IProfileOutboxRepository.d.ts +36 -0
  18. package/dist/interfaces/IProfileOutboxRepository.js +2 -0
  19. package/dist/interfaces/IWallet.d.ts +74 -0
  20. package/dist/interfaces/IWallet.js +2 -0
  21. package/dist/interfaces/IWalletQueue.d.ts +30 -0
  22. package/dist/interfaces/IWalletQueue.js +2 -0
  23. package/dist/interfaces/index.d.ts +8 -0
  24. package/dist/interfaces/index.js +8 -0
  25. package/dist/models/communication-attached-bundle-session.d.ts +193 -0
  26. package/dist/models/communication-attached-bundle-session.js +34 -0
  27. package/dist/models/index.d.ts +3 -0
  28. package/dist/models/index.js +3 -0
  29. package/dist/models/interoperable-claims/diagnostic-report-claims.d.ts +1 -2
  30. package/dist/models/interoperable-claims/diagnostic-report-claims.js +1 -2
  31. package/dist/models/profile-manager.d.ts +85 -0
  32. package/dist/models/profile-manager.js +2 -0
  33. package/dist/models/wallet.d.ts +59 -0
  34. package/dist/models/wallet.js +22 -0
  35. package/dist/utils/backend-message-manager-mem.d.ts +67 -0
  36. package/dist/utils/backend-message-manager-mem.js +227 -0
  37. package/dist/utils/bundle-didcomm-payload.d.ts +29 -0
  38. package/dist/utils/bundle-didcomm-payload.js +48 -0
  39. package/dist/utils/communication-attached-bundle-session-helpers.d.ts +54 -0
  40. package/dist/utils/communication-attached-bundle-session-helpers.js +517 -0
  41. package/dist/utils/communication-attached-bundle-session.d.ts +59 -302
  42. package/dist/utils/communication-attached-bundle-session.js +114 -839
  43. package/dist/utils/communication-consent-access-editor.d.ts +85 -0
  44. package/dist/utils/communication-consent-access-editor.js +244 -0
  45. package/dist/utils/communication-didcomm-payload.d.ts +33 -0
  46. package/dist/utils/communication-didcomm-payload.js +75 -0
  47. package/dist/utils/index.d.ts +8 -0
  48. package/dist/utils/index.js +8 -0
  49. package/dist/utils/profile-manager-mem.d.ts +69 -0
  50. package/dist/utils/profile-manager-mem.js +79 -0
  51. package/dist/utils/profile-outbox-memory-repository.d.ts +34 -0
  52. package/dist/utils/profile-outbox-memory-repository.js +57 -0
  53. package/dist/utils/wallet-mem.d.ts +93 -0
  54. package/dist/utils/wallet-mem.js +277 -0
  55. package/dist/utils/wallet-memory-queue.d.ts +32 -0
  56. package/dist/utils/wallet-memory-queue.js +82 -0
  57. package/package.json +1 -1
@@ -0,0 +1,227 @@
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
+ export class BackendMessageManagerMem {
17
+ wallet;
18
+ entityId;
19
+ transport;
20
+ outboxRepository;
21
+ responsesByThread = new Map();
22
+ constructor(options) {
23
+ this.wallet = options.wallet;
24
+ this.entityId = String(options.entityId || '').trim();
25
+ this.transport = options.transport;
26
+ this.outboxRepository = options.outboxRepository;
27
+ }
28
+ /**
29
+ * Queues one outbound business payload for later submission.
30
+ */
31
+ async queueMessage(input) {
32
+ if (!this.wallet.enqueueMessage) {
33
+ throw new Error('BackendMessageManagerMem requires wallet.enqueueMessage().');
34
+ }
35
+ const thid = typeof input.payload['thid'] === 'string' ? String(input.payload['thid']) : undefined;
36
+ const messageType = typeof input.payload['type'] === 'string' ? String(input.payload['type']) : undefined;
37
+ return this.wallet.enqueueMessage({
38
+ payload: input.payload,
39
+ priority: input.priority,
40
+ thid,
41
+ messageType,
42
+ });
43
+ }
44
+ /**
45
+ * Returns queued messages in current priority/FIFO processing order.
46
+ */
47
+ async listQueuedMessages() {
48
+ if (!this.wallet.listMessages) {
49
+ throw new Error('BackendMessageManagerMem requires wallet.listMessages().');
50
+ }
51
+ return this.wallet.listMessages();
52
+ }
53
+ /**
54
+ * Submits the next pending message through the injected transport and stores
55
+ * the decoded response by `thid` when one response envelope is returned.
56
+ */
57
+ async submitNextPendingMessage(recipientEncryptionJwk) {
58
+ if (!this.wallet.peekNextMessage) {
59
+ throw new Error('BackendMessageManagerMem requires wallet.peekNextMessage().');
60
+ }
61
+ if (!this.wallet.pack) {
62
+ throw new Error('BackendMessageManagerMem requires wallet.pack().');
63
+ }
64
+ if (!this.wallet.markMessageDelivered || !this.wallet.markMessageFailed) {
65
+ throw new Error('BackendMessageManagerMem requires wallet.markMessageDelivered() and wallet.markMessageFailed().');
66
+ }
67
+ const next = await this.wallet.peekNextMessage();
68
+ if (!next || next.status !== WalletQueueStatuses.Pending) {
69
+ throw new Error('BackendMessageManagerMem found no pending queued message.');
70
+ }
71
+ const envelope = await this.wallet.pack(next.payload, recipientEncryptionJwk, this.entityId);
72
+ const transportResult = await this.transport.submit({
73
+ message: next,
74
+ envelope,
75
+ });
76
+ if (!transportResult.accepted) {
77
+ const failed = await this.wallet.markMessageFailed(next.id, String(transportResult.errorMessage || 'transport rejected queued message'));
78
+ await this.persistMessageRecord(failed, envelope, undefined, {
79
+ transportStatus: 'failed',
80
+ });
81
+ return { message: failed };
82
+ }
83
+ const delivered = await this.wallet.markMessageDelivered(next.id);
84
+ if (!transportResult.responseEnvelope) {
85
+ await this.persistMessageRecord(delivered, envelope, undefined, {
86
+ transportStatus: 'submitted',
87
+ locationUrl: transportResult.locationUrl,
88
+ pollCount: 0,
89
+ });
90
+ return { message: delivered, pending: true };
91
+ }
92
+ if (!this.wallet.unpack) {
93
+ throw new Error('BackendMessageManagerMem requires wallet.unpack() to decode response envelopes.');
94
+ }
95
+ const unpacked = await this.wallet.unpack(transportResult.responseEnvelope, this.entityId);
96
+ const responseContent = clone(unpacked.content);
97
+ const thid = String(responseContent['thid'] || delivered.thid || delivered.id).trim();
98
+ const response = {
99
+ thid,
100
+ receivedAt: new Date().toISOString(),
101
+ content: responseContent,
102
+ meta: clone(unpacked.meta),
103
+ };
104
+ this.responsesByThread.set(thid, response);
105
+ await this.outboxRepository.putResponse(response);
106
+ await this.persistMessageRecord(delivered, envelope, response.thid, {
107
+ transportStatus: 'completed',
108
+ });
109
+ return {
110
+ message: delivered,
111
+ response,
112
+ };
113
+ }
114
+ /**
115
+ * Returns the latest decoded response for one DIDComm thread.
116
+ */
117
+ getResponseByThreadId(thid) {
118
+ const normalizedThreadId = String(thid || '').trim();
119
+ const stored = this.responsesByThread.get(normalizedThreadId);
120
+ return stored ? clone(stored) : undefined;
121
+ }
122
+ /**
123
+ * Lists submitted message history records preserved by the outbox repository.
124
+ */
125
+ async listOutboxHistory() {
126
+ return this.outboxRepository.listMessages();
127
+ }
128
+ /**
129
+ * Polls one previously submitted asynchronous message by id.
130
+ */
131
+ async pollSubmittedMessage(messageId) {
132
+ if (!this.transport.poll) {
133
+ throw new Error('BackendMessageManagerMem requires transport.poll() for asynchronous polling.');
134
+ }
135
+ const currentRecord = await this.requireOutboxRecord(messageId);
136
+ const pollResult = await this.transport.poll({
137
+ message: currentRecord,
138
+ });
139
+ if (pollResult.pending) {
140
+ const pendingRecord = await this.outboxRepository.putMessage({
141
+ ...currentRecord,
142
+ transportStatus: 'submitted',
143
+ pollCount: (currentRecord.pollCount || 0) + 1,
144
+ recordedAt: new Date().toISOString(),
145
+ });
146
+ return {
147
+ pending: true,
148
+ record: pendingRecord,
149
+ };
150
+ }
151
+ if (!pollResult.completed || !pollResult.responseEnvelope) {
152
+ const failedRecord = await this.outboxRepository.putMessage({
153
+ ...currentRecord,
154
+ transportStatus: pollResult.retryable ? 'submitted' : 'failed',
155
+ pollCount: (currentRecord.pollCount || 0) + 1,
156
+ recordedAt: new Date().toISOString(),
157
+ });
158
+ return {
159
+ pending: Boolean(pollResult.retryable),
160
+ record: failedRecord,
161
+ };
162
+ }
163
+ if (!this.wallet.unpack) {
164
+ throw new Error('BackendMessageManagerMem requires wallet.unpack() to decode polled response envelopes.');
165
+ }
166
+ const unpacked = await this.wallet.unpack(pollResult.responseEnvelope, this.entityId);
167
+ const responseContent = clone(unpacked.content);
168
+ const thid = String(responseContent['thid'] || currentRecord.thid || currentRecord.id).trim();
169
+ const response = {
170
+ thid,
171
+ receivedAt: new Date().toISOString(),
172
+ content: responseContent,
173
+ meta: clone(unpacked.meta),
174
+ };
175
+ this.responsesByThread.set(thid, response);
176
+ await this.outboxRepository.putResponse(response);
177
+ const completedRecord = await this.outboxRepository.putMessage({
178
+ ...currentRecord,
179
+ transportStatus: 'completed',
180
+ responseThid: response.thid,
181
+ pollCount: (currentRecord.pollCount || 0) + 1,
182
+ recordedAt: new Date().toISOString(),
183
+ });
184
+ return {
185
+ pending: false,
186
+ response,
187
+ record: completedRecord,
188
+ };
189
+ }
190
+ /**
191
+ * Reads one decoded response by thread id, first from memory and then from the repository.
192
+ */
193
+ async readResponseByThreadId(thid) {
194
+ const inMemory = this.getResponseByThreadId(thid);
195
+ if (inMemory)
196
+ return inMemory;
197
+ const stored = await this.outboxRepository.getLatestResponseByThreadId(thid);
198
+ if (stored) {
199
+ this.responsesByThread.set(String(stored.thid || '').trim(), clone(stored));
200
+ }
201
+ return stored;
202
+ }
203
+ async persistMessageRecord(message, envelope, responseThid, options = {}) {
204
+ await this.outboxRepository.putMessage({
205
+ id: message.id,
206
+ thid: message.thid,
207
+ messageType: message.messageType,
208
+ priority: message.priority,
209
+ status: message.status,
210
+ transportStatus: options.transportStatus,
211
+ recordedAt: new Date().toISOString(),
212
+ payload: clone(message.payload),
213
+ envelope,
214
+ ...(responseThid ? { responseThid } : {}),
215
+ ...(options.locationUrl ? { locationUrl: options.locationUrl } : {}),
216
+ ...(typeof options.pollCount === 'number' ? { pollCount: options.pollCount } : {}),
217
+ });
218
+ }
219
+ async requireOutboxRecord(messageId) {
220
+ const normalizedMessageId = String(messageId || '').trim();
221
+ const stored = await this.outboxRepository.getMessage(normalizedMessageId);
222
+ if (!stored) {
223
+ throw new Error(`BackendMessageManagerMem outbox record not found: ${normalizedMessageId}`);
224
+ }
225
+ return stored;
226
+ }
227
+ }
@@ -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,48 @@
1
+ // Copyright 2026 Conectate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
2
+ export const BundleDidcommEntryTypes = Object.freeze({
3
+ Batch: 'Bundle-batch-request-v1.0',
4
+ Document: 'Bundle-document-request-v1.0',
5
+ });
6
+ function clone(value) {
7
+ return JSON.parse(JSON.stringify(value));
8
+ }
9
+ /**
10
+ * Builds one low-level DIDComm-style payload carrying one direct `Bundle`
11
+ * resource in `body.data[0].resource`.
12
+ *
13
+ * This path is meant for operational bundles such as employee/search/purge
14
+ * flows that travel as a bundle itself, not nested inside a `Communication`.
15
+ */
16
+ export function buildDidcommPayloadFromBundle(input) {
17
+ return {
18
+ iss: String(input.iss || '').trim(),
19
+ aud: String(input.aud || '').trim(),
20
+ jti: String(input.jti || '').trim(),
21
+ thid: String(input.thid || '').trim(),
22
+ type: String(input.entryType || BundleDidcommEntryTypes.Batch).trim(),
23
+ body: {
24
+ data: [
25
+ {
26
+ id: String(input.jti || '').trim(),
27
+ type: String(input.entryType || BundleDidcommEntryTypes.Batch).trim(),
28
+ resource: clone(input.bundle),
29
+ },
30
+ ],
31
+ },
32
+ };
33
+ }
34
+ /**
35
+ * Reads the first `Bundle` resource carried under `body.data[0].resource`.
36
+ */
37
+ export function getFirstBundleResourceFromDidcommPayload(payload) {
38
+ const first = Array.isArray(payload?.body?.data)
39
+ ? payload.body.data[0]
40
+ : undefined;
41
+ const resource = first?.resource && typeof first.resource === 'object'
42
+ ? clone(first.resource)
43
+ : {};
44
+ if (String(resource['resourceType'] || '').trim() !== 'Bundle') {
45
+ throw new Error('getFirstBundleResourceFromDidcommPayload requires body.data[0].resource.resourceType = Bundle.');
46
+ }
47
+ return resource;
48
+ }
@@ -0,0 +1,54 @@
1
+ import { type HealthcareCanonicalSectionFamily } from '../constants/healthcare';
2
+ import type { BundleEntry, BundleEntryResource, BundleJsonApi } from '../models/bundle';
3
+ import type { CommunicationAttachedBundleSessionMode, ConsentEditorClassifiedActorRole, ConsentEditorClassifiedActors, ConsentEditorClassifiedOrganization, ConsentEditorClassifiedPurpose, ConsentEditorClassifiedRole, ConsentEditorClassifiedRoles, ConsentEditorClassifiedTarget, ConsentViewModel, ConsentEditorScopeCode, ConsentEditorTargetKind } from '../models/communication-attached-bundle-session';
4
+ export declare const CSV_SEPARATOR = ",";
5
+ export declare function ensureEntryResource(entry: BundleEntry, mode: CommunicationAttachedBundleSessionMode): BundleEntryResource;
6
+ export declare function validateBundleLike(bundle: BundleJsonApi<BundleEntry>, mode: CommunicationAttachedBundleSessionMode): void;
7
+ export declare function createEmptyBundle(): BundleJsonApi<BundleEntry>;
8
+ export declare function encodeBundleToBase64(bundle: BundleJsonApi<BundleEntry>): string;
9
+ export declare function cloneBundle(bundle: BundleJsonApi<BundleEntry>): BundleJsonApi<BundleEntry>;
10
+ export declare function cloneEntry(entry: BundleEntry): BundleEntry;
11
+ export declare function cloneUnknownValue<T>(value: T): T;
12
+ export declare function asTrimmedString(value: unknown): string;
13
+ export declare function resolveContainedDocumentsClaimKey(resourceType: string): string;
14
+ export declare function resolveSubjectFromClaims(claims: Record<string, unknown>): string;
15
+ /**
16
+ * Resolves the canonical resource-scoped identifier used for upsert matching.
17
+ */
18
+ export declare function resolveBundleEntryIdentifier(claims: Record<string, unknown>): string;
19
+ /**
20
+ * Resolves the stable entry id value stored in `BundleEntry.id`.
21
+ */
22
+ export declare function resolveBundleEntryCanonicalIdValue(claims: Record<string, unknown>): string;
23
+ export declare function setIfMissing(target: Record<string, unknown>, key: string, value: unknown): void;
24
+ export declare function runtimeUuid(prefix: string): string;
25
+ export declare function splitCsv(value: unknown): string[];
26
+ export declare function normalizeCsvValues(values: readonly unknown[]): string[];
27
+ export declare function buildClassifiedConsentTarget(kind: ConsentEditorTargetKind, code: string, scopeCodes: readonly ConsentEditorScopeCode[], sectionFamily?: HealthcareCanonicalSectionFamily): ConsentEditorClassifiedTarget;
28
+ export declare function buildSectionCatalogOptions(sectionKind: HealthcareCanonicalSectionFamily): ConsentEditorClassifiedTarget[];
29
+ export declare function normalizeScopeCodes(scopeCodes: readonly ConsentEditorScopeCode[]): ConsentEditorScopeCode[];
30
+ export declare function normalizeClassifiedTargets(targets: readonly ConsentEditorClassifiedTarget[]): ConsentEditorClassifiedTarget[];
31
+ export declare function resolveConsentTargetDisplay(kind: ConsentEditorTargetKind, code: string): string | undefined;
32
+ export declare function resolveConsentScopeDisplay(scopeCode: ConsentEditorScopeCode): string;
33
+ export declare function resolveConsentPurposeDisplay(code: string): string;
34
+ export declare function looksLikeEmailToken(value: string): boolean;
35
+ export declare function looksLikePhoneToken(value: string): boolean;
36
+ export declare function looksLikeJurisdictionToken(value: string): boolean;
37
+ export declare function parseDidWebOrganizationToken(value: string): ConsentEditorClassifiedOrganization | undefined;
38
+ export declare function parseConsentActorRole(value: string): ConsentEditorClassifiedActorRole | undefined;
39
+ export declare function flattenClassifiedActors(classifiedActors: ConsentEditorClassifiedActors): string[];
40
+ export declare function serializeClassifiedOrganization(organization: ConsentEditorClassifiedOrganization): string;
41
+ export declare function flattenClassifiedRoles(classifiedRoles: ConsentEditorClassifiedRoles): string[];
42
+ export declare function serializeClassifiedRole(role: ConsentEditorClassifiedRole): string;
43
+ export declare function flattenClassifiedTargets(classifiedTargets: readonly ConsentEditorClassifiedTarget[]): Readonly<{
44
+ coreSections: readonly string[];
45
+ kindOfDocuments: readonly string[];
46
+ typeOfServices: readonly string[];
47
+ subjectMatterDomains: readonly string[];
48
+ resourceTypes: readonly string[];
49
+ }>;
50
+ export declare function classifyConsentTargetsFromClaims(claims: Record<string, unknown>): ConsentEditorClassifiedTarget[];
51
+ export declare function classifyConsentPurposes(purposeCodes: readonly string[]): ConsentEditorClassifiedPurpose[];
52
+ export declare function classifyConsentRoles(roleTokens: readonly string[]): ConsentEditorClassifiedRoles;
53
+ export declare function classifyConsentActors(actorTokens: readonly string[], actorRoleToken?: string): ConsentEditorClassifiedActors;
54
+ export declare function buildConsentViewModel(activeEntry: BundleEntry | null, decision: string, classifiedActors: ConsentEditorClassifiedActors, classifiedRoles: ConsentEditorClassifiedRoles, classifiedPurposes: readonly ConsentEditorClassifiedPurpose[], classifiedTargets: readonly ConsentEditorClassifiedTarget[]): ConsentViewModel;