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 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
@@ -4,4 +4,6 @@ export * from './digital-twin.js';
4
4
  export * from './emergency.js';
5
5
  export * from './financial.js';
6
6
  export * from './iso-jurisdictions.js';
7
+ export * from './organization-application.js';
8
+ export * from './sectors.js';
7
9
  export * from './veterinary-sections.js';
package/dist/index.js CHANGED
@@ -4,4 +4,6 @@ export * from './digital-twin.js';
4
4
  export * from './emergency.js';
5
5
  export * from './financial.js';
6
6
  export * from './iso-jurisdictions.js';
7
+ export * from './organization-application.js';
8
+ export * from './sectors.js';
7
9
  export * from './veterinary-sections.js';
@@ -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];
@@ -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.1",
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
  }