gdc-common-utils-ts 2.6.4 → 2.7.1
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 +6 -0
- package/dist/constants/didcomm.d.ts +1 -0
- package/dist/constants/didcomm.js +1 -0
- package/dist/examples/didcomm-identity.d.ts +23 -0
- package/dist/examples/didcomm-identity.js +65 -0
- package/dist/examples/index.d.ts +1 -0
- package/dist/examples/index.js +1 -0
- package/dist/examples/shared.d.ts +1 -1
- package/dist/examples/shared.js +3 -1
- package/dist/models/confidential-message.d.ts +12 -3
- package/dist/utils/authority-resolution.js +3 -1
- package/dist/utils/bundle-document-builder.d.ts +8 -0
- package/dist/utils/bundle-document-builder.js +30 -12
- package/dist/utils/did.js +14 -1
- package/dist/utils/license-list-search.js +14 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -59,6 +59,11 @@ It is intentionally not a full backend orchestration layer.
|
|
|
59
59
|
- Only define custom names when no canonical FHIR SearchParameter exists.
|
|
60
60
|
- `resource.meta.claims` is the canonical project-specific claims container and must be preserved across conversions/transports.
|
|
61
61
|
- `resource.meta.claims` is not part of base FHIR; it is a claims-first extension carried by FHIR-like resources in GDC contracts.
|
|
62
|
+
- Claims-first aggregates may intentionally mix related resource families. A
|
|
63
|
+
Digital Twin `ResearchSubject.meta.claims` contains both `ResearchSubject.*`
|
|
64
|
+
and `Composition.*`; strict FHIR R4/R5 translates the Composition into
|
|
65
|
+
`ResearchSubject.contained[]`. Producers never invent a
|
|
66
|
+
`ResearchSubject.composition` wire property.
|
|
62
67
|
- Native FHIR resources received from EHR systems do not need that extension:
|
|
63
68
|
normalize them with `normalizeClaimsFromFhirResource(...)` at the processing
|
|
64
69
|
boundary before indexed storage. Existing `resource.meta.claims` take
|
|
@@ -147,6 +152,7 @@ entry types, FHIR-like resources, and `resource.meta.claims` fit together,
|
|
|
147
152
|
read first:
|
|
148
153
|
|
|
149
154
|
- [`docs/101-ID_TOKEN.md`](docs/101-ID_TOKEN.md)
|
|
155
|
+
- [`docs/101-DIDCOMM-IDENTITY-BOUNDARY.md`](docs/101-DIDCOMM-IDENTITY-BOUNDARY.md)
|
|
150
156
|
- [`docs/101-COMMUNICATION_LAYERING.md`](docs/101-COMMUNICATION_LAYERING.md)
|
|
151
157
|
- [`docs/101-BUNDLE_EDITOR_READER.md`](docs/101-BUNDLE_EDITOR_READER.md)
|
|
152
158
|
- [`docs/101-CLINICAL-IPS.md`](docs/101-CLINICAL-IPS.md)
|
|
@@ -10,6 +10,7 @@ export declare const DidcommMessageTypes: Readonly<{
|
|
|
10
10
|
readonly CommunicationAttachedBundle: "Communication-attached-bundle-v1.0";
|
|
11
11
|
readonly CommunicationBundleSubmit: "Communication-bundle-submit-v1.0";
|
|
12
12
|
readonly CommunicationResponse: "Communication-response-v1.0";
|
|
13
|
+
readonly SmartTokenRequest: "Smart-token-request-v1.0";
|
|
13
14
|
}>;
|
|
14
15
|
/**
|
|
15
16
|
* Canonical low-level DIDComm acknowledgement body keys used in shared tests
|
|
@@ -10,6 +10,7 @@ export const DidcommMessageTypes = Object.freeze({
|
|
|
10
10
|
CommunicationAttachedBundle: 'Communication-attached-bundle-v1.0',
|
|
11
11
|
CommunicationBundleSubmit: 'Communication-bundle-submit-v1.0',
|
|
12
12
|
CommunicationResponse: 'Communication-response-v1.0',
|
|
13
|
+
SmartTokenRequest: 'Smart-token-request-v1.0',
|
|
13
14
|
});
|
|
14
15
|
/**
|
|
15
16
|
* Canonical low-level DIDComm acknowledgement body keys used in shared tests
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { IDecodedDidcommPayload } from '../models/confidential-message';
|
|
2
|
+
/** Signing verification method controlled by the direct actor DID. */
|
|
3
|
+
export declare const EXAMPLE_DIRECT_ACTOR_SIGNING_KEY_ID: `${string}#communication-signing`;
|
|
4
|
+
/** Signing verification method registered for the DCR client/device. */
|
|
5
|
+
export declare const EXAMPLE_DCR_DEVICE_SIGNING_KEY_ID: "did:web:device-001#dcr-signing";
|
|
6
|
+
/**
|
|
7
|
+
* Canonical direct DIDComm/FAPI message.
|
|
8
|
+
*
|
|
9
|
+
* `from` and `iss` identify the same direct sender. `kid` identifies the
|
|
10
|
+
* concrete signing key and therefore remains in the signed JWS header. The
|
|
11
|
+
* FHIR-like Communication stays inside `body`; transport identity never
|
|
12
|
+
* becomes a FHIR field.
|
|
13
|
+
*/
|
|
14
|
+
export declare const EXAMPLE_DIRECT_ACTOR_DIDCOMM_MESSAGE: IDecodedDidcommPayload;
|
|
15
|
+
/**
|
|
16
|
+
* Canonical device-bound SMART request.
|
|
17
|
+
*
|
|
18
|
+
* The DCR client/device is the DIDComm/FAPI sender and issuer because it signs
|
|
19
|
+
* the request. Its registered `kid` selects the proof key. SMART `sub`
|
|
20
|
+
* separately identifies the professional actor and `scope` separately limits
|
|
21
|
+
* the subject data requested by that actor.
|
|
22
|
+
*/
|
|
23
|
+
export declare const EXAMPLE_DEVICE_BOUND_SMART_DIDCOMM_MESSAGE: IDecodedDidcommPayload;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { DidcommMessageTypes } from '../constants/didcomm.js';
|
|
2
|
+
import { buildSmartCompositionReadScope } from '../utils/smart-scope.js';
|
|
3
|
+
import { EXAMPLE_DEVICE_CLIENT_ID, EXAMPLE_HOSTING_OPERATOR_DID, EXAMPLE_PROFESSIONAL_DID, EXAMPLE_SUBJECT_DID, buildExampleCommunicationIngestionPayload, } from './shared.js';
|
|
4
|
+
/** Signing verification method controlled by the direct actor DID. */
|
|
5
|
+
export const EXAMPLE_DIRECT_ACTOR_SIGNING_KEY_ID = `${EXAMPLE_PROFESSIONAL_DID}#communication-signing`;
|
|
6
|
+
/** Signing verification method registered for the DCR client/device. */
|
|
7
|
+
export const EXAMPLE_DCR_DEVICE_SIGNING_KEY_ID = `${EXAMPLE_DEVICE_CLIENT_ID}#dcr-signing`;
|
|
8
|
+
/**
|
|
9
|
+
* Canonical direct DIDComm/FAPI message.
|
|
10
|
+
*
|
|
11
|
+
* `from` and `iss` identify the same direct sender. `kid` identifies the
|
|
12
|
+
* concrete signing key and therefore remains in the signed JWS header. The
|
|
13
|
+
* FHIR-like Communication stays inside `body`; transport identity never
|
|
14
|
+
* becomes a FHIR field.
|
|
15
|
+
*/
|
|
16
|
+
export const EXAMPLE_DIRECT_ACTOR_DIDCOMM_MESSAGE = {
|
|
17
|
+
meta: {
|
|
18
|
+
jws: {
|
|
19
|
+
protected: {
|
|
20
|
+
alg: 'ES384',
|
|
21
|
+
kid: EXAMPLE_DIRECT_ACTOR_SIGNING_KEY_ID,
|
|
22
|
+
typ: 'JWT',
|
|
23
|
+
cty: 'application/didcomm-signed+json',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
iss: EXAMPLE_PROFESSIONAL_DID,
|
|
28
|
+
from: EXAMPLE_PROFESSIONAL_DID,
|
|
29
|
+
aud: EXAMPLE_HOSTING_OPERATOR_DID,
|
|
30
|
+
jti: 'urn:uuid:didcomm-direct-example',
|
|
31
|
+
thid: 'didcomm-direct-example',
|
|
32
|
+
type: DidcommMessageTypes.CommunicationAttachedBundle,
|
|
33
|
+
body: buildExampleCommunicationIngestionPayload().body,
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Canonical device-bound SMART request.
|
|
37
|
+
*
|
|
38
|
+
* The DCR client/device is the DIDComm/FAPI sender and issuer because it signs
|
|
39
|
+
* the request. Its registered `kid` selects the proof key. SMART `sub`
|
|
40
|
+
* separately identifies the professional actor and `scope` separately limits
|
|
41
|
+
* the subject data requested by that actor.
|
|
42
|
+
*/
|
|
43
|
+
export const EXAMPLE_DEVICE_BOUND_SMART_DIDCOMM_MESSAGE = {
|
|
44
|
+
meta: {
|
|
45
|
+
jws: {
|
|
46
|
+
protected: {
|
|
47
|
+
alg: 'ES384',
|
|
48
|
+
kid: EXAMPLE_DCR_DEVICE_SIGNING_KEY_ID,
|
|
49
|
+
typ: 'JWT',
|
|
50
|
+
cty: 'application/didcomm-signed+json',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
iss: EXAMPLE_DEVICE_CLIENT_ID,
|
|
55
|
+
from: EXAMPLE_DEVICE_CLIENT_ID,
|
|
56
|
+
aud: EXAMPLE_HOSTING_OPERATOR_DID,
|
|
57
|
+
jti: 'urn:uuid:didcomm-smart-example',
|
|
58
|
+
thid: 'didcomm-smart-example',
|
|
59
|
+
type: DidcommMessageTypes.SmartTokenRequest,
|
|
60
|
+
body: {
|
|
61
|
+
client_id: EXAMPLE_DEVICE_CLIENT_ID,
|
|
62
|
+
sub: EXAMPLE_PROFESSIONAL_DID,
|
|
63
|
+
scope: buildSmartCompositionReadScope({ subjectDid: EXAMPLE_SUBJECT_DID }),
|
|
64
|
+
},
|
|
65
|
+
};
|
package/dist/examples/index.d.ts
CHANGED
package/dist/examples/index.js
CHANGED
|
@@ -661,7 +661,7 @@ export declare function buildExampleCommunicationIngestionPayload({ subjectDid,
|
|
|
661
661
|
}[];
|
|
662
662
|
meta: {
|
|
663
663
|
claims: {
|
|
664
|
-
'@context': "org.hl7.fhir.
|
|
664
|
+
'@context': "org.hl7.fhir.api";
|
|
665
665
|
"Communication.category": string;
|
|
666
666
|
"Communication.subject": string;
|
|
667
667
|
"Communication.sent": string;
|
package/dist/examples/shared.js
CHANGED
|
@@ -640,7 +640,9 @@ export function buildExampleCommunicationIngestionPayload({ subjectDid = EXAMPLE
|
|
|
640
640
|
note: [{ text: EXAMPLE_IPS_BUNDLE_NOTE_TEXT }],
|
|
641
641
|
meta: {
|
|
642
642
|
claims: {
|
|
643
|
-
|
|
643
|
+
// The surrounding resource is native FHIR R4, while flat
|
|
644
|
+
// claims remain version-neutral and can later render as R4 or R5.
|
|
645
|
+
'@context': Format.FHIR_API,
|
|
644
646
|
[CommunicationClaim.Category]: CommunicationCategoryCodes.Notification.claim,
|
|
645
647
|
[CommunicationClaim.Subject]: subjectDid,
|
|
646
648
|
[CommunicationClaim.Sent]: sent,
|
|
@@ -43,8 +43,11 @@ export interface IDecodedDidcommPayload {
|
|
|
43
43
|
/** Relevant information available through the decryption and verification process */
|
|
44
44
|
meta?: DidCommDecodedMetadata;
|
|
45
45
|
/**
|
|
46
|
-
* (Issuer)
|
|
47
|
-
* REQUIRED for FAPI. MUST
|
|
46
|
+
* (Issuer) DID of the entity that issued and signed the message.
|
|
47
|
+
* REQUIRED for FAPI. It MUST be bound to the signer of the enclosing JWS.
|
|
48
|
+
* The signing key is identified separately by `meta.jws.protected.kid`;
|
|
49
|
+
* `iss` is never the key id. For a DCR-bound client this is normally the
|
|
50
|
+
* client/device DID, while a SMART human actor remains in `body.sub`.
|
|
48
51
|
*/
|
|
49
52
|
iss: string;
|
|
50
53
|
/**
|
|
@@ -71,7 +74,13 @@ export interface IDecodedDidcommPayload {
|
|
|
71
74
|
pthid?: string;
|
|
72
75
|
/** The DID of the intended recipient. Used for P2P messaging, informational in client-server requests. */
|
|
73
76
|
to?: string[];
|
|
74
|
-
/**
|
|
77
|
+
/**
|
|
78
|
+
* DIDComm sender DID. In a direct signed request this normally equals `iss`.
|
|
79
|
+
* It is never a raw email, telephone, card id or `urn:multibase` alias. When
|
|
80
|
+
* a DCR client acts for a human, `from`/`iss` identify the client and SMART
|
|
81
|
+
* `body.sub` identifies the human actor. `iss` remains authoritative for
|
|
82
|
+
* the FAPI signature boundary.
|
|
83
|
+
*/
|
|
75
84
|
from?: string;
|
|
76
85
|
/**
|
|
77
86
|
* The Message Type URI, identifying the type of data in the body or protocol used.
|
|
@@ -4,7 +4,9 @@ function normalizeString(value) {
|
|
|
4
4
|
return typeof value === 'string' ? value.trim() : '';
|
|
5
5
|
}
|
|
6
6
|
function encodeDidWebAuthority(authority) {
|
|
7
|
-
|
|
7
|
+
// DNS hostnames are case-insensitive, while the canonical did:web spelling
|
|
8
|
+
// keeps URI percent escapes uppercase for exact DID-document comparisons.
|
|
9
|
+
return authority.toLowerCase().replace(/:/g, '%3A');
|
|
8
10
|
}
|
|
9
11
|
function normalizeAuthorityHost(value) {
|
|
10
12
|
const raw = normalizeString(value).replace(/\/+$/, '');
|
|
@@ -51,6 +51,14 @@ export declare function getBundleDocumentPatientFullName(bundle: Record<string,
|
|
|
51
51
|
* equal by accident.
|
|
52
52
|
*/
|
|
53
53
|
export declare function normalizeFullNameForComparison(value: unknown): string;
|
|
54
|
+
/**
|
|
55
|
+
* Detects the native FHIR resource represented by one version-neutral claims
|
|
56
|
+
* map. A Composition-only map is a complete resource input: it is not merely
|
|
57
|
+
* metadata for another resource and must remain translatable for strict FHIR
|
|
58
|
+
* `contained[]` output. When another resource prefix is present,
|
|
59
|
+
* `Composition.*` remains section-membership metadata and cannot replace that
|
|
60
|
+
* resource's type.
|
|
61
|
+
*/
|
|
54
62
|
export declare function detectClaimsResourceType(claims: BundleDocumentClaims): string | undefined;
|
|
55
63
|
export declare function convertClaimsToFhirResource(claims: BundleDocumentClaims, version?: 'r4'): FhirResource;
|
|
56
64
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ResourceTypesFhirR4 } from '../constants/fhir-resource-types.js';
|
|
2
|
+
import { Format } from '../constants/Schemas.js';
|
|
2
3
|
import { AllergyIntoleranceClaim } from '../models/interoperable-claims/allergy-intolerance-claims.js';
|
|
3
4
|
import { CompositionClaim } from '../models/interoperable-claims/composition-claims.js';
|
|
4
5
|
import { ConditionClaim } from '../models/interoperable-claims/condition-claims.js';
|
|
@@ -7,7 +8,13 @@ import { MedicationStatementClaim } from '../models/interoperable-claims/medicat
|
|
|
7
8
|
import { ClaimConsent } from '../models/consent-rule.js';
|
|
8
9
|
import { allergyIntoleranceFlatToFhirR4, appointmentResponseFlatToFhirR4, appointmentFlatToFhirR4, carePlanFlatToFhirR4, clinicalImpressionFlatToFhirR4, compositionFlatToFhirR4, conditionFlatToFhirR4, consentFlatToFhirR4, convertFhirResourceToClaims, coverageFlatToFhirR4, deviceFlatToFhirR4, deviceUseStatementFlatToFhirR4, documentReferenceFlatToFhirR4, diagnosticReportFlatToFhirR4, encounterFlatToFhirR4, flagFlatToFhirR4, flatClaimsToFhirResource, immunizationFlatToFhirR4, locationFlatToFhirR4, medicationStatementFlatToFhirR4, observationFromFlatToFhirR4, organizationFlatToFhirR4, procedureFlatToFhirR4, practitionerRoleFlatToFhirR4, relatedPersonFlatToFhirR4, } from './clinical-resource-converters.js';
|
|
9
10
|
/** Version-independent FHIR SearchParameter claim context. */
|
|
10
|
-
const FHIR_API_CLAIMS_CONTEXT =
|
|
11
|
+
const FHIR_API_CLAIMS_CONTEXT = Format.FHIR_API;
|
|
12
|
+
const READABLE_FHIR_CLAIMS_CONTEXTS = [
|
|
13
|
+
Format.FHIR_API,
|
|
14
|
+
// Deprecated response compatibility. New flat-claim writers use FHIR_API
|
|
15
|
+
// regardless of the selected native FHIR representation version.
|
|
16
|
+
Format.FHIR_R4,
|
|
17
|
+
];
|
|
11
18
|
function asTrimmedString(value) {
|
|
12
19
|
if (value === undefined || value === null)
|
|
13
20
|
return '';
|
|
@@ -23,9 +30,7 @@ export function getSimpleClaimAttributeName(key) {
|
|
|
23
30
|
const value = asTrimmedString(key);
|
|
24
31
|
if (!value)
|
|
25
32
|
return '';
|
|
26
|
-
const knownPrefixes =
|
|
27
|
-
`${FHIR_API_CLAIMS_CONTEXT}.`,
|
|
28
|
-
];
|
|
33
|
+
const knownPrefixes = READABLE_FHIR_CLAIMS_CONTEXTS.map((context) => `${context}.`);
|
|
29
34
|
for (const prefix of knownPrefixes) {
|
|
30
35
|
if (value.startsWith(prefix)) {
|
|
31
36
|
return value.slice(prefix.length);
|
|
@@ -40,15 +45,19 @@ export function extractFlatClaimValue(record, key) {
|
|
|
40
45
|
const direct = record[normalizedKey];
|
|
41
46
|
if (typeof direct === 'string' && direct.trim())
|
|
42
47
|
return direct.trim();
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
for (const context of READABLE_FHIR_CLAIMS_CONTEXTS) {
|
|
49
|
+
const contextualized = record[`${context}.${normalizedKey}`];
|
|
50
|
+
if (typeof contextualized === 'string' && contextualized.trim())
|
|
51
|
+
return contextualized.trim();
|
|
52
|
+
}
|
|
46
53
|
const nested = record?.meta?.claims?.[normalizedKey];
|
|
47
54
|
if (typeof nested === 'string' && nested.trim())
|
|
48
55
|
return nested.trim();
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
56
|
+
for (const context of READABLE_FHIR_CLAIMS_CONTEXTS) {
|
|
57
|
+
const nestedContextualized = record?.meta?.claims?.[`${context}.${normalizedKey}`];
|
|
58
|
+
if (typeof nestedContextualized === 'string' && nestedContextualized.trim())
|
|
59
|
+
return nestedContextualized.trim();
|
|
60
|
+
}
|
|
52
61
|
return '';
|
|
53
62
|
}
|
|
54
63
|
function claimsToFlatStrings(claims) {
|
|
@@ -328,6 +337,14 @@ function mergeContainedResourceReferenceList(claims, references) {
|
|
|
328
337
|
[claimKey]: next.join(','),
|
|
329
338
|
};
|
|
330
339
|
}
|
|
340
|
+
/**
|
|
341
|
+
* Detects the native FHIR resource represented by one version-neutral claims
|
|
342
|
+
* map. A Composition-only map is a complete resource input: it is not merely
|
|
343
|
+
* metadata for another resource and must remain translatable for strict FHIR
|
|
344
|
+
* `contained[]` output. When another resource prefix is present,
|
|
345
|
+
* `Composition.*` remains section-membership metadata and cannot replace that
|
|
346
|
+
* resource's type.
|
|
347
|
+
*/
|
|
331
348
|
export function detectClaimsResourceType(claims) {
|
|
332
349
|
const keys = Object.keys(claims || {});
|
|
333
350
|
if (keys.some((key) => key.startsWith('MedicationStatement.')))
|
|
@@ -338,9 +355,10 @@ export function detectClaimsResourceType(claims) {
|
|
|
338
355
|
return ResourceTypesFhirR4.Condition;
|
|
339
356
|
if (keys.some((key) => key.startsWith('DocumentReference.')))
|
|
340
357
|
return ResourceTypesFhirR4.DocumentReference;
|
|
341
|
-
const
|
|
358
|
+
const qualifiedKeys = keys
|
|
342
359
|
.map((key) => getSimpleClaimAttributeName(key))
|
|
343
|
-
.
|
|
360
|
+
.filter((key) => key.includes('.') && !key.startsWith('@'));
|
|
361
|
+
const firstContextualized = qualifiedKeys.find((key) => !key.startsWith(`${ResourceTypesFhirR4.Composition}.`)) || qualifiedKeys.find((key) => key.startsWith(`${ResourceTypesFhirR4.Composition}.`));
|
|
344
362
|
if (firstContextualized) {
|
|
345
363
|
return firstContextualized.split('.')[0];
|
|
346
364
|
}
|
package/dist/utils/did.js
CHANGED
|
@@ -224,12 +224,25 @@ export function getBaseUrlFromDidWeb(did) {
|
|
|
224
224
|
const domainPart = didParts[0];
|
|
225
225
|
const pathParts = didParts.slice(1);
|
|
226
226
|
const decodedDomain = decodeURIComponent(domainPart);
|
|
227
|
-
const protocol = decodedDomain
|
|
227
|
+
const protocol = isLoopbackDidWebAuthority(decodedDomain) ? 'http' : 'https';
|
|
228
228
|
const path = pathParts.join('/').replace(/cds-(es|us|gb)/, (match, p1) => `cds-${p1.toUpperCase()}`);
|
|
229
229
|
// Ensure a trailing slash for the base URL, without double slashes when no path is present.
|
|
230
230
|
const normalizedPath = path ? `${path}/` : '';
|
|
231
231
|
return `${protocol}://${decodedDomain}/${normalizedPath}`;
|
|
232
232
|
}
|
|
233
|
+
/** Keeps local did:web discovery usable without weakening non-loopback HTTPS. */
|
|
234
|
+
function isLoopbackDidWebAuthority(authority) {
|
|
235
|
+
try {
|
|
236
|
+
const hostname = new URL(`http://${authority}`).hostname.toLowerCase().replace(/^\[|\]$/g, '');
|
|
237
|
+
return hostname === 'localhost'
|
|
238
|
+
|| hostname.endsWith('.localhost')
|
|
239
|
+
|| hostname === '::1'
|
|
240
|
+
|| /^127(?:\.\d{1,3}){3}$/.test(hostname);
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
233
246
|
/**
|
|
234
247
|
* Builds the canonical hosted organization/tenant DID in the data space.
|
|
235
248
|
*
|
|
@@ -179,15 +179,23 @@ export function readLicenseListRecords(body) {
|
|
|
179
179
|
return data
|
|
180
180
|
.filter((entry) => !!entry && typeof entry === 'object')
|
|
181
181
|
.map((entry) => {
|
|
182
|
-
|
|
183
|
-
|
|
182
|
+
// Canonical search responses place one license row in `entry.resource`;
|
|
183
|
+
// deprecated aggregate responses placed rows in `resource.data[]`.
|
|
184
|
+
const primaryResource = entry.resource && typeof entry.resource === 'object' && !Array.isArray(entry.resource)
|
|
185
|
+
? entry.resource
|
|
186
|
+
: undefined;
|
|
187
|
+
const row = primaryResource && !Array.isArray(primaryResource.data) && isLicenseListEntry(primaryResource)
|
|
188
|
+
? primaryResource
|
|
189
|
+
: entry;
|
|
190
|
+
const claims = extractClaims(row);
|
|
191
|
+
const meta = row.meta && typeof row.meta === 'object' ? row.meta : {};
|
|
184
192
|
return {
|
|
185
193
|
id: normalizeText(claims[ClaimsIndividualProductSchemaorg.serialNumber]
|
|
186
194
|
|| claims[ClaimsOfferSchemaorg.serialNumber]
|
|
187
|
-
||
|
|
188
|
-
status: normalizeText(meta.status ||
|
|
189
|
-
subjectId: normalizeText(meta.subjectId ||
|
|
190
|
-
ownerOrganizationId: normalizeText(meta.ownerOrganizationId ||
|
|
195
|
+
|| row.id),
|
|
196
|
+
status: normalizeText(meta.status || row.status),
|
|
197
|
+
subjectId: normalizeText(meta.subjectId || row.subjectId),
|
|
198
|
+
ownerOrganizationId: normalizeText(meta.ownerOrganizationId || row.ownerOrganizationId || claims['License.ownerOrganizationId']),
|
|
191
199
|
email: normalizeText(claims[ClaimsPersonSchemaorg.email]),
|
|
192
200
|
telephone: normalizeText(claims[ClaimsPersonSchemaorg.telephone]),
|
|
193
201
|
role: normalizeText(claims[ClaimsPersonSchemaorg.hasOccupationalRoleValue]),
|