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,181 @@
1
+ import type { BundleEntry, BundleJsonApi } from '../models/bundle';
2
+ import { AllowedResourceType, BundleType, type BuiltBundleEntry, type BundleOperation, type ResourceTypeEntryEditor } from '../models/bundle-editor-types';
3
+ import { type ValidateBundleDocumentResult } from './bundle-document-builder';
4
+ import { buildEmployeePurgeBundle, buildEmployeeSearchBundle } from './employee';
5
+ import { BundleEntryEditor } from './bundle-entry-editor';
6
+ export declare class BundleEditor {
7
+ private bundleOperation;
8
+ private allowedResourceType;
9
+ private bundleType;
10
+ private compositionClaims;
11
+ private readonly entries;
12
+ /**
13
+ * Declares which business action this in-memory bundle is staging.
14
+ *
15
+ * Important distinction:
16
+ * - this is **not** the same concept as FHIR `Bundle.entry.request.method`
17
+ * - this is the higher-level action the editor is helping to assemble, for
18
+ * example `create`, `search`, `disable`, or `purge`
19
+ * - the lower-level transport/request method may later be derived from that
20
+ * action, and may differ by backend contract
21
+ *
22
+ * Example:
23
+ * - bundle operation `disable`
24
+ * - current employee GW contract -> inner `entry.request.method = DELETE`
25
+ * - current individual organization GW contract -> explicit `/_disable`
26
+ * route with inner `entry.request.method = POST`
27
+ */
28
+ setBundleOperation(operation: BundleOperation): this;
29
+ /**
30
+ * Returns the current high-level business action assigned to the bundle.
31
+ *
32
+ * Read this as:
33
+ * - "what am I trying to do?"
34
+ *
35
+ * not as:
36
+ * - "which HTTP/FHIR request method will the final entry use?"
37
+ */
38
+ getBundleOperation(): BundleOperation | null;
39
+ /**
40
+ * Restricts the bundle to one resource type.
41
+ *
42
+ * For employee batch bundles this should be fixed to
43
+ * `EmployeeResourceTypes.employee` so the editor cannot mix unrelated
44
+ * resource kinds.
45
+ */
46
+ setAllowedResourceType(resourceType: AllowedResourceType): this;
47
+ /** Returns the bundle resource type restriction when already declared. */
48
+ getAllowedResourceType(): AllowedResourceType | null;
49
+ /** Declares which FHIR bundle shape the editor should materialize. */
50
+ setBundleType(type: BundleType): this;
51
+ /** Returns the declared target bundle shape. */
52
+ getBundleType(): BundleType;
53
+ setCompositionIdentifier(identifier?: string | null): this;
54
+ /** Returns the normalized `Composition.identifier` staged for the future document root. */
55
+ getCompositionIdentifier(): string | undefined;
56
+ setCompositionSubject(subject?: string | null): this;
57
+ /** Returns the normalized `Composition.subject` staged for the future document root. */
58
+ getCompositionSubject(): string | undefined;
59
+ setCompositionType(type?: string | null): this;
60
+ /** Returns the normalized `Composition.type` staged for the future document root. */
61
+ getCompositionType(): string | undefined;
62
+ setCompositionTitle(title?: string | null): this;
63
+ /** Returns the normalized `Composition.title` staged for the future document root. */
64
+ getCompositionTitle(): string | undefined;
65
+ setCompositionDate(date?: string | null): this;
66
+ /** Returns the normalized `Composition.date` staged for the future document root. */
67
+ getCompositionDate(): string | undefined;
68
+ setCompositionAuthorList(references: readonly string[]): this;
69
+ /** Returns the normalized `Composition.author` reference list staged for the future document root. */
70
+ getCompositionAuthorList(): string[];
71
+ /**
72
+ * Opens one new entry and returns a generic entry editor for that slot.
73
+ *
74
+ * The entry editor can later expose resource-specific semantics through
75
+ * methods such as `asEmployee()`.
76
+ */
77
+ newEntry(resourceId?: string, resourceTypeOverride?: AllowedResourceType): BundleEntryEditor;
78
+ /** Opens one new entry with an explicit resource type, mainly for document bundles. */
79
+ newEntryAs<T extends AllowedResourceType>(resourceType: T, resourceId?: string): ResourceTypeEntryEditor<T>;
80
+ /** Reopens one existing entry by `resource.id` or `fullUrl`. */
81
+ openEntry(resourceIdOrFullUrl: string): BundleEntryEditor;
82
+ /** Returns cloned staged entries for inspection or debugging. */
83
+ getEntries(): readonly BuiltBundleEntry[];
84
+ /** Returns the current Composition-level claims used when building a document bundle. */
85
+ getCompositionClaims(): Readonly<Record<string, unknown>>;
86
+ /**
87
+ * Validates whether the current editor state is sufficient to build one
88
+ * clinical `Bundle.type=document`.
89
+ */
90
+ validateDocumentAuthoring(): ValidateBundleDocumentResult;
91
+ /** Builds one Composition-first document bundle from the staged resource entries. */
92
+ buildDocument(): Record<string, unknown>;
93
+ /**
94
+ * Materializes the final bundle payload from the editor state.
95
+ *
96
+ * `build()` does not send, sign, or wrap the payload. It only returns the
97
+ * final bundle object for the declared operation and staged entries.
98
+ */
99
+ build(): ReturnType<typeof buildEmployeePurgeBundle> | ReturnType<typeof buildEmployeeSearchBundle> | {
100
+ resourceType: 'Bundle';
101
+ type: BundleType;
102
+ entry: BuiltBundleEntry[];
103
+ };
104
+ /**
105
+ * Materializes the staged entries as the `BundleJsonApi` shape used by
106
+ * `CommunicationAttachedBundleSession` and `ConsentAccessEditor`.
107
+ *
108
+ * Use this when the next layer edits bundle entries in-memory before another
109
+ * runtime wraps or sends the bundle.
110
+ */
111
+ buildJsonApi(): BundleJsonApi<BundleEntry>;
112
+ /** @internal */
113
+ getMutableEntry(entryIndex: number): BuiltBundleEntry;
114
+ /** @internal */
115
+ removeMutableEntry(entryIndex: number): void;
116
+ /** @internal */
117
+ findEntryIndexByIdentifierOrFullUrl(resourceIdOrFullUrl: string): number;
118
+ /**
119
+ * Creates the mutable entry state that will later be wrapped by one entry editor.
120
+ *
121
+ * The draft seeds bundle-owned defaults such as:
122
+ * - canonical internal identifier (`resource.id` / `fullUrl`)
123
+ * - request method derived from the high-level bundle operation
124
+ * - generic entry type string
125
+ *
126
+ * It does not yet imply that the public `<ResourceType>.identifier` claim was
127
+ * written; the entry editor decides when that claim must be synchronized.
128
+ */
129
+ private createEntryDraft;
130
+ /**
131
+ * Resolves which resource type one new entry should use.
132
+ *
133
+ * Non-document bundles stay homogeneous once an allowed resource type was
134
+ * declared. Document bundles may stage multiple resource types and therefore
135
+ * can use the override path repeatedly.
136
+ */
137
+ private resolveEntryResourceType;
138
+ /**
139
+ * Normalizes one single-entry search bundle into a plain claims object.
140
+ *
141
+ * Keep this internal because it is specific to the current search serialization
142
+ * contract and should not become the public reader/editor story.
143
+ */
144
+ private getSingleSearchClaims;
145
+ /** Returns the declared bundle operation or throws one onboarding-grade error. */
146
+ private requireBundleOperation;
147
+ /** Returns the allowed resource type or throws when the editor was not initialized correctly. */
148
+ private requireAllowedResourceType;
149
+ /**
150
+ * Stores one Composition scalar claim in the staged document metadata.
151
+ *
152
+ * Callers should use semantic wrappers such as `setCompositionTitle(...)`
153
+ * instead of touching claim keys directly.
154
+ */
155
+ private setCompositionScalarClaim;
156
+ /**
157
+ * Reads one normalized Composition scalar claim.
158
+ *
159
+ * This internal helper explains the otherwise opaque `getComposition...()`
160
+ * implementations: they all delegate to the same normalized metadata store.
161
+ */
162
+ private getCompositionScalarClaim;
163
+ /**
164
+ * Stores one CSV-backed Composition claim list such as `Composition.author`.
165
+ *
166
+ * The editor keeps the flattened storage representation internal while the
167
+ * public API exposes simple `string[]` setters/getters.
168
+ */
169
+ private setCompositionCsvClaimList;
170
+ /** Reads one CSV-backed Composition claim list as a normalized string array. */
171
+ private getCompositionCsvClaimList;
172
+ }
173
+ /**
174
+ * Generic editor for one staged bundle entry.
175
+ *
176
+ * This class only knows generic entry concerns such as:
177
+ * - resource id
178
+ * - fullUrl
179
+ * - meta claims
180
+ * - conversion to one resource-specific entry editor
181
+ */
@@ -0,0 +1,467 @@
1
+ /**
2
+ * File discipline note:
3
+ * - Read `ARCHITECTURE.md` and `CONTRIBUTING.md` before changing this module.
4
+ * - This file owns only bundle-level orchestration for `BundleEditor`.
5
+ * - Shared helper logic belongs in `bundle-editor-helpers.ts`.
6
+ * - Entry/resource-specific behavior belongs in dedicated `*-entry-editor.ts`
7
+ * modules, one exported class per file.
8
+ */
9
+ import { ClaimsPersonSchemaorg } from '../constants/schemaorg.js';
10
+ import { ResourceTypesFhirR4 } from '../constants/fhir-resource-types.js';
11
+ import { BundleEditorValidationIssues, BundleTypes, } from '../models/bundle-editor-types.js';
12
+ import { CompositionClaim } from '../models/interoperable-claims/composition-claims.js';
13
+ import { getClaimValues, setClaimValues } from '../claims/claim-list-helpers.js';
14
+ import { buildBundleDocumentFromClaims } from './bundle-document-builder.js';
15
+ import { buildEmployeeBatchEntry, buildEmployeePurgeBundle, buildEmployeeSearchBundle, EmployeeBundleOperations, EmployeeResourceTypes, } from './employee.js';
16
+ import { BundleEntryEditor } from './bundle-entry-editor.js';
17
+ import { cloneEntry, createCanonicalIdentifierUrn, inferGenericEntryType, normalizeOptionalIdentifier, resolveEntryTypeForOperation, resolveRequestMethodForOperation, } from './bundle-editor-helpers.js';
18
+ export class BundleEditor {
19
+ bundleOperation = null;
20
+ allowedResourceType = null;
21
+ bundleType = BundleTypes.batch;
22
+ compositionClaims = {};
23
+ entries = [];
24
+ /**
25
+ * Declares which business action this in-memory bundle is staging.
26
+ *
27
+ * Important distinction:
28
+ * - this is **not** the same concept as FHIR `Bundle.entry.request.method`
29
+ * - this is the higher-level action the editor is helping to assemble, for
30
+ * example `create`, `search`, `disable`, or `purge`
31
+ * - the lower-level transport/request method may later be derived from that
32
+ * action, and may differ by backend contract
33
+ *
34
+ * Example:
35
+ * - bundle operation `disable`
36
+ * - current employee GW contract -> inner `entry.request.method = DELETE`
37
+ * - current individual organization GW contract -> explicit `/_disable`
38
+ * route with inner `entry.request.method = POST`
39
+ */
40
+ setBundleOperation(operation) {
41
+ this.bundleOperation = operation;
42
+ return this;
43
+ }
44
+ /**
45
+ * Returns the current high-level business action assigned to the bundle.
46
+ *
47
+ * Read this as:
48
+ * - "what am I trying to do?"
49
+ *
50
+ * not as:
51
+ * - "which HTTP/FHIR request method will the final entry use?"
52
+ */
53
+ getBundleOperation() {
54
+ return this.bundleOperation;
55
+ }
56
+ /**
57
+ * Restricts the bundle to one resource type.
58
+ *
59
+ * For employee batch bundles this should be fixed to
60
+ * `EmployeeResourceTypes.employee` so the editor cannot mix unrelated
61
+ * resource kinds.
62
+ */
63
+ setAllowedResourceType(resourceType) {
64
+ this.allowedResourceType = resourceType;
65
+ return this;
66
+ }
67
+ /** Returns the bundle resource type restriction when already declared. */
68
+ getAllowedResourceType() {
69
+ return this.allowedResourceType;
70
+ }
71
+ /** Declares which FHIR bundle shape the editor should materialize. */
72
+ setBundleType(type) {
73
+ this.bundleType = type;
74
+ return this;
75
+ }
76
+ /** Returns the declared target bundle shape. */
77
+ getBundleType() {
78
+ return this.bundleType;
79
+ }
80
+ setCompositionIdentifier(identifier) {
81
+ return this.setCompositionScalarClaim(CompositionClaim.Identifier, identifier);
82
+ }
83
+ /** Returns the normalized `Composition.identifier` staged for the future document root. */
84
+ getCompositionIdentifier() {
85
+ return this.getCompositionScalarClaim(CompositionClaim.Identifier);
86
+ }
87
+ setCompositionSubject(subject) {
88
+ return this.setCompositionScalarClaim(CompositionClaim.Subject, subject);
89
+ }
90
+ /** Returns the normalized `Composition.subject` staged for the future document root. */
91
+ getCompositionSubject() {
92
+ return this.getCompositionScalarClaim(CompositionClaim.Subject);
93
+ }
94
+ setCompositionType(type) {
95
+ return this.setCompositionScalarClaim(CompositionClaim.Type, type);
96
+ }
97
+ /** Returns the normalized `Composition.type` staged for the future document root. */
98
+ getCompositionType() {
99
+ return this.getCompositionScalarClaim(CompositionClaim.Type);
100
+ }
101
+ setCompositionTitle(title) {
102
+ return this.setCompositionScalarClaim(CompositionClaim.Title, title);
103
+ }
104
+ /** Returns the normalized `Composition.title` staged for the future document root. */
105
+ getCompositionTitle() {
106
+ return this.getCompositionScalarClaim(CompositionClaim.Title);
107
+ }
108
+ setCompositionDate(date) {
109
+ return this.setCompositionScalarClaim(CompositionClaim.Date, date);
110
+ }
111
+ /** Returns the normalized `Composition.date` staged for the future document root. */
112
+ getCompositionDate() {
113
+ return this.getCompositionScalarClaim(CompositionClaim.Date);
114
+ }
115
+ setCompositionAuthorList(references) {
116
+ return this.setCompositionCsvClaimList(CompositionClaim.Author, references);
117
+ }
118
+ /** Returns the normalized `Composition.author` reference list staged for the future document root. */
119
+ getCompositionAuthorList() {
120
+ return this.getCompositionCsvClaimList(CompositionClaim.Author);
121
+ }
122
+ /**
123
+ * Opens one new entry and returns a generic entry editor for that slot.
124
+ *
125
+ * The entry editor can later expose resource-specific semantics through
126
+ * methods such as `asEmployee()`.
127
+ */
128
+ newEntry(resourceId, resourceTypeOverride) {
129
+ const operation = this.requireBundleOperation();
130
+ const resourceType = this.resolveEntryResourceType(resourceTypeOverride);
131
+ const entry = this.createEntryDraft(operation, resourceType, resourceId);
132
+ this.entries.push(entry);
133
+ return new BundleEntryEditor(this, this.entries.length - 1);
134
+ }
135
+ /** Opens one new entry with an explicit resource type, mainly for document bundles. */
136
+ newEntryAs(resourceType, resourceId) {
137
+ return this.newEntry(resourceId, resourceType).asResourceType(resourceType);
138
+ }
139
+ /** Reopens one existing entry by `resource.id` or `fullUrl`. */
140
+ openEntry(resourceIdOrFullUrl) {
141
+ const normalizedIdentifier = normalizeOptionalIdentifier(resourceIdOrFullUrl);
142
+ if (!normalizedIdentifier) {
143
+ throw new Error('openEntry requires a non-empty resource identifier or fullUrl.');
144
+ }
145
+ const entryIndex = this.entries.findIndex((entry) => {
146
+ return normalizeOptionalIdentifier(entry.resource?.id) === normalizedIdentifier
147
+ || normalizeOptionalIdentifier(entry.fullUrl) === normalizedIdentifier;
148
+ });
149
+ if (entryIndex < 0) {
150
+ throw new Error(`openEntry could not find resource identifier or fullUrl: ${normalizedIdentifier}`);
151
+ }
152
+ return new BundleEntryEditor(this, entryIndex);
153
+ }
154
+ /** Returns cloned staged entries for inspection or debugging. */
155
+ getEntries() {
156
+ return this.entries.map((entry) => cloneEntry(entry));
157
+ }
158
+ /** Returns the current Composition-level claims used when building a document bundle. */
159
+ getCompositionClaims() {
160
+ return { ...this.compositionClaims };
161
+ }
162
+ /**
163
+ * Validates whether the current editor state is sufficient to build one
164
+ * clinical `Bundle.type=document`.
165
+ */
166
+ validateDocumentAuthoring() {
167
+ const issues = [];
168
+ if (this.bundleType !== BundleTypes.document) {
169
+ issues.push(BundleEditorValidationIssues.DocumentModeRequired);
170
+ }
171
+ if (!this.getCompositionSubject()) {
172
+ issues.push(BundleEditorValidationIssues.CompositionSubjectRequired);
173
+ }
174
+ if (!this.getCompositionType()) {
175
+ issues.push(BundleEditorValidationIssues.CompositionTypeRequired);
176
+ }
177
+ if (!this.getCompositionTitle()) {
178
+ issues.push(BundleEditorValidationIssues.CompositionTitleRequired);
179
+ }
180
+ if (!this.getCompositionDate()) {
181
+ issues.push(BundleEditorValidationIssues.CompositionDateRequired);
182
+ }
183
+ if (this.getCompositionAuthorList().length === 0) {
184
+ issues.push(BundleEditorValidationIssues.CompositionAuthorRequired);
185
+ }
186
+ if (this.entries.length === 0) {
187
+ issues.push(BundleEditorValidationIssues.DocumentEntryRequired);
188
+ }
189
+ return {
190
+ ok: issues.length === 0,
191
+ issues,
192
+ };
193
+ }
194
+ /** Builds one Composition-first document bundle from the staged resource entries. */
195
+ buildDocument() {
196
+ const validation = this.validateDocumentAuthoring();
197
+ if (!validation.ok) {
198
+ throw new Error(`BundleEditor cannot build document: ${validation.issues.join(' ')}`);
199
+ }
200
+ return buildBundleDocumentFromClaims({
201
+ claimsList: this.entries.map((entry) => ({ ...(entry.resource?.meta?.claims || {}) })),
202
+ subjectDid: this.getCompositionSubject(),
203
+ compositionType: this.getCompositionType(),
204
+ compositionClaims: this.getCompositionClaims(),
205
+ });
206
+ }
207
+ /**
208
+ * Materializes the final bundle payload from the editor state.
209
+ *
210
+ * `build()` does not send, sign, or wrap the payload. It only returns the
211
+ * final bundle object for the declared operation and staged entries.
212
+ */
213
+ build() {
214
+ if (this.bundleType === BundleTypes.document) {
215
+ return this.buildDocument();
216
+ }
217
+ const operation = this.requireBundleOperation();
218
+ const resourceType = this.requireAllowedResourceType();
219
+ if (operation === EmployeeBundleOperations.search) {
220
+ if (resourceType !== EmployeeResourceTypes.employee) {
221
+ throw new Error(`BundleEditor search currently supports only resource type: ${EmployeeResourceTypes.employee}`);
222
+ }
223
+ return buildEmployeeSearchBundle({
224
+ claims: this.getSingleSearchClaims(),
225
+ resourceType,
226
+ });
227
+ }
228
+ if (operation === EmployeeBundleOperations.purge) {
229
+ if (resourceType !== EmployeeResourceTypes.employee) {
230
+ throw new Error(`BundleEditor purge currently supports only resource type: ${EmployeeResourceTypes.employee}`);
231
+ }
232
+ return {
233
+ resourceType: EmployeeResourceTypes.bundle,
234
+ type: EmployeeResourceTypes.batch,
235
+ entry: this.entries.map((entry) => {
236
+ const identifier = normalizeOptionalIdentifier(entry.resource?.meta?.claims?.[ClaimsPersonSchemaorg.identifier]
237
+ || entry.resource?.id
238
+ || entry.fullUrl);
239
+ if (!identifier) {
240
+ throw new Error('Every purge entry requires one canonical employee identifier.');
241
+ }
242
+ return buildEmployeePurgeBundle({
243
+ identifier,
244
+ resourceType,
245
+ }).entry[0];
246
+ }),
247
+ };
248
+ }
249
+ return {
250
+ resourceType: ResourceTypesFhirR4.Bundle,
251
+ type: this.bundleType,
252
+ entry: this.entries.map((entry) => cloneEntry(entry)),
253
+ };
254
+ }
255
+ /**
256
+ * Materializes the staged entries as the `BundleJsonApi` shape used by
257
+ * `CommunicationAttachedBundleSession` and `ConsentAccessEditor`.
258
+ *
259
+ * Use this when the next layer edits bundle entries in-memory before another
260
+ * runtime wraps or sends the bundle.
261
+ */
262
+ buildJsonApi() {
263
+ return {
264
+ resourceType: ResourceTypesFhirR4.Bundle,
265
+ type: this.bundleType,
266
+ data: this.entries.map((entry) => {
267
+ const clonedEntry = cloneEntry(entry);
268
+ const { request: _requestIgnored, ...clonedEntryWithoutRequest } = clonedEntry;
269
+ const normalizedRequest = clonedEntry.request
270
+ ? {
271
+ method: clonedEntry.request.method,
272
+ url: clonedEntry.request.url || '',
273
+ }
274
+ : undefined;
275
+ return normalizedRequest
276
+ ? {
277
+ ...clonedEntryWithoutRequest,
278
+ request: normalizedRequest,
279
+ }
280
+ : clonedEntryWithoutRequest;
281
+ }),
282
+ };
283
+ }
284
+ /** @internal */
285
+ getMutableEntry(entryIndex) {
286
+ if (!Number.isInteger(entryIndex) || entryIndex < 0 || entryIndex >= this.entries.length) {
287
+ throw new Error(`BundleEditor could not open entry index: ${entryIndex}`);
288
+ }
289
+ return this.entries[entryIndex];
290
+ }
291
+ /** @internal */
292
+ removeMutableEntry(entryIndex) {
293
+ if (!Number.isInteger(entryIndex) || entryIndex < 0 || entryIndex >= this.entries.length) {
294
+ throw new Error(`BundleEditor could not remove entry index: ${entryIndex}`);
295
+ }
296
+ this.entries.splice(entryIndex, 1);
297
+ }
298
+ /** @internal */
299
+ findEntryIndexByIdentifierOrFullUrl(resourceIdOrFullUrl) {
300
+ const normalizedIdentifier = normalizeOptionalIdentifier(resourceIdOrFullUrl);
301
+ if (!normalizedIdentifier) {
302
+ return -1;
303
+ }
304
+ return this.entries.findIndex((entry) => {
305
+ return normalizeOptionalIdentifier(entry.resource?.id) === normalizedIdentifier
306
+ || normalizeOptionalIdentifier(entry.fullUrl) === normalizedIdentifier;
307
+ });
308
+ }
309
+ /**
310
+ * Creates the mutable entry state that will later be wrapped by one entry editor.
311
+ *
312
+ * The draft seeds bundle-owned defaults such as:
313
+ * - canonical internal identifier (`resource.id` / `fullUrl`)
314
+ * - request method derived from the high-level bundle operation
315
+ * - generic entry type string
316
+ *
317
+ * It does not yet imply that the public `<ResourceType>.identifier` claim was
318
+ * written; the entry editor decides when that claim must be synchronized.
319
+ */
320
+ createEntryDraft(operation, resourceType, resourceId) {
321
+ const normalizedIdentifier = operation === EmployeeBundleOperations.search
322
+ ? normalizeOptionalIdentifier(resourceId)
323
+ : (normalizeOptionalIdentifier(resourceId) || createCanonicalIdentifierUrn());
324
+ const claims = {
325
+ '@context': 'org.schema',
326
+ ...(normalizedIdentifier ? { [ClaimsPersonSchemaorg.identifier]: normalizedIdentifier } : {}),
327
+ };
328
+ if (resourceType !== EmployeeResourceTypes.employee) {
329
+ return {
330
+ type: inferGenericEntryType(resourceType, operation),
331
+ request: { method: resolveRequestMethodForOperation(operation) },
332
+ fullUrl: normalizedIdentifier,
333
+ resource: {
334
+ resourceType,
335
+ ...(normalizedIdentifier ? { id: normalizedIdentifier } : {}),
336
+ meta: { claims: {} },
337
+ },
338
+ };
339
+ }
340
+ const entry = buildEmployeeBatchEntry({
341
+ type: resolveEntryTypeForOperation(operation),
342
+ method: resolveRequestMethodForOperation(operation),
343
+ resourceId: normalizedIdentifier,
344
+ resourceType,
345
+ claims,
346
+ });
347
+ if (normalizedIdentifier) {
348
+ entry.fullUrl = normalizedIdentifier;
349
+ }
350
+ return entry;
351
+ }
352
+ /**
353
+ * Resolves which resource type one new entry should use.
354
+ *
355
+ * Non-document bundles stay homogeneous once an allowed resource type was
356
+ * declared. Document bundles may stage multiple resource types and therefore
357
+ * can use the override path repeatedly.
358
+ */
359
+ resolveEntryResourceType(resourceTypeOverride) {
360
+ const explicitResourceType = normalizeOptionalIdentifier(resourceTypeOverride);
361
+ if (explicitResourceType) {
362
+ if (this.bundleType !== BundleTypes.document
363
+ && this.allowedResourceType
364
+ && explicitResourceType !== this.allowedResourceType) {
365
+ throw new Error(`BundleEditor cannot mix resource types in ${this.bundleType} mode: ${this.allowedResourceType} vs ${explicitResourceType}`);
366
+ }
367
+ return explicitResourceType;
368
+ }
369
+ if (!this.allowedResourceType) {
370
+ throw new Error('BundleEditor requires setAllowedResourceType(...) before newEntry() or build().');
371
+ }
372
+ return this.allowedResourceType;
373
+ }
374
+ /**
375
+ * Normalizes one single-entry search bundle into a plain claims object.
376
+ *
377
+ * Keep this internal because it is specific to the current search serialization
378
+ * contract and should not become the public reader/editor story.
379
+ */
380
+ getSingleSearchClaims() {
381
+ if (this.entries.length === 0) {
382
+ return {};
383
+ }
384
+ if (this.entries.length > 1) {
385
+ throw new Error('Search bundles currently support one entry per bundle.');
386
+ }
387
+ const claims = this.entries[0].resource?.meta?.claims || {};
388
+ const searchClaims = {};
389
+ for (const [key, value] of Object.entries(claims)) {
390
+ if (value === undefined
391
+ || value === null
392
+ || typeof value === 'string'
393
+ || typeof value === 'number'
394
+ || typeof value === 'boolean') {
395
+ searchClaims[key] = value;
396
+ }
397
+ }
398
+ return searchClaims;
399
+ }
400
+ /** Returns the declared bundle operation or throws one onboarding-grade error. */
401
+ requireBundleOperation() {
402
+ if (!this.bundleOperation) {
403
+ throw new Error('BundleEditor requires setBundleOperation(...) before newEntry() or build().');
404
+ }
405
+ return this.bundleOperation;
406
+ }
407
+ /** Returns the allowed resource type or throws when the editor was not initialized correctly. */
408
+ requireAllowedResourceType() {
409
+ if (!this.allowedResourceType) {
410
+ throw new Error('BundleEditor requires setAllowedResourceType(...) before newEntry() or build().');
411
+ }
412
+ return this.allowedResourceType;
413
+ }
414
+ /**
415
+ * Stores one Composition scalar claim in the staged document metadata.
416
+ *
417
+ * Callers should use semantic wrappers such as `setCompositionTitle(...)`
418
+ * instead of touching claim keys directly.
419
+ */
420
+ setCompositionScalarClaim(claimKey, value) {
421
+ const normalized = normalizeOptionalIdentifier(value);
422
+ if (!normalized) {
423
+ delete this.compositionClaims[claimKey];
424
+ return this;
425
+ }
426
+ this.compositionClaims[claimKey] = normalized;
427
+ return this;
428
+ }
429
+ /**
430
+ * Reads one normalized Composition scalar claim.
431
+ *
432
+ * This internal helper explains the otherwise opaque `getComposition...()`
433
+ * implementations: they all delegate to the same normalized metadata store.
434
+ */
435
+ getCompositionScalarClaim(claimKey) {
436
+ return normalizeOptionalIdentifier(this.compositionClaims[claimKey]);
437
+ }
438
+ /**
439
+ * Stores one CSV-backed Composition claim list such as `Composition.author`.
440
+ *
441
+ * The editor keeps the flattened storage representation internal while the
442
+ * public API exposes simple `string[]` setters/getters.
443
+ */
444
+ setCompositionCsvClaimList(claimKey, values) {
445
+ const next = setClaimValues({}, claimKey, values);
446
+ const normalized = normalizeOptionalIdentifier(next[claimKey]);
447
+ if (!normalized) {
448
+ delete this.compositionClaims[claimKey];
449
+ return this;
450
+ }
451
+ this.compositionClaims[claimKey] = normalized;
452
+ return this;
453
+ }
454
+ /** Reads one CSV-backed Composition claim list as a normalized string array. */
455
+ getCompositionCsvClaimList(claimKey) {
456
+ return getClaimValues(this.compositionClaims, claimKey);
457
+ }
458
+ }
459
+ /**
460
+ * Generic editor for one staged bundle entry.
461
+ *
462
+ * This class only knows generic entry concerns such as:
463
+ * - resource id
464
+ * - fullUrl
465
+ * - meta claims
466
+ * - conversion to one resource-specific entry editor
467
+ */