sos-sdk-node-ts 0.1.3 → 0.1.4
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 +13 -0
- package/dist/bundle-sync-service.d.ts +46 -0
- package/dist/bundle-sync-service.js +82 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -26,6 +26,19 @@ persistence, jobs and prompt-audio runtime behavior. It does not own
|
|
|
26
26
|
deterministic data contracts, common authorization or sector policy. Vet, UHC
|
|
27
27
|
and SOSChain may consume it without depending on one another.
|
|
28
28
|
|
|
29
|
+
`BundleSyncDraftService` is the trusted-runtime adapter for the shared durable
|
|
30
|
+
editor outbox. The application injects protected storage, payload protection
|
|
31
|
+
and the real provider executor. The service stores no clear clinical Bundle,
|
|
32
|
+
reuses the same idempotent draft after transient failures, and does not report
|
|
33
|
+
confirmation until the provider write has been followed by authoritative
|
|
34
|
+
readback.
|
|
35
|
+
|
|
36
|
+
It also does not duplicate the typed clinical Bundle editors exposed by the
|
|
37
|
+
GDC Node SDK. Add a SOS Node adapter only when a real shared channel workflow
|
|
38
|
+
requires runtime behavior, following the
|
|
39
|
+
[SOS/Vet/UHC clinical accessor adoption matrix](https://github.com/Fundacion-UNID/sos-data-utils-ts/blob/main/docs/CLINICAL_TYPED_ACCESSOR_ADOPTION.md).
|
|
40
|
+
Portals and assistants without that editor remain on their verified versions.
|
|
41
|
+
|
|
29
42
|
The first adapter, `SosEmergencyDispatchClient`, sends an already-authorized
|
|
30
43
|
emergency request over HTTPS with a stable idempotency key and validates the
|
|
31
44
|
provider acknowledgement. The bearer credential remains only in the trusted
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type BundleSyncDraft, type BundleSyncPayload, type BundleSyncState } from 'sos-sdk-core-ts/bundle-sync-draft';
|
|
2
|
+
export type ProtectedBundleSyncRecord = Readonly<{
|
|
3
|
+
id: string;
|
|
4
|
+
kind: BundleSyncDraft['kind'];
|
|
5
|
+
subjectReference: string;
|
|
6
|
+
state: BundleSyncState;
|
|
7
|
+
protectedPayload: string;
|
|
8
|
+
createdAt: string;
|
|
9
|
+
updatedAt: string;
|
|
10
|
+
}>;
|
|
11
|
+
export interface BundleSyncRepository {
|
|
12
|
+
put(record: ProtectedBundleSyncRecord): Promise<void>;
|
|
13
|
+
get(id: string): Promise<ProtectedBundleSyncRecord | undefined>;
|
|
14
|
+
list(): Promise<readonly ProtectedBundleSyncRecord[]>;
|
|
15
|
+
}
|
|
16
|
+
export interface BundleSyncProtection {
|
|
17
|
+
protect(draft: BundleSyncDraft): Promise<string>;
|
|
18
|
+
unprotect(protectedPayload: string): Promise<BundleSyncDraft>;
|
|
19
|
+
}
|
|
20
|
+
export interface BundleSyncExecutor {
|
|
21
|
+
submit(draft: BundleSyncDraft): Promise<BundleSyncPayload>;
|
|
22
|
+
readback(draft: BundleSyncDraft): Promise<BundleSyncPayload>;
|
|
23
|
+
}
|
|
24
|
+
export type BundleSyncDraftServiceOptions = Readonly<{
|
|
25
|
+
repository: BundleSyncRepository;
|
|
26
|
+
protection: BundleSyncProtection;
|
|
27
|
+
executor: BundleSyncExecutor;
|
|
28
|
+
now?: () => Date;
|
|
29
|
+
}>;
|
|
30
|
+
/** Deterministic test adapter. Production runtimes inject durable storage. */
|
|
31
|
+
export declare class BundleSyncRepositoryMemory implements BundleSyncRepository {
|
|
32
|
+
#private;
|
|
33
|
+
put(record: ProtectedBundleSyncRecord): Promise<void>;
|
|
34
|
+
get(id: string): Promise<ProtectedBundleSyncRecord | undefined>;
|
|
35
|
+
list(): Promise<readonly ProtectedBundleSyncRecord[]>;
|
|
36
|
+
}
|
|
37
|
+
/** Protected outbox shared by clinical, Consent, contact and Encounter Bundles. */
|
|
38
|
+
export declare class BundleSyncDraftService {
|
|
39
|
+
private readonly options;
|
|
40
|
+
constructor(options: BundleSyncDraftServiceOptions);
|
|
41
|
+
enqueue(draft: BundleSyncDraft): Promise<BundleSyncDraft>;
|
|
42
|
+
read(id: string): Promise<BundleSyncDraft | undefined>;
|
|
43
|
+
process(id: string): Promise<BundleSyncDraft>;
|
|
44
|
+
private persist;
|
|
45
|
+
private now;
|
|
46
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Copyright 2026 Connecting Solution & Applications Ltd under the Apache License, Version 2.0.
|
|
2
|
+
import { BundleSyncStates, confirmBundleSyncDraft, markBundleSyncRetryable, reconcileBundleSyncSubmission, submitBundleSyncDraft, } from 'sos-sdk-core-ts/bundle-sync-draft';
|
|
3
|
+
/** Deterministic test adapter. Production runtimes inject durable storage. */
|
|
4
|
+
export class BundleSyncRepositoryMemory {
|
|
5
|
+
#records = new Map();
|
|
6
|
+
async put(record) {
|
|
7
|
+
this.#records.set(record.id, structuredClone(record));
|
|
8
|
+
}
|
|
9
|
+
async get(id) {
|
|
10
|
+
const record = this.#records.get(id);
|
|
11
|
+
return record ? structuredClone(record) : undefined;
|
|
12
|
+
}
|
|
13
|
+
async list() {
|
|
14
|
+
return [...this.#records.values()].map((record) => structuredClone(record));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** Protected outbox shared by clinical, Consent, contact and Encounter Bundles. */
|
|
18
|
+
export class BundleSyncDraftService {
|
|
19
|
+
options;
|
|
20
|
+
constructor(options) {
|
|
21
|
+
this.options = options;
|
|
22
|
+
}
|
|
23
|
+
async enqueue(draft) {
|
|
24
|
+
const current = await this.options.repository.get(draft.id);
|
|
25
|
+
if (current) {
|
|
26
|
+
const restored = await this.options.protection.unprotect(current.protectedPayload);
|
|
27
|
+
if (JSON.stringify(restored.changesBundle) !== JSON.stringify(draft.changesBundle)) {
|
|
28
|
+
throw new Error('bundle_sync_idempotency_conflict');
|
|
29
|
+
}
|
|
30
|
+
return restored;
|
|
31
|
+
}
|
|
32
|
+
await this.persist(draft);
|
|
33
|
+
return draft;
|
|
34
|
+
}
|
|
35
|
+
async read(id) {
|
|
36
|
+
const record = await this.options.repository.get(id);
|
|
37
|
+
return record ? this.options.protection.unprotect(record.protectedPayload) : undefined;
|
|
38
|
+
}
|
|
39
|
+
async process(id) {
|
|
40
|
+
const draft = await this.read(id);
|
|
41
|
+
if (!draft)
|
|
42
|
+
throw new Error('bundle_sync_draft_not_found');
|
|
43
|
+
if (draft.state === BundleSyncStates.Confirmed || draft.state === BundleSyncStates.Rejected)
|
|
44
|
+
return draft;
|
|
45
|
+
const submitting = submitBundleSyncDraft(draft, this.now());
|
|
46
|
+
await this.persist(submitting);
|
|
47
|
+
try {
|
|
48
|
+
const response = await this.options.executor.submit(submitting);
|
|
49
|
+
const reconciled = reconcileBundleSyncSubmission(submitting, response, this.now());
|
|
50
|
+
await this.persist(reconciled);
|
|
51
|
+
if (reconciled.state !== BundleSyncStates.Reconciling)
|
|
52
|
+
return reconciled;
|
|
53
|
+
const authoritativeBundle = await this.options.executor.readback(reconciled);
|
|
54
|
+
const confirmed = confirmBundleSyncDraft(reconciled, authoritativeBundle, this.now());
|
|
55
|
+
await this.persist(confirmed);
|
|
56
|
+
return confirmed;
|
|
57
|
+
}
|
|
58
|
+
catch (reason) {
|
|
59
|
+
const retryable = markBundleSyncRetryable(submitting, [classify(reason)], this.now());
|
|
60
|
+
await this.persist(retryable);
|
|
61
|
+
return retryable;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async persist(draft) {
|
|
65
|
+
await this.options.repository.put({
|
|
66
|
+
id: draft.id,
|
|
67
|
+
kind: draft.kind,
|
|
68
|
+
subjectReference: draft.subjectReference,
|
|
69
|
+
state: draft.state,
|
|
70
|
+
protectedPayload: await this.options.protection.protect(draft),
|
|
71
|
+
createdAt: draft.createdAt,
|
|
72
|
+
updatedAt: draft.updatedAt,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
now() {
|
|
76
|
+
return (this.options.now?.() ?? new Date()).toISOString();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function classify(reason) {
|
|
80
|
+
const message = reason instanceof Error ? reason.message : String(reason || 'bundle_sync_failed');
|
|
81
|
+
return message.slice(0, 300);
|
|
82
|
+
}
|
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-node-ts",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Product-neutral Node adapters shared by SOSChain, Vet and UHC runtimes",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Connecting Solution & Applications Ltd",
|
|
@@ -27,6 +27,10 @@
|
|
|
27
27
|
"./secure-message-crypto": {
|
|
28
28
|
"types": "./dist/secure-message-crypto.d.ts",
|
|
29
29
|
"default": "./dist/secure-message-crypto.js"
|
|
30
|
+
},
|
|
31
|
+
"./bundle-sync-service": {
|
|
32
|
+
"types": "./dist/bundle-sync-service.d.ts",
|
|
33
|
+
"default": "./dist/bundle-sync-service.js"
|
|
30
34
|
}
|
|
31
35
|
},
|
|
32
36
|
"files": [
|
|
@@ -43,7 +47,7 @@
|
|
|
43
47
|
"prepack": "npm run check"
|
|
44
48
|
},
|
|
45
49
|
"dependencies": {
|
|
46
|
-
"sos-sdk-core-ts": "0.
|
|
50
|
+
"sos-sdk-core-ts": "0.5.1"
|
|
47
51
|
},
|
|
48
52
|
"devDependencies": {
|
|
49
53
|
"@types/node": "^24.10.0",
|