sos-sdk-node-ts 0.1.17 → 0.1.18
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/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.1.18 - 2026-11-03
|
|
6
|
+
|
|
7
|
+
- Add a trusted Node client for subject-card provider advertisements and
|
|
8
|
+
federated authorized-subject reads. Provider failures remain visible while
|
|
9
|
+
successful directories continue to return their cards.
|
|
10
|
+
- Consume `sos-data-utils-ts@0.6.21` and `sos-sdk-core-ts@0.5.4`, the exact
|
|
11
|
+
published provider-discovery contracts validated by this release.
|
|
12
|
+
|
|
5
13
|
## 0.1.17 - 2026-10-22
|
|
6
14
|
|
|
7
15
|
- Consume the shared historical NCBI OBO taxonomy read compatibility used by
|
package/dist/index.d.ts
CHANGED
|
@@ -11,3 +11,4 @@ export * from './server-passkey-profile.js';
|
|
|
11
11
|
export * from './host-organization-directory-client.js';
|
|
12
12
|
export * from './secure-organization-registration.js';
|
|
13
13
|
export * from './portable-professional-evidence.js';
|
|
14
|
+
export * from './subject-card-provider-catalog-client.js';
|
package/dist/index.js
CHANGED
|
@@ -11,3 +11,4 @@ export * from './server-passkey-profile.js';
|
|
|
11
11
|
export * from './host-organization-directory-client.js';
|
|
12
12
|
export * from './secure-organization-registration.js';
|
|
13
13
|
export * from './portable-professional-evidence.js';
|
|
14
|
+
export * from './subject-card-provider-catalog-client.js';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { SubjectCardProviderDescriptor } from 'sos-data-utils-ts/subject-card-directory';
|
|
2
|
+
import { type SubjectCardProviderDiscoveryFilter } from 'sos-sdk-core-ts/subject-card-provider-discovery';
|
|
3
|
+
import { type AuthorizedIndividualSubject, type AuthorizedSubjectVerifiedContact } from './subject-card-directory-client.js';
|
|
4
|
+
export type SosSubjectCardProviderCatalogClientOptions = Readonly<{
|
|
5
|
+
catalogUrl: string;
|
|
6
|
+
bearerToken?: string;
|
|
7
|
+
fetchImpl?: typeof fetch;
|
|
8
|
+
}>;
|
|
9
|
+
/** Reads public provider advertisements from a data-space JSON:API catalog. */
|
|
10
|
+
export declare class SosSubjectCardProviderCatalogClient {
|
|
11
|
+
private readonly catalogUrl;
|
|
12
|
+
private readonly bearerToken?;
|
|
13
|
+
private readonly fetchImpl;
|
|
14
|
+
constructor(options: SosSubjectCardProviderCatalogClientOptions);
|
|
15
|
+
discover(filter: SubjectCardProviderDiscoveryFilter): Promise<readonly SubjectCardProviderDescriptor[]>;
|
|
16
|
+
}
|
|
17
|
+
export type FederatedProviderSubject = Readonly<{
|
|
18
|
+
providerId: string;
|
|
19
|
+
subject: AuthorizedIndividualSubject;
|
|
20
|
+
}>;
|
|
21
|
+
export type FederatedProviderSubjectResult = Readonly<{
|
|
22
|
+
subjects: readonly FederatedProviderSubject[];
|
|
23
|
+
unavailableProviderIds: readonly string[];
|
|
24
|
+
}>;
|
|
25
|
+
/** Queries every advertised directory independently and keeps partial success. */
|
|
26
|
+
export declare function readFederatedProviderSubjects(input: Readonly<{
|
|
27
|
+
providers: readonly SubjectCardProviderDescriptor[];
|
|
28
|
+
subjectKind?: 'human' | 'animal';
|
|
29
|
+
verifiedContact: AuthorizedSubjectVerifiedContact;
|
|
30
|
+
bearerToken?: string;
|
|
31
|
+
readProvider?: (provider: SubjectCardProviderDescriptor, verifiedContact: AuthorizedSubjectVerifiedContact) => Promise<readonly AuthorizedIndividualSubject[]>;
|
|
32
|
+
}>): Promise<FederatedProviderSubjectResult>;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { discoverSubjectCardProviders, } from 'sos-sdk-core-ts/subject-card-provider-discovery';
|
|
2
|
+
import { SosGatewaySubjectDirectoryClient, } from './subject-card-directory-client.js';
|
|
3
|
+
/** Reads public provider advertisements from a data-space JSON:API catalog. */
|
|
4
|
+
export class SosSubjectCardProviderCatalogClient {
|
|
5
|
+
catalogUrl;
|
|
6
|
+
bearerToken;
|
|
7
|
+
fetchImpl;
|
|
8
|
+
constructor(options) {
|
|
9
|
+
this.catalogUrl = secureUrl(options.catalogUrl);
|
|
10
|
+
this.bearerToken = optional(options.bearerToken);
|
|
11
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
12
|
+
}
|
|
13
|
+
async discover(filter) {
|
|
14
|
+
const url = new URL(this.catalogUrl);
|
|
15
|
+
url.searchParams.set('subjectKind', filter.subjectKind);
|
|
16
|
+
url.searchParams.set('operation', filter.operation);
|
|
17
|
+
const response = await this.fetchImpl(url, {
|
|
18
|
+
headers: {
|
|
19
|
+
Accept: 'application/vnd.api+json',
|
|
20
|
+
...(this.bearerToken ? { Authorization: `Bearer ${this.bearerToken}` } : {}),
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
if (!response.ok)
|
|
24
|
+
throw new Error(`provider_catalog_failed:${response.status}`);
|
|
25
|
+
const body = await response.json();
|
|
26
|
+
if (!Array.isArray(body.data))
|
|
27
|
+
throw new Error('provider_catalog_response_invalid');
|
|
28
|
+
const catalog = body.data.map(entry => {
|
|
29
|
+
const row = record(entry);
|
|
30
|
+
if (row.type !== 'subject-card-provider')
|
|
31
|
+
throw new Error('provider_catalog_entry_invalid');
|
|
32
|
+
return record(row.attributes);
|
|
33
|
+
});
|
|
34
|
+
return discoverSubjectCardProviders(catalog, filter);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** Queries every advertised directory independently and keeps partial success. */
|
|
38
|
+
export async function readFederatedProviderSubjects(input) {
|
|
39
|
+
const providers = discoverSubjectCardProviders(input.providers, {
|
|
40
|
+
subjectKind: input.subjectKind ?? commonSubjectKind(input.providers),
|
|
41
|
+
operation: 'directory',
|
|
42
|
+
});
|
|
43
|
+
const readProvider = input.readProvider ?? (async (provider) => {
|
|
44
|
+
const route = parseDirectoryRoute(provider.directoryRoute);
|
|
45
|
+
const client = new SosGatewaySubjectDirectoryClient({
|
|
46
|
+
baseUrl: route.baseUrl,
|
|
47
|
+
bearerToken: input.bearerToken,
|
|
48
|
+
});
|
|
49
|
+
return client.listAuthorizedIndividualSubjects(route.context, {
|
|
50
|
+
verifiedContact: input.verifiedContact,
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
const settled = await Promise.allSettled(providers.map(provider => readProvider(provider, input.verifiedContact)));
|
|
54
|
+
const subjects = [];
|
|
55
|
+
const unavailableProviderIds = [];
|
|
56
|
+
for (const [index, result] of settled.entries()) {
|
|
57
|
+
const provider = providers[index];
|
|
58
|
+
if (result.status === 'rejected') {
|
|
59
|
+
unavailableProviderIds.push(provider.providerId);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
for (const subject of result.value)
|
|
63
|
+
subjects.push(Object.freeze({ providerId: provider.providerId, subject }));
|
|
64
|
+
}
|
|
65
|
+
return Object.freeze({
|
|
66
|
+
subjects: Object.freeze(subjects),
|
|
67
|
+
unavailableProviderIds: Object.freeze(unavailableProviderIds),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function commonSubjectKind(providers) {
|
|
71
|
+
const kinds = new Set(providers.flatMap(provider => provider.subjectKinds));
|
|
72
|
+
if (kinds.size !== 1)
|
|
73
|
+
throw new Error('provider_subject_kind_filter_required');
|
|
74
|
+
return [...kinds][0];
|
|
75
|
+
}
|
|
76
|
+
function parseDirectoryRoute(value) {
|
|
77
|
+
const url = secureUrl(value);
|
|
78
|
+
const segments = url.pathname.split('/').filter(Boolean);
|
|
79
|
+
if (segments.length !== 4 || !segments[1]?.startsWith('cds-') || segments[2] !== 'v1') {
|
|
80
|
+
throw new Error('provider_directory_route_invalid');
|
|
81
|
+
}
|
|
82
|
+
return Object.freeze({
|
|
83
|
+
baseUrl: url.origin,
|
|
84
|
+
context: Object.freeze({ tenantId: segments[0], jurisdiction: segments[1].slice(4), sector: segments[3] }),
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
function secureUrl(value) {
|
|
88
|
+
const url = new URL(value);
|
|
89
|
+
const local = url.hostname === 'localhost' || url.hostname === '127.0.0.1';
|
|
90
|
+
if (url.protocol !== 'https:' && !(url.protocol === 'http:' && local))
|
|
91
|
+
throw new Error('provider_url_insecure');
|
|
92
|
+
return url;
|
|
93
|
+
}
|
|
94
|
+
function optional(value) {
|
|
95
|
+
const normalized = String(value ?? '').trim();
|
|
96
|
+
return normalized || undefined;
|
|
97
|
+
}
|
|
98
|
+
function record(value) {
|
|
99
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
100
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sos-sdk-node-ts",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.18",
|
|
4
4
|
"description": "Product-neutral Node adapters shared by SOSChain, Vet and UHC runtimes",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Connecting Solution & Applications Ltd",
|
|
@@ -59,6 +59,10 @@
|
|
|
59
59
|
"./portable-professional-evidence": {
|
|
60
60
|
"types": "./dist/portable-professional-evidence.d.ts",
|
|
61
61
|
"default": "./dist/portable-professional-evidence.js"
|
|
62
|
+
},
|
|
63
|
+
"./subject-card-provider-catalog-client": {
|
|
64
|
+
"types": "./dist/subject-card-provider-catalog-client.d.ts",
|
|
65
|
+
"default": "./dist/subject-card-provider-catalog-client.js"
|
|
62
66
|
}
|
|
63
67
|
},
|
|
64
68
|
"files": [
|
|
@@ -80,8 +84,8 @@
|
|
|
80
84
|
"@simplewebauthn/server": "13.3.2",
|
|
81
85
|
"gdc-common-utils-ts": "2.9.30",
|
|
82
86
|
"gdc-sdk-node-ts": "2.9.32",
|
|
83
|
-
"sos-data-utils-ts": "0.6.
|
|
84
|
-
"sos-sdk-core-ts": "0.5.
|
|
87
|
+
"sos-data-utils-ts": "0.6.21",
|
|
88
|
+
"sos-sdk-core-ts": "0.5.4"
|
|
85
89
|
},
|
|
86
90
|
"devDependencies": {
|
|
87
91
|
"@types/node": "^24.10.0",
|