gdc-common-utils-ts 2.0.2 → 2.0.5
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 +45 -0
- package/dist/constants/verifiable-credentials.d.ts +6 -0
- package/dist/constants/verifiable-credentials.js +6 -0
- package/dist/examples/employee.d.ts +15 -0
- package/dist/examples/employee.js +15 -0
- package/dist/examples/ica-verify-response.d.ts +5 -0
- package/dist/examples/ica-verify-response.js +6 -0
- package/dist/examples/index.d.ts +2 -0
- package/dist/examples/index.js +2 -0
- package/dist/examples/individual-controller.d.ts +25 -0
- package/dist/examples/individual-controller.js +23 -0
- package/dist/examples/legal-organization-verification-transaction.d.ts +1 -0
- package/dist/examples/legal-organization-verification-transaction.js +76 -0
- package/dist/examples/organization-did-binding.d.ts +1 -0
- package/dist/examples/organization-did-binding.js +14 -0
- package/dist/examples/shared.d.ts +10 -0
- package/dist/examples/shared.js +11 -1
- package/dist/utils/bundle-reader.d.ts +2 -0
- package/dist/utils/bundle-reader.js +14 -0
- package/dist/utils/clinical-bundle-summary.d.ts +20 -0
- package/dist/utils/clinical-bundle-summary.js +34 -0
- package/dist/utils/didcomm-submit-policy.d.ts +20 -3
- package/dist/utils/didcomm-submit-policy.js +37 -6
- package/dist/utils/didcomm-submit.d.ts +10 -3
- package/dist/utils/didcomm-submit.js +12 -5
- package/dist/utils/family-organization-summary.d.ts +24 -0
- package/dist/utils/family-organization-summary.js +59 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.js +6 -0
- package/dist/utils/legal-organization-onboarding-editor.d.ts +156 -0
- package/dist/utils/legal-organization-onboarding-editor.js +350 -0
- package/dist/utils/legal-organization-verification-transaction.d.ts +130 -0
- package/dist/utils/legal-organization-verification-transaction.js +118 -0
- package/dist/utils/organization-did-binding.d.ts +60 -0
- package/dist/utils/organization-did-binding.js +103 -0
- package/dist/utils/professional-smart.d.ts +21 -0
- package/dist/utils/professional-smart.js +37 -0
- package/dist/utils/related-person-list.d.ts +14 -0
- package/dist/utils/related-person-list.js +31 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,6 +10,26 @@ Short rule:
|
|
|
10
10
|
- do not add ad hoc literals in `101` tests when `gdc-common-utils-ts` can own
|
|
11
11
|
the reusable value instead
|
|
12
12
|
|
|
13
|
+
## Shared Workspace
|
|
14
|
+
|
|
15
|
+
Recommended local layout for the shared ICA/GDC repos and fixture PDFs:
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
~/GITS/gdc-workspace/
|
|
19
|
+
dataspace-ica-ts/
|
|
20
|
+
ica-client-sdk-ts/
|
|
21
|
+
gdc-common-utils-ts/
|
|
22
|
+
examples/
|
|
23
|
+
<example-pdf-1>.pdf
|
|
24
|
+
<example-pdf-2>.pdf
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
This is recommended because:
|
|
28
|
+
|
|
29
|
+
- cross-repo docs and fixture-based tests often refer to sibling repos
|
|
30
|
+
- real PDF examples are expected under `~/GITS/gdc-workspace/examples/`
|
|
31
|
+
- keeping a single shared workspace reduces path drift between repos
|
|
32
|
+
|
|
13
33
|
Employee shared examples live in `src/examples/employee.ts`.
|
|
14
34
|
Employee pure helper functions live in `src/utils/employee.ts`.
|
|
15
35
|
|
|
@@ -61,6 +81,31 @@ They are not interchangeable:
|
|
|
61
81
|
Production-grade flows should prefer ICA-issued representative VCs that carry
|
|
62
82
|
both dimensions.
|
|
63
83
|
|
|
84
|
+
## Legal Organization Verification Transaction
|
|
85
|
+
|
|
86
|
+
The first host-side legal-organization onboarding step now has one canonical
|
|
87
|
+
shared payload builder in this package:
|
|
88
|
+
|
|
89
|
+
- `buildLegalOrganizationVerificationTransactionBundle(...)`
|
|
90
|
+
- `EXAMPLE_LEGAL_ORGANIZATION_VERIFICATION_TRANSACTION_BUNDLE`
|
|
91
|
+
|
|
92
|
+
This builder owns the business payload only:
|
|
93
|
+
|
|
94
|
+
- signed PDF evidence attachment references
|
|
95
|
+
- `controller.publicKeyJwk` as the controller business binding key
|
|
96
|
+
- optional `organization.publicKeyJwk`
|
|
97
|
+
- legal representative payload
|
|
98
|
+
- `meta.claims` business claims
|
|
99
|
+
|
|
100
|
+
It intentionally does not own:
|
|
101
|
+
|
|
102
|
+
- `fetch`
|
|
103
|
+
- polling
|
|
104
|
+
- JOSE transport execution
|
|
105
|
+
- BFF/frontend runtime crypto
|
|
106
|
+
|
|
107
|
+
Those runtime concerns belong in `gdc-sdk-node-ts`, `gdc-sdk-front-ts`, or GW.
|
|
108
|
+
|
|
64
109
|
Step by step:
|
|
65
110
|
|
|
66
111
|
1. ICA verifies the signed PDF and emits the representative VC.
|
|
@@ -30,5 +30,11 @@ export declare const ActivationCredentialTypes: Readonly<{
|
|
|
30
30
|
LegalRepresentativeCredential: "LegalRepresentativeCredential";
|
|
31
31
|
PersonCredential: "PersonCredential";
|
|
32
32
|
}>;
|
|
33
|
+
/**
|
|
34
|
+
* Canonical credential subtype names used by professional/member access flows.
|
|
35
|
+
*/
|
|
36
|
+
export declare const ProfessionalCredentialTypes: Readonly<{
|
|
37
|
+
EmployeeCredential: "EmployeeCredential";
|
|
38
|
+
}>;
|
|
33
39
|
export declare const ORGANIZATION_ACTIVATION_VC_TYPES: readonly ("OrganizationCredential" | "LegalOrganizationCredential")[];
|
|
34
40
|
export declare const REPRESENTATIVE_ACTIVATION_VC_TYPES: readonly ("LegalRepresentativeCredential" | "PersonCredential")[];
|
|
@@ -32,6 +32,12 @@ export const ActivationCredentialTypes = Object.freeze({
|
|
|
32
32
|
LegalRepresentativeCredential: 'LegalRepresentativeCredential',
|
|
33
33
|
PersonCredential: 'PersonCredential',
|
|
34
34
|
});
|
|
35
|
+
/**
|
|
36
|
+
* Canonical credential subtype names used by professional/member access flows.
|
|
37
|
+
*/
|
|
38
|
+
export const ProfessionalCredentialTypes = Object.freeze({
|
|
39
|
+
EmployeeCredential: 'EmployeeCredential',
|
|
40
|
+
});
|
|
35
41
|
export const ORGANIZATION_ACTIVATION_VC_TYPES = Object.freeze([
|
|
36
42
|
ActivationCredentialTypes.OrganizationCredential,
|
|
37
43
|
ActivationCredentialTypes.LegalOrganizationCredential,
|
|
@@ -39,3 +39,18 @@ export declare const EXAMPLE_EMPLOYEE_DIRECTORY_RECORDS: readonly [Readonly<{
|
|
|
39
39
|
status: "active" | "inactive" | "purged";
|
|
40
40
|
}>];
|
|
41
41
|
export declare function buildExampleEmployeeClaims(record: ExampleEmployeeRecord): Readonly<Record<string, string>>;
|
|
42
|
+
/**
|
|
43
|
+
* Canonical employee search/list response body reused by runtime and doc tests.
|
|
44
|
+
*
|
|
45
|
+
* This keeps one stable GW-style envelope for high-level tutorials that want
|
|
46
|
+
* to show employee directory flows without reauthoring `meta.claims` by hand.
|
|
47
|
+
*/
|
|
48
|
+
export declare const EXAMPLE_EMPLOYEE_SEARCH_RESPONSE_BODY: Readonly<{
|
|
49
|
+
readonly data: {
|
|
50
|
+
id: string;
|
|
51
|
+
meta: {
|
|
52
|
+
status: "active" | "inactive" | "purged";
|
|
53
|
+
claims: Readonly<Record<string, string>>;
|
|
54
|
+
};
|
|
55
|
+
}[];
|
|
56
|
+
}>;
|
|
@@ -37,3 +37,18 @@ export function buildExampleEmployeeClaims(record) {
|
|
|
37
37
|
[ClaimsPersonSchemaorg.hasOccupationalRoleValue]: record.role,
|
|
38
38
|
});
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Canonical employee search/list response body reused by runtime and doc tests.
|
|
42
|
+
*
|
|
43
|
+
* This keeps one stable GW-style envelope for high-level tutorials that want
|
|
44
|
+
* to show employee directory flows without reauthoring `meta.claims` by hand.
|
|
45
|
+
*/
|
|
46
|
+
export const EXAMPLE_EMPLOYEE_SEARCH_RESPONSE_BODY = Object.freeze({
|
|
47
|
+
data: EXAMPLE_EMPLOYEE_DIRECTORY_RECORDS.map((record) => ({
|
|
48
|
+
id: record.identifier,
|
|
49
|
+
meta: {
|
|
50
|
+
status: record.status,
|
|
51
|
+
claims: buildExampleEmployeeClaims(record),
|
|
52
|
+
},
|
|
53
|
+
})),
|
|
54
|
+
});
|
|
@@ -136,6 +136,7 @@ export declare const EXAMPLE_VERIFY_RESPONSE_PERSON_PUBLIC_KEY_JWK: Readonly<{
|
|
|
136
136
|
alg: "ES384";
|
|
137
137
|
kid: "controller-es384-001";
|
|
138
138
|
}>;
|
|
139
|
+
export declare const EXAMPLE_VERIFY_RESPONSE_ORG_AUTHORIZED_CATEGORY: "health-care";
|
|
139
140
|
export declare const EXAMPLE_VERIFY_RESPONSE_ORG_CREDENTIAL: Readonly<{
|
|
140
141
|
id: "urn:uuid:org-vc-001";
|
|
141
142
|
'@context': ("https://www.w3.org/ns/credentials/v2" | "https://schema.org")[];
|
|
@@ -154,6 +155,10 @@ export declare const EXAMPLE_VERIFY_RESPONSE_ORG_CREDENTIAL: Readonly<{
|
|
|
154
155
|
url: "health-care.provider.example.org";
|
|
155
156
|
alternateName: string;
|
|
156
157
|
additionalType: "sector=onehealth;section=dataprovider;kind=clinic;action=_index-provider,_research-provider";
|
|
158
|
+
makesOffer: {
|
|
159
|
+
'@type': string;
|
|
160
|
+
category: "health-care";
|
|
161
|
+
};
|
|
157
162
|
address: {
|
|
158
163
|
'@type': "PostalAddress";
|
|
159
164
|
addressCountry: "ES";
|
|
@@ -2,6 +2,7 @@
|
|
|
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 { ActivationCredentialTypes, W3cCredentialContexts, W3cCredentialTypes, } from '../constants/verifiable-credentials.js';
|
|
4
4
|
import { ResourceTypesFhirR4 } from '../constants/fhir-resource-types.js';
|
|
5
|
+
import { DataspaceSectors } from '../constants/sectors.js';
|
|
5
6
|
import { IssueSeverity, IssueType } from '../models/issue.js';
|
|
6
7
|
import { EXAMPLE_DEFAULT_ICA_DID, EXAMPLE_BUNDLE_RESOURCE_TYPE, EXAMPLE_PROVIDER_DOMAIN, EXAMPLE_PROVIDER_LEGAL_NAME, EXAMPLE_PROVIDER_TAX_ID, EXAMPLE_TENANT_SERVICE_DID, } from './shared.js';
|
|
7
8
|
import { EXAMPLE_ORG_CONTROLLER_SIGNING_KEY_ID, EXAMPLE_REPRESENTATIVE_IDENTIFIER, EXAMPLE_REPRESENTATIVE_SAME_AS, EXAMPLE_REPRESENTATIVE_SUBJECT_URN, } from './ica-activation-proof.js';
|
|
@@ -102,6 +103,7 @@ export const EXAMPLE_VERIFY_RESPONSE_PERSON_PUBLIC_KEY_JWK = Object.freeze({
|
|
|
102
103
|
alg: 'ES384',
|
|
103
104
|
kid: EXAMPLE_VERIFY_RESPONSE_PERSON_KID,
|
|
104
105
|
});
|
|
106
|
+
export const EXAMPLE_VERIFY_RESPONSE_ORG_AUTHORIZED_CATEGORY = DataspaceSectors.HealthCare;
|
|
105
107
|
export const EXAMPLE_VERIFY_RESPONSE_ORG_CREDENTIAL = Object.freeze({
|
|
106
108
|
id: EXAMPLE_VERIFY_RESPONSE_ORG_VC_ID,
|
|
107
109
|
'@context': [W3cCredentialContexts.V2, EXAMPLE_VERIFY_RESPONSE_SCHEMA_ORG_CONTEXT],
|
|
@@ -120,6 +122,10 @@ export const EXAMPLE_VERIFY_RESPONSE_ORG_CREDENTIAL = Object.freeze({
|
|
|
120
122
|
url: EXAMPLE_PROVIDER_DOMAIN,
|
|
121
123
|
alternateName: 'example-provider',
|
|
122
124
|
additionalType: EXAMPLE_VERIFY_RESPONSE_ORG_ADDITIONAL_TYPE,
|
|
125
|
+
makesOffer: {
|
|
126
|
+
'@type': 'Offer',
|
|
127
|
+
category: EXAMPLE_VERIFY_RESPONSE_ORG_AUTHORIZED_CATEGORY,
|
|
128
|
+
},
|
|
123
129
|
address: {
|
|
124
130
|
'@type': EXAMPLE_VERIFY_RESPONSE_ADDRESS_TYPE,
|
|
125
131
|
addressCountry: EXAMPLE_VERIFY_RESPONSE_ADDRESS_COUNTRY,
|
package/dist/examples/index.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ export * from './ica-activation-proof';
|
|
|
3
3
|
export * from './ica-verify-response';
|
|
4
4
|
export * from './dataspace-discovery';
|
|
5
5
|
export * from './organization-controller';
|
|
6
|
+
export * from './organization-did-binding';
|
|
7
|
+
export * from './legal-organization-verification-transaction';
|
|
6
8
|
export * from './individual-controller';
|
|
7
9
|
export * from './professional';
|
|
8
10
|
export * from './employee';
|
package/dist/examples/index.js
CHANGED
|
@@ -3,6 +3,8 @@ export * from './ica-activation-proof.js';
|
|
|
3
3
|
export * from './ica-verify-response.js';
|
|
4
4
|
export * from './dataspace-discovery.js';
|
|
5
5
|
export * from './organization-controller.js';
|
|
6
|
+
export * from './organization-did-binding.js';
|
|
7
|
+
export * from './legal-organization-verification-transaction.js';
|
|
6
8
|
export * from './individual-controller.js';
|
|
7
9
|
export * from './professional.js';
|
|
8
10
|
export * from './employee.js';
|
|
@@ -98,3 +98,28 @@ export declare const EXAMPLE_DIGITAL_TWIN_COMPOSITION_INPUT: {
|
|
|
98
98
|
};
|
|
99
99
|
export declare const EXAMPLE_CLINICAL_BUNDLE_SEARCH_INPUT: ExampleClinicalBundleSearchInput;
|
|
100
100
|
export declare const EXAMPLE_LATEST_IPS_SEARCH_INPUT: ExampleLatestIpsSearchInput;
|
|
101
|
+
export declare const EXAMPLE_FAMILY_ORGANIZATION_SEARCH_INPUT: {
|
|
102
|
+
readonly controllerPhone: "+34600000001";
|
|
103
|
+
readonly usualname: "Ana";
|
|
104
|
+
readonly birthDate: "2010-05-20";
|
|
105
|
+
};
|
|
106
|
+
export declare const EXAMPLE_FAMILY_ORGANIZATION_SEARCH_RESPONSE_BODY: {
|
|
107
|
+
readonly body: {
|
|
108
|
+
readonly data: readonly [{
|
|
109
|
+
readonly type: "Family-search-result-v1.0";
|
|
110
|
+
readonly meta: {
|
|
111
|
+
readonly claims: {
|
|
112
|
+
readonly 'org.schema.FamilyRegistration.status': "already_exists";
|
|
113
|
+
readonly 'org.schema.Offer.identifier': "offer-uuid-001";
|
|
114
|
+
readonly 'org.schema.Organization.identifier.value': "org-uuid-001";
|
|
115
|
+
readonly 'org.schema.Organization.alternateName': "Ana";
|
|
116
|
+
readonly 'org.schema.Organization.owner.telephone': "+34600000001";
|
|
117
|
+
readonly 'org.schema.Organization.foundingDate': "2010-05-20";
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
readonly resource: {
|
|
121
|
+
readonly id: "org-uuid-001";
|
|
122
|
+
};
|
|
123
|
+
}];
|
|
124
|
+
};
|
|
125
|
+
};
|
|
@@ -69,3 +69,26 @@ export const EXAMPLE_CLINICAL_BUNDLE_SEARCH_INPUT = {
|
|
|
69
69
|
export const EXAMPLE_LATEST_IPS_SEARCH_INPUT = {
|
|
70
70
|
subject: EXAMPLE_SUBJECT_DID,
|
|
71
71
|
};
|
|
72
|
+
export const EXAMPLE_FAMILY_ORGANIZATION_SEARCH_INPUT = {
|
|
73
|
+
controllerPhone: '+34600000001',
|
|
74
|
+
usualname: 'Ana',
|
|
75
|
+
birthDate: '2010-05-20',
|
|
76
|
+
};
|
|
77
|
+
export const EXAMPLE_FAMILY_ORGANIZATION_SEARCH_RESPONSE_BODY = {
|
|
78
|
+
body: {
|
|
79
|
+
data: [{
|
|
80
|
+
type: 'Family-search-result-v1.0',
|
|
81
|
+
meta: {
|
|
82
|
+
claims: {
|
|
83
|
+
'org.schema.FamilyRegistration.status': 'already_exists',
|
|
84
|
+
'org.schema.Offer.identifier': 'offer-uuid-001',
|
|
85
|
+
'org.schema.Organization.identifier.value': 'org-uuid-001',
|
|
86
|
+
'org.schema.Organization.alternateName': EXAMPLE_FAMILY_ORGANIZATION_SEARCH_INPUT.usualname,
|
|
87
|
+
'org.schema.Organization.owner.telephone': EXAMPLE_FAMILY_ORGANIZATION_SEARCH_INPUT.controllerPhone,
|
|
88
|
+
'org.schema.Organization.foundingDate': EXAMPLE_FAMILY_ORGANIZATION_SEARCH_INPUT.birthDate,
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
resource: { id: 'org-uuid-001' },
|
|
92
|
+
}],
|
|
93
|
+
},
|
|
94
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const EXAMPLE_LEGAL_ORGANIZATION_VERIFICATION_TRANSACTION_BUNDLE: import("..").BundleJsonApi<import("..").ErrorEntry | import("..").BundleEntry>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
|
+
import { ClaimsOrganizationSchemaorg, ClaimsPersonSchemaorg, ClaimsServiceSchemaorg, } from '../constants/schemaorg.js';
|
|
3
|
+
import { serializeServiceCapabilityTokens, ServiceCapability, } from '../constants/service-capabilities.js';
|
|
4
|
+
import { buildLegalOrganizationVerificationTransactionBundle, } from '../utils/legal-organization-verification-transaction.js';
|
|
5
|
+
import { EXAMPLE_CONTROLLER_BINDING, EXAMPLE_EMAIL_CONTROLLER_ORG, EXAMPLE_JURISDICTION, EXAMPLE_ORGANIZATION_LEGAL_NAME, EXAMPLE_LEGAL_ORGANIZATION_TAX_ID, EXAMPLE_SECTOR, EXAMPLE_SIGNED_TERMS_PDF_URL, EXAMPLE_TENANT_SERVICE_DID, } from './shared.js';
|
|
6
|
+
/**
|
|
7
|
+
* Canonical host-onboarding transaction example for the new ICA verification
|
|
8
|
+
* first step.
|
|
9
|
+
*
|
|
10
|
+
* Why this exists:
|
|
11
|
+
* - BFF/portal integrations need one stable example of the GW CORE request
|
|
12
|
+
* that wraps an ICA `_verify`
|
|
13
|
+
* - the example keeps the controller business binding separate from any
|
|
14
|
+
* technical DIDComm communication key
|
|
15
|
+
*
|
|
16
|
+
* Contract note:
|
|
17
|
+
* - the same request must already declare the requested tenant service
|
|
18
|
+
* capabilities because GW uses them to prepare the pending Offer that the
|
|
19
|
+
* client will later confirm via `Order/_batch`
|
|
20
|
+
*/
|
|
21
|
+
const exampleControllerBinding = {
|
|
22
|
+
did: EXAMPLE_CONTROLLER_BINDING.did,
|
|
23
|
+
sameAs: EXAMPLE_CONTROLLER_BINDING.sameAs,
|
|
24
|
+
publicKeyJwk: { ...EXAMPLE_CONTROLLER_BINDING.publicKeyJwk },
|
|
25
|
+
...(EXAMPLE_CONTROLLER_BINDING.jwks
|
|
26
|
+
? {
|
|
27
|
+
jwks: {
|
|
28
|
+
keys: EXAMPLE_CONTROLLER_BINDING.jwks.keys.map((item) => ({ ...item })),
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
: {}),
|
|
32
|
+
};
|
|
33
|
+
export const EXAMPLE_LEGAL_ORGANIZATION_VERIFICATION_TRANSACTION_BUNDLE = buildLegalOrganizationVerificationTransactionBundle({
|
|
34
|
+
claims: {
|
|
35
|
+
'@context': 'org.schema',
|
|
36
|
+
[ClaimsOrganizationSchemaorg.legalName]: EXAMPLE_ORGANIZATION_LEGAL_NAME,
|
|
37
|
+
[ClaimsOrganizationSchemaorg.identifierType]: 'taxID',
|
|
38
|
+
[ClaimsOrganizationSchemaorg.identifierValue]: EXAMPLE_LEGAL_ORGANIZATION_TAX_ID,
|
|
39
|
+
[ClaimsOrganizationSchemaorg.taxId]: EXAMPLE_LEGAL_ORGANIZATION_TAX_ID,
|
|
40
|
+
[ClaimsOrganizationSchemaorg.addressCountry]: EXAMPLE_JURISDICTION,
|
|
41
|
+
[ClaimsPersonSchemaorg.email]: EXAMPLE_EMAIL_CONTROLLER_ORG,
|
|
42
|
+
[ClaimsServiceSchemaorg.category]: EXAMPLE_SECTOR,
|
|
43
|
+
[ClaimsServiceSchemaorg.identifier]: EXAMPLE_TENANT_SERVICE_DID,
|
|
44
|
+
[ClaimsServiceSchemaorg.url]: `https://operator.example.net/acme/cds-${String(EXAMPLE_JURISDICTION).toLowerCase()}/v1/${EXAMPLE_SECTOR}`,
|
|
45
|
+
[ClaimsServiceSchemaorg.serviceType]: serializeServiceCapabilityTokens([
|
|
46
|
+
ServiceCapability.IndexProvider,
|
|
47
|
+
ServiceCapability.DigitalTwinReader,
|
|
48
|
+
]),
|
|
49
|
+
},
|
|
50
|
+
controller: exampleControllerBinding,
|
|
51
|
+
organization: {
|
|
52
|
+
did: EXAMPLE_TENANT_SERVICE_DID,
|
|
53
|
+
publicKeyJwk: {
|
|
54
|
+
kid: 'organization-signing-es384-001',
|
|
55
|
+
kty: 'EC',
|
|
56
|
+
crv: 'P-384',
|
|
57
|
+
x: '<organization-sign-x>',
|
|
58
|
+
y: '<organization-sign-y>',
|
|
59
|
+
alg: 'ES384',
|
|
60
|
+
use: 'sig',
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
legalRepresentativePayload: {
|
|
64
|
+
email: EXAMPLE_EMAIL_CONTROLLER_ORG,
|
|
65
|
+
},
|
|
66
|
+
verification: {
|
|
67
|
+
resourceType: 'contract',
|
|
68
|
+
},
|
|
69
|
+
attachments: [{
|
|
70
|
+
id: 'signed-terms-pdf-001',
|
|
71
|
+
media_type: 'application/pdf',
|
|
72
|
+
data: {
|
|
73
|
+
links: [EXAMPLE_SIGNED_TERMS_PDF_URL],
|
|
74
|
+
},
|
|
75
|
+
}],
|
|
76
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const EXAMPLE_ORGANIZATION_DID_BINDING_BUNDLE: import("..").BundleJsonApi<import("..").ErrorEntry | import("..").BundleEntry>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
|
+
import { buildOrganizationDidBindingBundle } from '../utils/organization-did-binding.js';
|
|
3
|
+
import { EXAMPLE_CONTROLLER_BINDING, EXAMPLE_TENANT_SERVICE_DID, } from './shared.js';
|
|
4
|
+
export const EXAMPLE_ORGANIZATION_DID_BINDING_BUNDLE = buildOrganizationDidBindingBundle({
|
|
5
|
+
organization: {
|
|
6
|
+
url: [
|
|
7
|
+
'https://provider.example.org',
|
|
8
|
+
EXAMPLE_TENANT_SERVICE_DID,
|
|
9
|
+
],
|
|
10
|
+
},
|
|
11
|
+
controller: {
|
|
12
|
+
sameAs: EXAMPLE_CONTROLLER_BINDING.sameAs,
|
|
13
|
+
},
|
|
14
|
+
});
|
|
@@ -17,6 +17,8 @@ export declare const EXAMPLE_NETWORK_TYPE: "test";
|
|
|
17
17
|
export declare const EXAMPLE_ROUTE_VERSION: "v1";
|
|
18
18
|
export declare const EXAMPLE_SECTOR: "health-care";
|
|
19
19
|
export declare const EXAMPLE_EMAIL_CONTROLLER_ORG: "controller@acme.org";
|
|
20
|
+
export declare const EXAMPLE_ORGANIZATION_LEGAL_NAME: "ACME HEALTH SL";
|
|
21
|
+
export declare const EXAMPLE_LEGAL_ORGANIZATION_TAX_ID: "VATES-B00112233";
|
|
20
22
|
export declare const EXAMPLE_EMAIL_CONTROLLER_INDIVIDUAL: "ana.parent@example.org";
|
|
21
23
|
export declare const EXAMPLE_INTEROPERABLE_CONTEXT_FHIR_API: "org.hl7.fhir.api";
|
|
22
24
|
export declare const EXAMPLE_SELF_REGISTERED_INDIVIDUAL_ALTERNATE_NAME: "Jane";
|
|
@@ -27,6 +29,7 @@ export declare const EXAMPLE_SELF_REGISTERED_INDIVIDUAL_BIRTH_YEAR: "1980";
|
|
|
27
29
|
export declare const EXAMPLE_REGISTERED_SUBJECT_ALTERNATE_NAME: "Doraemon";
|
|
28
30
|
export declare const EXAMPLE_REGISTERED_SUBJECT_BIRTH_YEAR: "2020";
|
|
29
31
|
export declare const EXAMPLE_PDF_CONSENT_DATE: "2026-06-08";
|
|
32
|
+
export declare const EXAMPLE_SIGNED_TERMS_PDF_URL: "https://portal.example.org/files/legal-organization-signed-terms.pdf";
|
|
30
33
|
/**
|
|
31
34
|
* Public provider domain selected during autodiscovery.
|
|
32
35
|
*
|
|
@@ -88,6 +91,13 @@ export declare const EXAMPLE_TENANT_ROUTE_CONTEXT: {
|
|
|
88
91
|
export declare const EXAMPLE_HOST_ROUTE_CONTEXT: {
|
|
89
92
|
readonly hostCoverageScope: "EU";
|
|
90
93
|
readonly jurisdiction: "ES";
|
|
94
|
+
/**
|
|
95
|
+
* Host route segment used in `/host/cds-{hostCoverageScope}/v1/{hostNetwork}/...`.
|
|
96
|
+
*
|
|
97
|
+
* This is intentionally not the tenant business sector.
|
|
98
|
+
*/
|
|
99
|
+
readonly hostNetwork: "test";
|
|
100
|
+
/** @deprecated Use `hostNetwork`. */
|
|
91
101
|
readonly sector: "test";
|
|
92
102
|
};
|
|
93
103
|
export declare const EXAMPLE_CONTROLLER_DID: "did:web:people.acme.org:controllers:primary";
|
package/dist/examples/shared.js
CHANGED
|
@@ -32,6 +32,8 @@ export const EXAMPLE_NETWORK_TYPE = HostNetworkTypes.Test;
|
|
|
32
32
|
export const EXAMPLE_ROUTE_VERSION = 'v1';
|
|
33
33
|
export const EXAMPLE_SECTOR = DataspaceSectors.HealthCare;
|
|
34
34
|
export const EXAMPLE_EMAIL_CONTROLLER_ORG = 'controller@acme.org';
|
|
35
|
+
export const EXAMPLE_ORGANIZATION_LEGAL_NAME = 'ACME HEALTH SL';
|
|
36
|
+
export const EXAMPLE_LEGAL_ORGANIZATION_TAX_ID = 'VATES-B00112233';
|
|
35
37
|
export const EXAMPLE_EMAIL_CONTROLLER_INDIVIDUAL = 'ana.parent@example.org';
|
|
36
38
|
export const EXAMPLE_INTEROPERABLE_CONTEXT_FHIR_API = Format.FHIR_API;
|
|
37
39
|
export const EXAMPLE_SELF_REGISTERED_INDIVIDUAL_ALTERNATE_NAME = 'Jane';
|
|
@@ -42,6 +44,7 @@ export const EXAMPLE_SELF_REGISTERED_INDIVIDUAL_BIRTH_YEAR = '1980';
|
|
|
42
44
|
export const EXAMPLE_REGISTERED_SUBJECT_ALTERNATE_NAME = 'Doraemon';
|
|
43
45
|
export const EXAMPLE_REGISTERED_SUBJECT_BIRTH_YEAR = '2020';
|
|
44
46
|
export const EXAMPLE_PDF_CONSENT_DATE = '2026-06-08';
|
|
47
|
+
export const EXAMPLE_SIGNED_TERMS_PDF_URL = 'https://portal.example.org/files/legal-organization-signed-terms.pdf';
|
|
45
48
|
/**
|
|
46
49
|
* Public provider domain selected during autodiscovery.
|
|
47
50
|
*
|
|
@@ -103,7 +106,14 @@ export const EXAMPLE_TENANT_ROUTE_CONTEXT = {
|
|
|
103
106
|
export const EXAMPLE_HOST_ROUTE_CONTEXT = {
|
|
104
107
|
hostCoverageScope: EXAMPLE_HOST_COVERAGE_SCOPE,
|
|
105
108
|
jurisdiction: EXAMPLE_JURISDICTION,
|
|
106
|
-
|
|
109
|
+
/**
|
|
110
|
+
* Host route segment used in `/host/cds-{hostCoverageScope}/v1/{hostNetwork}/...`.
|
|
111
|
+
*
|
|
112
|
+
* This is intentionally not the tenant business sector.
|
|
113
|
+
*/
|
|
114
|
+
hostNetwork: EXAMPLE_NETWORK_TYPE,
|
|
115
|
+
/** @deprecated Use `hostNetwork`. */
|
|
116
|
+
sector: EXAMPLE_NETWORK_TYPE,
|
|
107
117
|
};
|
|
108
118
|
export const EXAMPLE_CONTROLLER_DID = 'did:web:people.acme.org:controllers:primary';
|
|
109
119
|
export const EXAMPLE_CONTROLLER_EMAIL = EXAMPLE_EMAIL_CONTROLLER_ORG;
|
|
@@ -84,3 +84,5 @@ export declare class BundleReader {
|
|
|
84
84
|
private buildSeverityBucket;
|
|
85
85
|
private resolveEntryIdentifier;
|
|
86
86
|
}
|
|
87
|
+
export declare function unwrapBundleLikeResponseBody(input: unknown): Record<string, unknown>;
|
|
88
|
+
export declare function readFirstBundleResourceFromResponseBody(input: unknown): Record<string, unknown> | undefined;
|
|
@@ -221,3 +221,17 @@ export class BundleReader {
|
|
|
221
221
|
function normalizeOptionalString(value) {
|
|
222
222
|
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
223
223
|
}
|
|
224
|
+
export function unwrapBundleLikeResponseBody(input) {
|
|
225
|
+
const body = input && typeof input === 'object' ? input : {};
|
|
226
|
+
const nested = body.body && typeof body.body === 'object' ? body.body : undefined;
|
|
227
|
+
const candidate = nested && (Array.isArray(nested.data) || Array.isArray(nested.entry))
|
|
228
|
+
? nested
|
|
229
|
+
: body;
|
|
230
|
+
return candidate && typeof candidate === 'object' ? candidate : {};
|
|
231
|
+
}
|
|
232
|
+
export function readFirstBundleResourceFromResponseBody(input) {
|
|
233
|
+
const reader = new BundleReader(unwrapBundleLikeResponseBody(input));
|
|
234
|
+
const first = reader.getEntries()[0];
|
|
235
|
+
const resource = first?.resource;
|
|
236
|
+
return resource && typeof resource === 'object' ? resource : undefined;
|
|
237
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ClinicalResourceBundleLike } from './clinical-resource-view.js';
|
|
2
|
+
export type ClinicalBundleTypeSummary = Readonly<{
|
|
3
|
+
resourceType: string;
|
|
4
|
+
count: number;
|
|
5
|
+
}>;
|
|
6
|
+
export type ClinicalBundleSummary = Readonly<{
|
|
7
|
+
totalEntries: number;
|
|
8
|
+
resourceTypes: ClinicalBundleTypeSummary[];
|
|
9
|
+
xhtmlEntries: number;
|
|
10
|
+
notedEntries: number;
|
|
11
|
+
}>;
|
|
12
|
+
/**
|
|
13
|
+
* Builds one high-level summary from a FHIR/IPS bundle already normalized to
|
|
14
|
+
* the common clinical-resource view contract.
|
|
15
|
+
*
|
|
16
|
+
* Intended for BFF/frontend/call-center code that needs to announce simple
|
|
17
|
+
* menu facts such as "how many medications are present" without hand-reading
|
|
18
|
+
* bundle entries.
|
|
19
|
+
*/
|
|
20
|
+
export declare function summarizeClinicalBundle(bundle: ClinicalResourceBundleLike): ClinicalBundleSummary;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
|
+
import { toClinicalResourceExpandedViews } from './clinical-resource-view.js';
|
|
3
|
+
/**
|
|
4
|
+
* Builds one high-level summary from a FHIR/IPS bundle already normalized to
|
|
5
|
+
* the common clinical-resource view contract.
|
|
6
|
+
*
|
|
7
|
+
* Intended for BFF/frontend/call-center code that needs to announce simple
|
|
8
|
+
* menu facts such as "how many medications are present" without hand-reading
|
|
9
|
+
* bundle entries.
|
|
10
|
+
*/
|
|
11
|
+
export function summarizeClinicalBundle(bundle) {
|
|
12
|
+
const views = toClinicalResourceExpandedViews(bundle);
|
|
13
|
+
const counts = new Map();
|
|
14
|
+
let xhtmlEntries = 0;
|
|
15
|
+
let notedEntries = 0;
|
|
16
|
+
views.forEach((view) => {
|
|
17
|
+
const resourceType = String(view.common.resourceType || 'Unknown').trim() || 'Unknown';
|
|
18
|
+
counts.set(resourceType, (counts.get(resourceType) || 0) + 1);
|
|
19
|
+
if (String(view.xhtml || '').trim()) {
|
|
20
|
+
xhtmlEntries += 1;
|
|
21
|
+
}
|
|
22
|
+
if (Array.isArray(view.notes) && view.notes.length > 0) {
|
|
23
|
+
notedEntries += 1;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
return {
|
|
27
|
+
totalEntries: views.length,
|
|
28
|
+
resourceTypes: [...counts.entries()]
|
|
29
|
+
.map(([resourceType, count]) => ({ resourceType, count }))
|
|
30
|
+
.sort((left, right) => left.resourceType.localeCompare(right.resourceType)),
|
|
31
|
+
xhtmlEntries,
|
|
32
|
+
notedEntries,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -1,10 +1,27 @@
|
|
|
1
|
-
export
|
|
1
|
+
export declare const DIDCOMM_COMMUNICATION_MODES: Readonly<{
|
|
2
|
+
readonly Plain: "plain";
|
|
3
|
+
readonly Strict: "strict";
|
|
4
|
+
readonly AutoDetect: "auto-detect";
|
|
5
|
+
}>;
|
|
6
|
+
export type CommunicationMode = typeof DIDCOMM_COMMUNICATION_MODES[keyof typeof DIDCOMM_COMMUNICATION_MODES];
|
|
7
|
+
export declare const DIDCOMM_SUBMIT_KINDS: Readonly<{
|
|
8
|
+
readonly Plain: "plain";
|
|
9
|
+
readonly Encrypted: "encrypted";
|
|
10
|
+
}>;
|
|
11
|
+
export type DidcommSubmitKind = typeof DIDCOMM_SUBMIT_KINDS[keyof typeof DIDCOMM_SUBMIT_KINDS];
|
|
2
12
|
export type DidcommSubmissionCapabilities = {
|
|
3
13
|
hasRecipientEncryptionJwk: boolean;
|
|
4
14
|
};
|
|
15
|
+
export declare const DIDCOMM_SUBMISSION_REASONS: Readonly<{
|
|
16
|
+
readonly PlainMode: "plain-mode";
|
|
17
|
+
readonly StrictMode: "strict-mode";
|
|
18
|
+
readonly AutoDetectEncrypted: "auto-detect-encrypted";
|
|
19
|
+
readonly AutoDetectPlain: "auto-detect-plain";
|
|
20
|
+
}>;
|
|
21
|
+
export type DidcommSubmissionReason = typeof DIDCOMM_SUBMISSION_REASONS[keyof typeof DIDCOMM_SUBMISSION_REASONS];
|
|
5
22
|
export type DidcommSubmissionPlan = {
|
|
6
23
|
mode: CommunicationMode;
|
|
7
|
-
submitKind:
|
|
8
|
-
reason:
|
|
24
|
+
submitKind: DidcommSubmitKind;
|
|
25
|
+
reason: DidcommSubmissionReason;
|
|
9
26
|
};
|
|
10
27
|
export declare function resolveDidcommSubmissionPlan(mode: CommunicationMode, capabilities: DidcommSubmissionCapabilities): DidcommSubmissionPlan;
|
|
@@ -1,15 +1,46 @@
|
|
|
1
|
+
export const DIDCOMM_COMMUNICATION_MODES = Object.freeze({
|
|
2
|
+
Plain: 'plain',
|
|
3
|
+
Strict: 'strict',
|
|
4
|
+
AutoDetect: 'auto-detect',
|
|
5
|
+
});
|
|
6
|
+
export const DIDCOMM_SUBMIT_KINDS = Object.freeze({
|
|
7
|
+
Plain: 'plain',
|
|
8
|
+
Encrypted: 'encrypted',
|
|
9
|
+
});
|
|
10
|
+
export const DIDCOMM_SUBMISSION_REASONS = Object.freeze({
|
|
11
|
+
PlainMode: 'plain-mode',
|
|
12
|
+
StrictMode: 'strict-mode',
|
|
13
|
+
AutoDetectEncrypted: 'auto-detect-encrypted',
|
|
14
|
+
AutoDetectPlain: 'auto-detect-plain',
|
|
15
|
+
});
|
|
1
16
|
export function resolveDidcommSubmissionPlan(mode, capabilities) {
|
|
2
|
-
if (mode ===
|
|
3
|
-
return {
|
|
17
|
+
if (mode === DIDCOMM_COMMUNICATION_MODES.Plain) {
|
|
18
|
+
return {
|
|
19
|
+
mode,
|
|
20
|
+
submitKind: DIDCOMM_SUBMIT_KINDS.Plain,
|
|
21
|
+
reason: DIDCOMM_SUBMISSION_REASONS.PlainMode,
|
|
22
|
+
};
|
|
4
23
|
}
|
|
5
|
-
if (mode ===
|
|
24
|
+
if (mode === DIDCOMM_COMMUNICATION_MODES.Strict) {
|
|
6
25
|
if (!capabilities.hasRecipientEncryptionJwk) {
|
|
7
26
|
throw new Error('strict mode requires recipient encryption JWK.');
|
|
8
27
|
}
|
|
9
|
-
return {
|
|
28
|
+
return {
|
|
29
|
+
mode,
|
|
30
|
+
submitKind: DIDCOMM_SUBMIT_KINDS.Encrypted,
|
|
31
|
+
reason: DIDCOMM_SUBMISSION_REASONS.StrictMode,
|
|
32
|
+
};
|
|
10
33
|
}
|
|
11
34
|
if (capabilities.hasRecipientEncryptionJwk) {
|
|
12
|
-
return {
|
|
35
|
+
return {
|
|
36
|
+
mode,
|
|
37
|
+
submitKind: DIDCOMM_SUBMIT_KINDS.Encrypted,
|
|
38
|
+
reason: DIDCOMM_SUBMISSION_REASONS.AutoDetectEncrypted,
|
|
39
|
+
};
|
|
13
40
|
}
|
|
14
|
-
return {
|
|
41
|
+
return {
|
|
42
|
+
mode,
|
|
43
|
+
submitKind: DIDCOMM_SUBMIT_KINDS.Plain,
|
|
44
|
+
reason: DIDCOMM_SUBMISSION_REASONS.AutoDetectPlain,
|
|
45
|
+
};
|
|
15
46
|
}
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import { CommunicationMode } from './didcomm-submit-policy';
|
|
1
|
+
import { CommunicationMode, type DidcommSubmitKind } from './didcomm-submit-policy';
|
|
2
|
+
export declare const DIDCOMM_PLAINTEXT_JSON_MEDIA_TYPE: "application/didcomm-plaintext+json";
|
|
3
|
+
export declare const DIDCOMM_ENCRYPTED_JSON_MEDIA_TYPE: "application/didcomm-encrypted+json";
|
|
4
|
+
export declare const DIDCOMM_DEFAULT_ACCEPT_HEADER: "application/json, application/didcomm-plaintext+json, */*";
|
|
5
|
+
export declare const DIDCOMM_CONTENT_TYPE_BY_SUBMIT_KIND: Readonly<{
|
|
6
|
+
readonly plain: "application/didcomm-plaintext+json";
|
|
7
|
+
readonly encrypted: "application/didcomm-encrypted+json";
|
|
8
|
+
}>;
|
|
2
9
|
export type DidcommFetchInit = {
|
|
3
10
|
method: 'POST';
|
|
4
11
|
headers: Record<string, string>;
|
|
@@ -28,8 +35,8 @@ export type DidcommSubmitResult = {
|
|
|
28
35
|
status: number;
|
|
29
36
|
location?: string;
|
|
30
37
|
body: unknown;
|
|
31
|
-
submitKind:
|
|
32
|
-
contentType:
|
|
38
|
+
submitKind: DidcommSubmitKind;
|
|
39
|
+
contentType: typeof DIDCOMM_PLAINTEXT_JSON_MEDIA_TYPE | typeof DIDCOMM_ENCRYPTED_JSON_MEDIA_TYPE;
|
|
33
40
|
};
|
|
34
41
|
/**
|
|
35
42
|
* Submits a DIDComm payload using either plaintext or encrypted transport,
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import { resolveDidcommSubmissionPlan } from './didcomm-submit-policy.js';
|
|
1
|
+
import { DIDCOMM_SUBMIT_KINDS, resolveDidcommSubmissionPlan, } from './didcomm-submit-policy.js';
|
|
2
|
+
export const DIDCOMM_PLAINTEXT_JSON_MEDIA_TYPE = 'application/didcomm-plaintext+json';
|
|
3
|
+
export const DIDCOMM_ENCRYPTED_JSON_MEDIA_TYPE = 'application/didcomm-encrypted+json';
|
|
4
|
+
export const DIDCOMM_DEFAULT_ACCEPT_HEADER = `application/json, ${DIDCOMM_PLAINTEXT_JSON_MEDIA_TYPE}, */*`;
|
|
5
|
+
export const DIDCOMM_CONTENT_TYPE_BY_SUBMIT_KIND = Object.freeze({
|
|
6
|
+
[DIDCOMM_SUBMIT_KINDS.Plain]: DIDCOMM_PLAINTEXT_JSON_MEDIA_TYPE,
|
|
7
|
+
[DIDCOMM_SUBMIT_KINDS.Encrypted]: DIDCOMM_ENCRYPTED_JSON_MEDIA_TYPE,
|
|
8
|
+
});
|
|
2
9
|
function getHeaderValue(headers, name) {
|
|
3
10
|
if (!headers || typeof headers.get !== 'function') {
|
|
4
11
|
return undefined;
|
|
@@ -40,12 +47,12 @@ export async function submitDidcomm(input) {
|
|
|
40
47
|
});
|
|
41
48
|
const headers = {
|
|
42
49
|
...(input.defaultHeaders ?? {}),
|
|
43
|
-
Accept:
|
|
50
|
+
Accept: DIDCOMM_DEFAULT_ACCEPT_HEADER,
|
|
44
51
|
};
|
|
45
52
|
let body;
|
|
46
53
|
let contentType;
|
|
47
|
-
if (plan.submitKind ===
|
|
48
|
-
contentType =
|
|
54
|
+
if (plan.submitKind === DIDCOMM_SUBMIT_KINDS.Plain) {
|
|
55
|
+
contentType = DIDCOMM_CONTENT_TYPE_BY_SUBMIT_KIND[DIDCOMM_SUBMIT_KINDS.Plain];
|
|
49
56
|
body = JSON.stringify(input.payload);
|
|
50
57
|
}
|
|
51
58
|
else {
|
|
@@ -60,7 +67,7 @@ export async function submitDidcomm(input) {
|
|
|
60
67
|
}
|
|
61
68
|
const compactJws = await input.signCompactJws(input.payload);
|
|
62
69
|
body = await input.encryptCompactJwe(compactJws, input.recipientEncryptionJwk);
|
|
63
|
-
contentType =
|
|
70
|
+
contentType = DIDCOMM_CONTENT_TYPE_BY_SUBMIT_KIND[DIDCOMM_SUBMIT_KINDS.Encrypted];
|
|
64
71
|
}
|
|
65
72
|
headers['Content-Type'] = contentType;
|
|
66
73
|
if (input.bearerToken) {
|