vet-data-utils-ts 0.3.1 → 0.4.2
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 +25 -0
- package/THIRD_PARTY_NOTICES.md +13 -0
- package/dist/financial.d.ts +26 -0
- package/dist/financial.js +162 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/iso-jurisdictions-data.d.ts +15 -0
- package/dist/iso-jurisdictions-data.js +32736 -0
- package/dist/iso-jurisdictions.d.ts +16 -0
- package/dist/iso-jurisdictions.js +28 -0
- package/dist/organization-application.d.ts +44 -0
- package/dist/organization-application.js +105 -0
- package/package.json +50 -10
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type Iso3166Country = Readonly<{
|
|
2
|
+
code: string;
|
|
3
|
+
name: string;
|
|
4
|
+
}>;
|
|
5
|
+
export type Iso3166Subdivision = Readonly<{
|
|
6
|
+
code: string;
|
|
7
|
+
countryCode: string;
|
|
8
|
+
name: string;
|
|
9
|
+
parentCode?: string;
|
|
10
|
+
type: string;
|
|
11
|
+
}>;
|
|
12
|
+
export declare const Iso3166Countries: readonly Iso3166Country[];
|
|
13
|
+
export declare const Iso3166Subdivisions: readonly Iso3166Subdivision[];
|
|
14
|
+
export declare function isIso3166CountryCode(value: string): boolean;
|
|
15
|
+
export declare function iso3166SubdivisionsForCountry(countryCode: string): readonly Iso3166Subdivision[];
|
|
16
|
+
export declare function isIso3166SubdivisionForCountry(subdivisionCode: string, countryCode: string): boolean;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Iso3166CountryData, Iso3166SubdivisionData } from './iso-jurisdictions-data.js';
|
|
2
|
+
export const Iso3166Countries = Object.freeze(Iso3166CountryData.map(country => Object.freeze({ ...country })));
|
|
3
|
+
export const Iso3166Subdivisions = Object.freeze(Iso3166SubdivisionData.map(subdivision => Object.freeze({ ...subdivision })));
|
|
4
|
+
const countryCodes = new Set(Iso3166Countries.map(({ code }) => code));
|
|
5
|
+
const mutableSubdivisionsByCountry = new Map();
|
|
6
|
+
const subdivisionCodes = new Set();
|
|
7
|
+
for (const subdivision of Iso3166Subdivisions) {
|
|
8
|
+
subdivisionCodes.add(subdivision.code);
|
|
9
|
+
const current = mutableSubdivisionsByCountry.get(subdivision.countryCode) ?? [];
|
|
10
|
+
current.push(subdivision);
|
|
11
|
+
mutableSubdivisionsByCountry.set(subdivision.countryCode, current);
|
|
12
|
+
}
|
|
13
|
+
const subdivisionsByCountry = new Map([...mutableSubdivisionsByCountry].map(([countryCode, subdivisions]) => [
|
|
14
|
+
countryCode,
|
|
15
|
+
Object.freeze(subdivisions),
|
|
16
|
+
]));
|
|
17
|
+
export function isIso3166CountryCode(value) {
|
|
18
|
+
return countryCodes.has(value);
|
|
19
|
+
}
|
|
20
|
+
export function iso3166SubdivisionsForCountry(countryCode) {
|
|
21
|
+
if (!isIso3166CountryCode(countryCode))
|
|
22
|
+
return [];
|
|
23
|
+
return subdivisionsByCountry.get(countryCode) ?? [];
|
|
24
|
+
}
|
|
25
|
+
export function isIso3166SubdivisionForCountry(subdivisionCode, countryCode) {
|
|
26
|
+
return subdivisionCode.startsWith(`${countryCode}-`)
|
|
27
|
+
&& subdivisionCodes.has(subdivisionCode);
|
|
28
|
+
}
|
|
@@ -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
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vet-data-utils-ts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Browser-safe governed VetChain data contracts",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Connecting Solution & Applications Ltd",
|
|
@@ -8,22 +8,62 @@
|
|
|
8
8
|
"main": "dist/index.js",
|
|
9
9
|
"types": "dist/index.d.ts",
|
|
10
10
|
"exports": {
|
|
11
|
-
".": {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"./
|
|
16
|
-
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./animal-card": {
|
|
16
|
+
"types": "./dist/animal-card.d.ts",
|
|
17
|
+
"default": "./dist/animal-card.js"
|
|
18
|
+
},
|
|
19
|
+
"./digital-twin": {
|
|
20
|
+
"types": "./dist/digital-twin.d.ts",
|
|
21
|
+
"default": "./dist/digital-twin.js"
|
|
22
|
+
},
|
|
23
|
+
"./emergency": {
|
|
24
|
+
"types": "./dist/emergency.d.ts",
|
|
25
|
+
"default": "./dist/emergency.js"
|
|
26
|
+
},
|
|
27
|
+
"./financial": {
|
|
28
|
+
"types": "./dist/financial.d.ts",
|
|
29
|
+
"default": "./dist/financial.js"
|
|
30
|
+
},
|
|
31
|
+
"./iso-jurisdictions": {
|
|
32
|
+
"types": "./dist/iso-jurisdictions.d.ts",
|
|
33
|
+
"default": "./dist/iso-jurisdictions.js"
|
|
34
|
+
},
|
|
35
|
+
"./organization-application": {
|
|
36
|
+
"types": "./dist/organization-application.d.ts",
|
|
37
|
+
"default": "./dist/organization-application.js"
|
|
38
|
+
},
|
|
39
|
+
"./veterinary-sections": {
|
|
40
|
+
"types": "./dist/veterinary-sections.d.ts",
|
|
41
|
+
"default": "./dist/veterinary-sections.js"
|
|
42
|
+
},
|
|
43
|
+
"./assistant": {
|
|
44
|
+
"types": "./dist/assistant.d.ts",
|
|
45
|
+
"default": "./dist/assistant.js"
|
|
46
|
+
}
|
|
17
47
|
},
|
|
18
|
-
"files": [
|
|
48
|
+
"files": [
|
|
49
|
+
"dist",
|
|
50
|
+
"README.md",
|
|
51
|
+
"THIRD_PARTY_NOTICES.md"
|
|
52
|
+
],
|
|
19
53
|
"scripts": {
|
|
20
54
|
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
21
55
|
"build": "npm run clean && tsc -p tsconfig.json",
|
|
22
56
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
23
57
|
"test": "npm run build && node --test tests/*.test.mjs",
|
|
58
|
+
"update:iso-jurisdictions": "node scripts/update-iso-jurisdictions.mjs",
|
|
24
59
|
"prepare": "npm run build",
|
|
25
60
|
"prepublishOnly": "npm run typecheck && npm test"
|
|
26
61
|
},
|
|
27
|
-
"devDependencies": {
|
|
28
|
-
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@types/node": "^22.0.0",
|
|
64
|
+
"typescript": "^5.5.4"
|
|
65
|
+
},
|
|
66
|
+
"engines": {
|
|
67
|
+
"node": ">=20"
|
|
68
|
+
}
|
|
29
69
|
}
|