gdc-common-utils-ts 1.14.15 → 1.15.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.
package/README.md CHANGED
@@ -35,6 +35,7 @@ entry types, FHIR-like resources, and `resource.meta.claims` fit together,
35
35
  read first:
36
36
 
37
37
  - [`docs/101-COMMUNICATION_LAYERING.md`](docs/101-COMMUNICATION_LAYERING.md)
38
+ - [`docs/101-BUNDLE_EDITOR_READER.md`](docs/101-BUNDLE_EDITOR_READER.md)
38
39
 
39
40
  ## Install
40
41
 
@@ -0,0 +1,88 @@
1
+ import { buildEmployeeBatchBundle, buildEmployeeBatchEntry, buildEmployeeSearchBundle, EmployeeBundleOperations } from './employee';
2
+ export type BundleOperation = (typeof EmployeeBundleOperations)[keyof typeof EmployeeBundleOperations];
3
+ export type BuiltEmployeeBatchEntry = ReturnType<typeof buildEmployeeBatchEntry> & {
4
+ fullUrl?: string;
5
+ };
6
+ /**
7
+ * Generic bundle editor with an employee adapter surface.
8
+ *
9
+ * Current scope:
10
+ * - one declared business operation per bundle
11
+ * - active-entry editing through generic claim helpers
12
+ * - employee convenience setters layered on the same active entry
13
+ *
14
+ * This editor lives in `common-utils` because it is runtime-neutral and can be
15
+ * consumed by frontend SDKs, backend SDKs, and backend services.
16
+ */
17
+ export declare class BundleEditor {
18
+ private bundleOperation;
19
+ private readonly entries;
20
+ private activeEntryIndex;
21
+ /** Declares which business operation the bundle is assembling. */
22
+ setBundleOperation(operation: BundleOperation): this;
23
+ /** Returns the operation currently assigned to this bundle. */
24
+ getBundleOperation(): BundleOperation | null;
25
+ /**
26
+ * Opens one new active entry.
27
+ *
28
+ * If the current bundle operation needs an identifier and none is supplied,
29
+ * a canonical `urn:uuid:*` identifier is generated and aligned across
30
+ * `fullUrl`, `resource.id`, and `org.schema.Person.identifier`.
31
+ */
32
+ newEntry(resourceId?: string): this;
33
+ /** Reopens an existing entry by identifier or `fullUrl`. */
34
+ openEntry(resourceId: string): this;
35
+ /** Closes the current active entry while preserving it inside the bundle draft. */
36
+ doneEntry(): this;
37
+ /** Returns a cloned snapshot of the currently staged entries. */
38
+ getEntries(): readonly BuiltEmployeeBatchEntry[];
39
+ /**
40
+ * Builds the final bundle payload for the declared operation.
41
+ *
42
+ * `search` returns one canonical search bundle.
43
+ * `purge` returns a batch bundle whose entries are routed later to
44
+ * `Employee/_purge`.
45
+ * `create` and `disable` return canonical batch bundles.
46
+ */
47
+ build(): ReturnType<typeof buildEmployeeBatchBundle> | ReturnType<typeof buildEmployeeSearchBundle>;
48
+ /** Reads one claim from the active entry. */
49
+ getClaim(key: string): unknown;
50
+ /** Checks whether the active entry contains one claim key. */
51
+ hasClaim(key: string): boolean;
52
+ /** Writes one claim on the active entry. */
53
+ setClaim(key: string, value: unknown): this;
54
+ /** Appends one claim value on the active entry. */
55
+ addClaim(key: string, value: unknown): this;
56
+ /** Removes one claim from the active entry. */
57
+ removeClaim(key: string): this;
58
+ /**
59
+ * Sets the active entry identifier.
60
+ *
61
+ * When informed, the value is synchronized into `fullUrl`, `resource.id`,
62
+ * and the canonical identifier claim.
63
+ * Empty values remove the identifier from all three places.
64
+ */
65
+ setIdentifier(identifier?: string | null): this;
66
+ /** Reads the active entry identifier from claims, resource id, or fullUrl. */
67
+ getIdentifier(): string | undefined;
68
+ /** Ensures the active entry carries one canonical identifier. */
69
+ ensureIdentifier(): string;
70
+ /** Sets the active entry `fullUrl` explicitly. */
71
+ setFullUrl(fullUrl?: string | null): this;
72
+ /** Returns the active entry `fullUrl` when present. */
73
+ getFullUrl(): string | undefined;
74
+ /** Convenience employee setter for email. */
75
+ setEmail(email: string): this;
76
+ /** Convenience employee setter for occupational role. */
77
+ setRole(role: string): this;
78
+ /** Convenience employee setter for `worksFor`. */
79
+ setWorksFor(worksFor: string): this;
80
+ /** Convenience employee setter for `memberOf`. */
81
+ setMemberOf(memberOf: string): this;
82
+ /** Convenience employee setter for `memberOf.taxID`. */
83
+ setMemberOfOrgTaxId(taxId: string): this;
84
+ private requireBundleOperation;
85
+ private getRequiredActiveEntry;
86
+ private getActiveEntryClaims;
87
+ private getActiveOrSingleSearchClaims;
88
+ }
@@ -0,0 +1,316 @@
1
+ import { ClaimsPersonSchemaorg } from '../constants/schemaorg.js';
2
+ import { EmployeeBatchEntryTypes, buildEmployeeBatchEntry, buildEmployeeClaims, buildEmployeePurgeBundle, buildEmployeeSearchBundle, EmployeeBundleMethods, EmployeeBundleOperations, } from './employee.js';
3
+ function cloneEntry(entry) {
4
+ return JSON.parse(JSON.stringify(entry));
5
+ }
6
+ function cloneClaimValue(value) {
7
+ if (Array.isArray(value)) {
8
+ return [...value];
9
+ }
10
+ return value;
11
+ }
12
+ function normalizeOptionalIdentifier(value) {
13
+ if (value === undefined || value === null) {
14
+ return undefined;
15
+ }
16
+ const normalized = String(value).trim();
17
+ return normalized ? normalized : undefined;
18
+ }
19
+ function createEmployeeIdentifierUrn() {
20
+ const cryptoLike = globalThis;
21
+ const uuid = typeof cryptoLike.crypto?.randomUUID === 'function'
22
+ ? cryptoLike.crypto.randomUUID()
23
+ : `employee-${Date.now()}-${Math.random().toString(16).slice(2)}`;
24
+ return `urn:uuid:${uuid}`;
25
+ }
26
+ function resolveRequestMethodForOperation(operation) {
27
+ switch (operation) {
28
+ case EmployeeBundleOperations.disable:
29
+ return EmployeeBundleMethods.disable;
30
+ case EmployeeBundleOperations.create:
31
+ case EmployeeBundleOperations.purge:
32
+ case EmployeeBundleOperations.search:
33
+ default:
34
+ return EmployeeBundleMethods.create;
35
+ }
36
+ }
37
+ function resolveEntryTypeForOperation(operation) {
38
+ switch (operation) {
39
+ case EmployeeBundleOperations.disable:
40
+ return EmployeeBatchEntryTypes.disable;
41
+ case EmployeeBundleOperations.purge:
42
+ return EmployeeBatchEntryTypes.purge;
43
+ case EmployeeBundleOperations.search:
44
+ return EmployeeBatchEntryTypes.search;
45
+ case EmployeeBundleOperations.create:
46
+ default:
47
+ return EmployeeBatchEntryTypes.create;
48
+ }
49
+ }
50
+ /**
51
+ * Generic bundle editor with an employee adapter surface.
52
+ *
53
+ * Current scope:
54
+ * - one declared business operation per bundle
55
+ * - active-entry editing through generic claim helpers
56
+ * - employee convenience setters layered on the same active entry
57
+ *
58
+ * This editor lives in `common-utils` because it is runtime-neutral and can be
59
+ * consumed by frontend SDKs, backend SDKs, and backend services.
60
+ */
61
+ export class BundleEditor {
62
+ bundleOperation = null;
63
+ entries = [];
64
+ activeEntryIndex = null;
65
+ /** Declares which business operation the bundle is assembling. */
66
+ setBundleOperation(operation) {
67
+ this.bundleOperation = operation;
68
+ return this;
69
+ }
70
+ /** Returns the operation currently assigned to this bundle. */
71
+ getBundleOperation() {
72
+ return this.bundleOperation;
73
+ }
74
+ /**
75
+ * Opens one new active entry.
76
+ *
77
+ * If the current bundle operation needs an identifier and none is supplied,
78
+ * a canonical `urn:uuid:*` identifier is generated and aligned across
79
+ * `fullUrl`, `resource.id`, and `org.schema.Person.identifier`.
80
+ */
81
+ newEntry(resourceId) {
82
+ const operation = this.requireBundleOperation();
83
+ const normalizedIdentifier = operation === EmployeeBundleOperations.search
84
+ ? normalizeOptionalIdentifier(resourceId)
85
+ : (normalizeOptionalIdentifier(resourceId) || createEmployeeIdentifierUrn());
86
+ const claims = normalizedIdentifier
87
+ ? buildEmployeeClaims({ identifier: normalizedIdentifier })
88
+ : buildEmployeeClaims({});
89
+ const entry = buildEmployeeBatchEntry({
90
+ type: resolveEntryTypeForOperation(operation),
91
+ method: resolveRequestMethodForOperation(operation),
92
+ resourceId: normalizedIdentifier,
93
+ claims,
94
+ });
95
+ if (normalizedIdentifier) {
96
+ entry.fullUrl = normalizedIdentifier;
97
+ }
98
+ this.entries.push(entry);
99
+ this.activeEntryIndex = this.entries.length - 1;
100
+ return this;
101
+ }
102
+ /** Reopens an existing entry by identifier or `fullUrl`. */
103
+ openEntry(resourceId) {
104
+ const normalizedIdentifier = normalizeOptionalIdentifier(resourceId);
105
+ if (!normalizedIdentifier) {
106
+ throw new Error('openEntry requires a non-empty resource identifier.');
107
+ }
108
+ const nextIndex = this.entries.findIndex((entry) => {
109
+ const claims = entry.resource?.meta?.claims || {};
110
+ return normalizeOptionalIdentifier(entry.resource?.id) === normalizedIdentifier
111
+ || normalizeOptionalIdentifier(entry.fullUrl) === normalizedIdentifier
112
+ || normalizeOptionalIdentifier(claims[ClaimsPersonSchemaorg.identifier]) === normalizedIdentifier;
113
+ });
114
+ if (nextIndex < 0) {
115
+ throw new Error(`openEntry could not find resource identifier: ${normalizedIdentifier}`);
116
+ }
117
+ this.activeEntryIndex = nextIndex;
118
+ return this;
119
+ }
120
+ /** Closes the current active entry while preserving it inside the bundle draft. */
121
+ doneEntry() {
122
+ this.activeEntryIndex = null;
123
+ return this;
124
+ }
125
+ /** Returns a cloned snapshot of the currently staged entries. */
126
+ getEntries() {
127
+ return this.entries.map((entry) => cloneEntry(entry));
128
+ }
129
+ /**
130
+ * Builds the final bundle payload for the declared operation.
131
+ *
132
+ * `search` returns one canonical search bundle.
133
+ * `purge` returns a batch bundle whose entries are routed later to
134
+ * `Employee/_purge`.
135
+ * `create` and `disable` return canonical batch bundles.
136
+ */
137
+ build() {
138
+ const operation = this.requireBundleOperation();
139
+ if (operation === EmployeeBundleOperations.search) {
140
+ const claims = this.getActiveOrSingleSearchClaims();
141
+ return buildEmployeeSearchBundle({ claims });
142
+ }
143
+ if (operation === EmployeeBundleOperations.purge) {
144
+ return {
145
+ resourceType: 'Bundle',
146
+ type: 'batch',
147
+ entry: this.entries.map((entry) => {
148
+ const identifier = normalizeOptionalIdentifier(entry.resource?.meta?.claims?.[ClaimsPersonSchemaorg.identifier]);
149
+ if (!identifier) {
150
+ throw new Error('Every purge entry requires org.schema.Person.identifier.');
151
+ }
152
+ return buildEmployeePurgeBundle({ identifier }).entry[0];
153
+ }),
154
+ };
155
+ }
156
+ return {
157
+ resourceType: 'Bundle',
158
+ type: 'batch',
159
+ entry: this.entries.map((entry) => cloneEntry(entry)),
160
+ };
161
+ }
162
+ /** Reads one claim from the active entry. */
163
+ getClaim(key) {
164
+ return cloneClaimValue(this.getActiveEntryClaims()[String(key).trim()]);
165
+ }
166
+ /** Checks whether the active entry contains one claim key. */
167
+ hasClaim(key) {
168
+ return Object.prototype.hasOwnProperty.call(this.getActiveEntryClaims(), String(key).trim());
169
+ }
170
+ /** Writes one claim on the active entry. */
171
+ setClaim(key, value) {
172
+ const entry = this.getRequiredActiveEntry();
173
+ entry.resource = entry.resource || { resourceType: 'Employee', meta: { claims: {} } };
174
+ entry.resource.meta = entry.resource.meta || {};
175
+ entry.resource.meta.claims = {
176
+ ...(entry.resource.meta.claims || {}),
177
+ [String(key).trim()]: cloneClaimValue(value),
178
+ };
179
+ return this;
180
+ }
181
+ /** Appends one claim value on the active entry. */
182
+ addClaim(key, value) {
183
+ const normalizedKey = String(key).trim();
184
+ const current = this.getClaim(normalizedKey);
185
+ if (current === undefined) {
186
+ return this.setClaim(normalizedKey, value);
187
+ }
188
+ if (Array.isArray(current)) {
189
+ return this.setClaim(normalizedKey, [...current, cloneClaimValue(value)]);
190
+ }
191
+ return this.setClaim(normalizedKey, [current, cloneClaimValue(value)]);
192
+ }
193
+ /** Removes one claim from the active entry. */
194
+ removeClaim(key) {
195
+ const entry = this.getRequiredActiveEntry();
196
+ const claims = {
197
+ ...(entry.resource?.meta?.claims || {}),
198
+ };
199
+ delete claims[String(key).trim()];
200
+ entry.resource = entry.resource || { resourceType: 'Employee', meta: { claims: {} } };
201
+ entry.resource.meta = entry.resource.meta || {};
202
+ entry.resource.meta.claims = claims;
203
+ return this;
204
+ }
205
+ /**
206
+ * Sets the active entry identifier.
207
+ *
208
+ * When informed, the value is synchronized into `fullUrl`, `resource.id`,
209
+ * and the canonical identifier claim.
210
+ * Empty values remove the identifier from all three places.
211
+ */
212
+ setIdentifier(identifier) {
213
+ const normalized = normalizeOptionalIdentifier(identifier);
214
+ const entry = this.getRequiredActiveEntry();
215
+ if (!normalized) {
216
+ this.removeClaim(ClaimsPersonSchemaorg.identifier);
217
+ delete entry.resource?.id;
218
+ delete entry.fullUrl;
219
+ return this;
220
+ }
221
+ this.setClaim(ClaimsPersonSchemaorg.identifier, normalized);
222
+ entry.resource = entry.resource || { resourceType: 'Employee', meta: { claims: {} } };
223
+ entry.resource.id = normalized;
224
+ entry.fullUrl = normalized;
225
+ return this;
226
+ }
227
+ /** Reads the active entry identifier from claims, resource id, or fullUrl. */
228
+ getIdentifier() {
229
+ const entry = this.getRequiredActiveEntry();
230
+ return normalizeOptionalIdentifier(entry.resource?.meta?.claims?.[ClaimsPersonSchemaorg.identifier]
231
+ || entry.resource?.id
232
+ || entry.fullUrl);
233
+ }
234
+ /** Ensures the active entry carries one canonical identifier. */
235
+ ensureIdentifier() {
236
+ const existing = this.getIdentifier();
237
+ if (existing) {
238
+ return existing;
239
+ }
240
+ const generated = createEmployeeIdentifierUrn();
241
+ this.setIdentifier(generated);
242
+ return generated;
243
+ }
244
+ /** Sets the active entry `fullUrl` explicitly. */
245
+ setFullUrl(fullUrl) {
246
+ const entry = this.getRequiredActiveEntry();
247
+ const normalized = normalizeOptionalIdentifier(fullUrl);
248
+ if (!normalized) {
249
+ delete entry.fullUrl;
250
+ return this;
251
+ }
252
+ entry.fullUrl = normalized;
253
+ return this;
254
+ }
255
+ /** Returns the active entry `fullUrl` when present. */
256
+ getFullUrl() {
257
+ return normalizeOptionalIdentifier(this.getRequiredActiveEntry().fullUrl);
258
+ }
259
+ /** Convenience employee setter for email. */
260
+ setEmail(email) {
261
+ return this.setClaim(ClaimsPersonSchemaorg.email, String(email).trim());
262
+ }
263
+ /** Convenience employee setter for occupational role. */
264
+ setRole(role) {
265
+ return this.setClaim(ClaimsPersonSchemaorg.hasOccupationalRoleValue, String(role).trim());
266
+ }
267
+ /** Convenience employee setter for `worksFor`. */
268
+ setWorksFor(worksFor) {
269
+ return this.setClaim(ClaimsPersonSchemaorg.worksFor, String(worksFor).trim());
270
+ }
271
+ /** Convenience employee setter for `memberOf`. */
272
+ setMemberOf(memberOf) {
273
+ return this.setClaim(ClaimsPersonSchemaorg.memberOf, String(memberOf).trim());
274
+ }
275
+ /** Convenience employee setter for `memberOf.taxID`. */
276
+ setMemberOfOrgTaxId(taxId) {
277
+ return this.setClaim(ClaimsPersonSchemaorg.memberOfOrgTaxId, String(taxId).trim());
278
+ }
279
+ requireBundleOperation() {
280
+ if (!this.bundleOperation) {
281
+ throw new Error('BundleEditor requires setBundleOperation(...) before newEntry() or build().');
282
+ }
283
+ return this.bundleOperation;
284
+ }
285
+ getRequiredActiveEntry() {
286
+ if (this.activeEntryIndex === null) {
287
+ throw new Error('BundleEditor requires one active entry. Call newEntry(...) or openEntry(...) first.');
288
+ }
289
+ return this.entries[this.activeEntryIndex];
290
+ }
291
+ getActiveEntryClaims() {
292
+ return {
293
+ ...(this.getRequiredActiveEntry().resource?.meta?.claims || {}),
294
+ };
295
+ }
296
+ getActiveOrSingleSearchClaims() {
297
+ if (this.entries.length === 0) {
298
+ return {};
299
+ }
300
+ if (this.entries.length > 1) {
301
+ throw new Error('Search bundles currently support one search entry per bundle.');
302
+ }
303
+ const claims = this.entries[0].resource?.meta?.claims || {};
304
+ const searchClaims = {};
305
+ for (const [key, value] of Object.entries(claims)) {
306
+ if (value === undefined
307
+ || value === null
308
+ || typeof value === 'string'
309
+ || typeof value === 'number'
310
+ || typeof value === 'boolean') {
311
+ searchClaims[key] = value;
312
+ }
313
+ }
314
+ return searchClaims;
315
+ }
316
+ }
@@ -38,6 +38,8 @@ export type AddContainedDocumentToActiveEntryInput = Readonly<{
38
38
  * - `Communication.content-attachment-data` is always derived from the
39
39
  * in-memory bundle after each committed update.
40
40
  * - saving can release active entry memory via `saveAndReleaseActiveEntry()`.
41
+ * - consent onboarding should prefer semantic helpers first, but this session
42
+ * also exposes direct claim-level editing on the selected active entry.
41
43
  */
42
44
  export declare class CommunicationAttachedBundleSession {
43
45
  private communicationClaims;
@@ -53,6 +55,16 @@ export declare class CommunicationAttachedBundleSession {
53
55
  getActiveEntryIndex(): number | null;
54
56
  /** Returns a deep copy of the active entry when selected. */
55
57
  getActiveEntry(): BundleEntry | null;
58
+ /** Returns one claim from the currently selected active entry. */
59
+ getActiveEntryClaim(key: string): unknown;
60
+ /** Returns whether the currently selected active entry carries one claim key. */
61
+ hasActiveEntryClaim(key: string): boolean;
62
+ /** Sets one claim on the currently selected active entry and syncs the bundle attachment. */
63
+ setActiveEntryClaim(key: string, value: unknown): this;
64
+ /** Appends one claim value on the currently selected active entry and syncs the bundle attachment. */
65
+ addActiveEntryClaim(key: string, value: unknown): this;
66
+ /** Removes one claim from the currently selected active entry and syncs the bundle attachment. */
67
+ removeActiveEntryClaim(key: string): this;
56
68
  /** Selects an active entry by index or fullUrl. */
57
69
  selectActiveEntry(selection: ActiveEntrySelection): this;
58
70
  /** Clears active entry selection from memory. */
@@ -164,6 +176,8 @@ export declare class CommunicationAttachedBundleSession {
164
176
  * Resolves the entry URL (`fullUrl`) for a given entry/resource identifier.
165
177
  */
166
178
  getEntryUrl(entryId: string): string | undefined;
179
+ private getRequiredActiveEntry;
180
+ private getRequiredActiveEntryClaims;
167
181
  private decodeBundleFromClaims;
168
182
  private syncAttachmentFromBundle;
169
183
  private resolveCurrentSubject;
@@ -18,6 +18,8 @@ import { MedicationStatementClaim, } from '../models/interoperable-claims/medica
18
18
  * - `Communication.content-attachment-data` is always derived from the
19
19
  * in-memory bundle after each committed update.
20
20
  * - saving can release active entry memory via `saveAndReleaseActiveEntry()`.
21
+ * - consent onboarding should prefer semantic helpers first, but this session
22
+ * also exposes direct claim-level editing on the selected active entry.
21
23
  */
22
24
  export class CommunicationAttachedBundleSession {
23
25
  communicationClaims;
@@ -55,6 +57,70 @@ export class CommunicationAttachedBundleSession {
55
57
  }
56
58
  return cloneEntry(this.bundleInMemory.data[this.activeEntryIndex]);
57
59
  }
60
+ /** Returns one claim from the currently selected active entry. */
61
+ getActiveEntryClaim(key) {
62
+ const claims = this.getRequiredActiveEntryClaims();
63
+ return cloneUnknownValue(claims[key]);
64
+ }
65
+ /** Returns whether the currently selected active entry carries one claim key. */
66
+ hasActiveEntryClaim(key) {
67
+ const claims = this.getRequiredActiveEntryClaims();
68
+ return Object.prototype.hasOwnProperty.call(claims, key);
69
+ }
70
+ /** Sets one claim on the currently selected active entry and syncs the bundle attachment. */
71
+ setActiveEntryClaim(key, value) {
72
+ const current = cloneEntry(this.getRequiredActiveEntry());
73
+ const resource = ensureEntryResource(current, this.mode);
74
+ resource.meta = resource.meta || {};
75
+ resource.meta.claims = {
76
+ ...(resource.meta.claims || {}),
77
+ [String(key).trim()]: cloneUnknownValue(value),
78
+ };
79
+ current.resource = resource;
80
+ this.bundleInMemory.data[this.activeEntryIndex] = current;
81
+ this.syncAttachmentFromBundle();
82
+ return this;
83
+ }
84
+ /** Appends one claim value on the currently selected active entry and syncs the bundle attachment. */
85
+ addActiveEntryClaim(key, value) {
86
+ const current = cloneEntry(this.getRequiredActiveEntry());
87
+ const resource = ensureEntryResource(current, this.mode);
88
+ resource.meta = resource.meta || {};
89
+ const claims = {
90
+ ...(resource.meta.claims || {}),
91
+ };
92
+ const normalizedKey = String(key).trim();
93
+ const currentValue = claims[normalizedKey];
94
+ if (currentValue === undefined) {
95
+ claims[normalizedKey] = cloneUnknownValue(value);
96
+ }
97
+ else if (Array.isArray(currentValue)) {
98
+ claims[normalizedKey] = [...currentValue, cloneUnknownValue(value)];
99
+ }
100
+ else {
101
+ claims[normalizedKey] = [currentValue, cloneUnknownValue(value)];
102
+ }
103
+ resource.meta.claims = claims;
104
+ current.resource = resource;
105
+ this.bundleInMemory.data[this.activeEntryIndex] = current;
106
+ this.syncAttachmentFromBundle();
107
+ return this;
108
+ }
109
+ /** Removes one claim from the currently selected active entry and syncs the bundle attachment. */
110
+ removeActiveEntryClaim(key) {
111
+ const current = cloneEntry(this.getRequiredActiveEntry());
112
+ const resource = ensureEntryResource(current, this.mode);
113
+ resource.meta = resource.meta || {};
114
+ const claims = {
115
+ ...(resource.meta.claims || {}),
116
+ };
117
+ delete claims[String(key).trim()];
118
+ resource.meta.claims = claims;
119
+ current.resource = resource;
120
+ this.bundleInMemory.data[this.activeEntryIndex] = current;
121
+ this.syncAttachmentFromBundle();
122
+ return this;
123
+ }
58
124
  /** Selects an active entry by index or fullUrl. */
59
125
  selectActiveEntry(selection) {
60
126
  if (typeof selection.index === 'number') {
@@ -297,6 +363,20 @@ export class CommunicationAttachedBundleSession {
297
363
  const query = new BundleQuery(this.bundleInMemory);
298
364
  return query.getEntryUrl(entryId);
299
365
  }
366
+ getRequiredActiveEntry() {
367
+ if (this.activeEntryIndex === null) {
368
+ throw new Error('No active entry selected.');
369
+ }
370
+ return this.bundleInMemory.data[this.activeEntryIndex];
371
+ }
372
+ getRequiredActiveEntryClaims() {
373
+ const current = cloneEntry(this.getRequiredActiveEntry());
374
+ const resource = ensureEntryResource(current, this.mode);
375
+ resource.meta = resource.meta || {};
376
+ return {
377
+ ...(resource.meta.claims || {}),
378
+ };
379
+ }
300
380
  decodeBundleFromClaims(claims) {
301
381
  const encoded = asTrimmedString(claims[CommunicationClaim.ContentAttachmentData]);
302
382
  if (!encoded) {
@@ -495,6 +575,12 @@ function cloneBundle(bundle) {
495
575
  function cloneEntry(entry) {
496
576
  return JSON.parse(JSON.stringify(entry));
497
577
  }
578
+ function cloneUnknownValue(value) {
579
+ if (value === undefined) {
580
+ return value;
581
+ }
582
+ return JSON.parse(JSON.stringify(value));
583
+ }
498
584
  function asTrimmedString(value) {
499
585
  if (value === undefined || value === null) {
500
586
  return '';
@@ -21,12 +21,39 @@ export type EmployeeBatchEntryInput = Readonly<{
21
21
  export type EmployeeBatchBundleInput = Readonly<{
22
22
  entries: readonly EmployeeBatchEntryInput[];
23
23
  }>;
24
+ export type EmployeePurgeBundleInput = Readonly<{
25
+ identifier: string;
26
+ resourceId?: string;
27
+ resourceType?: 'Employee';
28
+ requestType?: (typeof EmployeeBatchEntryTypes)['purge'];
29
+ }>;
24
30
  export type EmployeeSearchBundleInput = Readonly<{
25
31
  claims?: Record<string, EmployeeSearchValue | undefined>;
26
32
  method?: 'GET' | 'POST';
27
33
  encoding?: SearchRequestEncoding;
28
34
  resourceType?: 'Employee';
29
35
  }>;
36
+ export declare const EmployeeBundleOperations: Readonly<{
37
+ readonly create: "create";
38
+ readonly search: "search";
39
+ readonly disable: "disable";
40
+ readonly purge: "purge";
41
+ }>;
42
+ export declare const EmployeeBundleMethods: Readonly<{
43
+ readonly create: "POST";
44
+ readonly search: "POST";
45
+ readonly disable: "DELETE";
46
+ readonly purge: "POST";
47
+ }>;
48
+ export declare const EmployeeBundleRoutes: Readonly<{
49
+ readonly search: "Employee/_search";
50
+ }>;
51
+ export declare const EmployeeBatchEntryTypes: Readonly<{
52
+ readonly create: "Employee-create-request-v1.0";
53
+ readonly disable: "Employee-disable-request-v1.0";
54
+ readonly search: "Employee-search-request-v1.0";
55
+ readonly purge: "Employee-purge-request-v1.0";
56
+ }>;
30
57
  /**
31
58
  * Builds canonical `org.schema.Person.*` employee claims from semantic input.
32
59
  */
@@ -60,6 +87,19 @@ export declare function buildEmployeeBatchBundle(input: EmployeeBatchBundleInput
60
87
  type: 'batch';
61
88
  entry: Array<ReturnType<typeof buildEmployeeBatchEntry>>;
62
89
  };
90
+ /**
91
+ * Builds the canonical employee purge batch bundle.
92
+ *
93
+ * Purge is a terminal lifecycle operation routed to the explicit
94
+ * `Employee/_purge` endpoint by runtime layers. The bundle selector should be
95
+ * one concrete employee identity, therefore this helper keeps the payload
96
+ * focused on the canonical employee identifier.
97
+ */
98
+ export declare function buildEmployeePurgeBundle(input: EmployeePurgeBundleInput): {
99
+ resourceType: 'Bundle';
100
+ type: 'batch';
101
+ entry: Array<ReturnType<typeof buildEmployeeBatchEntry>>;
102
+ };
63
103
  /**
64
104
  * Builds the legacy query-string employee search target kept for compatibility
65
105
  * with older `_search` wrappers.
@@ -1,5 +1,26 @@
1
1
  import { ClaimsPersonSchemaorg } from '../constants/schemaorg.js';
2
2
  import { buildFhirParametersResourceFromSearchParams, buildSearchQueryString, } from './fhir-search.js';
3
+ export const EmployeeBundleOperations = Object.freeze({
4
+ create: 'create',
5
+ search: 'search',
6
+ disable: 'disable',
7
+ purge: 'purge',
8
+ });
9
+ export const EmployeeBundleMethods = Object.freeze({
10
+ create: 'POST',
11
+ search: 'POST',
12
+ disable: 'DELETE',
13
+ purge: 'POST',
14
+ });
15
+ export const EmployeeBundleRoutes = Object.freeze({
16
+ search: 'Employee/_search',
17
+ });
18
+ export const EmployeeBatchEntryTypes = Object.freeze({
19
+ create: 'Employee-create-request-v1.0',
20
+ disable: 'Employee-disable-request-v1.0',
21
+ search: 'Employee-search-request-v1.0',
22
+ purge: 'Employee-purge-request-v1.0',
23
+ });
3
24
  function cloneClaims(claims) {
4
25
  return { ...(claims || {}) };
5
26
  }
@@ -15,15 +36,15 @@ function normalizeEmployeeSearchClaims(claims) {
15
36
  function inferEmployeeEntryType(method) {
16
37
  switch (method) {
17
38
  case 'DELETE':
18
- return 'Employee-delete-request-v1.0';
39
+ return EmployeeBatchEntryTypes.disable;
19
40
  case 'PUT':
20
41
  case 'PATCH':
21
42
  return 'Employee-update-request-v1.0';
22
43
  case 'GET':
23
- return 'Employee-search-request-v1.0';
44
+ return EmployeeBatchEntryTypes.search;
24
45
  case 'POST':
25
46
  default:
26
- return 'Employee-create-request-v1.0';
47
+ return EmployeeBatchEntryTypes.create;
27
48
  }
28
49
  }
29
50
  /**
@@ -85,6 +106,28 @@ export function buildEmployeeBatchBundle(input) {
85
106
  entry: [...input.entries].map((entry) => buildEmployeeBatchEntry(entry)),
86
107
  };
87
108
  }
109
+ /**
110
+ * Builds the canonical employee purge batch bundle.
111
+ *
112
+ * Purge is a terminal lifecycle operation routed to the explicit
113
+ * `Employee/_purge` endpoint by runtime layers. The bundle selector should be
114
+ * one concrete employee identity, therefore this helper keeps the payload
115
+ * focused on the canonical employee identifier.
116
+ */
117
+ export function buildEmployeePurgeBundle(input) {
118
+ const identifier = input.identifier.trim();
119
+ return buildEmployeeBatchBundle({
120
+ entries: [
121
+ {
122
+ type: input.requestType || EmployeeBatchEntryTypes.purge,
123
+ method: EmployeeBundleMethods.purge,
124
+ resourceId: input.resourceId || identifier,
125
+ resourceType: input.resourceType,
126
+ claims: buildEmployeeClaims({ identifier }),
127
+ },
128
+ ],
129
+ });
130
+ }
88
131
  /**
89
132
  * Builds the legacy query-string employee search target kept for compatibility
90
133
  * with older `_search` wrappers.
@@ -103,7 +146,7 @@ export function buildEmployeeSearchQuery(input = {}) {
103
146
  export function buildEmployeeSearchBundle(input = {}) {
104
147
  const resourceType = input.resourceType || 'Employee';
105
148
  const claims = normalizeEmployeeSearchClaims(input.claims);
106
- const method = input.method || (input.encoding === 'get-query' ? 'GET' : 'POST');
149
+ const method = input.method || (input.encoding === 'get-query' ? 'GET' : EmployeeBundleMethods.search);
107
150
  if (method === 'GET') {
108
151
  return {
109
152
  resourceType: 'Bundle',
@@ -124,8 +167,8 @@ export function buildEmployeeSearchBundle(input = {}) {
124
167
  entry: [
125
168
  {
126
169
  request: {
127
- method: 'POST',
128
- url: `${resourceType}/_search`,
170
+ method: EmployeeBundleMethods.search,
171
+ url: EmployeeBundleRoutes.search,
129
172
  },
130
173
  resource: buildFhirParametersResourceFromSearchParams(claims),
131
174
  },
@@ -3,6 +3,7 @@ export * from './activation-policy';
3
3
  export * from './base-convert';
4
4
  export * from './baseN';
5
5
  export * from './bundle';
6
+ export * from './bundle-editor';
6
7
  export * from './bundle-query';
7
8
  export * from '../claims';
8
9
  export * from './content';
@@ -3,6 +3,7 @@ export * from './activation-policy.js';
3
3
  export * from './base-convert.js';
4
4
  export * from './baseN.js';
5
5
  export * from './bundle.js';
6
+ export * from './bundle-editor.js';
6
7
  export * from './bundle-query.js';
7
8
  export * from '../claims/index.js';
8
9
  export * from './content.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdc-common-utils-ts",
3
- "version": "1.14.15",
3
+ "version": "1.15.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },