vet-sdk-core-ts 0.4.29 → 0.4.32
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 +5 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/place-service-directory.d.ts +45 -0
- package/dist/place-service-directory.js +45 -0
- package/dist/secure-clinic-channel.js +38 -29
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# VetChain Core SDK
|
|
2
2
|
|
|
3
|
+
`vet-sdk-core-ts/place-service-directory` is the VetChain-only bundle boundary
|
|
4
|
+
for claims-first Schema.org `Place` and `Service` resources. It builds GW VET
|
|
5
|
+
batch/search envelopes and joins authorized directory search responses for the
|
|
6
|
+
PetChain marketplace; it does not extend `gdc-*`.
|
|
7
|
+
|
|
3
8
|
Development and releases follow the mandatory
|
|
4
9
|
[`local-first TDD and release contract`](docs/LOCAL_FIRST_RELEASE_CONTRACT.md).
|
|
5
10
|
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export * from "./animal-onboarding.js";
|
|
|
6
6
|
export * from "./reusable-bff.js";
|
|
7
7
|
export * from "./research-study.js";
|
|
8
8
|
export * from "./payment.js";
|
|
9
|
+
export * from "./place-service-directory.js";
|
|
9
10
|
export * from "./health-card-issuance.js";
|
|
10
11
|
export * from "./secure-clinic-channel.js";
|
|
11
12
|
export * from "./scheduling.js";
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export * from "./animal-onboarding.js";
|
|
|
6
6
|
export * from "./reusable-bff.js";
|
|
7
7
|
export * from "./research-study.js";
|
|
8
8
|
export * from "./payment.js";
|
|
9
|
+
export * from "./place-service-directory.js";
|
|
9
10
|
export * from "./health-card-issuance.js";
|
|
10
11
|
export * from "./secure-clinic-channel.js";
|
|
11
12
|
export * from "./scheduling.js";
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type ClinicMarketplaceResult, type DirectoryFlatClaimsResource, type DirectoryResourceType } from 'vet-data-utils-ts/place-service-directory';
|
|
2
|
+
export type DirectoryBatchMethod = 'POST' | 'PUT';
|
|
3
|
+
/** Builds the claims-first Schema.org Place/Service batch accepted by GW VET. */
|
|
4
|
+
export declare function buildDirectoryBatch(resources: readonly unknown[], method?: DirectoryBatchMethod): Readonly<{
|
|
5
|
+
data: readonly Readonly<{
|
|
6
|
+
type: "Place-schema.org" | "Service-schema.org";
|
|
7
|
+
resource: Readonly<{
|
|
8
|
+
resourceType: DirectoryResourceType;
|
|
9
|
+
id: string;
|
|
10
|
+
meta: Readonly<{
|
|
11
|
+
claims: Readonly<Record<string, readonly string[]>>;
|
|
12
|
+
}>;
|
|
13
|
+
}>;
|
|
14
|
+
request: Readonly<{
|
|
15
|
+
method: DirectoryBatchMethod;
|
|
16
|
+
url: `Place/${string}` | `Service/${string}`;
|
|
17
|
+
}>;
|
|
18
|
+
}>[];
|
|
19
|
+
}>;
|
|
20
|
+
/** Builds one claims-first Place or Service `_search` envelope. Empty claims list the authorized directory. */
|
|
21
|
+
export declare function buildDirectorySearch(resourceType: DirectoryResourceType, claims?: Readonly<Record<string, unknown>>): Readonly<{
|
|
22
|
+
data: readonly Readonly<{
|
|
23
|
+
resource: Readonly<{
|
|
24
|
+
resourceType: DirectoryResourceType;
|
|
25
|
+
id: string;
|
|
26
|
+
meta: Readonly<{
|
|
27
|
+
claims: Readonly<Record<string, readonly string[]>>;
|
|
28
|
+
}>;
|
|
29
|
+
}>;
|
|
30
|
+
}>[];
|
|
31
|
+
}>;
|
|
32
|
+
/** Reads canonical GW directory matches from `body.data[].resource`. */
|
|
33
|
+
export declare function readDirectorySearch(body: unknown, expectedResourceType: DirectoryResourceType): readonly DirectoryFlatClaimsResource[];
|
|
34
|
+
/** Reads two GW searches and returns the joined, filtered public clinic marketplace. */
|
|
35
|
+
export declare function readClinicMarketplaceSearch(input: Readonly<{
|
|
36
|
+
placesBody: unknown;
|
|
37
|
+
servicesBody: unknown;
|
|
38
|
+
postalCode?: string;
|
|
39
|
+
serviceTypes?: readonly string[];
|
|
40
|
+
near?: Readonly<{
|
|
41
|
+
latitude: number;
|
|
42
|
+
longitude: number;
|
|
43
|
+
radiusKm: number;
|
|
44
|
+
}>;
|
|
45
|
+
}>): readonly ClinicMarketplaceResult[];
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { normalizeDirectoryFlatClaimsResource, searchClinicMarketplace, } from 'vet-data-utils-ts/place-service-directory';
|
|
2
|
+
/** Builds the claims-first Schema.org Place/Service batch accepted by GW VET. */
|
|
3
|
+
export function buildDirectoryBatch(resources, method = 'POST') {
|
|
4
|
+
return Object.freeze({ data: Object.freeze(resources.map(candidate => {
|
|
5
|
+
const resource = normalizeDirectoryFlatClaimsResource(candidate);
|
|
6
|
+
return Object.freeze({
|
|
7
|
+
type: `${resource.resourceType}-schema.org`,
|
|
8
|
+
resource,
|
|
9
|
+
request: Object.freeze({ method, url: `${resource.resourceType}/${resource.id}` }),
|
|
10
|
+
});
|
|
11
|
+
})) });
|
|
12
|
+
}
|
|
13
|
+
/** Builds one claims-first Place or Service `_search` envelope. Empty claims list the authorized directory. */
|
|
14
|
+
export function buildDirectorySearch(resourceType, claims = {}) {
|
|
15
|
+
const resource = normalizeDirectoryFlatClaimsResource({ resourceType, id: 'search', meta: { claims } });
|
|
16
|
+
return Object.freeze({ data: Object.freeze([Object.freeze({ resource })]) });
|
|
17
|
+
}
|
|
18
|
+
/** Reads canonical GW directory matches from `body.data[].resource`. */
|
|
19
|
+
export function readDirectorySearch(body, expectedResourceType) {
|
|
20
|
+
if (!body || typeof body !== 'object' || !Array.isArray(body.data))
|
|
21
|
+
throw new TypeError('directory_search_invalid');
|
|
22
|
+
return Object.freeze(body.data.map(entry => {
|
|
23
|
+
if (!entry || typeof entry !== 'object' || !('resource' in entry))
|
|
24
|
+
throw new TypeError('directory_search_resource_invalid');
|
|
25
|
+
try {
|
|
26
|
+
const resource = normalizeDirectoryFlatClaimsResource(entry.resource);
|
|
27
|
+
if (resource.resourceType !== expectedResourceType)
|
|
28
|
+
throw new Error('wrong type');
|
|
29
|
+
return resource;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
throw new TypeError('directory_search_resource_invalid');
|
|
33
|
+
}
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
/** Reads two GW searches and returns the joined, filtered public clinic marketplace. */
|
|
37
|
+
export function readClinicMarketplaceSearch(input) {
|
|
38
|
+
return searchClinicMarketplace({
|
|
39
|
+
places: readDirectorySearch(input.placesBody, 'Place'),
|
|
40
|
+
services: readDirectorySearch(input.servicesBody, 'Service'),
|
|
41
|
+
...(input.postalCode ? { postalCode: input.postalCode } : {}),
|
|
42
|
+
...(input.serviceTypes ? { serviceTypes: input.serviceTypes } : {}),
|
|
43
|
+
...(input.near ? { near: input.near } : {}),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
@@ -81,33 +81,6 @@ function extractIndexProjection(bundle, policy) {
|
|
|
81
81
|
const resource = entry.resource;
|
|
82
82
|
if (!resource || typeof resource.resourceType !== 'string' || typeof resource.id !== 'string')
|
|
83
83
|
continue;
|
|
84
|
-
if (typeof entry.fullUrl !== 'string')
|
|
85
|
-
throw new Error('Each indexed resource requires an absolute HTTPS fullUrl');
|
|
86
|
-
const relative = /^([A-Z][A-Za-z0-9]+)\/([0-9a-f-]+)$/i.exec(entry.fullUrl);
|
|
87
|
-
let urlResourceType;
|
|
88
|
-
let urlId;
|
|
89
|
-
if (relative) {
|
|
90
|
-
;
|
|
91
|
-
[, urlResourceType, urlId] = relative;
|
|
92
|
-
}
|
|
93
|
-
else {
|
|
94
|
-
let parsed;
|
|
95
|
-
try {
|
|
96
|
-
parsed = new URL(entry.fullUrl);
|
|
97
|
-
}
|
|
98
|
-
catch {
|
|
99
|
-
throw new Error('The index fullUrl must contain /<ResourceType>/<UUID>');
|
|
100
|
-
}
|
|
101
|
-
if (parsed.protocol !== 'https:')
|
|
102
|
-
throw new Error('The index fullUrl must contain /<ResourceType>/<UUID>');
|
|
103
|
-
const segments = parsed.pathname.split('/').filter(Boolean);
|
|
104
|
-
urlResourceType = segments.at(-2);
|
|
105
|
-
urlId = segments.at(-1);
|
|
106
|
-
}
|
|
107
|
-
if (urlResourceType !== resource.resourceType
|
|
108
|
-
|| urlId !== resource.id || !UUID_PATTERN.test(resource.id)) {
|
|
109
|
-
throw new Error('The fullUrl must end in /<ResourceType>/<UUID> matching the resource');
|
|
110
|
-
}
|
|
111
84
|
if (resource.resourceType === 'Binary' && resource.contentType === 'application/pdf') {
|
|
112
85
|
throw new Error('PDF content must be an Attachment inside DocumentReference');
|
|
113
86
|
}
|
|
@@ -128,8 +101,44 @@ function extractIndexProjection(bundle, policy) {
|
|
|
128
101
|
(grouped[_a = tag.system] ?? (grouped[_a] = [])).push(tag.code);
|
|
129
102
|
}
|
|
130
103
|
const claims = Object.fromEntries(Object.entries(grouped).map(([key, values]) => [key, values.join(',')]));
|
|
131
|
-
if (Object.keys(claims).length)
|
|
132
|
-
|
|
104
|
+
if (!Object.keys(claims).length)
|
|
105
|
+
continue;
|
|
106
|
+
// A private FHIR document can legitimately contain contained-only or
|
|
107
|
+
// human-readable entry identities. They remain in the encrypted payload,
|
|
108
|
+
// but only independently retrievable UUID resources enter the index.
|
|
109
|
+
if (typeof entry.fullUrl !== 'string' || !UUID_PATTERN.test(resource.id))
|
|
110
|
+
continue;
|
|
111
|
+
const relative = /^([A-Z][A-Za-z0-9]+)\/([0-9a-f-]+)$/i.exec(entry.fullUrl);
|
|
112
|
+
const documentUrn = /^urn:uuid:([0-9a-f-]+)$/i.exec(entry.fullUrl);
|
|
113
|
+
let urlResourceType;
|
|
114
|
+
let urlId;
|
|
115
|
+
let indexFullUrl = entry.fullUrl;
|
|
116
|
+
if (relative) {
|
|
117
|
+
;
|
|
118
|
+
[, urlResourceType, urlId] = relative;
|
|
119
|
+
}
|
|
120
|
+
else if (documentUrn) {
|
|
121
|
+
urlResourceType = resource.resourceType;
|
|
122
|
+
[, urlId] = documentUrn;
|
|
123
|
+
indexFullUrl = `${resource.resourceType}/${resource.id}`;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
let parsed;
|
|
127
|
+
try {
|
|
128
|
+
parsed = new URL(entry.fullUrl);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (parsed.protocol !== 'https:')
|
|
134
|
+
continue;
|
|
135
|
+
const segments = parsed.pathname.split('/').filter(Boolean);
|
|
136
|
+
urlResourceType = segments.at(-2);
|
|
137
|
+
urlId = segments.at(-1);
|
|
138
|
+
}
|
|
139
|
+
if (urlResourceType !== resource.resourceType || urlId !== resource.id)
|
|
140
|
+
continue;
|
|
141
|
+
entries.push({ fullUrl: indexFullUrl, claims });
|
|
133
142
|
}
|
|
134
143
|
return { entries };
|
|
135
144
|
}
|
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.32",
|
|
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",
|
|
@@ -44,6 +44,10 @@
|
|
|
44
44
|
"types": "./dist/payment.d.ts",
|
|
45
45
|
"default": "./dist/payment.js"
|
|
46
46
|
},
|
|
47
|
+
"./place-service-directory": {
|
|
48
|
+
"types": "./dist/place-service-directory.d.ts",
|
|
49
|
+
"default": "./dist/place-service-directory.js"
|
|
50
|
+
},
|
|
47
51
|
"./health-card-issuance": {
|
|
48
52
|
"types": "./dist/health-card-issuance.d.ts",
|
|
49
53
|
"default": "./dist/health-card-issuance.js"
|
|
@@ -81,6 +85,6 @@
|
|
|
81
85
|
"@noble/hashes": "^2.2.0",
|
|
82
86
|
"@noble/post-quantum": "0.5.4",
|
|
83
87
|
"gdc-common-utils-ts": "2.9.10",
|
|
84
|
-
"vet-data-utils-ts": "0.5.
|
|
88
|
+
"vet-data-utils-ts": "0.5.21"
|
|
85
89
|
}
|
|
86
90
|
}
|