sos-sdk-core-ts 0.5.0 → 0.5.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.
- package/CHANGELOG.md +8 -0
- package/README.md +7 -0
- package/dist/bundle-sync-draft.d.ts +61 -0
- package/dist/bundle-sync-draft.js +115 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.5.1 - 2026-10-04
|
|
6
|
+
|
|
7
|
+
- Add the shared Bundle synchronization draft contract for clinical documents,
|
|
8
|
+
Consent, emergency contacts and Encounter transfers. Optimistic records stay
|
|
9
|
+
visibly pending after ambiguous or transient provider failures, definite
|
|
10
|
+
rejections restore the last confirmed Bundle, and only authoritative
|
|
11
|
+
provider readback marks a mutation as confirmed.
|
|
12
|
+
|
|
5
13
|
- Add the claims-first Encounter transfer projection helper. It keeps a
|
|
6
14
|
returning source `on-hold`, completes a definitive source departure, starts
|
|
7
15
|
the destination `in-progress`, records both `EncounterHistory` milestones,
|
package/README.md
CHANGED
|
@@ -62,6 +62,13 @@ The shared scheduling and response-correlation boundary is documented in
|
|
|
62
62
|
Source-to-destination care movement and subject-index projection are documented
|
|
63
63
|
in [Encounter transfer projection](docs/ENCOUNTER_TRANSFER_PROJECTION.md).
|
|
64
64
|
|
|
65
|
+
`BundleSyncDraft` is the shared serializable state for editors that must keep a
|
|
66
|
+
local clinical document, Consent, emergency-contact or Encounter mutation
|
|
67
|
+
visible while its provider write is pending. A successful HTTP response is not
|
|
68
|
+
confirmation: the draft becomes `confirmed` only after authoritative provider
|
|
69
|
+
readback. Ambiguous and transport failures remain `retryable`; definite remote
|
|
70
|
+
rejections restore the last confirmed Bundle.
|
|
71
|
+
|
|
65
72
|
The emergency-group contract stores subject references only on an enumerated
|
|
66
73
|
FHIR Group. A group assistance request references that Group from one standard,
|
|
67
74
|
compact-coded ServiceRequest; it never copies members or persists split coding
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { BundleEntry, BundleJsonApi } from 'gdc-common-utils-ts/models/bundle';
|
|
2
|
+
export declare const BundleSyncKinds: Readonly<{
|
|
3
|
+
readonly ClinicalDocument: "clinical-document";
|
|
4
|
+
readonly EncounterTransfer: "encounter-transfer";
|
|
5
|
+
readonly Consent: "consent";
|
|
6
|
+
readonly EmergencyContact: "emergency-contact";
|
|
7
|
+
}>;
|
|
8
|
+
export type BundleSyncKind = typeof BundleSyncKinds[keyof typeof BundleSyncKinds];
|
|
9
|
+
export declare const BundleSyncStates: Readonly<{
|
|
10
|
+
readonly Pending: "pending";
|
|
11
|
+
readonly Submitting: "submitting";
|
|
12
|
+
readonly Retryable: "retryable";
|
|
13
|
+
readonly Reconciling: "reconciling";
|
|
14
|
+
readonly Confirmed: "confirmed";
|
|
15
|
+
readonly Rejected: "rejected";
|
|
16
|
+
}>;
|
|
17
|
+
export type BundleSyncState = typeof BundleSyncStates[keyof typeof BundleSyncStates];
|
|
18
|
+
export type BundleSyncPayload = BundleJsonApi<BundleEntry>;
|
|
19
|
+
export type BundleSyncDraft = Readonly<{
|
|
20
|
+
id: string;
|
|
21
|
+
kind: BundleSyncKind;
|
|
22
|
+
subjectReference: string;
|
|
23
|
+
state: BundleSyncState;
|
|
24
|
+
changesBundle: BundleSyncPayload;
|
|
25
|
+
lastConfirmedBundle: BundleSyncPayload;
|
|
26
|
+
optimisticBundle?: BundleSyncPayload;
|
|
27
|
+
visibleBundle: BundleSyncPayload;
|
|
28
|
+
pendingIdentifiers: readonly string[];
|
|
29
|
+
diagnostics: readonly string[];
|
|
30
|
+
createdAt: string;
|
|
31
|
+
updatedAt: string;
|
|
32
|
+
}>;
|
|
33
|
+
export type CreateBundleSyncDraftInput = Readonly<{
|
|
34
|
+
id: string;
|
|
35
|
+
kind: BundleSyncKind;
|
|
36
|
+
subjectReference: string;
|
|
37
|
+
changesBundle: BundleSyncPayload;
|
|
38
|
+
lastConfirmedBundle: BundleSyncPayload;
|
|
39
|
+
optimisticBundle: BundleSyncPayload;
|
|
40
|
+
createdAt: string;
|
|
41
|
+
}>;
|
|
42
|
+
/**
|
|
43
|
+
* Creates the portable, serializable state shown while the BFF owns delivery.
|
|
44
|
+
* The optimistic Bundle remains visibly marked by `state`; it is never proof
|
|
45
|
+
* of provider persistence.
|
|
46
|
+
*/
|
|
47
|
+
export declare function createBundleSyncDraft(input: CreateBundleSyncDraftInput): BundleSyncDraft;
|
|
48
|
+
/** Marks a durable draft as actively submitted without changing its preview. */
|
|
49
|
+
export declare function submitBundleSyncDraft(draft: BundleSyncDraft, updatedAt: string): BundleSyncDraft;
|
|
50
|
+
/** Retains the exact optimistic draft after a transient transport failure. */
|
|
51
|
+
export declare function markBundleSyncRetryable(draft: BundleSyncDraft, diagnostics: readonly string[], updatedAt: string): BundleSyncDraft;
|
|
52
|
+
/**
|
|
53
|
+
* Interprets the correlated remote Bundle response.
|
|
54
|
+
*
|
|
55
|
+
* Missing or ambiguous results keep the optimistic Bundle as a retryable
|
|
56
|
+
* draft. A definite rejection removes it. Successful submission moves to
|
|
57
|
+
* reconciling and still requires an authoritative provider readback.
|
|
58
|
+
*/
|
|
59
|
+
export declare function reconcileBundleSyncSubmission(draft: BundleSyncDraft, responseBundle: BundleSyncPayload, updatedAt: string): BundleSyncDraft;
|
|
60
|
+
/** Replaces the preview only after exact provider readback succeeds. */
|
|
61
|
+
export declare function confirmBundleSyncDraft(draft: BundleSyncDraft, authoritativeBundle: BundleSyncPayload, updatedAt: string): BundleSyncDraft;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// Copyright 2026 Connecting Solution & Applications Ltd under the Apache License, Version 2.0.
|
|
2
|
+
import { analyzeBundleChangesResponse } from './bundle-changes-response.js';
|
|
3
|
+
export const BundleSyncKinds = Object.freeze({
|
|
4
|
+
ClinicalDocument: 'clinical-document',
|
|
5
|
+
EncounterTransfer: 'encounter-transfer',
|
|
6
|
+
Consent: 'consent',
|
|
7
|
+
EmergencyContact: 'emergency-contact',
|
|
8
|
+
});
|
|
9
|
+
export const BundleSyncStates = Object.freeze({
|
|
10
|
+
Pending: 'pending',
|
|
11
|
+
Submitting: 'submitting',
|
|
12
|
+
Retryable: 'retryable',
|
|
13
|
+
Reconciling: 'reconciling',
|
|
14
|
+
Confirmed: 'confirmed',
|
|
15
|
+
Rejected: 'rejected',
|
|
16
|
+
});
|
|
17
|
+
/**
|
|
18
|
+
* Creates the portable, serializable state shown while the BFF owns delivery.
|
|
19
|
+
* The optimistic Bundle remains visibly marked by `state`; it is never proof
|
|
20
|
+
* of provider persistence.
|
|
21
|
+
*/
|
|
22
|
+
export function createBundleSyncDraft(input) {
|
|
23
|
+
requireText(input.id, 'bundle_sync_id_required');
|
|
24
|
+
requireText(input.subjectReference, 'bundle_sync_subject_required');
|
|
25
|
+
requireText(input.createdAt, 'bundle_sync_created_at_required');
|
|
26
|
+
if (!Object.values(BundleSyncKinds).includes(input.kind))
|
|
27
|
+
throw new TypeError('bundle_sync_kind_invalid');
|
|
28
|
+
return Object.freeze({
|
|
29
|
+
...input,
|
|
30
|
+
state: BundleSyncStates.Pending,
|
|
31
|
+
visibleBundle: input.optimisticBundle,
|
|
32
|
+
pendingIdentifiers: Object.freeze([]),
|
|
33
|
+
diagnostics: Object.freeze([]),
|
|
34
|
+
updatedAt: input.createdAt,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/** Marks a durable draft as actively submitted without changing its preview. */
|
|
38
|
+
export function submitBundleSyncDraft(draft, updatedAt) {
|
|
39
|
+
if (![BundleSyncStates.Pending, BundleSyncStates.Retryable].includes(draft.state)) {
|
|
40
|
+
throw new TypeError('bundle_sync_not_submittable');
|
|
41
|
+
}
|
|
42
|
+
return Object.freeze({ ...draft, state: BundleSyncStates.Submitting, updatedAt });
|
|
43
|
+
}
|
|
44
|
+
/** Retains the exact optimistic draft after a transient transport failure. */
|
|
45
|
+
export function markBundleSyncRetryable(draft, diagnostics, updatedAt) {
|
|
46
|
+
if ([BundleSyncStates.Confirmed, BundleSyncStates.Rejected].includes(draft.state)) {
|
|
47
|
+
throw new TypeError('bundle_sync_retry_not_allowed');
|
|
48
|
+
}
|
|
49
|
+
return Object.freeze({
|
|
50
|
+
...draft,
|
|
51
|
+
state: BundleSyncStates.Retryable,
|
|
52
|
+
diagnostics: Object.freeze([...diagnostics]),
|
|
53
|
+
updatedAt,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Interprets the correlated remote Bundle response.
|
|
58
|
+
*
|
|
59
|
+
* Missing or ambiguous results keep the optimistic Bundle as a retryable
|
|
60
|
+
* draft. A definite rejection removes it. Successful submission moves to
|
|
61
|
+
* reconciling and still requires an authoritative provider readback.
|
|
62
|
+
*/
|
|
63
|
+
export function reconcileBundleSyncSubmission(draft, responseBundle, updatedAt) {
|
|
64
|
+
if (![BundleSyncStates.Pending, BundleSyncStates.Submitting, BundleSyncStates.Retryable].includes(draft.state)) {
|
|
65
|
+
throw new TypeError('bundle_sync_response_not_expected');
|
|
66
|
+
}
|
|
67
|
+
const analysis = analyzeBundleChangesResponse(draft.changesBundle, responseBundle);
|
|
68
|
+
if (analysis.failedIdentifiers.length) {
|
|
69
|
+
return Object.freeze({
|
|
70
|
+
...draft,
|
|
71
|
+
state: BundleSyncStates.Rejected,
|
|
72
|
+
optimisticBundle: undefined,
|
|
73
|
+
visibleBundle: draft.lastConfirmedBundle,
|
|
74
|
+
pendingIdentifiers: Object.freeze([]),
|
|
75
|
+
diagnostics: Object.freeze([...analysis.diagnostics, ...analysis.entries.flatMap(({ diagnostics }) => diagnostics)]),
|
|
76
|
+
updatedAt,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
if (analysis.pendingIdentifiers.length) {
|
|
80
|
+
return Object.freeze({
|
|
81
|
+
...draft,
|
|
82
|
+
state: BundleSyncStates.Retryable,
|
|
83
|
+
pendingIdentifiers: Object.freeze([...analysis.pendingIdentifiers]),
|
|
84
|
+
diagnostics: Object.freeze([...analysis.diagnostics]),
|
|
85
|
+
updatedAt,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return Object.freeze({
|
|
89
|
+
...draft,
|
|
90
|
+
state: BundleSyncStates.Reconciling,
|
|
91
|
+
pendingIdentifiers: Object.freeze([]),
|
|
92
|
+
diagnostics: Object.freeze([...analysis.diagnostics]),
|
|
93
|
+
updatedAt,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/** Replaces the preview only after exact provider readback succeeds. */
|
|
97
|
+
export function confirmBundleSyncDraft(draft, authoritativeBundle, updatedAt) {
|
|
98
|
+
if (draft.state !== BundleSyncStates.Reconciling)
|
|
99
|
+
throw new TypeError('bundle_sync_readback_not_expected');
|
|
100
|
+
return Object.freeze({
|
|
101
|
+
...draft,
|
|
102
|
+
state: BundleSyncStates.Confirmed,
|
|
103
|
+
lastConfirmedBundle: authoritativeBundle,
|
|
104
|
+
optimisticBundle: undefined,
|
|
105
|
+
visibleBundle: authoritativeBundle,
|
|
106
|
+
pendingIdentifiers: Object.freeze([]),
|
|
107
|
+
diagnostics: Object.freeze([]),
|
|
108
|
+
updatedAt,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
function requireText(value, issue) {
|
|
112
|
+
if (!value.trim())
|
|
113
|
+
throw new TypeError(issue);
|
|
114
|
+
return value;
|
|
115
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sos-sdk-core-ts",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Runtime-neutral shared orchestration for SOS, Vet and UHC products",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Connecting Solution & Applications Ltd",
|
|
@@ -47,6 +47,10 @@
|
|
|
47
47
|
"./encounter-transfer-communication": {
|
|
48
48
|
"types": "./dist/encounter-transfer-communication.d.ts",
|
|
49
49
|
"default": "./dist/encounter-transfer-communication.js"
|
|
50
|
+
},
|
|
51
|
+
"./bundle-sync-draft": {
|
|
52
|
+
"types": "./dist/bundle-sync-draft.d.ts",
|
|
53
|
+
"default": "./dist/bundle-sync-draft.js"
|
|
50
54
|
}
|
|
51
55
|
},
|
|
52
56
|
"files": [
|