gdc-common-utils-ts 1.24.1 → 2.0.1

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 (54) hide show
  1. package/README.md +39 -0
  2. package/dist/constants/index.d.ts +1 -0
  3. package/dist/constants/index.js +1 -0
  4. package/dist/constants/profile-runtime.d.ts +33 -0
  5. package/dist/constants/profile-runtime.js +30 -0
  6. package/dist/examples/frontend-session.js +2 -1
  7. package/dist/examples/ica-activation-proof.d.ts +11 -4
  8. package/dist/examples/ica-activation-proof.js +11 -4
  9. package/dist/examples/index.d.ts +1 -0
  10. package/dist/examples/index.js +1 -0
  11. package/dist/examples/lifecycle.js +5 -5
  12. package/dist/examples/organization-controller.d.ts +0 -1
  13. package/dist/examples/organization-controller.js +0 -1
  14. package/dist/examples/profile-runtime.d.ts +16 -0
  15. package/dist/examples/profile-runtime.js +18 -0
  16. package/dist/examples/shared.d.ts +9 -0
  17. package/dist/examples/shared.js +9 -0
  18. package/dist/interfaces/Cryptography.types.d.ts +15 -0
  19. package/dist/models/identity-bootstrap.d.ts +9 -0
  20. package/dist/utils/activation-policy.d.ts +45 -1
  21. package/dist/utils/activation-policy.js +65 -7
  22. package/dist/utils/activation-request.d.ts +63 -2
  23. package/dist/utils/activation-request.js +82 -0
  24. package/dist/utils/communication-participant-search-test-data.d.ts +17 -0
  25. package/dist/utils/communication-participant-search-test-data.js +39 -0
  26. package/dist/utils/communication-participant-search.d.ts +108 -0
  27. package/dist/utils/communication-participant-search.js +425 -0
  28. package/dist/utils/communication-retention-policy.d.ts +32 -0
  29. package/dist/utils/communication-retention-policy.js +41 -0
  30. package/dist/utils/communication-search-editor.d.ts +54 -0
  31. package/dist/utils/communication-search-editor.js +156 -0
  32. package/dist/utils/fhir-search.d.ts +32 -0
  33. package/dist/utils/fhir-search.js +45 -0
  34. package/dist/utils/index.d.ts +8 -0
  35. package/dist/utils/index.js +8 -0
  36. package/dist/utils/individual-organization-lifecycle.d.ts +15 -5
  37. package/dist/utils/individual-organization-lifecycle.js +53 -6
  38. package/dist/utils/interoperable-resource-operation.d.ts +4 -4
  39. package/dist/utils/interoperable-resource-operation.js +22 -22
  40. package/dist/utils/jwk-thumbprint.d.ts +40 -0
  41. package/dist/utils/jwk-thumbprint.js +57 -0
  42. package/dist/utils/legal-organization-onboarding.d.ts +97 -0
  43. package/dist/utils/legal-organization-onboarding.js +128 -0
  44. package/dist/utils/license-commercial-search.d.ts +6 -6
  45. package/dist/utils/license-commercial-search.js +2 -2
  46. package/dist/utils/license-list-search.d.ts +7 -7
  47. package/dist/utils/license-list-search.js +23 -23
  48. package/dist/utils/license-offer-order.d.ts +19 -19
  49. package/dist/utils/license-offer-order.js +68 -68
  50. package/dist/utils/organization-lifecycle.d.ts +59 -0
  51. package/dist/utils/organization-lifecycle.js +155 -0
  52. package/dist/utils/same-as.d.ts +41 -0
  53. package/dist/utils/same-as.js +83 -0
  54. package/package.json +2 -2
@@ -0,0 +1,425 @@
1
+ import { ResourceTypesFhirR4 } from '../constants/fhir-resource-types.js';
2
+ import { CommunicationClaim } from '../models/interoperable-claims/communication-claims.js';
3
+ import { buildSearchBundle, buildFhirParametersResourceFromSearchParams, SearchBundleTypes, } from './fhir-search.js';
4
+ /**
5
+ * Canonical participant-token prefixes used by communication search helpers.
6
+ */
7
+ export const CommunicationParticipantPrefixes = Object.freeze({
8
+ Did: 'did:',
9
+ Email: 'email:',
10
+ Mailto: 'mailto:',
11
+ Tel: 'tel:',
12
+ Phone: 'phone:',
13
+ Wildcard: '*',
14
+ });
15
+ /**
16
+ * Canonical parameter names supported by `Communication/_search`.
17
+ */
18
+ export const CommunicationParticipantSearchParameterNames = Object.freeze({
19
+ Subject: 'subject',
20
+ Actor: 'actor',
21
+ Sender: 'sender',
22
+ Recipient: 'recipient',
23
+ User: 'user',
24
+ Target: 'target',
25
+ PeriodStart: 'period-start',
26
+ PeriodEnd: 'period-end',
27
+ Page: 'page',
28
+ Count: 'count',
29
+ });
30
+ /**
31
+ * Backward-compatible aliases still accepted while callers migrate to the
32
+ * canonical control names.
33
+ */
34
+ export const CommunicationParticipantSearchParameterAliases = Object.freeze({
35
+ SentFrom: 'sent-from',
36
+ SentTo: 'sent-to',
37
+ });
38
+ /**
39
+ * Indexed-attribute names used by communication projections.
40
+ */
41
+ export const CommunicationParticipantIndexNames = Object.freeze({
42
+ Subject: CommunicationClaim.Subject,
43
+ Participant: 'Communication.participant-token',
44
+ Sender: 'Communication.sender-token',
45
+ Recipient: 'Communication.recipient-token',
46
+ });
47
+ const COMMUNICATION_PARTICIPANT_INDEX_ATTRIBUTE_UNIQUE = Object.freeze({
48
+ subject: true,
49
+ participant: false,
50
+ sender: false,
51
+ recipient: false,
52
+ });
53
+ const COMMUNICATION_PARTICIPANT_SEARCH_KEYS = Object.freeze([
54
+ CommunicationParticipantSearchParameterNames.Subject,
55
+ CommunicationParticipantSearchParameterNames.Actor,
56
+ CommunicationParticipantSearchParameterNames.Sender,
57
+ CommunicationParticipantSearchParameterNames.Recipient,
58
+ CommunicationParticipantSearchParameterNames.User,
59
+ CommunicationParticipantSearchParameterNames.Target,
60
+ CommunicationParticipantSearchParameterNames.PeriodStart,
61
+ CommunicationParticipantSearchParameterNames.PeriodEnd,
62
+ CommunicationParticipantSearchParameterNames.Page,
63
+ CommunicationParticipantSearchParameterNames.Count,
64
+ CommunicationParticipantSearchParameterAliases.SentFrom,
65
+ CommunicationParticipantSearchParameterAliases.SentTo,
66
+ ]);
67
+ export function normalizeCommunicationParticipantToken(value) {
68
+ const trimmed = String(value || '').trim();
69
+ if (!trimmed)
70
+ return '';
71
+ if (trimmed === CommunicationParticipantPrefixes.Wildcard) {
72
+ return CommunicationParticipantPrefixes.Wildcard;
73
+ }
74
+ const lower = trimmed.toLowerCase();
75
+ if (lower.startsWith(CommunicationParticipantPrefixes.Did)) {
76
+ return `${CommunicationParticipantPrefixes.Did}${trimmed.slice(CommunicationParticipantPrefixes.Did.length)}`;
77
+ }
78
+ if (lower.startsWith(CommunicationParticipantPrefixes.Mailto)) {
79
+ const email = trimmed.slice(CommunicationParticipantPrefixes.Mailto.length).trim().toLowerCase();
80
+ return email ? `${CommunicationParticipantPrefixes.Email}${email}` : '';
81
+ }
82
+ if (lower.startsWith(CommunicationParticipantPrefixes.Email)) {
83
+ const email = trimmed.slice(CommunicationParticipantPrefixes.Email.length).trim().toLowerCase();
84
+ return email ? `${CommunicationParticipantPrefixes.Email}${email}` : '';
85
+ }
86
+ if (looksLikeEmailIdentifier(trimmed)) {
87
+ return `${CommunicationParticipantPrefixes.Email}${trimmed.toLowerCase()}`;
88
+ }
89
+ if (lower.startsWith(CommunicationParticipantPrefixes.Phone)) {
90
+ return normalizeTelephoneParticipantToken(trimmed.slice(CommunicationParticipantPrefixes.Phone.length));
91
+ }
92
+ if (lower.startsWith(CommunicationParticipantPrefixes.Tel)) {
93
+ return normalizeTelephoneParticipantToken(trimmed.slice(CommunicationParticipantPrefixes.Tel.length));
94
+ }
95
+ if (looksLikePhoneIdentifier(trimmed)) {
96
+ return normalizeTelephoneParticipantToken(trimmed);
97
+ }
98
+ return trimmed;
99
+ }
100
+ export function normalizeCommunicationParticipantTokenList(value) {
101
+ const values = toFlatStringList(value)
102
+ .map((item) => normalizeCommunicationParticipantToken(item))
103
+ .filter(Boolean);
104
+ return Array.from(new Set(values));
105
+ }
106
+ export function isCommunicationParticipantWildcardToken(value) {
107
+ return normalizeCommunicationParticipantToken(value) === CommunicationParticipantPrefixes.Wildcard;
108
+ }
109
+ export function buildCommunicationParticipantSearchParameters(input) {
110
+ return buildFhirParametersResourceFromSearchParams(communicationParticipantSearchInputToSearchParams(input));
111
+ }
112
+ export function buildCommunicationParticipantSearchBundle(input) {
113
+ return buildSearchBundle({
114
+ resourceType: ResourceTypesFhirR4.Communication,
115
+ encoding: 'post-parameters',
116
+ bundleType: SearchBundleTypes.Search,
117
+ searchParams: communicationParticipantSearchInputToSearchParams(input),
118
+ });
119
+ }
120
+ export function parseCommunicationParticipantSearchCriteria(input) {
121
+ const flat = extractFlatCommunicationParticipantSearchMap(input);
122
+ return {
123
+ subjectActorIds: withoutWildcard(flat[CommunicationParticipantSearchParameterNames.Subject]),
124
+ anySubject: hasWildcard(flat[CommunicationParticipantSearchParameterNames.Subject]),
125
+ actorIds: withoutWildcard(flat[CommunicationParticipantSearchParameterNames.Actor]),
126
+ anyActor: hasWildcard(flat[CommunicationParticipantSearchParameterNames.Actor]),
127
+ senderActorIds: withoutWildcard(flat[CommunicationParticipantSearchParameterNames.Sender]),
128
+ anySender: hasWildcard(flat[CommunicationParticipantSearchParameterNames.Sender]),
129
+ recipientActorIds: withoutWildcard(flat[CommunicationParticipantSearchParameterNames.Recipient]),
130
+ anyRecipient: hasWildcard(flat[CommunicationParticipantSearchParameterNames.Recipient]),
131
+ userActorIds: withoutWildcard(flat[CommunicationParticipantSearchParameterNames.User]),
132
+ anyUser: hasWildcard(flat[CommunicationParticipantSearchParameterNames.User]),
133
+ targetActorIds: withoutWildcard(flat[CommunicationParticipantSearchParameterNames.Target]),
134
+ anyTarget: hasWildcard(flat[CommunicationParticipantSearchParameterNames.Target]),
135
+ claimSearchParams: buildClaimSearchParams(flat),
136
+ periodStart: firstDefinedDateValue(flat[CommunicationParticipantSearchParameterNames.PeriodStart][0], flat[CommunicationParticipantSearchParameterAliases.SentFrom][0]),
137
+ periodEnd: firstDefinedDateValue(flat[CommunicationParticipantSearchParameterNames.PeriodEnd][0], flat[CommunicationParticipantSearchParameterAliases.SentTo][0]),
138
+ page: normalizePageValue(flat[CommunicationParticipantSearchParameterNames.Page][0]),
139
+ count: normalizePositiveInteger(flat[CommunicationParticipantSearchParameterNames.Count][0]),
140
+ };
141
+ }
142
+ export function buildCommunicationParticipantIndexAttributes(projection) {
143
+ const subjectTokens = normalizeCommunicationParticipantTokenList(projection.subject);
144
+ const senderTokens = normalizeCommunicationParticipantTokenList(projection.sender ?? projection.from);
145
+ const recipientTokens = normalizeCommunicationParticipantTokenList(projection.recipients ?? projection.to);
146
+ const participantTokens = Array.from(new Set([...senderTokens, ...recipientTokens]));
147
+ const attributes = [];
148
+ for (const subjectToken of subjectTokens) {
149
+ attributes.push({
150
+ name: CommunicationParticipantIndexNames.Subject,
151
+ value: subjectToken,
152
+ unique: COMMUNICATION_PARTICIPANT_INDEX_ATTRIBUTE_UNIQUE.subject,
153
+ });
154
+ }
155
+ for (const senderToken of senderTokens) {
156
+ attributes.push({
157
+ name: CommunicationParticipantIndexNames.Sender,
158
+ value: senderToken,
159
+ unique: COMMUNICATION_PARTICIPANT_INDEX_ATTRIBUTE_UNIQUE.sender,
160
+ });
161
+ }
162
+ for (const recipientToken of recipientTokens) {
163
+ attributes.push({
164
+ name: CommunicationParticipantIndexNames.Recipient,
165
+ value: recipientToken,
166
+ unique: COMMUNICATION_PARTICIPANT_INDEX_ATTRIBUTE_UNIQUE.recipient,
167
+ });
168
+ }
169
+ for (const participantToken of participantTokens) {
170
+ attributes.push({
171
+ name: CommunicationParticipantIndexNames.Participant,
172
+ value: participantToken,
173
+ unique: COMMUNICATION_PARTICIPANT_INDEX_ATTRIBUTE_UNIQUE.participant,
174
+ });
175
+ }
176
+ return dedupeIndexedAttributes(attributes);
177
+ }
178
+ export function matchesCommunicationParticipantSearch(projection, criteria) {
179
+ const subjectTokens = normalizeCommunicationParticipantTokenList(projection.subject);
180
+ const senderTokens = normalizeCommunicationParticipantTokenList(projection.sender ?? projection.from);
181
+ const recipientTokens = normalizeCommunicationParticipantTokenList(projection.recipients ?? projection.to);
182
+ const participantTokens = Array.from(new Set([...senderTokens, ...recipientTokens]));
183
+ const sent = normalizeDateParameter(projection.sent);
184
+ const categoryTokens = normalizeScalarClaimTokenList(projection.category);
185
+ const topicTokens = normalizeScalarClaimTokenList(projection.topic);
186
+ return matchesOperand(subjectTokens, criteria.subjectActorIds, criteria.anySubject)
187
+ && matchesOperand(participantTokens, criteria.actorIds, criteria.anyActor)
188
+ && matchesOperand(senderTokens, criteria.senderActorIds, criteria.anySender)
189
+ && matchesOperand(recipientTokens, criteria.recipientActorIds, criteria.anyRecipient)
190
+ && matchesOperand(participantTokens, criteria.userActorIds, criteria.anyUser)
191
+ && matchesOperand(participantTokens, criteria.targetActorIds, criteria.anyTarget)
192
+ && matchesClaimSearchParams(criteria.claimSearchParams, {
193
+ [CommunicationClaim.Subject]: subjectTokens,
194
+ [CommunicationClaim.Sender]: senderTokens,
195
+ [CommunicationClaim.Recipient]: recipientTokens,
196
+ [CommunicationClaim.Category]: categoryTokens,
197
+ [CommunicationClaim.Topic]: topicTokens,
198
+ })
199
+ && matchesDateRange(sent, criteria.periodStart, criteria.periodEnd);
200
+ }
201
+ export function paginateCommunicationParticipantMatches(records, criteria) {
202
+ const page = normalizePageValue(criteria.page);
203
+ const count = normalizePositiveInteger(criteria.count);
204
+ if (!count) {
205
+ return [...records];
206
+ }
207
+ const offset = (page - 1) * count;
208
+ return records.slice(offset, offset + count);
209
+ }
210
+ function dedupeIndexedAttributes(attributes) {
211
+ const seen = new Set();
212
+ const result = [];
213
+ for (const attribute of attributes) {
214
+ const key = `${attribute.name}\u0000${attribute.value}`;
215
+ if (seen.has(key))
216
+ continue;
217
+ seen.add(key);
218
+ result.push(attribute);
219
+ }
220
+ return result;
221
+ }
222
+ function matchesOperand(actualTokens, expectedTokens, anyExpected) {
223
+ if (anyExpected || expectedTokens.length === 0)
224
+ return true;
225
+ if (actualTokens.length === 0)
226
+ return false;
227
+ return expectedTokens.some((expectedToken) => actualTokens.includes(expectedToken));
228
+ }
229
+ function matchesDateRange(sent, sentFrom, sentTo) {
230
+ if (!sentFrom && !sentTo)
231
+ return true;
232
+ if (!sent)
233
+ return false;
234
+ if (sentFrom && sent < sentFrom)
235
+ return false;
236
+ if (sentTo && sent > sentTo)
237
+ return false;
238
+ return true;
239
+ }
240
+ function hasWildcard(values) {
241
+ return values.includes(CommunicationParticipantPrefixes.Wildcard);
242
+ }
243
+ function withoutWildcard(values) {
244
+ return values.filter((value) => value !== CommunicationParticipantPrefixes.Wildcard);
245
+ }
246
+ function extractFlatCommunicationParticipantSearchMap(input) {
247
+ const result = Object.fromEntries(COMMUNICATION_PARTICIPANT_SEARCH_KEYS.map((key) => [key, []]));
248
+ if (!input || typeof input !== 'object') {
249
+ return result;
250
+ }
251
+ if (input.resourceType === 'Parameters' && Array.isArray(input.parameter)) {
252
+ for (const parameter of input.parameter) {
253
+ const name = String(parameter?.name || '').trim();
254
+ if (!name) {
255
+ continue;
256
+ }
257
+ const rawValue = parameter.valueString
258
+ ?? parameter.valueUri
259
+ ?? parameter.valueCode
260
+ ?? parameter.valueReference?.reference
261
+ ?? parameter.valueCoding?.code
262
+ ?? parameter.valueInteger
263
+ ?? parameter.valueDecimal;
264
+ if (!result[name]) {
265
+ result[name] = [];
266
+ }
267
+ result[name].push(...normalizeSearchValueList(name, rawValue));
268
+ }
269
+ return normalizeFlatSearchMap(result);
270
+ }
271
+ for (const key of COMMUNICATION_PARTICIPANT_SEARCH_KEYS) {
272
+ result[key] = normalizeSearchValueList(key, input[key]);
273
+ }
274
+ for (const [key, value] of Object.entries(input)) {
275
+ if (COMMUNICATION_PARTICIPANT_SEARCH_KEYS.includes(key)) {
276
+ continue;
277
+ }
278
+ result[key] = normalizeSearchValueList(key, value);
279
+ }
280
+ return normalizeFlatSearchMap(result);
281
+ }
282
+ function normalizeFlatSearchMap(result) {
283
+ for (const key of COMMUNICATION_PARTICIPANT_SEARCH_KEYS) {
284
+ result[key] = Array.from(new Set(result[key].filter(Boolean)));
285
+ }
286
+ return result;
287
+ }
288
+ function communicationParticipantSearchInputToSearchParams(input) {
289
+ const normalizedSearchParams = normalizeClaimSearchParams(input.searchParams);
290
+ return {
291
+ ...normalizedSearchParams,
292
+ [CommunicationParticipantSearchParameterNames.Subject]: normalizeCommunicationParticipantTokenList(input.subject),
293
+ [CommunicationParticipantSearchParameterNames.Actor]: normalizeCommunicationParticipantTokenList(input.actorId),
294
+ [CommunicationParticipantSearchParameterNames.Sender]: normalizeCommunicationParticipantTokenList(input.senderActorId),
295
+ [CommunicationParticipantSearchParameterNames.Recipient]: normalizeCommunicationParticipantTokenList(input.recipientActorId),
296
+ [CommunicationParticipantSearchParameterNames.User]: normalizeCommunicationParticipantTokenList(input.userActorId),
297
+ [CommunicationParticipantSearchParameterNames.Target]: normalizeCommunicationParticipantTokenList(input.targetActorId),
298
+ [CommunicationParticipantSearchParameterNames.PeriodStart]: firstDefinedDateValue(input.periodStart, input.sentFrom),
299
+ [CommunicationParticipantSearchParameterNames.PeriodEnd]: firstDefinedDateValue(input.periodEnd, input.sentTo),
300
+ [CommunicationParticipantSearchParameterNames.Page]: normalizePositiveInteger(input.page),
301
+ [CommunicationParticipantSearchParameterNames.Count]: normalizePositiveInteger(input.count),
302
+ };
303
+ }
304
+ function normalizeClaimSearchParams(searchParams) {
305
+ const result = {};
306
+ for (const [key, value] of Object.entries(searchParams || {})) {
307
+ const normalizedKey = String(key || '').trim();
308
+ if (!normalizedKey) {
309
+ continue;
310
+ }
311
+ if (isParticipantClaimKey(normalizedKey)) {
312
+ result[normalizedKey] = normalizeCommunicationParticipantTokenList(value);
313
+ continue;
314
+ }
315
+ result[normalizedKey] = normalizeScalarSearchValue(value);
316
+ }
317
+ return result;
318
+ }
319
+ function buildClaimSearchParams(flat) {
320
+ const result = {};
321
+ for (const [key, values] of Object.entries(flat)) {
322
+ if (COMMUNICATION_PARTICIPANT_SEARCH_KEYS.includes(key)) {
323
+ continue;
324
+ }
325
+ if (values.length === 0) {
326
+ continue;
327
+ }
328
+ result[key] = Array.from(new Set(values));
329
+ }
330
+ return result;
331
+ }
332
+ function matchesClaimSearchParams(expectedSearchParams, projectionIndex) {
333
+ for (const [claimKey, expectedValues] of Object.entries(expectedSearchParams)) {
334
+ const actualValues = projectionIndex[claimKey] || [];
335
+ if (!matchesOperand(actualValues, withoutWildcard(expectedValues), hasWildcard(expectedValues))) {
336
+ return false;
337
+ }
338
+ }
339
+ return true;
340
+ }
341
+ function normalizeSearchValueList(key, value) {
342
+ return isParticipantKey(key)
343
+ ? normalizeCommunicationParticipantTokenList(value)
344
+ : normalizeScalarClaimTokenList(value);
345
+ }
346
+ function normalizeScalarClaimTokenList(value) {
347
+ return Array.from(new Set(toFlatStringList(value)));
348
+ }
349
+ function normalizeScalarSearchValue(value) {
350
+ if (Array.isArray(value)) {
351
+ const normalized = value.map((item) => String(item).trim()).filter(Boolean);
352
+ return normalized.length > 0 ? normalized : undefined;
353
+ }
354
+ if (value === undefined || value === null) {
355
+ return undefined;
356
+ }
357
+ const normalized = String(value).trim();
358
+ return normalized || undefined;
359
+ }
360
+ function isParticipantKey(key) {
361
+ return isParticipantControlKey(key) || isParticipantClaimKey(key);
362
+ }
363
+ function isParticipantControlKey(key) {
364
+ return key === CommunicationParticipantSearchParameterNames.Subject
365
+ || key === CommunicationParticipantSearchParameterNames.Actor
366
+ || key === CommunicationParticipantSearchParameterNames.Sender
367
+ || key === CommunicationParticipantSearchParameterNames.Recipient
368
+ || key === CommunicationParticipantSearchParameterNames.User
369
+ || key === CommunicationParticipantSearchParameterNames.Target;
370
+ }
371
+ function isParticipantClaimKey(key) {
372
+ return key === CommunicationClaim.Subject
373
+ || key === CommunicationClaim.Sender
374
+ || key === CommunicationClaim.Recipient;
375
+ }
376
+ function firstDefinedDateValue(...values) {
377
+ for (const value of values) {
378
+ const normalized = normalizeDateParameter(value);
379
+ if (normalized) {
380
+ return normalized;
381
+ }
382
+ }
383
+ return undefined;
384
+ }
385
+ function toFlatStringList(value) {
386
+ if (Array.isArray(value)) {
387
+ return value.flatMap((item) => toFlatStringList(item));
388
+ }
389
+ const trimmed = String(value || '').trim();
390
+ if (!trimmed) {
391
+ return [];
392
+ }
393
+ return trimmed.split(',').map((item) => item.trim()).filter(Boolean);
394
+ }
395
+ function looksLikeEmailIdentifier(value) {
396
+ return /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(String(value || '').trim());
397
+ }
398
+ function looksLikePhoneIdentifier(value) {
399
+ const normalized = String(value || '').trim();
400
+ return /^[+()0-9.\-\s]{6,}$/.test(normalized);
401
+ }
402
+ function normalizeTelephoneParticipantToken(value) {
403
+ const trimmed = String(value || '').trim();
404
+ if (!trimmed)
405
+ return '';
406
+ const hasExplicitPlus = trimmed.startsWith('+');
407
+ const digits = trimmed.replace(/[^0-9]/g, '');
408
+ if (!digits)
409
+ return '';
410
+ return `${CommunicationParticipantPrefixes.Tel}${hasExplicitPlus ? '+' : ''}${digits}`;
411
+ }
412
+ function normalizeDateParameter(value) {
413
+ const trimmed = String(value || '').trim();
414
+ return trimmed || undefined;
415
+ }
416
+ function normalizePositiveInteger(value) {
417
+ const numeric = typeof value === 'number' ? value : Number(String(value || '').trim());
418
+ if (!Number.isInteger(numeric) || numeric <= 0) {
419
+ return undefined;
420
+ }
421
+ return numeric;
422
+ }
423
+ function normalizePageValue(value) {
424
+ return normalizePositiveInteger(value) || 1;
425
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Environment variable controlling whether communication retention is disabled.
3
+ *
4
+ * Default behavior:
5
+ * - unset => retention stays enabled
6
+ * - `false` => retention stays enabled
7
+ * - `true` => retention is disabled and purge flows may remove communications
8
+ */
9
+ export declare const CommunicationRetentionEnv: Readonly<{
10
+ readonly Disabled: "COMMUNICATION_RETENTION_DISABLED";
11
+ }>;
12
+ /**
13
+ * Canonical retention decisions reused by lifecycle/purge flows.
14
+ */
15
+ export declare const CommunicationRetentionDecisions: Readonly<{
16
+ readonly SkipPurge: "skip-purge";
17
+ readonly AllowPurge: "allow-purge";
18
+ }>;
19
+ export type CommunicationRetentionDecision = typeof CommunicationRetentionDecisions[keyof typeof CommunicationRetentionDecisions];
20
+ /**
21
+ * Returns whether the communication-retention safety rail is disabled.
22
+ *
23
+ * This helper intentionally defaults to `false`. Production callers must opt in
24
+ * explicitly to purge communication records by setting
25
+ * `COMMUNICATION_RETENTION_DISABLED=true`.
26
+ */
27
+ export declare function isCommunicationRetentionDisabled(env?: Readonly<Record<string, unknown>>): boolean;
28
+ /**
29
+ * Resolves the retention decision that lifecycle/purge code should apply to
30
+ * communication records.
31
+ */
32
+ export declare function resolveCommunicationRetentionDecision(env?: Readonly<Record<string, unknown>>): CommunicationRetentionDecision;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Environment variable controlling whether communication retention is disabled.
3
+ *
4
+ * Default behavior:
5
+ * - unset => retention stays enabled
6
+ * - `false` => retention stays enabled
7
+ * - `true` => retention is disabled and purge flows may remove communications
8
+ */
9
+ export const CommunicationRetentionEnv = Object.freeze({
10
+ Disabled: 'COMMUNICATION_RETENTION_DISABLED',
11
+ });
12
+ /**
13
+ * Canonical retention decisions reused by lifecycle/purge flows.
14
+ */
15
+ export const CommunicationRetentionDecisions = Object.freeze({
16
+ SkipPurge: 'skip-purge',
17
+ AllowPurge: 'allow-purge',
18
+ });
19
+ function normalizeBooleanText(value) {
20
+ const normalized = String(value ?? '').trim().toLowerCase();
21
+ return normalized || undefined;
22
+ }
23
+ /**
24
+ * Returns whether the communication-retention safety rail is disabled.
25
+ *
26
+ * This helper intentionally defaults to `false`. Production callers must opt in
27
+ * explicitly to purge communication records by setting
28
+ * `COMMUNICATION_RETENTION_DISABLED=true`.
29
+ */
30
+ export function isCommunicationRetentionDisabled(env = process.env) {
31
+ return normalizeBooleanText(env[CommunicationRetentionEnv.Disabled]) === 'true';
32
+ }
33
+ /**
34
+ * Resolves the retention decision that lifecycle/purge code should apply to
35
+ * communication records.
36
+ */
37
+ export function resolveCommunicationRetentionDecision(env = process.env) {
38
+ return isCommunicationRetentionDisabled(env)
39
+ ? CommunicationRetentionDecisions.AllowPurge
40
+ : CommunicationRetentionDecisions.SkipPurge;
41
+ }
@@ -0,0 +1,54 @@
1
+ import type { FhirParametersResource } from './fhir-search.js';
2
+ import { buildCommunicationParticipantSearchBundle, type CommunicationParticipantSearchInput } from './communication-participant-search.js';
3
+ export declare const CommunicationSearchEntryTypes: Readonly<{
4
+ readonly Search: "Communication-search-request-v1.0";
5
+ }>;
6
+ export declare const CommunicationSearchOperationTypes: Readonly<{
7
+ readonly Search: "search";
8
+ }>;
9
+ export type CommunicationSearchState = Readonly<{
10
+ searchParams: Readonly<Record<string, string | number | boolean | readonly (string | number | boolean)[] | undefined>>;
11
+ periodStart?: string;
12
+ periodEnd?: string;
13
+ page?: number;
14
+ count?: number;
15
+ }>;
16
+ /**
17
+ * High-level chainable editor for `Communication/_search`.
18
+ *
19
+ * Design goals:
20
+ * - keep business filters near the caller instead of forcing ad-hoc `Parameters`
21
+ * - reuse canonical `CommunicationClaim.*` keys for search params
22
+ * - keep shared controls (`periodStart`, `periodEnd`, `page`, `count`) outside
23
+ * the claim bag because they are search controls, not resource claims
24
+ */
25
+ export declare class CommunicationSearchEditor {
26
+ private draft;
27
+ constructor(initial?: Partial<CommunicationSearchState>);
28
+ setSearchParams(value: Readonly<Record<string, string | number | boolean | readonly (string | number | boolean)[] | undefined>>): this;
29
+ setSearchParam(claimKey: string, value: string | number | boolean | readonly (string | number | boolean)[] | undefined): this;
30
+ setSearchParamSender(value: string | readonly string[]): this;
31
+ setSearchParamRecipient(value: string | readonly string[]): this;
32
+ setSearchParamCategory(value: string | readonly string[]): this;
33
+ setSearchParamTopic(value: string): this;
34
+ getSearchParams(): Readonly<Record<string, string | number | boolean | readonly (string | number | boolean)[] | undefined>>;
35
+ setPeriodStart(value: string): this;
36
+ setPeriodEnd(value: string): this;
37
+ setPaginationCount(value: number): this;
38
+ setPageNumber(value: number): this;
39
+ getState(): CommunicationSearchState;
40
+ toSearchInput(): CommunicationParticipantSearchInput;
41
+ buildRequest(): FhirParametersResource;
42
+ buildEntry(): {
43
+ type: string;
44
+ request: {
45
+ method: 'POST';
46
+ url: string;
47
+ };
48
+ resource: FhirParametersResource;
49
+ meta: {
50
+ operationType: string;
51
+ };
52
+ };
53
+ buildBundle(): ReturnType<typeof buildCommunicationParticipantSearchBundle>;
54
+ }