gdc-common-utils-ts 1.12.0 → 1.13.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 CHANGED
@@ -92,6 +92,54 @@ import { JweObject, JwtCompactParts } from 'gdc-common-utils-ts/models';
92
92
  - [docs/consent-access-matrix-task.md](docs/consent-access-matrix-task.md)
93
93
  - next-step design/task document for active consent aggregation, explicit deny precedence, controller views, permission-request communications, and SMART access evaluation
94
94
 
95
+ ## Dataspace Protocol And Discovery
96
+
97
+ Use `gdc-common-utils-ts` as the shared source of truth for DSP route building,
98
+ `dspace-version` metadata, and normalized discovery DTOs.
99
+
100
+ Main entry points:
101
+
102
+ - [`src/utils/dataspace-protocol.ts`](src/utils/dataspace-protocol.ts)
103
+ - canonical GW CORE path builders for host-scoped and tenant-scoped DSP
104
+ routes
105
+ - [`src/utils/dataspace-discovery.ts`](src/utils/dataspace-discovery.ts)
106
+ - semantic extraction, provider filtering, default DTO builders, and the
107
+ copy/paste fetcher harness used by docs/tests
108
+ - [`src/examples/dataspace-discovery.ts`](src/examples/dataspace-discovery.ts)
109
+ - synthetic provider/operator examples that distinguish discovery URL from
110
+ derived catalog artifact URL
111
+ - [`__tests__/dataspace-protocol.test.ts`](__tests__/dataspace-protocol.test.ts)
112
+ - executable path and `dspace-version` examples
113
+ - [`__tests__/dataspace-discovery.test.ts`](__tests__/dataspace-discovery.test.ts)
114
+ - executable semantic extraction and filtering examples
115
+
116
+ Copy/paste example:
117
+
118
+ ```ts
119
+ import {
120
+ buildDspaceVersionMetadata,
121
+ buildGwCatalogArtifactPath,
122
+ buildGwDspaceVersionWellKnownPath,
123
+ deriveGwCatalogArtifactUrlFromDspaceVersion,
124
+ } from 'gdc-common-utils-ts/utils/dataspace-protocol';
125
+ import { HostNetworkTypes } from 'gdc-common-utils-ts/constants/network';
126
+
127
+ const hostContext = {
128
+ participantId: 'host',
129
+ jurisdiction: 'ES',
130
+ version: 'v1',
131
+ hostNetwork: HostNetworkTypes.Test,
132
+ };
133
+
134
+ const discoveryPath = buildGwDspaceVersionWellKnownPath(hostContext);
135
+ const metadata = buildDspaceVersionMetadata('/host/cds-ES/v1/test/dsp');
136
+ const catalogPath = buildGwCatalogArtifactPath(hostContext);
137
+ const catalogUrl = deriveGwCatalogArtifactUrlFromDspaceVersion(
138
+ `https://host.example.org${discoveryPath}`,
139
+ metadata,
140
+ );
141
+ ```
142
+
95
143
  ## API Index
96
144
 
97
145
  The canonical API contract should live in JSDoc on exported code. The README acts as a navigable index.
@@ -116,8 +164,8 @@ The canonical API contract should live in JSDoc on exported code. The README act
116
164
  - Reusable professional role/permission examples tying actor role, consent action, SMART scope, and expected FHIR resource types together.
117
165
  - [`DeviceUserClasses`, `DeviceAppTypes`](src/constants/device.ts)
118
166
  - Shared user-class and app/device-type constants used by licensing and SDK flows.
119
- - [`NodeOperatorNetworkTypes`](src/constants/network.ts)
120
- - Shared network/environment labels for node-operator discovery/bootstrap.
167
+ - [`HostNetworkTypes`](src/constants/network.ts)
168
+ - Shared network/environment labels for host discovery/bootstrap.
121
169
  - [`SmartGatewayScopesFhirR4`](src/constants/smart.ts)
122
170
  - Current CORE GW SMART scope literals such as `organization/Consent.cruds`.
123
171
  - Treat these as optional elevated scopes. Do not add them to the first read-only tutorial by default.
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Canonical Dataspace Protocol version identifiers used by the current GW/SDK
3
+ * discovery flows.
4
+ */
5
+ export declare const DataspaceProtocolVersions: Readonly<{
6
+ readonly Current: "2025-1";
7
+ }>;
8
+ /**
9
+ * Canonical well-known paths used by Dataspace Protocol discovery.
10
+ */
11
+ export declare const DataspaceWellKnownPaths: Readonly<{
12
+ readonly VersionMetadata: "/.well-known/dspace-version";
13
+ }>;
14
+ /**
15
+ * Project-local GW CORE DSP binding paths.
16
+ *
17
+ * These preserve the current internal API structure while still ending in the
18
+ * DSP catalog route shapes.
19
+ */
20
+ export declare const GwDataspaceBindingPaths: Readonly<{
21
+ readonly Base: "/dsp";
22
+ readonly CatalogCollectionSuffix: "/catalog";
23
+ readonly CatalogRequestSuffix: "/catalog/request";
24
+ readonly CatalogArtifactSuffix: "/catalog/dcat.json";
25
+ readonly CatalogDatasetsPrefix: "/catalog/datasets";
26
+ }>;
27
+ export type DataspaceProtocolVersionValue = typeof DataspaceProtocolVersions[keyof typeof DataspaceProtocolVersions];
@@ -0,0 +1,27 @@
1
+ // Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
2
+ /**
3
+ * Canonical Dataspace Protocol version identifiers used by the current GW/SDK
4
+ * discovery flows.
5
+ */
6
+ export const DataspaceProtocolVersions = Object.freeze({
7
+ Current: '2025-1',
8
+ });
9
+ /**
10
+ * Canonical well-known paths used by Dataspace Protocol discovery.
11
+ */
12
+ export const DataspaceWellKnownPaths = Object.freeze({
13
+ VersionMetadata: '/.well-known/dspace-version',
14
+ });
15
+ /**
16
+ * Project-local GW CORE DSP binding paths.
17
+ *
18
+ * These preserve the current internal API structure while still ending in the
19
+ * DSP catalog route shapes.
20
+ */
21
+ export const GwDataspaceBindingPaths = Object.freeze({
22
+ Base: '/dsp',
23
+ CatalogCollectionSuffix: '/catalog',
24
+ CatalogRequestSuffix: '/catalog/request',
25
+ CatalogArtifactSuffix: '/catalog/dcat.json',
26
+ CatalogDatasetsPrefix: '/catalog/datasets',
27
+ });
@@ -1,6 +1,7 @@
1
1
  export * from './actor-session';
2
2
  export * from './communication';
3
3
  export * from './cryptography';
4
+ export * from './dataspace-protocol';
4
5
  export * from './device';
5
6
  export * from './did-services';
6
7
  export * from './eu-countries';
@@ -1,6 +1,7 @@
1
1
  export * from './actor-session.js';
2
2
  export * from './communication.js';
3
3
  export * from './cryptography.js';
4
+ export * from './dataspace-protocol.js';
4
5
  export * from './device.js';
5
6
  export * from './did-services.js';
6
7
  export * from './eu-countries.js';
@@ -1,13 +1,25 @@
1
1
  /**
2
- * Canonical network/environment labels used to identify node-operator networks
3
- * during discovery/bootstrap flows.
2
+ * Canonical network/environment labels used to identify host networks during
3
+ * discovery/bootstrap flows.
4
4
  *
5
- * These labels do not replace the clinical route `sector`. They describe the
6
- * operator environment itself.
5
+ * These labels do not replace the business route `sector`. They describe the
6
+ * host environment itself.
7
+ */
8
+ export declare const HostNetworkTypes: Readonly<{
9
+ readonly Test: "test";
10
+ readonly TestNetwork: "test-network";
11
+ readonly Network: "network";
12
+ }>;
13
+ export type HostNetworkType = typeof HostNetworkTypes[keyof typeof HostNetworkTypes];
14
+ /**
15
+ * @deprecated Use `HostNetworkTypes`.
7
16
  */
8
17
  export declare const NodeOperatorNetworkTypes: Readonly<{
9
18
  readonly Test: "test";
10
19
  readonly TestNetwork: "test-network";
11
20
  readonly Network: "network";
12
21
  }>;
13
- export type NodeOperatorNetworkType = typeof NodeOperatorNetworkTypes[keyof typeof NodeOperatorNetworkTypes];
22
+ /**
23
+ * @deprecated Use `HostNetworkType`.
24
+ */
25
+ export type NodeOperatorNetworkType = HostNetworkType;
@@ -1,13 +1,17 @@
1
1
  // Copyright 2025 Antifraud Services Inc. under the Apache License, Version 2.0.
2
2
  /**
3
- * Canonical network/environment labels used to identify node-operator networks
4
- * during discovery/bootstrap flows.
3
+ * Canonical network/environment labels used to identify host networks during
4
+ * discovery/bootstrap flows.
5
5
  *
6
- * These labels do not replace the clinical route `sector`. They describe the
7
- * operator environment itself.
6
+ * These labels do not replace the business route `sector`. They describe the
7
+ * host environment itself.
8
8
  */
9
- export const NodeOperatorNetworkTypes = Object.freeze({
9
+ export const HostNetworkTypes = Object.freeze({
10
10
  Test: 'test',
11
11
  TestNetwork: 'test-network',
12
12
  Network: 'network',
13
13
  });
14
+ /**
15
+ * @deprecated Use `HostNetworkTypes`.
16
+ */
17
+ export const NodeOperatorNetworkTypes = HostNetworkTypes;
@@ -75,6 +75,11 @@ export declare function buildExampleTenantServiceMetaClaims(input?: ExampleDatas
75
75
  * Builds a synthetic published-provider record as it would appear in a host
76
76
  * service-autodiscovery catalog.
77
77
  *
78
+ * URL rule:
79
+ * - `discoveryUrl` is the participant-scoped `/.well-known/dspace-version`
80
+ * entrypoint
81
+ * - `catalogUrl` is the derived `/dsp/catalog/dcat.json` artifact
82
+ *
78
83
  * @param input Optional overrides for the synthetic provider publication.
79
84
  * @returns Shared host-catalog provider entry.
80
85
  */
@@ -82,6 +87,11 @@ export declare function buildExamplePublishedProviderCatalogRecord(input?: Examp
82
87
  /**
83
88
  * Builds a synthetic host/operator service-autodiscovery catalog.
84
89
  *
90
+ * URL rule:
91
+ * - `discoveryUrl` is the canonical entrypoint clients should fetch first
92
+ * - `catalogUrl` is the read-only DSP artifact derived from the advertised
93
+ * base path
94
+ *
85
95
  * @param providers Optional published providers to include.
86
96
  * @returns Shared catalog DTO for host-side public service autodiscovery.
87
97
  */
@@ -1,7 +1,7 @@
1
1
  // Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
2
2
  import { ClaimsOrganizationSchemaorg, ClaimsServiceSchemaorg } from '../constants/schemaorg.js';
3
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';
4
+ import { EXAMPLE_HOSTING_OPERATOR_CATALOG_ARTIFACT_URL, EXAMPLE_HOSTING_OPERATOR_DSPACE_VERSION_URL, EXAMPLE_HOSTING_OPERATOR_DID, EXAMPLE_PROVIDER_PUBLISHED_ENDPOINT_URL, EXAMPLE_JURISDICTION, EXAMPLE_SECTOR, EXAMPLE_TENANT_SERVICE_DID, } from './shared.js';
5
5
  function firstOrCsv(values) {
6
6
  return values.length <= 1 ? (values[0] || '') : values.join(',');
7
7
  }
@@ -98,6 +98,11 @@ export function buildExampleTenantServiceMetaClaims(input = {}) {
98
98
  * Builds a synthetic published-provider record as it would appear in a host
99
99
  * service-autodiscovery catalog.
100
100
  *
101
+ * URL rule:
102
+ * - `discoveryUrl` is the participant-scoped `/.well-known/dspace-version`
103
+ * entrypoint
104
+ * - `catalogUrl` is the derived `/dsp/catalog/dcat.json` artifact
105
+ *
101
106
  * @param input Optional overrides for the synthetic provider publication.
102
107
  * @returns Shared host-catalog provider entry.
103
108
  */
@@ -109,21 +114,28 @@ export function buildExamplePublishedProviderCatalogRecord(input = {}) {
109
114
  providerDid: input.did || EXAMPLE_TENANT_SERVICE_DID,
110
115
  serviceType: serviceTypes[0] || ServiceCapabilityToken.IndexProvider,
111
116
  category: categories[0] || EXAMPLE_SECTOR,
112
- areaServed: areaServed[0] || 'EU',
117
+ areaServed: firstOrCsv(areaServed) || 'EU',
113
118
  endpointUrl: EXAMPLE_PROVIDER_PUBLISHED_ENDPOINT_URL,
114
- catalogUrl: EXAMPLE_HOSTING_OPERATOR_CATALOG_URL,
119
+ discoveryUrl: EXAMPLE_HOSTING_OPERATOR_DSPACE_VERSION_URL,
120
+ catalogUrl: EXAMPLE_HOSTING_OPERATOR_CATALOG_ARTIFACT_URL,
115
121
  };
116
122
  }
117
123
  /**
118
124
  * Builds a synthetic host/operator service-autodiscovery catalog.
119
125
  *
126
+ * URL rule:
127
+ * - `discoveryUrl` is the canonical entrypoint clients should fetch first
128
+ * - `catalogUrl` is the read-only DSP artifact derived from the advertised
129
+ * base path
130
+ *
120
131
  * @param providers Optional published providers to include.
121
132
  * @returns Shared catalog DTO for host-side public service autodiscovery.
122
133
  */
123
134
  export function buildExampleHostingOperatorDiscoveryCatalog(providers = [buildExamplePublishedProviderCatalogRecord()]) {
124
135
  return {
125
136
  hostingOperatorDid: EXAMPLE_HOSTING_OPERATOR_DID,
126
- catalogUrl: EXAMPLE_HOSTING_OPERATOR_CATALOG_URL,
137
+ discoveryUrl: EXAMPLE_HOSTING_OPERATOR_DSPACE_VERSION_URL,
138
+ catalogUrl: EXAMPLE_HOSTING_OPERATOR_CATALOG_ARTIFACT_URL,
127
139
  providers: [...providers],
128
140
  };
129
141
  }
@@ -20,7 +20,7 @@ export declare const EXAMPLE_TENANT_ROUTE_CONTEXT: {
20
20
  };
21
21
  export declare const EXAMPLE_HOST_ROUTE_CONTEXT: {
22
22
  readonly jurisdiction: "ES";
23
- readonly sector: "health-care";
23
+ readonly sector: "test";
24
24
  };
25
25
  export declare const EXAMPLE_CONTROLLER_DID: "did:web:people.acme.org:controllers:primary";
26
26
  export declare const EXAMPLE_CONTROLLER_EMAIL: "controller@acme.org";
@@ -36,7 +36,10 @@ export declare const EXAMPLE_HOST_PUBLIC_HOSTNAME: "host.example.com";
36
36
  export declare const EXAMPLE_HOSTING_OPERATOR_DID: "did:web:host.example.org";
37
37
  export declare const EXAMPLE_TENANT_SERVICE_DID: "did:web:provider.example.org";
38
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";
39
+ export declare const EXAMPLE_HOSTING_OPERATOR_DSPACE_VERSION_URL: "https://host.example.org/host/cds-ES/v1/test/.well-known/dspace-version";
40
+ export declare const EXAMPLE_HOSTING_OPERATOR_CATALOG_ARTIFACT_URL: "https://host.example.org/host/cds-ES/v1/test/dsp/catalog/dcat.json";
41
+ /** @deprecated Use `EXAMPLE_HOSTING_OPERATOR_DSPACE_VERSION_URL`. */
42
+ export declare const EXAMPLE_HOSTING_OPERATOR_CATALOG_URL: "https://host.example.org/host/cds-ES/v1/test/.well-known/dspace-version";
40
43
  export declare const EXAMPLE_PROVIDER_PUBLISHED_ENDPOINT_URL: "https://host.example.org/catalog/provider-a";
41
44
  export declare const EXAMPLE_PROVIDER_LEGAL_NAME: "ACME Health Provider";
42
45
  export declare const EXAMPLE_SECONDARY_PROVIDER_LEGAL_NAME: "Reader Only Provider";
@@ -1,6 +1,7 @@
1
1
  // Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
2
2
  // Always create JSDoc, do not use strings inline in keys nor values, use types instead, and reuse the data test examples.
3
3
  import { DataspaceSectors } from '../constants/sectors.js';
4
+ import { HostNetworkTypes } from '../constants/network.js';
4
5
  import { HealthcareActorRoles, HealthcareBasicSections, HealthcareConsentPurposes, } from '../constants/healthcare.js';
5
6
  import { CommunicationClaim } from '../models/interoperable-claims/communication-claims.js';
6
7
  /**
@@ -25,7 +26,7 @@ export const EXAMPLE_TENANT_ROUTE_CONTEXT = {
25
26
  };
26
27
  export const EXAMPLE_HOST_ROUTE_CONTEXT = {
27
28
  jurisdiction: EXAMPLE_JURISDICTION,
28
- sector: EXAMPLE_SECTOR,
29
+ sector: HostNetworkTypes.Test,
29
30
  };
30
31
  export const EXAMPLE_CONTROLLER_DID = 'did:web:people.acme.org:controllers:primary';
31
32
  export const EXAMPLE_CONTROLLER_EMAIL = EXAMPLE_EMAIL_CONTROLLER_ORG;
@@ -41,7 +42,10 @@ export const EXAMPLE_HOST_PUBLIC_HOSTNAME = 'host.example.com';
41
42
  export const EXAMPLE_HOSTING_OPERATOR_DID = 'did:web:host.example.org';
42
43
  export const EXAMPLE_TENANT_SERVICE_DID = 'did:web:provider.example.org';
43
44
  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_HOSTING_OPERATOR_DSPACE_VERSION_URL = `https://host.example.org/host/cds-ES/v1/${HostNetworkTypes.Test}/.well-known/dspace-version`;
46
+ export const EXAMPLE_HOSTING_OPERATOR_CATALOG_ARTIFACT_URL = `https://host.example.org/host/cds-ES/v1/${HostNetworkTypes.Test}/dsp/catalog/dcat.json`;
47
+ /** @deprecated Use `EXAMPLE_HOSTING_OPERATOR_DSPACE_VERSION_URL`. */
48
+ export const EXAMPLE_HOSTING_OPERATOR_CATALOG_URL = EXAMPLE_HOSTING_OPERATOR_DSPACE_VERSION_URL;
45
49
  export const EXAMPLE_PROVIDER_PUBLISHED_ENDPOINT_URL = 'https://host.example.org/catalog/provider-a';
46
50
  export const EXAMPLE_PROVIDER_LEGAL_NAME = 'ACME Health Provider';
47
51
  export const EXAMPLE_SECONDARY_PROVIDER_LEGAL_NAME = 'Reader Only Provider';
@@ -38,6 +38,7 @@ export type PublishedProviderCatalogRecord = Readonly<{
38
38
  category: string;
39
39
  areaServed?: string;
40
40
  endpointUrl?: string;
41
+ discoveryUrl?: string;
41
42
  catalogUrl?: string;
42
43
  }>;
43
44
  /**
@@ -61,6 +62,7 @@ export type DataspaceDiscoveryFilter = Readonly<{
61
62
  */
62
63
  export type HostingOperatorDiscoveryCatalog = Readonly<{
63
64
  hostingOperatorDid?: string;
65
+ discoveryUrl?: string;
64
66
  catalogUrl?: string;
65
67
  providers: PublishedProviderCatalogRecord[];
66
68
  }>;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * One advertised Dataspace Protocol version entry from `/.well-known/dspace-version`.
3
+ */
4
+ export type DspaceProtocolVersionEntry = Readonly<{
5
+ version: string;
6
+ path: string;
7
+ }>;
8
+ /**
9
+ * Minimal version metadata payload returned by the DSP well-known discovery
10
+ * endpoint.
11
+ */
12
+ export type DspaceVersionMetadata = Readonly<{
13
+ protocolVersions: readonly DspaceProtocolVersionEntry[];
14
+ }>;
@@ -0,0 +1,2 @@
1
+ // Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
2
+ export {};
@@ -13,6 +13,7 @@ export * from './consent-rule';
13
13
  export * from './consent-access';
14
14
  export * from './crypto';
15
15
  export * from './dataspace-discovery';
16
+ export * from './dataspace-protocol';
16
17
  export * from './device-license';
17
18
  export * from './did';
18
19
  export * from './fhir-documents';
@@ -13,6 +13,7 @@ export * from './consent-rule.js';
13
13
  export * from './consent-access.js';
14
14
  export * from './crypto.js';
15
15
  export * from './dataspace-discovery.js';
16
+ export * from './dataspace-protocol.js';
16
17
  export * from './device-license.js';
17
18
  export * from './did.js';
18
19
  export * from './fhir-documents.js';
@@ -1,4 +1,46 @@
1
1
  import { type DataspaceDiscoveryFilter, type DataspaceServiceSemanticRecord, type HostingOperatorSemanticRecord, type HostingOperatorDiscoveryCatalog, type PublishedProviderCatalogRecord, type TenantServiceSemanticRecord } from '../models/dataspace-discovery';
2
+ export type DiscoveryCatalogFetchResponse = Readonly<{
3
+ ok: boolean;
4
+ status: number;
5
+ json(): Promise<unknown>;
6
+ text(): Promise<string>;
7
+ }>;
8
+ export declare const DiscoveryCatalogSource: Readonly<{
9
+ readonly Internet: "internet";
10
+ readonly Cache: "cache";
11
+ readonly Default: "default";
12
+ }>;
13
+ export type DiscoveryCatalogSourceValue = typeof DiscoveryCatalogSource[keyof typeof DiscoveryCatalogSource];
14
+ export type DiscoveryCatalogFetcherOptions = Readonly<{
15
+ internetCatalogs?: Record<string, HostingOperatorDiscoveryCatalog>;
16
+ internetJsonByUrl?: Record<string, unknown>;
17
+ defaultCatalogs?: Record<string, HostingOperatorDiscoveryCatalog>;
18
+ }>;
19
+ export type DiscoveryCatalogFetcherHarness = Readonly<{
20
+ fetcher(input: string, init?: unknown): Promise<DiscoveryCatalogFetchResponse>;
21
+ calls: string[];
22
+ sources: Map<string, DiscoveryCatalogSourceValue>;
23
+ cache: Map<string, HostingOperatorDiscoveryCatalog>;
24
+ setInternetCatalog(url: string, catalog: HostingOperatorDiscoveryCatalog): void;
25
+ setInternetJson(url: string, payload: unknown): void;
26
+ setInternetFailure(url: string, status?: number, body?: unknown): void;
27
+ clearInternetRoute(url: string): void;
28
+ }>;
29
+ export type DefaultPublishedProviderCatalogRecordInput = Readonly<{
30
+ providerDid: string;
31
+ serviceType: string;
32
+ category: string;
33
+ areaServed?: string | readonly string[];
34
+ endpointUrl?: string;
35
+ discoveryUrl?: string;
36
+ catalogUrl?: string;
37
+ }>;
38
+ export type DefaultHostingOperatorDiscoveryCatalogInput = Readonly<{
39
+ hostingOperatorDid?: string;
40
+ discoveryUrl?: string;
41
+ catalogUrl?: string;
42
+ providers?: ReadonlyArray<PublishedProviderCatalogRecord>;
43
+ }>;
2
44
  /**
3
45
  * Parses the CSV or array representation of `serviceType`.
4
46
  *
@@ -144,3 +186,50 @@ export declare function filterPublishedProvidersByDiscoveryFilter(records: Reado
144
186
  * the requested service-autodiscovery filter.
145
187
  */
146
188
  export declare function filterHostingOperatorDiscoveryCatalog(catalog: HostingOperatorDiscoveryCatalog, filter: DataspaceDiscoveryFilter): HostingOperatorDiscoveryCatalog;
189
+ /**
190
+ * Builds a normalized published-provider catalog DTO suitable for:
191
+ *
192
+ * - backend-owned fallback catalogs
193
+ * - default discovery data loaded from configuration
194
+ * - tests that should use the same DTO constructors as production code
195
+ *
196
+ * This helper is intentionally neutral:
197
+ * - no example DIDs
198
+ * - no example URLs
199
+ * - no hidden business defaults
200
+ *
201
+ * @param input Required provider catalog fields plus optional URLs.
202
+ * @returns Normalized published-provider catalog record.
203
+ */
204
+ export declare function buildDefaultPublishedProviderCatalogRecord(input: DefaultPublishedProviderCatalogRecordInput): PublishedProviderCatalogRecord;
205
+ /**
206
+ * Builds a normalized hosting-operator discovery catalog DTO suitable for:
207
+ *
208
+ * - backend-owned fallback catalogs
209
+ * - default host catalogs assembled from configuration
210
+ * - tests that should reuse the same constructor as production code
211
+ *
212
+ * @param input Optional host identity fields plus the provider list.
213
+ * @returns Normalized host discovery catalog.
214
+ */
215
+ export declare function buildDefaultHostingOperatorDiscoveryCatalog(input?: DefaultHostingOperatorDiscoveryCatalogInput): HostingOperatorDiscoveryCatalog;
216
+ /**
217
+ * Creates a generic host-catalog fetcher with cache and default fallback.
218
+ *
219
+ * Intended use:
220
+ * - docs
221
+ * - SDK integration examples
222
+ * - tests that need to demonstrate the transport boundary around discovery
223
+ *
224
+ * Behavior:
225
+ * - successful network catalog refreshes cache
226
+ * - later network failures reuse cached catalog when available
227
+ * - when both network and cache are unavailable, configured defaults are used
228
+ *
229
+ * Production integrations may follow the same policy while replacing this
230
+ * in-memory harness with real logging, metrics, and storage.
231
+ *
232
+ * @param options Optional initial network and default catalogs keyed by URL.
233
+ * @returns Fetcher plus observable state and mutation helpers.
234
+ */
235
+ export declare function createDiscoveryCatalogFetcher(options?: DiscoveryCatalogFetcherOptions): DiscoveryCatalogFetcherHarness;
@@ -11,6 +11,19 @@ function asObject(value) {
11
11
  function asNonEmptyString(value) {
12
12
  return typeof value === 'string' ? value.trim() : '';
13
13
  }
14
+ export const DiscoveryCatalogSource = Object.freeze({
15
+ Internet: 'internet',
16
+ Cache: 'cache',
17
+ Default: 'default',
18
+ });
19
+ function createDiscoveryCatalogFetchResponse(payload, init = {}) {
20
+ return {
21
+ ok: init.ok ?? true,
22
+ status: init.status ?? 200,
23
+ json: async () => payload,
24
+ text: async () => JSON.stringify(payload),
25
+ };
26
+ }
14
27
  function toStringList(value) {
15
28
  if (Array.isArray(value)) {
16
29
  return Array.from(new Set(value
@@ -339,3 +352,131 @@ export function filterHostingOperatorDiscoveryCatalog(catalog, filter) {
339
352
  providers: filterPublishedProvidersByDiscoveryFilter(catalog.providers, filter),
340
353
  };
341
354
  }
355
+ /**
356
+ * Builds a normalized published-provider catalog DTO suitable for:
357
+ *
358
+ * - backend-owned fallback catalogs
359
+ * - default discovery data loaded from configuration
360
+ * - tests that should use the same DTO constructors as production code
361
+ *
362
+ * This helper is intentionally neutral:
363
+ * - no example DIDs
364
+ * - no example URLs
365
+ * - no hidden business defaults
366
+ *
367
+ * @param input Required provider catalog fields plus optional URLs.
368
+ * @returns Normalized published-provider catalog record.
369
+ */
370
+ export function buildDefaultPublishedProviderCatalogRecord(input) {
371
+ const normalizedAreaServed = Array.isArray(input.areaServed)
372
+ ? normalizeList(input.areaServed.map((value) => asNonEmptyString(value)).filter(Boolean))
373
+ : toStringList(input.areaServed);
374
+ return {
375
+ providerDid: asNonEmptyString(input.providerDid),
376
+ serviceType: asNonEmptyString(input.serviceType),
377
+ category: asNonEmptyString(input.category),
378
+ areaServed: normalizedAreaServed.length ? normalizedAreaServed.join(',') : undefined,
379
+ endpointUrl: asNonEmptyString(input.endpointUrl) || undefined,
380
+ discoveryUrl: asNonEmptyString(input.discoveryUrl) || undefined,
381
+ catalogUrl: asNonEmptyString(input.catalogUrl) || undefined,
382
+ };
383
+ }
384
+ /**
385
+ * Builds a normalized hosting-operator discovery catalog DTO suitable for:
386
+ *
387
+ * - backend-owned fallback catalogs
388
+ * - default host catalogs assembled from configuration
389
+ * - tests that should reuse the same constructor as production code
390
+ *
391
+ * @param input Optional host identity fields plus the provider list.
392
+ * @returns Normalized host discovery catalog.
393
+ */
394
+ export function buildDefaultHostingOperatorDiscoveryCatalog(input = {}) {
395
+ return {
396
+ hostingOperatorDid: asNonEmptyString(input.hostingOperatorDid) || undefined,
397
+ discoveryUrl: asNonEmptyString(input.discoveryUrl) || undefined,
398
+ catalogUrl: asNonEmptyString(input.catalogUrl) || undefined,
399
+ providers: [...(input.providers || [])],
400
+ };
401
+ }
402
+ function isHostingOperatorCatalogPayload(value) {
403
+ const objectValue = asObject(value);
404
+ return Boolean(objectValue && Array.isArray(objectValue.providers));
405
+ }
406
+ /**
407
+ * Creates a generic host-catalog fetcher with cache and default fallback.
408
+ *
409
+ * Intended use:
410
+ * - docs
411
+ * - SDK integration examples
412
+ * - tests that need to demonstrate the transport boundary around discovery
413
+ *
414
+ * Behavior:
415
+ * - successful network catalog refreshes cache
416
+ * - later network failures reuse cached catalog when available
417
+ * - when both network and cache are unavailable, configured defaults are used
418
+ *
419
+ * Production integrations may follow the same policy while replacing this
420
+ * in-memory harness with real logging, metrics, and storage.
421
+ *
422
+ * @param options Optional initial network and default catalogs keyed by URL.
423
+ * @returns Fetcher plus observable state and mutation helpers.
424
+ */
425
+ export function createDiscoveryCatalogFetcher(options = {}) {
426
+ const internetResponses = new Map([
427
+ ...Object.entries(options.internetCatalogs || {}).map(([url, catalog]) => [url, createDiscoveryCatalogFetchResponse(catalog, { ok: true, status: 200 })]),
428
+ ...Object.entries(options.internetJsonByUrl || {}).map(([url, payload]) => [url, createDiscoveryCatalogFetchResponse(payload, { ok: true, status: 200 })]),
429
+ ]);
430
+ const internetPayloads = new Map([
431
+ ...Object.entries(options.internetCatalogs || {}).map(([url, catalog]) => [String(url), catalog]),
432
+ ...Object.entries(options.internetJsonByUrl || {}).map(([url, payload]) => [String(url), payload]),
433
+ ]);
434
+ const defaultCatalogs = new Map(Object.entries(options.defaultCatalogs || {}));
435
+ const cache = new Map();
436
+ const sources = new Map();
437
+ const calls = [];
438
+ return {
439
+ calls,
440
+ sources,
441
+ cache,
442
+ setInternetCatalog(url, catalog) {
443
+ internetResponses.set(String(url), createDiscoveryCatalogFetchResponse(catalog, { ok: true, status: 200 }));
444
+ internetPayloads.set(String(url), catalog);
445
+ },
446
+ setInternetJson(url, payload) {
447
+ internetResponses.set(String(url), createDiscoveryCatalogFetchResponse(payload, { ok: true, status: 200 }));
448
+ internetPayloads.set(String(url), payload);
449
+ },
450
+ setInternetFailure(url, status = 503, body = { error: 'temporary failure' }) {
451
+ internetResponses.set(String(url), createDiscoveryCatalogFetchResponse(body, { ok: false, status }));
452
+ internetPayloads.delete(String(url));
453
+ },
454
+ clearInternetRoute(url) {
455
+ internetResponses.delete(String(url));
456
+ internetPayloads.delete(String(url));
457
+ },
458
+ async fetcher(input) {
459
+ const key = String(input);
460
+ calls.push(key);
461
+ const internetResponse = internetResponses.get(key);
462
+ if (internetResponse && internetResponse.ok) {
463
+ const payload = internetPayloads.get(key);
464
+ if (isHostingOperatorCatalogPayload(payload)) {
465
+ cache.set(key, payload);
466
+ }
467
+ sources.set(key, DiscoveryCatalogSource.Internet);
468
+ return internetResponse;
469
+ }
470
+ if (cache.has(key)) {
471
+ sources.set(key, DiscoveryCatalogSource.Cache);
472
+ return createDiscoveryCatalogFetchResponse(cache.get(key), { ok: true, status: 200 });
473
+ }
474
+ if (defaultCatalogs.has(key)) {
475
+ sources.set(key, DiscoveryCatalogSource.Default);
476
+ return createDiscoveryCatalogFetchResponse(defaultCatalogs.get(key), { ok: true, status: 200 });
477
+ }
478
+ sources.set(key, DiscoveryCatalogSource.Default);
479
+ return createDiscoveryCatalogFetchResponse({ error: 'not found' }, { ok: false, status: 404 });
480
+ },
481
+ };
482
+ }
@@ -0,0 +1,66 @@
1
+ import { type DataspaceProtocolVersionValue } from '../constants/dataspace-protocol';
2
+ import type { DspaceProtocolVersionEntry, DspaceVersionMetadata } from '../models/dataspace-protocol';
3
+ /**
4
+ * Route context used to build tenant-scoped or host-scoped GW CORE DSP paths.
5
+ *
6
+ * Semantic rule:
7
+ * - `tenantId` participants use `businessSector`
8
+ * - `host`/ICA/runtime participants use `hostNetwork`
9
+ */
10
+ export type GwDataspaceRouteContext = Readonly<{
11
+ participantId?: string;
12
+ tenantId?: string;
13
+ jurisdiction?: string;
14
+ version?: string;
15
+ hostNetwork?: string;
16
+ /** @deprecated Use `hostNetwork`. */
17
+ hostNetworkOrBusinessSector?: string;
18
+ businessSector?: string;
19
+ sector?: string;
20
+ }>;
21
+ /**
22
+ * Builds the GW CORE base DSP path.
23
+ *
24
+ * Participant scope:
25
+ * - `/{participantId}/cds-{jurisdiction}/{version}/{hostNetwork|businessSector}/dsp`
26
+ */
27
+ export declare function buildGwDataspaceBasePath(input?: GwDataspaceRouteContext): string;
28
+ /**
29
+ * Builds the canonical DSP version-discovery well-known path for GW CORE.
30
+ *
31
+ * Participant scope:
32
+ * - `/{participantId}/cds-{jurisdiction}/{version}/{hostNetwork|businessSector}/.well-known/dspace-version`
33
+ */
34
+ export declare function buildGwDspaceVersionWellKnownPath(input?: GwDataspaceRouteContext): string;
35
+ /**
36
+ * Builds the project-local GW CORE DSP catalog request endpoint.
37
+ */
38
+ export declare function buildGwCatalogRequestPath(input?: GwDataspaceRouteContext): string;
39
+ /**
40
+ * Builds the GW CORE catalog collection base path.
41
+ */
42
+ export declare function buildGwCatalogCollectionPath(input?: GwDataspaceRouteContext): string;
43
+ /**
44
+ * Builds the project-local GW CORE catalog artifact URL path used for public
45
+ * read-only autodiscovery.
46
+ */
47
+ export declare function buildGwCatalogArtifactPath(input?: GwDataspaceRouteContext): string;
48
+ /**
49
+ * Builds the project-local GW CORE dataset lookup path.
50
+ */
51
+ export declare function buildGwCatalogDatasetPath(input?: GwDataspaceRouteContext, datasetId?: string): string;
52
+ /**
53
+ * Builds a minimal DSP version metadata payload for a given GW CORE DSP base
54
+ * path.
55
+ */
56
+ export declare function buildDspaceVersionMetadata(path: string, version?: DataspaceProtocolVersionValue): DspaceVersionMetadata;
57
+ /**
58
+ * Selects the preferred advertised DSP version entry from a `dspace-version`
59
+ * payload.
60
+ */
61
+ export declare function selectDspaceProtocolVersionEntry(metadata: DspaceVersionMetadata | null | undefined, version?: string): DspaceProtocolVersionEntry | undefined;
62
+ /**
63
+ * Derives the GW CORE catalog artifact URL from a `dspace-version` endpoint URL
64
+ * and its parsed payload.
65
+ */
66
+ export declare function deriveGwCatalogArtifactUrlFromDspaceVersion(dspaceVersionUrl: string, metadata: DspaceVersionMetadata | null | undefined, version?: string): string | undefined;
@@ -0,0 +1,109 @@
1
+ // Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
2
+ import { DataspaceProtocolVersions, DataspaceWellKnownPaths, GwDataspaceBindingPaths, } from '../constants/dataspace-protocol.js';
3
+ function resolveParticipantId(input) {
4
+ return String(input.participantId || input.tenantId || '').trim();
5
+ }
6
+ function resolveSectorLikeSegment(input) {
7
+ return String(input.hostNetwork
8
+ || input.hostNetworkOrBusinessSector
9
+ || input.businessSector
10
+ || input.sector
11
+ || '').trim();
12
+ }
13
+ function hasParticipantRouteContext(input) {
14
+ return Boolean(resolveParticipantId(input)
15
+ && String(input.jurisdiction || '').trim()
16
+ && String(input.version || '').trim()
17
+ && resolveSectorLikeSegment(input));
18
+ }
19
+ /**
20
+ * Builds the GW CORE base DSP path.
21
+ *
22
+ * Participant scope:
23
+ * - `/{participantId}/cds-{jurisdiction}/{version}/{hostNetwork|businessSector}/dsp`
24
+ */
25
+ export function buildGwDataspaceBasePath(input = {}) {
26
+ if (!hasParticipantRouteContext(input)) {
27
+ return GwDataspaceBindingPaths.Base;
28
+ }
29
+ return `/${resolveParticipantId(input)}`
30
+ + `/cds-${String(input.jurisdiction).trim()}`
31
+ + `/${String(input.version).trim()}`
32
+ + `/${resolveSectorLikeSegment(input)}`
33
+ + GwDataspaceBindingPaths.Base;
34
+ }
35
+ /**
36
+ * Builds the canonical DSP version-discovery well-known path for GW CORE.
37
+ *
38
+ * Participant scope:
39
+ * - `/{participantId}/cds-{jurisdiction}/{version}/{hostNetwork|businessSector}/.well-known/dspace-version`
40
+ */
41
+ export function buildGwDspaceVersionWellKnownPath(input = {}) {
42
+ if (!hasParticipantRouteContext(input)) {
43
+ return DataspaceWellKnownPaths.VersionMetadata;
44
+ }
45
+ return `/${resolveParticipantId(input)}`
46
+ + `/cds-${String(input.jurisdiction).trim()}`
47
+ + `/${String(input.version).trim()}`
48
+ + `/${resolveSectorLikeSegment(input)}`
49
+ + DataspaceWellKnownPaths.VersionMetadata;
50
+ }
51
+ /**
52
+ * Builds the project-local GW CORE DSP catalog request endpoint.
53
+ */
54
+ export function buildGwCatalogRequestPath(input = {}) {
55
+ return `${buildGwDataspaceBasePath(input)}${GwDataspaceBindingPaths.CatalogRequestSuffix}`;
56
+ }
57
+ /**
58
+ * Builds the GW CORE catalog collection base path.
59
+ */
60
+ export function buildGwCatalogCollectionPath(input = {}) {
61
+ return `${buildGwDataspaceBasePath(input)}${GwDataspaceBindingPaths.CatalogCollectionSuffix}`;
62
+ }
63
+ /**
64
+ * Builds the project-local GW CORE catalog artifact URL path used for public
65
+ * read-only autodiscovery.
66
+ */
67
+ export function buildGwCatalogArtifactPath(input = {}) {
68
+ return `${buildGwDataspaceBasePath(input)}${GwDataspaceBindingPaths.CatalogArtifactSuffix}`;
69
+ }
70
+ /**
71
+ * Builds the project-local GW CORE dataset lookup path.
72
+ */
73
+ export function buildGwCatalogDatasetPath(input = {}, datasetId = ':id') {
74
+ return `${buildGwDataspaceBasePath(input)}${GwDataspaceBindingPaths.CatalogDatasetsPrefix}/${datasetId}`;
75
+ }
76
+ /**
77
+ * Builds a minimal DSP version metadata payload for a given GW CORE DSP base
78
+ * path.
79
+ */
80
+ export function buildDspaceVersionMetadata(path, version = DataspaceProtocolVersions.Current) {
81
+ return {
82
+ protocolVersions: [
83
+ {
84
+ version,
85
+ path,
86
+ },
87
+ ],
88
+ };
89
+ }
90
+ /**
91
+ * Selects the preferred advertised DSP version entry from a `dspace-version`
92
+ * payload.
93
+ */
94
+ export function selectDspaceProtocolVersionEntry(metadata, version = DataspaceProtocolVersions.Current) {
95
+ const entries = metadata?.protocolVersions || [];
96
+ return entries.find((entry) => String(entry.version || '').trim() === String(version).trim())
97
+ || entries[0];
98
+ }
99
+ /**
100
+ * Derives the GW CORE catalog artifact URL from a `dspace-version` endpoint URL
101
+ * and its parsed payload.
102
+ */
103
+ export function deriveGwCatalogArtifactUrlFromDspaceVersion(dspaceVersionUrl, metadata, version = DataspaceProtocolVersions.Current) {
104
+ const entry = selectDspaceProtocolVersionEntry(metadata, version);
105
+ if (!entry?.path)
106
+ return undefined;
107
+ const base = new URL(dspaceVersionUrl);
108
+ return new URL(`${String(entry.path).trim()}${GwDataspaceBindingPaths.CatalogArtifactSuffix}`, `${base.protocol}//${base.host}`).toString();
109
+ }
@@ -8,6 +8,7 @@ export * from './consent';
8
8
  export * from './did';
9
9
  export * from './did-resolution';
10
10
  export * from './dataspace-discovery';
11
+ export * from './dataspace-protocol';
11
12
  export * from './didcomm';
12
13
  export * from './didcomm-submit';
13
14
  export * from './didcomm-submit-policy';
@@ -8,6 +8,7 @@ export * from './consent.js';
8
8
  export * from './did.js';
9
9
  export * from './did-resolution.js';
10
10
  export * from './dataspace-discovery.js';
11
+ export * from './dataspace-protocol.js';
11
12
  export * from './didcomm.js';
12
13
  export * from './didcomm-submit.js';
13
14
  export * from './didcomm-submit-policy.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdc-common-utils-ts",
3
- "version": "1.12.0",
3
+ "version": "1.13.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },