vet-sdk-core-ts 0.4.2 → 0.4.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 +46 -0
- package/dist/gateway-contract.d.ts +43 -0
- package/dist/gateway-contract.js +67 -18
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -6,6 +6,31 @@ UHC SDK packages and must not import them.
|
|
|
6
6
|
The SDK consumes governed browser-safe values from `vet-data-utils-ts` and
|
|
7
7
|
owns gateway request construction. GW VET remains the policy authority.
|
|
8
8
|
|
|
9
|
+
## Professional break-glass authorization
|
|
10
|
+
|
|
11
|
+
Node BFFs call `buildVeterinaryBreakGlassSmartAuthorization` with only the
|
|
12
|
+
animal DID, governed sections, incident id and clinical justification. The
|
|
13
|
+
result can be passed directly to
|
|
14
|
+
`ServerProfileSessionManager.openProfessional().requestSmartToken(...)`.
|
|
15
|
+
It contains the veterinary purpose, scope, 15-minute cap and audited emergency
|
|
16
|
+
claims, but never a `client_id`, SMART audience, private-key assertion or VP.
|
|
17
|
+
Those OpenID proofs are reconstructed and signed by the registered server
|
|
18
|
+
wallet in `gdc-sdk-node-ts`.
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
const authorization = buildVeterinaryBreakGlassSmartAuthorization({
|
|
22
|
+
subjectDid,
|
|
23
|
+
requestedSections,
|
|
24
|
+
incidentId,
|
|
25
|
+
justification,
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
await openedProfessional.requestSmartToken(authorization)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`buildVeterinaryBreakGlassSmartRequest` remains only as a low-level
|
|
32
|
+
compatibility envelope for integrations that already own the OpenID fields.
|
|
33
|
+
|
|
9
34
|
The numeric animal identity and external evidence contract is documented in
|
|
10
35
|
[`docs/ANIMAL_IDENTITY.md`](docs/ANIMAL_IDENTITY.md).
|
|
11
36
|
|
|
@@ -35,3 +60,24 @@ import {
|
|
|
35
60
|
VetChainDomesticAnimalSpecies.DomesticFerret.ncbiTaxonomyId; // "9669"
|
|
36
61
|
findVetChainSpeciesByTaxonomyId("9685")?.key; // "Cat"
|
|
37
62
|
```
|
|
63
|
+
|
|
64
|
+
## Digital-twin search
|
|
65
|
+
|
|
66
|
+
`buildVeterinaryDigitalTwinSearchRequest` accepts only one governed section,
|
|
67
|
+
selected resource families, display text and dates. It returns both the FHIR
|
|
68
|
+
`Parameters` body and the `filters` map for `DigitalTwinSdk.search`; callers do
|
|
69
|
+
not author `Composition.section` or resource claim names.
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
const request = buildVeterinaryDigitalTwinSearchRequest({
|
|
73
|
+
thid: crypto.randomUUID(),
|
|
74
|
+
search: { section, resourceTypes, text, dateFrom, ...(dateTo ? { dateTo } : {}) },
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
await digitalTwins.search(routeContext, {
|
|
78
|
+
thid: request.payload.thid,
|
|
79
|
+
format: request.format,
|
|
80
|
+
resourceType: request.resourceType,
|
|
81
|
+
filters: request.filters,
|
|
82
|
+
})
|
|
83
|
+
```
|
|
@@ -26,15 +26,53 @@ export type VeterinaryBreakGlassSmartRequest = Readonly<{
|
|
|
26
26
|
}>;
|
|
27
27
|
}>;
|
|
28
28
|
}>;
|
|
29
|
+
/**
|
|
30
|
+
* Business-only SMART authorization consumed by a high-level professional
|
|
31
|
+
* runtime. Wallet keys, client identifiers, audiences, JWT assertions and VPs
|
|
32
|
+
* deliberately do not belong to this browser-safe contract.
|
|
33
|
+
*/
|
|
34
|
+
export type VeterinaryBreakGlassSmartAuthorization = Readonly<{
|
|
35
|
+
subjectDid: string;
|
|
36
|
+
purpose: 'EMERGENCY';
|
|
37
|
+
scopes: readonly [string];
|
|
38
|
+
acrValues: 'urn:antifraud:acr:openid4vp:employee';
|
|
39
|
+
requestBodyClaims: Readonly<{
|
|
40
|
+
expires_in: number;
|
|
41
|
+
break_glass: Readonly<{
|
|
42
|
+
incidentId: string;
|
|
43
|
+
subjectKind: 'animal';
|
|
44
|
+
reasonCode: 'animal-emergency';
|
|
45
|
+
justification: string;
|
|
46
|
+
}>;
|
|
47
|
+
}>;
|
|
48
|
+
}>;
|
|
29
49
|
export type VeterinaryDigitalTwinSearchRequest = Readonly<{
|
|
30
50
|
format: VeterinaryDigitalTwinFormat;
|
|
31
51
|
resourceType: 'Composition';
|
|
52
|
+
filters: Readonly<Record<string, string | readonly string[]>>;
|
|
32
53
|
payload: Readonly<{
|
|
33
54
|
thid: string;
|
|
34
55
|
body: Readonly<Record<string, unknown>>;
|
|
35
56
|
}>;
|
|
36
57
|
}>;
|
|
37
58
|
export declare function buildVetGatewayRoutePrefix(context: VetGatewayRouteContext): string;
|
|
59
|
+
/**
|
|
60
|
+
* Converts the portal's governed emergency form into the business fields for
|
|
61
|
+
* `ServerProfileSessionManager.openProfessional().requestSmartToken(...)`.
|
|
62
|
+
* The Node SDK derives and signs every OpenID/VP field from the registered
|
|
63
|
+
* wallet; the portal never supplies or persists that plumbing.
|
|
64
|
+
*/
|
|
65
|
+
export declare function buildVeterinaryBreakGlassSmartAuthorization(input: Readonly<{
|
|
66
|
+
subjectDid: string;
|
|
67
|
+
requestedSections: readonly string[];
|
|
68
|
+
incidentId: string;
|
|
69
|
+
justification: string;
|
|
70
|
+
}>): VeterinaryBreakGlassSmartAuthorization;
|
|
71
|
+
/**
|
|
72
|
+
* Low-level compatibility envelope for callers that already own OpenID
|
|
73
|
+
* plumbing. New Node BFFs should use
|
|
74
|
+
* `buildVeterinaryBreakGlassSmartAuthorization` with `openProfessional`.
|
|
75
|
+
*/
|
|
38
76
|
export declare function buildVeterinaryBreakGlassSmartRequest(input: Readonly<{
|
|
39
77
|
thid: string;
|
|
40
78
|
actorDid: string;
|
|
@@ -47,6 +85,11 @@ export declare function buildVeterinaryBreakGlassSmartRequest(input: Readonly<{
|
|
|
47
85
|
vpToken: string;
|
|
48
86
|
audience: string;
|
|
49
87
|
}>): VeterinaryBreakGlassSmartRequest;
|
|
88
|
+
/**
|
|
89
|
+
* Converts browser-safe search values into both FHIR Parameters and the exact
|
|
90
|
+
* filter map accepted by the Node DigitalTwin SDK. Callers never choose flat
|
|
91
|
+
* claim names.
|
|
92
|
+
*/
|
|
50
93
|
export declare function buildVeterinaryDigitalTwinSearchRequest(input: Readonly<{
|
|
51
94
|
thid: string;
|
|
52
95
|
format?: VeterinaryDigitalTwinFormat;
|
package/dist/gateway-contract.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { VeterinaryBreakGlassMaxLifetimeSeconds, VeterinaryDigitalTwinFormats, parseVetChainAnimalCardDid, parseVeterinaryBreakGlassRequestData, parseVeterinaryDigitalTwinSearch, } from 'vet-data-utils-ts';
|
|
1
|
+
import { VeterinaryBreakGlassMaxLifetimeSeconds, VeterinaryDigitalTwinFormats, VeterinaryDigitalTwinSectionSearchProfiles, parseVetChainAnimalCardDid, parseVeterinaryBreakGlassRequestData, parseVeterinaryDigitalTwinSearch, } from 'vet-data-utils-ts';
|
|
2
2
|
export function buildVetGatewayRoutePrefix(context) {
|
|
3
3
|
const tenantId = bounded(context.tenantId, 'tenantId');
|
|
4
4
|
const jurisdiction = bounded(context.jurisdiction, 'jurisdiction').toUpperCase();
|
|
@@ -6,11 +6,41 @@ export function buildVetGatewayRoutePrefix(context) {
|
|
|
6
6
|
throw new TypeError('vet_gateway_jurisdiction_invalid');
|
|
7
7
|
return `/${tenantId}/cds-${jurisdiction}/v1/${context.sector}`;
|
|
8
8
|
}
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Converts the portal's governed emergency form into the business fields for
|
|
11
|
+
* `ServerProfileSessionManager.openProfessional().requestSmartToken(...)`.
|
|
12
|
+
* The Node SDK derives and signs every OpenID/VP field from the registered
|
|
13
|
+
* wallet; the portal never supplies or persists that plumbing.
|
|
14
|
+
*/
|
|
15
|
+
export function buildVeterinaryBreakGlassSmartAuthorization(input) {
|
|
10
16
|
const emergency = parseVeterinaryBreakGlassRequestData(input);
|
|
11
|
-
const actorDid = did(input.actorDid, 'vet_break_glass_actor_invalid');
|
|
12
17
|
const section = emergency.requestedSections.join(',');
|
|
13
18
|
const scope = `organization/Composition.rs?subject=${encodeURIComponent(emergency.subjectDid)}§ion=${encodeURIComponent(section)}`;
|
|
19
|
+
return Object.freeze({
|
|
20
|
+
subjectDid: emergency.subjectDid,
|
|
21
|
+
purpose: 'EMERGENCY',
|
|
22
|
+
scopes: Object.freeze([scope]),
|
|
23
|
+
acrValues: 'urn:antifraud:acr:openid4vp:employee',
|
|
24
|
+
requestBodyClaims: Object.freeze({
|
|
25
|
+
expires_in: VeterinaryBreakGlassMaxLifetimeSeconds,
|
|
26
|
+
break_glass: Object.freeze({
|
|
27
|
+
incidentId: emergency.incidentId,
|
|
28
|
+
subjectKind: emergency.subjectKind,
|
|
29
|
+
reasonCode: emergency.reasonCode,
|
|
30
|
+
justification: emergency.justification,
|
|
31
|
+
}),
|
|
32
|
+
}),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Low-level compatibility envelope for callers that already own OpenID
|
|
37
|
+
* plumbing. New Node BFFs should use
|
|
38
|
+
* `buildVeterinaryBreakGlassSmartAuthorization` with `openProfessional`.
|
|
39
|
+
*/
|
|
40
|
+
export function buildVeterinaryBreakGlassSmartRequest(input) {
|
|
41
|
+
const emergency = parseVeterinaryBreakGlassRequestData(input);
|
|
42
|
+
const actorDid = did(input.actorDid, 'vet_break_glass_actor_invalid');
|
|
43
|
+
const authorization = buildVeterinaryBreakGlassSmartAuthorization(emergency);
|
|
14
44
|
return Object.freeze({
|
|
15
45
|
thid: bounded(input.thid, 'thid'),
|
|
16
46
|
iss: actorDid,
|
|
@@ -21,31 +51,50 @@ export function buildVeterinaryBreakGlassSmartRequest(input) {
|
|
|
21
51
|
client_assertion: bounded(input.clientAssertion, 'clientAssertion'),
|
|
22
52
|
client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
|
|
23
53
|
vp_token: bounded(input.vpToken, 'vpToken'),
|
|
24
|
-
acr_values:
|
|
25
|
-
purpose:
|
|
26
|
-
scope,
|
|
27
|
-
expires_in:
|
|
28
|
-
break_glass:
|
|
29
|
-
incidentId: emergency.incidentId,
|
|
30
|
-
subjectKind: emergency.subjectKind,
|
|
31
|
-
reasonCode: emergency.reasonCode,
|
|
32
|
-
justification: emergency.justification,
|
|
33
|
-
},
|
|
54
|
+
acr_values: authorization.acrValues,
|
|
55
|
+
purpose: authorization.purpose,
|
|
56
|
+
scope: authorization.scopes[0],
|
|
57
|
+
expires_in: authorization.requestBodyClaims.expires_in,
|
|
58
|
+
break_glass: authorization.requestBodyClaims.break_glass,
|
|
34
59
|
},
|
|
35
60
|
});
|
|
36
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Converts browser-safe search values into both FHIR Parameters and the exact
|
|
64
|
+
* filter map accepted by the Node DigitalTwin SDK. Callers never choose flat
|
|
65
|
+
* claim names.
|
|
66
|
+
*/
|
|
37
67
|
export function buildVeterinaryDigitalTwinSearchRequest(input) {
|
|
38
68
|
const search = parseVeterinaryDigitalTwinSearch(input.search);
|
|
39
69
|
const format = input.format || VeterinaryDigitalTwinFormats.R4;
|
|
40
70
|
const parameter = [
|
|
41
|
-
{ name: 'section', valueString: search.section },
|
|
42
|
-
{ name: search.claim, valueString: search.value },
|
|
71
|
+
{ name: 'Composition.section', valueString: search.section },
|
|
43
72
|
];
|
|
73
|
+
const profiles = VeterinaryDigitalTwinSectionSearchProfiles[search.section] || [];
|
|
74
|
+
for (const resourceType of search.resourceTypes) {
|
|
75
|
+
const selectedProfile = profiles.find(profile => profile.resourceType === resourceType);
|
|
76
|
+
if (!selectedProfile)
|
|
77
|
+
throw new TypeError('veterinary_digital_twin_resource_invalid');
|
|
78
|
+
parameter.push({ name: selectedProfile.codeDisplayClaim, valueString: search.text }, { name: selectedProfile.dateClaim, valueString: `ge${search.dateFrom}` });
|
|
79
|
+
if (search.dateTo)
|
|
80
|
+
parameter.push({ name: selectedProfile.dateClaim, valueString: `le${search.dateTo}` });
|
|
81
|
+
}
|
|
82
|
+
const filters = {};
|
|
83
|
+
for (const item of parameter) {
|
|
84
|
+
const existing = filters[item.name];
|
|
85
|
+
if (!existing)
|
|
86
|
+
filters[item.name] = item.valueString;
|
|
87
|
+
else if (Array.isArray(existing))
|
|
88
|
+
existing.push(item.valueString);
|
|
89
|
+
else
|
|
90
|
+
filters[item.name] = [existing, item.valueString];
|
|
91
|
+
}
|
|
44
92
|
return Object.freeze({
|
|
45
93
|
format,
|
|
46
|
-
|
|
47
|
-
//
|
|
48
|
-
//
|
|
94
|
+
filters: Object.freeze(filters),
|
|
95
|
+
// GW stores the pseudonymous twin as a Composition. The selected nested
|
|
96
|
+
// resource families become repeated Parameters entries; they never change
|
|
97
|
+
// the asynchronous route resource type.
|
|
49
98
|
resourceType: 'Composition',
|
|
50
99
|
payload: {
|
|
51
100
|
thid: bounded(input.thid, 'thid'),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vet-sdk-core-ts",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "Browser-safe VetChain core contracts and governed animal species identifiers",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Connecting Solution & Applications Ltd",
|
|
@@ -54,6 +54,6 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@noble/hashes": "^2.2.0",
|
|
57
|
-
"vet-data-utils-ts": "0.3.
|
|
57
|
+
"vet-data-utils-ts": "0.3.1"
|
|
58
58
|
}
|
|
59
59
|
}
|