gdc-common-utils-ts 1.11.0 → 1.12.0
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 +3 -0
- package/dist/constants/eu-countries.d.ts +36 -0
- package/dist/constants/eu-countries.js +69 -0
- package/dist/constants/index.d.ts +1 -0
- package/dist/constants/index.js +1 -0
- package/dist/constants/schemaorg.d.ts +1 -0
- package/dist/constants/schemaorg.js +1 -0
- package/dist/constants/service-capabilities.d.ts +5 -0
- package/dist/constants/service-capabilities.js +9 -0
- package/dist/examples/dataspace-discovery.d.ts +88 -0
- package/dist/examples/dataspace-discovery.js +129 -0
- package/dist/examples/index.d.ts +1 -0
- package/dist/examples/index.js +1 -0
- package/dist/examples/shared.d.ts +20 -0
- package/dist/examples/shared.js +16 -0
- package/dist/models/dataspace-discovery.d.ts +66 -0
- package/dist/models/dataspace-discovery.js +9 -0
- package/dist/models/index.d.ts +1 -0
- package/dist/models/index.js +1 -0
- package/dist/utils/dataspace-discovery.d.ts +146 -0
- package/dist/utils/dataspace-discovery.js +341 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -86,6 +86,9 @@ import { JweObject, JwtCompactParts } from 'gdc-common-utils-ts/models';
|
|
|
86
86
|
|
|
87
87
|
## Cross-Repo Task Docs
|
|
88
88
|
|
|
89
|
+
- [docs/DATASPACE_DISCOVERY_ROADMAP.md](docs/DATASPACE_DISCOVERY_ROADMAP.md)
|
|
90
|
+
- cross-repo contract for dataspace discovery semantics, EU coverage
|
|
91
|
+
inference, shared DTOs, and parameterized examples
|
|
89
92
|
- [docs/consent-access-matrix-task.md](docs/consent-access-matrix-task.md)
|
|
90
93
|
- next-step design/task document for active consent aggregation, explicit deny precedence, controller views, permission-request communications, and SMART access evaluation
|
|
91
94
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ISO 3166-1 alpha-2 country codes that currently belong to the European Union.
|
|
3
|
+
*
|
|
4
|
+
* This list is intentionally kept in a runtime-neutral shared package because
|
|
5
|
+
* dataspace discovery may need to infer a broader coverage scope such as `EU`
|
|
6
|
+
* from the semantic country carried in a VC `credentialSubject`.
|
|
7
|
+
*/
|
|
8
|
+
export declare const EU_COUNTRY_CODES: readonly ["AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "GR", "HU", "IE", "IT", "LV", "LT", "LU", "MT", "NL", "PL", "PT", "RO", "SK", "SI", "ES", "SE"];
|
|
9
|
+
export type EuCountryCode = typeof EU_COUNTRY_CODES[number];
|
|
10
|
+
/**
|
|
11
|
+
* Normalizes a country code into canonical uppercase ISO-2 form.
|
|
12
|
+
*
|
|
13
|
+
* @param countryCode Country code from `credentialSubject.address.addressCountry`
|
|
14
|
+
* or the flattened operational projection.
|
|
15
|
+
* @returns Uppercase ISO-2 form or an empty string when the input is blank.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* normalizeCountryCode('es');
|
|
20
|
+
* // 'ES'
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare function normalizeCountryCode(countryCode: string | undefined | null): string;
|
|
24
|
+
/**
|
|
25
|
+
* Checks whether the supplied country code belongs to the current EU member set.
|
|
26
|
+
*
|
|
27
|
+
* @param countryCode ISO-2 country code to evaluate.
|
|
28
|
+
* @returns `true` when the normalized code belongs to `EU_COUNTRY_CODES`.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* isEuCountryCode('ES');
|
|
33
|
+
* // true
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare function isEuCountryCode(countryCode: string | undefined | null): boolean;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
|
+
/**
|
|
3
|
+
* ISO 3166-1 alpha-2 country codes that currently belong to the European Union.
|
|
4
|
+
*
|
|
5
|
+
* This list is intentionally kept in a runtime-neutral shared package because
|
|
6
|
+
* dataspace discovery may need to infer a broader coverage scope such as `EU`
|
|
7
|
+
* from the semantic country carried in a VC `credentialSubject`.
|
|
8
|
+
*/
|
|
9
|
+
export const EU_COUNTRY_CODES = Object.freeze([
|
|
10
|
+
'AT',
|
|
11
|
+
'BE',
|
|
12
|
+
'BG',
|
|
13
|
+
'HR',
|
|
14
|
+
'CY',
|
|
15
|
+
'CZ',
|
|
16
|
+
'DK',
|
|
17
|
+
'EE',
|
|
18
|
+
'FI',
|
|
19
|
+
'FR',
|
|
20
|
+
'DE',
|
|
21
|
+
'GR',
|
|
22
|
+
'HU',
|
|
23
|
+
'IE',
|
|
24
|
+
'IT',
|
|
25
|
+
'LV',
|
|
26
|
+
'LT',
|
|
27
|
+
'LU',
|
|
28
|
+
'MT',
|
|
29
|
+
'NL',
|
|
30
|
+
'PL',
|
|
31
|
+
'PT',
|
|
32
|
+
'RO',
|
|
33
|
+
'SK',
|
|
34
|
+
'SI',
|
|
35
|
+
'ES',
|
|
36
|
+
'SE',
|
|
37
|
+
]);
|
|
38
|
+
/**
|
|
39
|
+
* Normalizes a country code into canonical uppercase ISO-2 form.
|
|
40
|
+
*
|
|
41
|
+
* @param countryCode Country code from `credentialSubject.address.addressCountry`
|
|
42
|
+
* or the flattened operational projection.
|
|
43
|
+
* @returns Uppercase ISO-2 form or an empty string when the input is blank.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* normalizeCountryCode('es');
|
|
48
|
+
* // 'ES'
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export function normalizeCountryCode(countryCode) {
|
|
52
|
+
return String(countryCode || '').trim().toUpperCase();
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Checks whether the supplied country code belongs to the current EU member set.
|
|
56
|
+
*
|
|
57
|
+
* @param countryCode ISO-2 country code to evaluate.
|
|
58
|
+
* @returns `true` when the normalized code belongs to `EU_COUNTRY_CODES`.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* isEuCountryCode('ES');
|
|
63
|
+
* // true
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export function isEuCountryCode(countryCode) {
|
|
67
|
+
const normalized = normalizeCountryCode(countryCode);
|
|
68
|
+
return normalized ? EU_COUNTRY_CODES.includes(normalized) : false;
|
|
69
|
+
}
|
|
@@ -3,6 +3,7 @@ export * from './communication';
|
|
|
3
3
|
export * from './cryptography';
|
|
4
4
|
export * from './device';
|
|
5
5
|
export * from './did-services';
|
|
6
|
+
export * from './eu-countries';
|
|
6
7
|
export * from './fhir-code-systems';
|
|
7
8
|
export * from './fhir-resource-types';
|
|
8
9
|
export * from './fhir-versions';
|
package/dist/constants/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export * from './communication.js';
|
|
|
3
3
|
export * from './cryptography.js';
|
|
4
4
|
export * from './device.js';
|
|
5
5
|
export * from './did-services.js';
|
|
6
|
+
export * from './eu-countries.js';
|
|
6
7
|
export * from './fhir-code-systems.js';
|
|
7
8
|
export * from './fhir-resource-types.js';
|
|
8
9
|
export * from './fhir-versions.js';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ParameterData } from "../models/params";
|
|
2
2
|
export declare enum ClaimsServiceSchemaorg {
|
|
3
|
+
areaServed = "org.schema.Service.areaServed",
|
|
3
4
|
category = "org.schema.Service.category",
|
|
4
5
|
identifier = "org.schema.Service.identifier",
|
|
5
6
|
serviceType = "org.schema.Service.serviceType",
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// File: src/models/schemaorg.ts
|
|
3
3
|
export var ClaimsServiceSchemaorg;
|
|
4
4
|
(function (ClaimsServiceSchemaorg) {
|
|
5
|
+
ClaimsServiceSchemaorg["areaServed"] = "org.schema.Service.areaServed";
|
|
5
6
|
ClaimsServiceSchemaorg["category"] = "org.schema.Service.category";
|
|
6
7
|
ClaimsServiceSchemaorg["identifier"] = "org.schema.Service.identifier";
|
|
7
8
|
ClaimsServiceSchemaorg["serviceType"] = "org.schema.Service.serviceType";
|
|
@@ -75,3 +75,8 @@ export declare function getServiceCapabilityFamily(value: string | undefined): s
|
|
|
75
75
|
* family.
|
|
76
76
|
*/
|
|
77
77
|
export declare function hasServiceCapabilityFamily(value: unknown, family: ServiceCapabilityFamilyValue | string): boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Returns whether a capability token denotes a discoverable provider/service
|
|
80
|
+
* role rather than a reader-only role.
|
|
81
|
+
*/
|
|
82
|
+
export declare function isProviderServiceCapability(value: string | undefined | null): boolean;
|
|
@@ -94,3 +94,12 @@ export function hasServiceCapabilityFamily(value, family) {
|
|
|
94
94
|
return false;
|
|
95
95
|
return parseServiceCapabilityTokens(value).some((item) => getServiceCapabilityFamily(item) === normalizedFamily);
|
|
96
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Returns whether a capability token denotes a discoverable provider/service
|
|
99
|
+
* role rather than a reader-only role.
|
|
100
|
+
*/
|
|
101
|
+
export function isProviderServiceCapability(value) {
|
|
102
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
103
|
+
return normalized === ServiceCapabilityToken.IndexProvider
|
|
104
|
+
|| normalized === ServiceCapabilityToken.DigitalTwinProvider;
|
|
105
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { HostingOperatorDiscoveryCatalog, PublishedProviderCatalogRecord } from '../models/dataspace-discovery';
|
|
2
|
+
export type ExampleDataspaceCredentialSubjectInput = Readonly<{
|
|
3
|
+
did?: string;
|
|
4
|
+
serviceTypes?: readonly string[];
|
|
5
|
+
categories?: readonly string[];
|
|
6
|
+
areaServed?: readonly string[];
|
|
7
|
+
addressCountry?: string;
|
|
8
|
+
}>;
|
|
9
|
+
/**
|
|
10
|
+
* Builds a synthetic hosting-operator semantic `credentialSubject`.
|
|
11
|
+
*
|
|
12
|
+
* This example is parameterized on purpose: public docs/tests must not hardcode
|
|
13
|
+
* business identities when demonstrating dataspace discovery semantics.
|
|
14
|
+
*
|
|
15
|
+
* @param input Optional overrides for the synthetic subject.
|
|
16
|
+
* @returns Schema.org-shaped semantic subject with service metadata.
|
|
17
|
+
*/
|
|
18
|
+
export declare function buildExampleHostingOperatorCredentialSubject(input?: ExampleDataspaceCredentialSubjectInput): {
|
|
19
|
+
id: string;
|
|
20
|
+
serviceType: string;
|
|
21
|
+
category: string;
|
|
22
|
+
areaServed: {
|
|
23
|
+
'@type': string;
|
|
24
|
+
name: string;
|
|
25
|
+
}[];
|
|
26
|
+
address: {
|
|
27
|
+
addressCountry: string;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Builds a synthetic tenant-service semantic `credentialSubject`.
|
|
32
|
+
*
|
|
33
|
+
* @param input Optional overrides for the synthetic tenant subject.
|
|
34
|
+
* @returns Schema.org-shaped semantic subject with public service metadata.
|
|
35
|
+
*/
|
|
36
|
+
export declare function buildExampleTenantServiceCredentialSubject(input?: ExampleDataspaceCredentialSubjectInput): {
|
|
37
|
+
id: string;
|
|
38
|
+
serviceType: string;
|
|
39
|
+
category: string;
|
|
40
|
+
areaServed: {
|
|
41
|
+
'@type': string;
|
|
42
|
+
name: string;
|
|
43
|
+
}[];
|
|
44
|
+
address: {
|
|
45
|
+
addressCountry: string;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Builds the flattened `meta.claims` projection for a hosting-operator semantic
|
|
50
|
+
* subject.
|
|
51
|
+
*
|
|
52
|
+
* @param input Optional overrides for the synthetic projection.
|
|
53
|
+
* @returns Flat operational claims derived from the semantic subject.
|
|
54
|
+
*/
|
|
55
|
+
export declare function buildExampleHostingOperatorMetaClaims(input?: ExampleDataspaceCredentialSubjectInput): {
|
|
56
|
+
"org.schema.Service.serviceType": string | undefined;
|
|
57
|
+
"org.schema.Service.category": string;
|
|
58
|
+
"org.schema.Service.areaServed": string;
|
|
59
|
+
"org.schema.Organization.address.addressCountry": string;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Builds the flattened `meta.claims` projection for a tenant-service semantic
|
|
63
|
+
* subject.
|
|
64
|
+
*
|
|
65
|
+
* @param input Optional overrides for the synthetic projection.
|
|
66
|
+
* @returns Flat operational claims derived from the semantic subject.
|
|
67
|
+
*/
|
|
68
|
+
export declare function buildExampleTenantServiceMetaClaims(input?: ExampleDataspaceCredentialSubjectInput): {
|
|
69
|
+
"org.schema.Service.serviceType": string | undefined;
|
|
70
|
+
"org.schema.Service.category": string;
|
|
71
|
+
"org.schema.Service.areaServed": string;
|
|
72
|
+
"org.schema.Organization.address.addressCountry": string;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Builds a synthetic published-provider record as it would appear in a host
|
|
76
|
+
* service-autodiscovery catalog.
|
|
77
|
+
*
|
|
78
|
+
* @param input Optional overrides for the synthetic provider publication.
|
|
79
|
+
* @returns Shared host-catalog provider entry.
|
|
80
|
+
*/
|
|
81
|
+
export declare function buildExamplePublishedProviderCatalogRecord(input?: ExampleDataspaceCredentialSubjectInput): PublishedProviderCatalogRecord;
|
|
82
|
+
/**
|
|
83
|
+
* Builds a synthetic host/operator service-autodiscovery catalog.
|
|
84
|
+
*
|
|
85
|
+
* @param providers Optional published providers to include.
|
|
86
|
+
* @returns Shared catalog DTO for host-side public service autodiscovery.
|
|
87
|
+
*/
|
|
88
|
+
export declare function buildExampleHostingOperatorDiscoveryCatalog(providers?: ReadonlyArray<PublishedProviderCatalogRecord>): HostingOperatorDiscoveryCatalog;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
|
+
import { ClaimsOrganizationSchemaorg, ClaimsServiceSchemaorg } from '../constants/schemaorg.js';
|
|
3
|
+
import { serializeServiceCapabilityTokens, ServiceCapabilityToken } from '../constants/service-capabilities.js';
|
|
4
|
+
import { EXAMPLE_HOSTING_OPERATOR_CATALOG_URL, EXAMPLE_HOSTING_OPERATOR_DID, EXAMPLE_PROVIDER_PUBLISHED_ENDPOINT_URL, EXAMPLE_JURISDICTION, EXAMPLE_SECTOR, EXAMPLE_TENANT_SERVICE_DID, } from './shared.js';
|
|
5
|
+
function firstOrCsv(values) {
|
|
6
|
+
return values.length <= 1 ? (values[0] || '') : values.join(',');
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Builds a synthetic hosting-operator semantic `credentialSubject`.
|
|
10
|
+
*
|
|
11
|
+
* This example is parameterized on purpose: public docs/tests must not hardcode
|
|
12
|
+
* business identities when demonstrating dataspace discovery semantics.
|
|
13
|
+
*
|
|
14
|
+
* @param input Optional overrides for the synthetic subject.
|
|
15
|
+
* @returns Schema.org-shaped semantic subject with service metadata.
|
|
16
|
+
*/
|
|
17
|
+
export function buildExampleHostingOperatorCredentialSubject(input = {}) {
|
|
18
|
+
const serviceTypes = input.serviceTypes || [
|
|
19
|
+
ServiceCapabilityToken.IndexProvider,
|
|
20
|
+
ServiceCapabilityToken.DigitalTwinProvider,
|
|
21
|
+
];
|
|
22
|
+
const categories = input.categories || [EXAMPLE_SECTOR];
|
|
23
|
+
const areaServed = input.areaServed || ['EU', EXAMPLE_JURISDICTION];
|
|
24
|
+
const addressCountry = input.addressCountry || EXAMPLE_JURISDICTION;
|
|
25
|
+
return {
|
|
26
|
+
id: input.did || 'did:web:host.example.org',
|
|
27
|
+
serviceType: firstOrCsv(serviceTypes),
|
|
28
|
+
category: firstOrCsv(categories),
|
|
29
|
+
areaServed: areaServed.map((name) => ({ '@type': 'AdministrativeArea', name })),
|
|
30
|
+
address: {
|
|
31
|
+
addressCountry,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Builds a synthetic tenant-service semantic `credentialSubject`.
|
|
37
|
+
*
|
|
38
|
+
* @param input Optional overrides for the synthetic tenant subject.
|
|
39
|
+
* @returns Schema.org-shaped semantic subject with public service metadata.
|
|
40
|
+
*/
|
|
41
|
+
export function buildExampleTenantServiceCredentialSubject(input = {}) {
|
|
42
|
+
const serviceTypes = input.serviceTypes || [ServiceCapabilityToken.IndexProvider];
|
|
43
|
+
const categories = input.categories || [EXAMPLE_SECTOR];
|
|
44
|
+
const areaServed = input.areaServed || ['EU'];
|
|
45
|
+
const addressCountry = input.addressCountry || EXAMPLE_JURISDICTION;
|
|
46
|
+
return {
|
|
47
|
+
id: input.did || 'did:web:provider.example.org',
|
|
48
|
+
serviceType: firstOrCsv(serviceTypes),
|
|
49
|
+
category: firstOrCsv(categories),
|
|
50
|
+
areaServed: areaServed.map((name) => ({ '@type': 'AdministrativeArea', name })),
|
|
51
|
+
address: {
|
|
52
|
+
addressCountry,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Builds the flattened `meta.claims` projection for a hosting-operator semantic
|
|
58
|
+
* subject.
|
|
59
|
+
*
|
|
60
|
+
* @param input Optional overrides for the synthetic projection.
|
|
61
|
+
* @returns Flat operational claims derived from the semantic subject.
|
|
62
|
+
*/
|
|
63
|
+
export function buildExampleHostingOperatorMetaClaims(input = {}) {
|
|
64
|
+
const serviceTypes = input.serviceTypes || [
|
|
65
|
+
ServiceCapabilityToken.IndexProvider,
|
|
66
|
+
ServiceCapabilityToken.DigitalTwinProvider,
|
|
67
|
+
];
|
|
68
|
+
const categories = input.categories || [EXAMPLE_SECTOR];
|
|
69
|
+
const areaServed = input.areaServed || ['EU', EXAMPLE_JURISDICTION];
|
|
70
|
+
const addressCountry = input.addressCountry || EXAMPLE_JURISDICTION;
|
|
71
|
+
return {
|
|
72
|
+
[ClaimsServiceSchemaorg.serviceType]: serializeServiceCapabilityTokens(serviceTypes),
|
|
73
|
+
[ClaimsServiceSchemaorg.category]: firstOrCsv(categories),
|
|
74
|
+
[ClaimsServiceSchemaorg.areaServed]: firstOrCsv(areaServed),
|
|
75
|
+
[ClaimsOrganizationSchemaorg.addressCountry]: addressCountry,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Builds the flattened `meta.claims` projection for a tenant-service semantic
|
|
80
|
+
* subject.
|
|
81
|
+
*
|
|
82
|
+
* @param input Optional overrides for the synthetic projection.
|
|
83
|
+
* @returns Flat operational claims derived from the semantic subject.
|
|
84
|
+
*/
|
|
85
|
+
export function buildExampleTenantServiceMetaClaims(input = {}) {
|
|
86
|
+
const serviceTypes = input.serviceTypes || [ServiceCapabilityToken.IndexProvider];
|
|
87
|
+
const categories = input.categories || [EXAMPLE_SECTOR];
|
|
88
|
+
const areaServed = input.areaServed || ['EU'];
|
|
89
|
+
const addressCountry = input.addressCountry || EXAMPLE_JURISDICTION;
|
|
90
|
+
return {
|
|
91
|
+
[ClaimsServiceSchemaorg.serviceType]: serializeServiceCapabilityTokens(serviceTypes),
|
|
92
|
+
[ClaimsServiceSchemaorg.category]: firstOrCsv(categories),
|
|
93
|
+
[ClaimsServiceSchemaorg.areaServed]: firstOrCsv(areaServed),
|
|
94
|
+
[ClaimsOrganizationSchemaorg.addressCountry]: addressCountry,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Builds a synthetic published-provider record as it would appear in a host
|
|
99
|
+
* service-autodiscovery catalog.
|
|
100
|
+
*
|
|
101
|
+
* @param input Optional overrides for the synthetic provider publication.
|
|
102
|
+
* @returns Shared host-catalog provider entry.
|
|
103
|
+
*/
|
|
104
|
+
export function buildExamplePublishedProviderCatalogRecord(input = {}) {
|
|
105
|
+
const serviceTypes = input.serviceTypes || [ServiceCapabilityToken.IndexProvider];
|
|
106
|
+
const categories = input.categories || [EXAMPLE_SECTOR];
|
|
107
|
+
const areaServed = input.areaServed || ['EU'];
|
|
108
|
+
return {
|
|
109
|
+
providerDid: input.did || EXAMPLE_TENANT_SERVICE_DID,
|
|
110
|
+
serviceType: serviceTypes[0] || ServiceCapabilityToken.IndexProvider,
|
|
111
|
+
category: categories[0] || EXAMPLE_SECTOR,
|
|
112
|
+
areaServed: areaServed[0] || 'EU',
|
|
113
|
+
endpointUrl: EXAMPLE_PROVIDER_PUBLISHED_ENDPOINT_URL,
|
|
114
|
+
catalogUrl: EXAMPLE_HOSTING_OPERATOR_CATALOG_URL,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Builds a synthetic host/operator service-autodiscovery catalog.
|
|
119
|
+
*
|
|
120
|
+
* @param providers Optional published providers to include.
|
|
121
|
+
* @returns Shared catalog DTO for host-side public service autodiscovery.
|
|
122
|
+
*/
|
|
123
|
+
export function buildExampleHostingOperatorDiscoveryCatalog(providers = [buildExamplePublishedProviderCatalogRecord()]) {
|
|
124
|
+
return {
|
|
125
|
+
hostingOperatorDid: EXAMPLE_HOSTING_OPERATOR_DID,
|
|
126
|
+
catalogUrl: EXAMPLE_HOSTING_OPERATOR_CATALOG_URL,
|
|
127
|
+
providers: [...providers],
|
|
128
|
+
};
|
|
129
|
+
}
|
package/dist/examples/index.d.ts
CHANGED
package/dist/examples/index.js
CHANGED
|
@@ -31,6 +31,19 @@ export declare const EXAMPLE_SUBJECT_DID: "did:web:api.acme.org:individual:123";
|
|
|
31
31
|
export declare const EXAMPLE_PROFESSIONAL_DID: "did:web:api.acme.org:professional:1";
|
|
32
32
|
export declare const EXAMPLE_PROVIDER_ORGANIZATION_DID: "did:web:hospital.acme.org";
|
|
33
33
|
export declare const EXAMPLE_PROVIDER_ORGANIZATION_URL: "https://hospital.acme.org";
|
|
34
|
+
export declare const EXAMPLE_GATEWAY_PUBLIC_ORIGIN: "https://gateway.example.com";
|
|
35
|
+
export declare const EXAMPLE_HOST_PUBLIC_HOSTNAME: "host.example.com";
|
|
36
|
+
export declare const EXAMPLE_HOSTING_OPERATOR_DID: "did:web:host.example.org";
|
|
37
|
+
export declare const EXAMPLE_TENANT_SERVICE_DID: "did:web:provider.example.org";
|
|
38
|
+
export declare const EXAMPLE_SECONDARY_TENANT_SERVICE_DID: "did:web:provider-b.example.org";
|
|
39
|
+
export declare const EXAMPLE_HOSTING_OPERATOR_CATALOG_URL: "https://host.example.org/.well-known/dcat3/catalog";
|
|
40
|
+
export declare const EXAMPLE_PROVIDER_PUBLISHED_ENDPOINT_URL: "https://host.example.org/catalog/provider-a";
|
|
41
|
+
export declare const EXAMPLE_PROVIDER_LEGAL_NAME: "ACME Health Provider";
|
|
42
|
+
export declare const EXAMPLE_SECONDARY_PROVIDER_LEGAL_NAME: "Reader Only Provider";
|
|
43
|
+
export declare const EXAMPLE_SECONDARY_PROVIDER_ALTERNATE_NAME: "reader-only";
|
|
44
|
+
export declare const EXAMPLE_COVERAGE_SCOPE_EU: "EU";
|
|
45
|
+
export declare const EXAMPLE_NON_EU_COUNTRY: "US";
|
|
46
|
+
export declare const EXAMPLE_SECONDARY_EU_COUNTRY: "PT";
|
|
34
47
|
export declare const EXAMPLE_PATIENT_DID: "did:web:patient.example";
|
|
35
48
|
export declare const EXAMPLE_PROFILE_PROVIDER_DID: "did:web:provider.example.org";
|
|
36
49
|
export declare const EXAMPLE_PROFILE_ORGANIZATION_DID: "did:web:org.example";
|
|
@@ -183,3 +196,10 @@ export declare function buildExampleDocumentReferenceSearchPayload(subjectDid?:
|
|
|
183
196
|
};
|
|
184
197
|
};
|
|
185
198
|
export declare function cloneExample<T>(value: T): T;
|
|
199
|
+
export type ExampleHostedTenantRouteContext = Readonly<{
|
|
200
|
+
alternateName: string;
|
|
201
|
+
jurisdiction: string;
|
|
202
|
+
version: string;
|
|
203
|
+
sector: string;
|
|
204
|
+
}>;
|
|
205
|
+
export declare function buildExampleHostedTenantBaseUrl(input: ExampleHostedTenantRouteContext): string;
|
package/dist/examples/shared.js
CHANGED
|
@@ -36,6 +36,19 @@ export const EXAMPLE_SUBJECT_DID = 'did:web:api.acme.org:individual:123';
|
|
|
36
36
|
export const EXAMPLE_PROFESSIONAL_DID = 'did:web:api.acme.org:professional:1';
|
|
37
37
|
export const EXAMPLE_PROVIDER_ORGANIZATION_DID = 'did:web:hospital.acme.org';
|
|
38
38
|
export const EXAMPLE_PROVIDER_ORGANIZATION_URL = 'https://hospital.acme.org';
|
|
39
|
+
export const EXAMPLE_GATEWAY_PUBLIC_ORIGIN = 'https://gateway.example.com';
|
|
40
|
+
export const EXAMPLE_HOST_PUBLIC_HOSTNAME = 'host.example.com';
|
|
41
|
+
export const EXAMPLE_HOSTING_OPERATOR_DID = 'did:web:host.example.org';
|
|
42
|
+
export const EXAMPLE_TENANT_SERVICE_DID = 'did:web:provider.example.org';
|
|
43
|
+
export const EXAMPLE_SECONDARY_TENANT_SERVICE_DID = 'did:web:provider-b.example.org';
|
|
44
|
+
export const EXAMPLE_HOSTING_OPERATOR_CATALOG_URL = 'https://host.example.org/.well-known/dcat3/catalog';
|
|
45
|
+
export const EXAMPLE_PROVIDER_PUBLISHED_ENDPOINT_URL = 'https://host.example.org/catalog/provider-a';
|
|
46
|
+
export const EXAMPLE_PROVIDER_LEGAL_NAME = 'ACME Health Provider';
|
|
47
|
+
export const EXAMPLE_SECONDARY_PROVIDER_LEGAL_NAME = 'Reader Only Provider';
|
|
48
|
+
export const EXAMPLE_SECONDARY_PROVIDER_ALTERNATE_NAME = 'reader-only';
|
|
49
|
+
export const EXAMPLE_COVERAGE_SCOPE_EU = 'EU';
|
|
50
|
+
export const EXAMPLE_NON_EU_COUNTRY = 'US';
|
|
51
|
+
export const EXAMPLE_SECONDARY_EU_COUNTRY = 'PT';
|
|
39
52
|
export const EXAMPLE_PATIENT_DID = 'did:web:patient.example';
|
|
40
53
|
export const EXAMPLE_PROFILE_PROVIDER_DID = 'did:web:provider.example.org';
|
|
41
54
|
export const EXAMPLE_PROFILE_ORGANIZATION_DID = 'did:web:org.example';
|
|
@@ -157,3 +170,6 @@ export function buildExampleDocumentReferenceSearchPayload(subjectDid = EXAMPLE_
|
|
|
157
170
|
export function cloneExample(value) {
|
|
158
171
|
return JSON.parse(JSON.stringify(value));
|
|
159
172
|
}
|
|
173
|
+
export function buildExampleHostedTenantBaseUrl(input) {
|
|
174
|
+
return `${EXAMPLE_GATEWAY_PUBLIC_ORIGIN}/${input.alternateName}/cds-${input.jurisdiction.toLowerCase()}/${input.version}/${input.sector}`;
|
|
175
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical shared coverage scopes derived from semantic service metadata.
|
|
3
|
+
*
|
|
4
|
+
* `EU` is a coverage scope, not a sector.
|
|
5
|
+
*/
|
|
6
|
+
export declare const DataspaceCoverageScope: {
|
|
7
|
+
readonly EuropeanUnion: "EU";
|
|
8
|
+
};
|
|
9
|
+
export type DataspaceCoverageScopeValue = typeof DataspaceCoverageScope[keyof typeof DataspaceCoverageScope];
|
|
10
|
+
/**
|
|
11
|
+
* Runtime-neutral service-discovery record normalized from a semantic
|
|
12
|
+
* `credentialSubject` and optionally its flattened `meta.claims` projection.
|
|
13
|
+
*/
|
|
14
|
+
export type DataspaceServiceSemanticRecord = Readonly<{
|
|
15
|
+
subjectId?: string;
|
|
16
|
+
serviceTypes: string[];
|
|
17
|
+
categories: string[];
|
|
18
|
+
areaServed: string[];
|
|
19
|
+
addressCountry?: string;
|
|
20
|
+
coverageScope?: string;
|
|
21
|
+
}>;
|
|
22
|
+
/**
|
|
23
|
+
* Semantic hosting-operator record extracted from an ICA-issued VC or
|
|
24
|
+
* equivalent semantic payload.
|
|
25
|
+
*/
|
|
26
|
+
export type HostingOperatorSemanticRecord = DataspaceServiceSemanticRecord;
|
|
27
|
+
/**
|
|
28
|
+
* Semantic tenant-service record extracted from an ICA-issued VC or equivalent
|
|
29
|
+
* semantic payload.
|
|
30
|
+
*/
|
|
31
|
+
export type TenantServiceSemanticRecord = DataspaceServiceSemanticRecord;
|
|
32
|
+
/**
|
|
33
|
+
* Public provider entry expected from a host discovery catalog.
|
|
34
|
+
*/
|
|
35
|
+
export type PublishedProviderCatalogRecord = Readonly<{
|
|
36
|
+
providerDid: string;
|
|
37
|
+
serviceType: string;
|
|
38
|
+
category: string;
|
|
39
|
+
areaServed?: string;
|
|
40
|
+
endpointUrl?: string;
|
|
41
|
+
catalogUrl?: string;
|
|
42
|
+
}>;
|
|
43
|
+
/**
|
|
44
|
+
* Shared host-catalog filter for service autodiscovery.
|
|
45
|
+
*
|
|
46
|
+
* This shape is runtime-neutral and can be reused by GW, ICA, backend SDKs,
|
|
47
|
+
* and portal/native-app backends.
|
|
48
|
+
*/
|
|
49
|
+
export type DataspaceDiscoveryFilter = Readonly<{
|
|
50
|
+
sector: string;
|
|
51
|
+
capability?: string;
|
|
52
|
+
requiredCapabilities?: readonly string[];
|
|
53
|
+
jurisdiction?: string;
|
|
54
|
+
coverageScope?: string;
|
|
55
|
+
}>;
|
|
56
|
+
/**
|
|
57
|
+
* Public host/operator service-autodiscovery catalog.
|
|
58
|
+
*
|
|
59
|
+
* This is distinct from any dataset-specific catalog exposed by an individual
|
|
60
|
+
* `DigitalTwinProvider`.
|
|
61
|
+
*/
|
|
62
|
+
export type HostingOperatorDiscoveryCatalog = Readonly<{
|
|
63
|
+
hostingOperatorDid?: string;
|
|
64
|
+
catalogUrl?: string;
|
|
65
|
+
providers: PublishedProviderCatalogRecord[];
|
|
66
|
+
}>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
|
+
/**
|
|
3
|
+
* Canonical shared coverage scopes derived from semantic service metadata.
|
|
4
|
+
*
|
|
5
|
+
* `EU` is a coverage scope, not a sector.
|
|
6
|
+
*/
|
|
7
|
+
export const DataspaceCoverageScope = {
|
|
8
|
+
EuropeanUnion: 'EU',
|
|
9
|
+
};
|
package/dist/models/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export * from './confidential-storage';
|
|
|
12
12
|
export * from './consent-rule';
|
|
13
13
|
export * from './consent-access';
|
|
14
14
|
export * from './crypto';
|
|
15
|
+
export * from './dataspace-discovery';
|
|
15
16
|
export * from './device-license';
|
|
16
17
|
export * from './did';
|
|
17
18
|
export * from './fhir-documents';
|
package/dist/models/index.js
CHANGED
|
@@ -12,6 +12,7 @@ export * from './confidential-storage.js';
|
|
|
12
12
|
export * from './consent-rule.js';
|
|
13
13
|
export * from './consent-access.js';
|
|
14
14
|
export * from './crypto.js';
|
|
15
|
+
export * from './dataspace-discovery.js';
|
|
15
16
|
export * from './device-license.js';
|
|
16
17
|
export * from './did.js';
|
|
17
18
|
export * from './fhir-documents.js';
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { type DataspaceDiscoveryFilter, type DataspaceServiceSemanticRecord, type HostingOperatorSemanticRecord, type HostingOperatorDiscoveryCatalog, type PublishedProviderCatalogRecord, type TenantServiceSemanticRecord } from '../models/dataspace-discovery';
|
|
2
|
+
/**
|
|
3
|
+
* Parses the CSV or array representation of `serviceType`.
|
|
4
|
+
*
|
|
5
|
+
* @param value Semantic `credentialSubject.serviceType` or flattened
|
|
6
|
+
* `meta.claims['org.schema.Service.serviceType']`.
|
|
7
|
+
* @returns Normalized unique service capability tokens.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* parseServiceTypeCsv('indexing.cruds,digitaltwin.rs');
|
|
12
|
+
* // ['indexing.cruds', 'digitaltwin.rs']
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export declare function parseServiceTypeCsv(value: unknown): string[];
|
|
16
|
+
/**
|
|
17
|
+
* Parses the Schema.org `category` service dimension used as the dataspace
|
|
18
|
+
* sector vocabulary in the current profile.
|
|
19
|
+
*
|
|
20
|
+
* @param value Semantic `credentialSubject.category` or flattened
|
|
21
|
+
* `meta.claims['org.schema.Service.category']`.
|
|
22
|
+
* @returns Normalized unique sector/category values.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* parseServiceCategories('animal-care,health-care');
|
|
27
|
+
* // ['animal-care', 'health-care']
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare function parseServiceCategories(value: unknown): string[];
|
|
31
|
+
/**
|
|
32
|
+
* Parses Schema.org `areaServed` values.
|
|
33
|
+
*
|
|
34
|
+
* Accepts scalar strings, CSV strings, arrays, and simple
|
|
35
|
+
* `AdministrativeArea`-like objects with `name` or `@id`.
|
|
36
|
+
*
|
|
37
|
+
* @param value Semantic `credentialSubject.areaServed` or flattened
|
|
38
|
+
* `meta.claims['org.schema.Service.areaServed']`.
|
|
39
|
+
* @returns Normalized unique coverage values.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* parseAreaServed([{ '@type': 'AdministrativeArea', name: 'EU' }, 'ES']);
|
|
44
|
+
* // ['EU', 'ES']
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare function parseAreaServed(value: unknown): string[];
|
|
48
|
+
/**
|
|
49
|
+
* Infers a broader coverage scope from an ISO-2 country code.
|
|
50
|
+
*
|
|
51
|
+
* `EU` is returned only as a coverage scope. It must not be treated as a
|
|
52
|
+
* sector.
|
|
53
|
+
*
|
|
54
|
+
* @param countryCode ISO-2 country code, typically from
|
|
55
|
+
* `credentialSubject.address.addressCountry`.
|
|
56
|
+
* @returns `EU` for EU member countries, otherwise the normalized country code.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* inferCoverageScopeFromCountryCode('ES');
|
|
61
|
+
* // 'EU'
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export declare function inferCoverageScopeFromCountryCode(countryCode: string | undefined | null): string | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* Infers a coverage scope from the semantic `credentialSubject`.
|
|
67
|
+
*
|
|
68
|
+
* @param subject Semantic service object containing `address.addressCountry`.
|
|
69
|
+
* @returns Broader coverage scope such as `EU`, or the normalized country code
|
|
70
|
+
* when the country is outside the EU set.
|
|
71
|
+
*/
|
|
72
|
+
export declare function inferCoverageScopeFromCredentialSubject(subject: unknown): string | undefined;
|
|
73
|
+
/**
|
|
74
|
+
* Extracts the shared dataspace service semantics from a VC-like payload.
|
|
75
|
+
*
|
|
76
|
+
* Source-of-truth rule:
|
|
77
|
+
* - semantic values come from `credentialSubject` first
|
|
78
|
+
* - flattened `meta.claims` is accepted as a compatibility projection/fallback
|
|
79
|
+
* - when both exist they must agree
|
|
80
|
+
*
|
|
81
|
+
* @param input VC-like payload, direct semantic object, or equivalent DTO.
|
|
82
|
+
* @returns Runtime-neutral normalized dataspace discovery semantics.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* extractDataspaceServiceSemanticRecord({
|
|
87
|
+
* credentialSubject: {
|
|
88
|
+
* id: 'did:web:provider.example.org',
|
|
89
|
+
* serviceType: 'indexing.cruds',
|
|
90
|
+
* category: 'animal-care',
|
|
91
|
+
* areaServed: { '@type': 'AdministrativeArea', name: 'EU' },
|
|
92
|
+
* address: { addressCountry: 'ES' },
|
|
93
|
+
* },
|
|
94
|
+
* meta: {
|
|
95
|
+
* claims: {
|
|
96
|
+
* 'org.schema.Service.serviceType': 'indexing.cruds',
|
|
97
|
+
* 'org.schema.Service.category': 'animal-care',
|
|
98
|
+
* 'org.schema.Service.areaServed': 'EU',
|
|
99
|
+
* 'org.schema.Organization.address.addressCountry': 'ES',
|
|
100
|
+
* },
|
|
101
|
+
* },
|
|
102
|
+
* });
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
export declare function extractDataspaceServiceSemanticRecord(input: unknown): DataspaceServiceSemanticRecord;
|
|
106
|
+
/**
|
|
107
|
+
* Extracts a hosting-operator semantic record from a VC-like payload.
|
|
108
|
+
*
|
|
109
|
+
* @param input VC-like payload or direct semantic object.
|
|
110
|
+
* @returns Hosting-operator semantic record normalized with the common
|
|
111
|
+
* dataspace extraction rules.
|
|
112
|
+
*/
|
|
113
|
+
export declare function extractHostingOperatorSemanticRecord(input: unknown): HostingOperatorSemanticRecord;
|
|
114
|
+
/**
|
|
115
|
+
* Extracts a tenant-service semantic record from a VC-like payload.
|
|
116
|
+
*
|
|
117
|
+
* @param input VC-like payload or direct semantic object.
|
|
118
|
+
* @returns Tenant-service semantic record normalized with the common dataspace
|
|
119
|
+
* extraction rules.
|
|
120
|
+
*/
|
|
121
|
+
export declare function extractTenantServiceSemanticRecord(input: unknown): TenantServiceSemanticRecord;
|
|
122
|
+
/**
|
|
123
|
+
* Returns whether a normalized hosting-operator record satisfies a service
|
|
124
|
+
* autodiscovery filter.
|
|
125
|
+
*/
|
|
126
|
+
export declare function matchesHostingOperatorDiscoveryFilter(record: HostingOperatorSemanticRecord, filter: DataspaceDiscoveryFilter): boolean;
|
|
127
|
+
/**
|
|
128
|
+
* Returns whether a published provider catalog entry satisfies a service
|
|
129
|
+
* autodiscovery filter.
|
|
130
|
+
*/
|
|
131
|
+
export declare function matchesPublishedProviderDiscoveryFilter(record: PublishedProviderCatalogRecord, filter: DataspaceDiscoveryFilter): boolean;
|
|
132
|
+
/**
|
|
133
|
+
* Filters hosting-operator records using the shared service-autodiscovery
|
|
134
|
+
* semantics.
|
|
135
|
+
*/
|
|
136
|
+
export declare function filterHostingOperatorsByDiscoveryFilter(records: ReadonlyArray<HostingOperatorSemanticRecord>, filter: DataspaceDiscoveryFilter): HostingOperatorSemanticRecord[];
|
|
137
|
+
/**
|
|
138
|
+
* Filters published provider entries using the shared service-autodiscovery
|
|
139
|
+
* semantics.
|
|
140
|
+
*/
|
|
141
|
+
export declare function filterPublishedProvidersByDiscoveryFilter(records: ReadonlyArray<PublishedProviderCatalogRecord>, filter: DataspaceDiscoveryFilter): PublishedProviderCatalogRecord[];
|
|
142
|
+
/**
|
|
143
|
+
* Filters a host/operator discovery catalog down to the providers that satisfy
|
|
144
|
+
* the requested service-autodiscovery filter.
|
|
145
|
+
*/
|
|
146
|
+
export declare function filterHostingOperatorDiscoveryCatalog(catalog: HostingOperatorDiscoveryCatalog, filter: DataspaceDiscoveryFilter): HostingOperatorDiscoveryCatalog;
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
// Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
|
+
import { ClaimsOrganizationSchemaorg, ClaimsServiceSchemaorg } from '../constants/schemaorg.js';
|
|
3
|
+
import { isEuCountryCode, normalizeCountryCode } from '../constants/eu-countries.js';
|
|
4
|
+
import { isProviderServiceCapability } from '../constants/service-capabilities.js';
|
|
5
|
+
import { DataspaceCoverageScope, } from '../models/dataspace-discovery.js';
|
|
6
|
+
function asObject(value) {
|
|
7
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
8
|
+
? value
|
|
9
|
+
: undefined;
|
|
10
|
+
}
|
|
11
|
+
function asNonEmptyString(value) {
|
|
12
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
13
|
+
}
|
|
14
|
+
function toStringList(value) {
|
|
15
|
+
if (Array.isArray(value)) {
|
|
16
|
+
return Array.from(new Set(value
|
|
17
|
+
.flatMap((entry) => toStringList(entry))
|
|
18
|
+
.map((entry) => entry.trim())
|
|
19
|
+
.filter(Boolean)));
|
|
20
|
+
}
|
|
21
|
+
const raw = asNonEmptyString(value);
|
|
22
|
+
if (!raw)
|
|
23
|
+
return [];
|
|
24
|
+
return Array.from(new Set(raw
|
|
25
|
+
.split(',')
|
|
26
|
+
.map((entry) => entry.trim())
|
|
27
|
+
.filter(Boolean)));
|
|
28
|
+
}
|
|
29
|
+
function normalizeList(values) {
|
|
30
|
+
return Array.from(new Set(values
|
|
31
|
+
.map((value) => value.trim())
|
|
32
|
+
.filter(Boolean)));
|
|
33
|
+
}
|
|
34
|
+
function sameNormalizedList(left, right) {
|
|
35
|
+
if (left.length !== right.length)
|
|
36
|
+
return false;
|
|
37
|
+
const normalizedLeft = [...normalizeList(left)].sort();
|
|
38
|
+
const normalizedRight = [...normalizeList(right)].sort();
|
|
39
|
+
return normalizedLeft.every((value, index) => value === normalizedRight[index]);
|
|
40
|
+
}
|
|
41
|
+
function parseAreaServedValue(value) {
|
|
42
|
+
if (Array.isArray(value)) {
|
|
43
|
+
return normalizeList(value.flatMap((entry) => parseAreaServedValue(entry)));
|
|
44
|
+
}
|
|
45
|
+
if (typeof value === 'string') {
|
|
46
|
+
return toStringList(value);
|
|
47
|
+
}
|
|
48
|
+
const objectValue = asObject(value);
|
|
49
|
+
if (!objectValue)
|
|
50
|
+
return [];
|
|
51
|
+
return normalizeList([
|
|
52
|
+
asNonEmptyString(objectValue.name),
|
|
53
|
+
asNonEmptyString(objectValue['@id']),
|
|
54
|
+
asNonEmptyString(objectValue.id),
|
|
55
|
+
].filter(Boolean));
|
|
56
|
+
}
|
|
57
|
+
function getSemanticCredentialSubject(input) {
|
|
58
|
+
const objectInput = asObject(input);
|
|
59
|
+
if (!objectInput)
|
|
60
|
+
return undefined;
|
|
61
|
+
const credentialSubject = asObject(objectInput.credentialSubject);
|
|
62
|
+
if (credentialSubject)
|
|
63
|
+
return credentialSubject;
|
|
64
|
+
return objectInput;
|
|
65
|
+
}
|
|
66
|
+
function getFlattenedClaims(input) {
|
|
67
|
+
const objectInput = asObject(input);
|
|
68
|
+
if (!objectInput)
|
|
69
|
+
return undefined;
|
|
70
|
+
const meta = asObject(objectInput.meta);
|
|
71
|
+
const claims = asObject(meta?.claims);
|
|
72
|
+
return claims;
|
|
73
|
+
}
|
|
74
|
+
function getSemanticServiceTypes(subject) {
|
|
75
|
+
return toStringList(subject?.serviceType);
|
|
76
|
+
}
|
|
77
|
+
function getSemanticCategories(subject) {
|
|
78
|
+
return toStringList(subject?.category);
|
|
79
|
+
}
|
|
80
|
+
function getSemanticAreaServed(subject) {
|
|
81
|
+
return parseAreaServedValue(subject?.areaServed);
|
|
82
|
+
}
|
|
83
|
+
function getSemanticAddressCountry(subject) {
|
|
84
|
+
const address = asObject(subject?.address);
|
|
85
|
+
return normalizeCountryCode(asNonEmptyString(address?.addressCountry));
|
|
86
|
+
}
|
|
87
|
+
function getFlattenedServiceTypes(claims) {
|
|
88
|
+
return toStringList(claims?.[ClaimsServiceSchemaorg.serviceType]);
|
|
89
|
+
}
|
|
90
|
+
function getFlattenedCategories(claims) {
|
|
91
|
+
return toStringList(claims?.[ClaimsServiceSchemaorg.category]);
|
|
92
|
+
}
|
|
93
|
+
function getFlattenedAreaServed(claims) {
|
|
94
|
+
return parseAreaServedValue(claims?.[ClaimsServiceSchemaorg.areaServed]);
|
|
95
|
+
}
|
|
96
|
+
function getFlattenedAddressCountry(claims) {
|
|
97
|
+
return normalizeCountryCode(asNonEmptyString(claims?.[ClaimsOrganizationSchemaorg.addressCountry]));
|
|
98
|
+
}
|
|
99
|
+
function assertNoMismatch(kind, semantic, flattened) {
|
|
100
|
+
if (!semantic.length || !flattened.length)
|
|
101
|
+
return;
|
|
102
|
+
if (!sameNormalizedList(semantic, flattened)) {
|
|
103
|
+
throw new Error(`Dataspace discovery mismatch for ${kind}: credentialSubject and meta.claims disagree.`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function assertNoScalarMismatch(kind, semantic, flattened) {
|
|
107
|
+
if (!semantic || !flattened)
|
|
108
|
+
return;
|
|
109
|
+
if (semantic !== flattened) {
|
|
110
|
+
throw new Error(`Dataspace discovery mismatch for ${kind}: credentialSubject and meta.claims disagree.`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Parses the CSV or array representation of `serviceType`.
|
|
115
|
+
*
|
|
116
|
+
* @param value Semantic `credentialSubject.serviceType` or flattened
|
|
117
|
+
* `meta.claims['org.schema.Service.serviceType']`.
|
|
118
|
+
* @returns Normalized unique service capability tokens.
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```ts
|
|
122
|
+
* parseServiceTypeCsv('indexing.cruds,digitaltwin.rs');
|
|
123
|
+
* // ['indexing.cruds', 'digitaltwin.rs']
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
export function parseServiceTypeCsv(value) {
|
|
127
|
+
return toStringList(value);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Parses the Schema.org `category` service dimension used as the dataspace
|
|
131
|
+
* sector vocabulary in the current profile.
|
|
132
|
+
*
|
|
133
|
+
* @param value Semantic `credentialSubject.category` or flattened
|
|
134
|
+
* `meta.claims['org.schema.Service.category']`.
|
|
135
|
+
* @returns Normalized unique sector/category values.
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```ts
|
|
139
|
+
* parseServiceCategories('animal-care,health-care');
|
|
140
|
+
* // ['animal-care', 'health-care']
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
export function parseServiceCategories(value) {
|
|
144
|
+
return toStringList(value);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Parses Schema.org `areaServed` values.
|
|
148
|
+
*
|
|
149
|
+
* Accepts scalar strings, CSV strings, arrays, and simple
|
|
150
|
+
* `AdministrativeArea`-like objects with `name` or `@id`.
|
|
151
|
+
*
|
|
152
|
+
* @param value Semantic `credentialSubject.areaServed` or flattened
|
|
153
|
+
* `meta.claims['org.schema.Service.areaServed']`.
|
|
154
|
+
* @returns Normalized unique coverage values.
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```ts
|
|
158
|
+
* parseAreaServed([{ '@type': 'AdministrativeArea', name: 'EU' }, 'ES']);
|
|
159
|
+
* // ['EU', 'ES']
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
export function parseAreaServed(value) {
|
|
163
|
+
return parseAreaServedValue(value);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Infers a broader coverage scope from an ISO-2 country code.
|
|
167
|
+
*
|
|
168
|
+
* `EU` is returned only as a coverage scope. It must not be treated as a
|
|
169
|
+
* sector.
|
|
170
|
+
*
|
|
171
|
+
* @param countryCode ISO-2 country code, typically from
|
|
172
|
+
* `credentialSubject.address.addressCountry`.
|
|
173
|
+
* @returns `EU` for EU member countries, otherwise the normalized country code.
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* ```ts
|
|
177
|
+
* inferCoverageScopeFromCountryCode('ES');
|
|
178
|
+
* // 'EU'
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
export function inferCoverageScopeFromCountryCode(countryCode) {
|
|
182
|
+
const normalized = normalizeCountryCode(countryCode);
|
|
183
|
+
if (!normalized)
|
|
184
|
+
return undefined;
|
|
185
|
+
return isEuCountryCode(normalized)
|
|
186
|
+
? DataspaceCoverageScope.EuropeanUnion
|
|
187
|
+
: normalized;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Infers a coverage scope from the semantic `credentialSubject`.
|
|
191
|
+
*
|
|
192
|
+
* @param subject Semantic service object containing `address.addressCountry`.
|
|
193
|
+
* @returns Broader coverage scope such as `EU`, or the normalized country code
|
|
194
|
+
* when the country is outside the EU set.
|
|
195
|
+
*/
|
|
196
|
+
export function inferCoverageScopeFromCredentialSubject(subject) {
|
|
197
|
+
const subjectObject = asObject(subject);
|
|
198
|
+
const address = asObject(subjectObject?.address);
|
|
199
|
+
return inferCoverageScopeFromCountryCode(asNonEmptyString(address?.addressCountry));
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Extracts the shared dataspace service semantics from a VC-like payload.
|
|
203
|
+
*
|
|
204
|
+
* Source-of-truth rule:
|
|
205
|
+
* - semantic values come from `credentialSubject` first
|
|
206
|
+
* - flattened `meta.claims` is accepted as a compatibility projection/fallback
|
|
207
|
+
* - when both exist they must agree
|
|
208
|
+
*
|
|
209
|
+
* @param input VC-like payload, direct semantic object, or equivalent DTO.
|
|
210
|
+
* @returns Runtime-neutral normalized dataspace discovery semantics.
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```ts
|
|
214
|
+
* extractDataspaceServiceSemanticRecord({
|
|
215
|
+
* credentialSubject: {
|
|
216
|
+
* id: 'did:web:provider.example.org',
|
|
217
|
+
* serviceType: 'indexing.cruds',
|
|
218
|
+
* category: 'animal-care',
|
|
219
|
+
* areaServed: { '@type': 'AdministrativeArea', name: 'EU' },
|
|
220
|
+
* address: { addressCountry: 'ES' },
|
|
221
|
+
* },
|
|
222
|
+
* meta: {
|
|
223
|
+
* claims: {
|
|
224
|
+
* 'org.schema.Service.serviceType': 'indexing.cruds',
|
|
225
|
+
* 'org.schema.Service.category': 'animal-care',
|
|
226
|
+
* 'org.schema.Service.areaServed': 'EU',
|
|
227
|
+
* 'org.schema.Organization.address.addressCountry': 'ES',
|
|
228
|
+
* },
|
|
229
|
+
* },
|
|
230
|
+
* });
|
|
231
|
+
* ```
|
|
232
|
+
*/
|
|
233
|
+
export function extractDataspaceServiceSemanticRecord(input) {
|
|
234
|
+
const subject = getSemanticCredentialSubject(input);
|
|
235
|
+
const claims = getFlattenedClaims(input);
|
|
236
|
+
const semanticServiceTypes = getSemanticServiceTypes(subject);
|
|
237
|
+
const semanticCategories = getSemanticCategories(subject);
|
|
238
|
+
const semanticAreaServed = getSemanticAreaServed(subject);
|
|
239
|
+
const semanticAddressCountry = getSemanticAddressCountry(subject);
|
|
240
|
+
const flattenedServiceTypes = getFlattenedServiceTypes(claims);
|
|
241
|
+
const flattenedCategories = getFlattenedCategories(claims);
|
|
242
|
+
const flattenedAreaServed = getFlattenedAreaServed(claims);
|
|
243
|
+
const flattenedAddressCountry = getFlattenedAddressCountry(claims);
|
|
244
|
+
assertNoMismatch('serviceType', semanticServiceTypes, flattenedServiceTypes);
|
|
245
|
+
assertNoMismatch('category', semanticCategories, flattenedCategories);
|
|
246
|
+
assertNoMismatch('areaServed', semanticAreaServed, flattenedAreaServed);
|
|
247
|
+
assertNoScalarMismatch('address.addressCountry', semanticAddressCountry, flattenedAddressCountry);
|
|
248
|
+
const serviceTypes = semanticServiceTypes.length ? semanticServiceTypes : flattenedServiceTypes;
|
|
249
|
+
const categories = semanticCategories.length ? semanticCategories : flattenedCategories;
|
|
250
|
+
const areaServed = semanticAreaServed.length ? semanticAreaServed : flattenedAreaServed;
|
|
251
|
+
const addressCountry = semanticAddressCountry || flattenedAddressCountry || undefined;
|
|
252
|
+
return {
|
|
253
|
+
subjectId: asNonEmptyString(subject?.id) || undefined,
|
|
254
|
+
serviceTypes,
|
|
255
|
+
categories,
|
|
256
|
+
areaServed,
|
|
257
|
+
addressCountry,
|
|
258
|
+
coverageScope: inferCoverageScopeFromCountryCode(addressCountry),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Extracts a hosting-operator semantic record from a VC-like payload.
|
|
263
|
+
*
|
|
264
|
+
* @param input VC-like payload or direct semantic object.
|
|
265
|
+
* @returns Hosting-operator semantic record normalized with the common
|
|
266
|
+
* dataspace extraction rules.
|
|
267
|
+
*/
|
|
268
|
+
export function extractHostingOperatorSemanticRecord(input) {
|
|
269
|
+
return extractDataspaceServiceSemanticRecord(input);
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Extracts a tenant-service semantic record from a VC-like payload.
|
|
273
|
+
*
|
|
274
|
+
* @param input VC-like payload or direct semantic object.
|
|
275
|
+
* @returns Tenant-service semantic record normalized with the common dataspace
|
|
276
|
+
* extraction rules.
|
|
277
|
+
*/
|
|
278
|
+
export function extractTenantServiceSemanticRecord(input) {
|
|
279
|
+
return extractDataspaceServiceSemanticRecord(input);
|
|
280
|
+
}
|
|
281
|
+
function matchesCoverageFilter(areaServed, jurisdiction, coverageScope) {
|
|
282
|
+
const normalizedAreaServed = normalizeList([...(areaServed || [])]);
|
|
283
|
+
if (jurisdiction && !normalizedAreaServed.includes(jurisdiction))
|
|
284
|
+
return false;
|
|
285
|
+
if (coverageScope && !normalizedAreaServed.includes(coverageScope))
|
|
286
|
+
return false;
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Returns whether a normalized hosting-operator record satisfies a service
|
|
291
|
+
* autodiscovery filter.
|
|
292
|
+
*/
|
|
293
|
+
export function matchesHostingOperatorDiscoveryFilter(record, filter) {
|
|
294
|
+
if (!record.categories.includes(filter.sector))
|
|
295
|
+
return false;
|
|
296
|
+
if (!matchesCoverageFilter(record.areaServed, filter.jurisdiction, filter.coverageScope))
|
|
297
|
+
return false;
|
|
298
|
+
if (filter.capability && !record.serviceTypes.includes(filter.capability))
|
|
299
|
+
return false;
|
|
300
|
+
if (filter.requiredCapabilities?.length) {
|
|
301
|
+
return filter.requiredCapabilities.every((capability) => record.serviceTypes.includes(capability));
|
|
302
|
+
}
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Returns whether a published provider catalog entry satisfies a service
|
|
307
|
+
* autodiscovery filter.
|
|
308
|
+
*/
|
|
309
|
+
export function matchesPublishedProviderDiscoveryFilter(record, filter) {
|
|
310
|
+
if (!isProviderServiceCapability(record.serviceType))
|
|
311
|
+
return false;
|
|
312
|
+
if (record.category !== filter.sector)
|
|
313
|
+
return false;
|
|
314
|
+
if (filter.capability && record.serviceType !== filter.capability)
|
|
315
|
+
return false;
|
|
316
|
+
return matchesCoverageFilter(record.areaServed ? [record.areaServed] : [], filter.jurisdiction, filter.coverageScope);
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Filters hosting-operator records using the shared service-autodiscovery
|
|
320
|
+
* semantics.
|
|
321
|
+
*/
|
|
322
|
+
export function filterHostingOperatorsByDiscoveryFilter(records, filter) {
|
|
323
|
+
return records.filter((record) => matchesHostingOperatorDiscoveryFilter(record, filter));
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Filters published provider entries using the shared service-autodiscovery
|
|
327
|
+
* semantics.
|
|
328
|
+
*/
|
|
329
|
+
export function filterPublishedProvidersByDiscoveryFilter(records, filter) {
|
|
330
|
+
return records.filter((record) => matchesPublishedProviderDiscoveryFilter(record, filter));
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Filters a host/operator discovery catalog down to the providers that satisfy
|
|
334
|
+
* the requested service-autodiscovery filter.
|
|
335
|
+
*/
|
|
336
|
+
export function filterHostingOperatorDiscoveryCatalog(catalog, filter) {
|
|
337
|
+
return {
|
|
338
|
+
...catalog,
|
|
339
|
+
providers: filterPublishedProvidersByDiscoveryFilter(catalog.providers, filter),
|
|
340
|
+
};
|
|
341
|
+
}
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export * from './content';
|
|
|
7
7
|
export * from './consent';
|
|
8
8
|
export * from './did';
|
|
9
9
|
export * from './did-resolution';
|
|
10
|
+
export * from './dataspace-discovery';
|
|
10
11
|
export * from './didcomm';
|
|
11
12
|
export * from './didcomm-submit';
|
|
12
13
|
export * from './didcomm-submit-policy';
|
package/dist/utils/index.js
CHANGED
|
@@ -7,6 +7,7 @@ export * from './content.js';
|
|
|
7
7
|
export * from './consent.js';
|
|
8
8
|
export * from './did.js';
|
|
9
9
|
export * from './did-resolution.js';
|
|
10
|
+
export * from './dataspace-discovery.js';
|
|
10
11
|
export * from './didcomm.js';
|
|
11
12
|
export * from './didcomm-submit.js';
|
|
12
13
|
export * from './didcomm-submit-policy.js';
|