vet-data-utils-ts 0.4.1 → 0.4.3
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 +28 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/organization-application.d.ts +44 -0
- package/dist/organization-application.js +105 -0
- package/dist/sectors.d.ts +21 -0
- package/dist/sectors.js +12 -0
- package/package.json +12 -1
package/README.md
CHANGED
|
@@ -8,6 +8,34 @@ Current surfaces cover the canonical animal-card DID, veterinary summary
|
|
|
8
8
|
sections, animal-only emergency data, pseudonymous DigitalTwin search input and
|
|
9
9
|
veterinary assistant intents.
|
|
10
10
|
|
|
11
|
+
## Active sector extension
|
|
12
|
+
|
|
13
|
+
`vet-data-utils-ts/sectors` extends the frozen CORE catalog without changing
|
|
14
|
+
`gdc-common-utils-ts` or GW CORE. It adds the product-neutral
|
|
15
|
+
`public-safety` value used by explicitly governed portal allowlists. Sector
|
|
16
|
+
selection classifies an organization service; it does not grant membership,
|
|
17
|
+
an occupation, an index role or access to any person or animal.
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { ExtendedDataspaceSectors } from 'vet-data-utils-ts/sectors'
|
|
21
|
+
|
|
22
|
+
// The portal chooses its exact subset. Do not expose the complete catalog as
|
|
23
|
+
// an authorization decision or infer capabilities from this selection.
|
|
24
|
+
const allowedSectors = [
|
|
25
|
+
ExtendedDataspaceSectors.PublicSafety,
|
|
26
|
+
ExtendedDataspaceSectors.AnimalCare,
|
|
27
|
+
] as const
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`vet-data-utils-ts/organization-application` is the product-neutral application
|
|
31
|
+
value used by UHC UNID, VetChain and SOSChain adapters. The host supplies the
|
|
32
|
+
allowed sector list and translated labels. The value contains one official
|
|
33
|
+
identifier (`TAX`, `EIN` or `BN`), ISO country, optional matching ISO
|
|
34
|
+
subdivision, optional official licence, legal representative, technical
|
|
35
|
+
controller and six independent index participation roles. It rejects a second
|
|
36
|
+
`taxId` and every PIN field: profile protection happens immediately after
|
|
37
|
+
login, before any application screen.
|
|
38
|
+
|
|
11
39
|
The financial surface defines provider-neutral flat claims for FHIR R5
|
|
12
40
|
`Account`, `Invoice`, `PaymentNotice` and `PaymentReconciliation`. `Account`
|
|
13
41
|
tracks charges and balances; the responsible party is represented through
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export declare const OrganizationOfficialIdTypes: Readonly<{
|
|
2
|
+
readonly Tax: "TAX";
|
|
3
|
+
readonly EmployerIdentificationNumber: "EIN";
|
|
4
|
+
readonly BusinessNumber: "BN";
|
|
5
|
+
}>;
|
|
6
|
+
export declare const OrganizationParticipationRoles: Readonly<{
|
|
7
|
+
readonly IndividualIndexServiceProvider: "individual-index-service-provider";
|
|
8
|
+
readonly IndividualIndexDataProvider: "individual-index-data-provider";
|
|
9
|
+
readonly IndividualIndexDataConsumer: "individual-index-data-consumer";
|
|
10
|
+
readonly DigitalTwinIndexServiceProvider: "digital-twin-index-service-provider";
|
|
11
|
+
readonly DigitalTwinDataProvider: "digital-twin-data-provider";
|
|
12
|
+
readonly DigitalTwinDataConsumer: "digital-twin-data-consumer";
|
|
13
|
+
}>;
|
|
14
|
+
export type OrganizationOfficialIdType = typeof OrganizationOfficialIdTypes[keyof typeof OrganizationOfficialIdTypes];
|
|
15
|
+
export type OrganizationParticipationRole = typeof OrganizationParticipationRoles[keyof typeof OrganizationParticipationRoles];
|
|
16
|
+
export type ReusableOrganizationApplication = Readonly<{
|
|
17
|
+
sector: string;
|
|
18
|
+
officialId: Readonly<{
|
|
19
|
+
type: OrganizationOfficialIdType;
|
|
20
|
+
value: string;
|
|
21
|
+
}>;
|
|
22
|
+
countryCode: string;
|
|
23
|
+
regionalId: boolean;
|
|
24
|
+
subdivisionCode?: string;
|
|
25
|
+
officialLicense?: string;
|
|
26
|
+
legalRepresentative: Readonly<{
|
|
27
|
+
name: string;
|
|
28
|
+
email: string;
|
|
29
|
+
}>;
|
|
30
|
+
technicalController: Readonly<{
|
|
31
|
+
email: string;
|
|
32
|
+
sameAsLegalRepresentative: boolean;
|
|
33
|
+
}>;
|
|
34
|
+
participationRoles: readonly OrganizationParticipationRole[];
|
|
35
|
+
}>;
|
|
36
|
+
/**
|
|
37
|
+
* Validates only reusable organization-application business data.
|
|
38
|
+
*
|
|
39
|
+
* Product adapters supply the sector allowlist and translated labels. Login,
|
|
40
|
+
* PIN/passkey protection, PDF rendering, delivery, activation-code exchange,
|
|
41
|
+
* DCR, wallet keys and gateway routing are deliberately outside this value
|
|
42
|
+
* object and remain inside the BFF/high-level Node runtime.
|
|
43
|
+
*/
|
|
44
|
+
export declare function parseReusableOrganizationApplication(input: unknown): ReusableOrganizationApplication;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { isIso3166CountryCode, isIso3166SubdivisionForCountry } from './iso-jurisdictions.js';
|
|
2
|
+
export const OrganizationOfficialIdTypes = Object.freeze({
|
|
3
|
+
Tax: 'TAX',
|
|
4
|
+
EmployerIdentificationNumber: 'EIN',
|
|
5
|
+
BusinessNumber: 'BN',
|
|
6
|
+
});
|
|
7
|
+
export const OrganizationParticipationRoles = Object.freeze({
|
|
8
|
+
IndividualIndexServiceProvider: 'individual-index-service-provider',
|
|
9
|
+
IndividualIndexDataProvider: 'individual-index-data-provider',
|
|
10
|
+
IndividualIndexDataConsumer: 'individual-index-data-consumer',
|
|
11
|
+
DigitalTwinIndexServiceProvider: 'digital-twin-index-service-provider',
|
|
12
|
+
DigitalTwinDataProvider: 'digital-twin-data-provider',
|
|
13
|
+
DigitalTwinDataConsumer: 'digital-twin-data-consumer',
|
|
14
|
+
});
|
|
15
|
+
const officialIdTypes = new Set(Object.values(OrganizationOfficialIdTypes));
|
|
16
|
+
const participationRoles = new Set(Object.values(OrganizationParticipationRoles));
|
|
17
|
+
/**
|
|
18
|
+
* Validates only reusable organization-application business data.
|
|
19
|
+
*
|
|
20
|
+
* Product adapters supply the sector allowlist and translated labels. Login,
|
|
21
|
+
* PIN/passkey protection, PDF rendering, delivery, activation-code exchange,
|
|
22
|
+
* DCR, wallet keys and gateway routing are deliberately outside this value
|
|
23
|
+
* object and remain inside the BFF/high-level Node runtime.
|
|
24
|
+
*/
|
|
25
|
+
export function parseReusableOrganizationApplication(input) {
|
|
26
|
+
const value = record(input, 'organization_application_invalid');
|
|
27
|
+
forbid(value, ['taxId', 'profilePin', 'newProfilePin', 'repeatProfilePin']);
|
|
28
|
+
const allowedSectors = strings(value.allowedSectors);
|
|
29
|
+
const sector = required(value.sector, 'organization_sector_required');
|
|
30
|
+
if (!allowedSectors.includes(sector))
|
|
31
|
+
throw new TypeError('organization_sector_not_allowed');
|
|
32
|
+
const officialId = record(value.officialId, 'organization_official_id_required');
|
|
33
|
+
const officialIdType = required(officialId.type, 'organization_official_id_type_required');
|
|
34
|
+
if (!officialIdTypes.has(officialIdType))
|
|
35
|
+
throw new TypeError('organization_official_id_type_invalid');
|
|
36
|
+
const countryCode = required(value.countryCode, 'organization_country_required').toUpperCase();
|
|
37
|
+
if (!isIso3166CountryCode(countryCode))
|
|
38
|
+
throw new TypeError('organization_country_invalid');
|
|
39
|
+
const regionalId = value.regionalId === true;
|
|
40
|
+
const subdivisionCode = optional(value.subdivisionCode)?.toUpperCase();
|
|
41
|
+
if (regionalId && (!subdivisionCode || !isIso3166SubdivisionForCountry(subdivisionCode, countryCode))) {
|
|
42
|
+
throw new TypeError('organization_subdivision_invalid');
|
|
43
|
+
}
|
|
44
|
+
if (!regionalId && subdivisionCode)
|
|
45
|
+
throw new TypeError('organization_subdivision_without_regional_id');
|
|
46
|
+
const legalRepresentative = record(value.legalRepresentative, 'organization_legal_representative_required');
|
|
47
|
+
const legalEmail = email(legalRepresentative.email, 'organization_legal_representative_email_required');
|
|
48
|
+
const technicalController = record(value.technicalController, 'organization_technical_controller_required');
|
|
49
|
+
const sameAsLegalRepresentative = technicalController.sameAsLegalRepresentative === true;
|
|
50
|
+
const technicalEmail = sameAsLegalRepresentative
|
|
51
|
+
? legalEmail
|
|
52
|
+
: email(technicalController.email, 'organization_technical_controller_email_required');
|
|
53
|
+
const selectedRoles = strings(value.participationRoles);
|
|
54
|
+
if (selectedRoles.some(role => !participationRoles.has(role))) {
|
|
55
|
+
throw new TypeError('organization_participation_role_invalid');
|
|
56
|
+
}
|
|
57
|
+
return Object.freeze({
|
|
58
|
+
sector,
|
|
59
|
+
officialId: Object.freeze({
|
|
60
|
+
type: officialIdType,
|
|
61
|
+
value: required(officialId.value, 'organization_official_id_value_required'),
|
|
62
|
+
}),
|
|
63
|
+
countryCode,
|
|
64
|
+
regionalId,
|
|
65
|
+
...(subdivisionCode ? { subdivisionCode } : {}),
|
|
66
|
+
...(optional(value.officialLicense) ? { officialLicense: optional(value.officialLicense) } : {}),
|
|
67
|
+
legalRepresentative: Object.freeze({
|
|
68
|
+
name: required(legalRepresentative.name, 'organization_legal_representative_name_required'),
|
|
69
|
+
email: legalEmail,
|
|
70
|
+
}),
|
|
71
|
+
technicalController: Object.freeze({ email: technicalEmail, sameAsLegalRepresentative }),
|
|
72
|
+
participationRoles: Object.freeze([...new Set(selectedRoles)]),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function record(value, error) {
|
|
76
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
77
|
+
throw new TypeError(error);
|
|
78
|
+
return value;
|
|
79
|
+
}
|
|
80
|
+
function strings(value) {
|
|
81
|
+
return Array.isArray(value) ? value.map(item => String(item).trim()).filter(Boolean) : [];
|
|
82
|
+
}
|
|
83
|
+
function required(value, error) {
|
|
84
|
+
const normalized = String(value ?? '').trim();
|
|
85
|
+
if (!normalized)
|
|
86
|
+
throw new TypeError(error);
|
|
87
|
+
return normalized;
|
|
88
|
+
}
|
|
89
|
+
function optional(value) {
|
|
90
|
+
const normalized = String(value ?? '').trim();
|
|
91
|
+
return normalized || undefined;
|
|
92
|
+
}
|
|
93
|
+
function email(value, error) {
|
|
94
|
+
const normalized = required(value, error).toLowerCase();
|
|
95
|
+
if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(normalized))
|
|
96
|
+
throw new TypeError(error);
|
|
97
|
+
return normalized;
|
|
98
|
+
}
|
|
99
|
+
function forbid(value, names) {
|
|
100
|
+
for (const name of names) {
|
|
101
|
+
if (Object.prototype.hasOwnProperty.call(value, name)) {
|
|
102
|
+
throw new TypeError(name === 'taxId' ? 'organization_tax_id_forbidden' : 'organization_profile_pin_forbidden');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Active product extension of the frozen CORE sector vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* CORE remains unchanged. Public safety is product-neutral and may be selected
|
|
5
|
+
* only by a consuming portal that explicitly includes it in its own allowlist;
|
|
6
|
+
* the value grants no organization role, membership or data capability.
|
|
7
|
+
*/
|
|
8
|
+
export declare const ExtendedDataspaceSectors: Readonly<{
|
|
9
|
+
readonly PublicSafety: "public-safety";
|
|
10
|
+
readonly HealthCare: "health-care";
|
|
11
|
+
readonly HealthResearch: "health-research";
|
|
12
|
+
readonly HealthTech: "health-tech";
|
|
13
|
+
readonly HealthInsurance: "health-insurance";
|
|
14
|
+
readonly AnimalCare: "animal-care";
|
|
15
|
+
readonly AnimalResearch: "animal-research";
|
|
16
|
+
readonly AnimalInsurance: "animal-insurance";
|
|
17
|
+
readonly AnimalTech: "animal-tech";
|
|
18
|
+
readonly OneHealthResearch: "onehealth-research";
|
|
19
|
+
readonly OneHealthTech: "onehealth-tech";
|
|
20
|
+
}>;
|
|
21
|
+
export type ExtendedDataspaceSector = typeof ExtendedDataspaceSectors[keyof typeof ExtendedDataspaceSectors];
|
package/dist/sectors.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { DataspaceSectors } from 'gdc-common-utils-ts/constants/sectors';
|
|
2
|
+
/**
|
|
3
|
+
* Active product extension of the frozen CORE sector vocabulary.
|
|
4
|
+
*
|
|
5
|
+
* CORE remains unchanged. Public safety is product-neutral and may be selected
|
|
6
|
+
* only by a consuming portal that explicitly includes it in its own allowlist;
|
|
7
|
+
* the value grants no organization role, membership or data capability.
|
|
8
|
+
*/
|
|
9
|
+
export const ExtendedDataspaceSectors = Object.freeze({
|
|
10
|
+
...DataspaceSectors,
|
|
11
|
+
PublicSafety: 'public-safety',
|
|
12
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vet-data-utils-ts",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "Browser-safe governed VetChain data contracts",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Connecting Solution & Applications Ltd",
|
|
@@ -32,6 +32,14 @@
|
|
|
32
32
|
"types": "./dist/iso-jurisdictions.d.ts",
|
|
33
33
|
"default": "./dist/iso-jurisdictions.js"
|
|
34
34
|
},
|
|
35
|
+
"./organization-application": {
|
|
36
|
+
"types": "./dist/organization-application.d.ts",
|
|
37
|
+
"default": "./dist/organization-application.js"
|
|
38
|
+
},
|
|
39
|
+
"./sectors": {
|
|
40
|
+
"types": "./dist/sectors.d.ts",
|
|
41
|
+
"default": "./dist/sectors.js"
|
|
42
|
+
},
|
|
35
43
|
"./veterinary-sections": {
|
|
36
44
|
"types": "./dist/veterinary-sections.d.ts",
|
|
37
45
|
"default": "./dist/veterinary-sections.js"
|
|
@@ -59,6 +67,9 @@
|
|
|
59
67
|
"@types/node": "^22.0.0",
|
|
60
68
|
"typescript": "^5.5.4"
|
|
61
69
|
},
|
|
70
|
+
"dependencies": {
|
|
71
|
+
"gdc-common-utils-ts": "2.5.21"
|
|
72
|
+
},
|
|
62
73
|
"engines": {
|
|
63
74
|
"node": ">=20"
|
|
64
75
|
}
|