vet-data-utils-ts 0.5.25 → 0.5.26
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 +9 -0
- package/dist/health-dcat.d.ts +78 -1
- package/dist/health-dcat.js +161 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -215,6 +215,15 @@ authorization are separate checks performed by their owning adapters.
|
|
|
215
215
|
`projectHealthDcatResourceToJsonLd()` is the explicit standards serialization
|
|
216
216
|
boundary used by host/dataspace adapters.
|
|
217
217
|
|
|
218
|
+
Research publication is study-scoped. A derived cohort Dataset links to its
|
|
219
|
+
generating `ResearchStudy` through PROV-O and a separate `CatalogRecord`
|
|
220
|
+
controls whether external catalogue searches may return it. Archiving that
|
|
221
|
+
record withdraws discovery without deleting the study, eligibility/team
|
|
222
|
+
Groups, ResearchSubjects or Dataset, and without changing `dct:accessRights`.
|
|
223
|
+
No individual ResearchSubject identifier is projected into HealthDCAT.
|
|
224
|
+
`Distribution` describes the HDAB-controlled representation; `DataService`
|
|
225
|
+
describes the host API serving Dataset metadata.
|
|
226
|
+
|
|
218
227
|
`vet-data-utils-ts/organization-application` is the product-neutral application
|
|
219
228
|
value used by UHC UNID, VetChain and SOSChain adapters. The host supplies the
|
|
220
229
|
allowed sector list and translated labels. The value contains one official
|
package/dist/health-dcat.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export declare enum ClaimsHealthDcatDataset {
|
|
|
19
19
|
distribution = "dcat.distribution",
|
|
20
20
|
source = "dct.source",
|
|
21
21
|
provenance = "dct.provenance",
|
|
22
|
+
wasGeneratedBy = "prov.wasGeneratedBy",
|
|
22
23
|
personalData = "dpv.hasPersonalData",
|
|
23
24
|
healthCategory = "healthdcatap.healthCategory",
|
|
24
25
|
healthDataAccessBody = "healthdcatap.hdab",
|
|
@@ -38,13 +39,47 @@ export declare enum ClaimsHealthDcatCatalog {
|
|
|
38
39
|
dataset = "dcat.dataset",
|
|
39
40
|
userSelected = "Catalog.user-selected"
|
|
40
41
|
}
|
|
42
|
+
/** DCAT Catalogue Record editorial claims; status is distinct from data access rights. */
|
|
43
|
+
export declare enum ClaimsHealthDcatCatalogRecord {
|
|
44
|
+
identifier = "dct.identifier",
|
|
45
|
+
primaryTopic = "foaf.primaryTopic",
|
|
46
|
+
status = "adms.status",
|
|
47
|
+
modified = "dct.modified",
|
|
48
|
+
conformsTo = "dct.conformsTo",
|
|
49
|
+
userSelected = "CatalogRecord.user-selected"
|
|
50
|
+
}
|
|
51
|
+
/** DCAT Distribution claims used for downloadable or controlled-access representations. */
|
|
52
|
+
export declare enum ClaimsHealthDcatDistribution {
|
|
53
|
+
identifier = "dct.identifier",
|
|
54
|
+
title = "dct.title",
|
|
55
|
+
accessUrl = "dcat.accessURL",
|
|
56
|
+
format = "dct.format",
|
|
57
|
+
mediaType = "dcat.mediaType",
|
|
58
|
+
accessRights = "dct.accessRights",
|
|
59
|
+
policy = "odrl.hasPolicy",
|
|
60
|
+
userSelected = "Distribution.user-selected"
|
|
61
|
+
}
|
|
62
|
+
/** DCAT DataService claims used for host APIs serving one or more Datasets. */
|
|
63
|
+
export declare enum ClaimsHealthDcatDataService {
|
|
64
|
+
identifier = "dct.identifier",
|
|
65
|
+
title = "dct.title",
|
|
66
|
+
publisher = "dct.publisher",
|
|
67
|
+
endpointUrl = "dcat.endpointURL",
|
|
68
|
+
servesDataset = "dcat.servesDataset",
|
|
69
|
+
accessRights = "dct.accessRights",
|
|
70
|
+
userSelected = "DataService.user-selected"
|
|
71
|
+
}
|
|
72
|
+
export type HealthDcatCatalogRecordStatus = 'draft' | 'published' | 'archived';
|
|
41
73
|
/** Complete allowlist for product-persisted HealthDCAT flat claims. */
|
|
42
74
|
export declare const HealthDcatFlatClaimCatalog: Readonly<{
|
|
43
75
|
readonly Dataset: readonly ClaimsHealthDcatDataset[];
|
|
44
76
|
readonly Catalog: readonly ClaimsHealthDcatCatalog[];
|
|
77
|
+
readonly CatalogRecord: readonly ClaimsHealthDcatCatalogRecord[];
|
|
78
|
+
readonly Distribution: readonly ClaimsHealthDcatDistribution[];
|
|
79
|
+
readonly DataService: readonly ClaimsHealthDcatDataService[];
|
|
45
80
|
}>;
|
|
46
81
|
export type HealthDcatResource = Readonly<{
|
|
47
|
-
resourceType: 'Dataset' | 'Catalog';
|
|
82
|
+
resourceType: 'Dataset' | 'Catalog' | 'CatalogRecord' | 'Distribution' | 'DataService';
|
|
48
83
|
id: string;
|
|
49
84
|
meta: Readonly<{
|
|
50
85
|
claims: Readonly<Record<string, ClaimValue>>;
|
|
@@ -87,6 +122,48 @@ export declare function buildHealthDcatCatalog(input: ControllerAuthority & Read
|
|
|
87
122
|
datasetIdentifiers: readonly string[];
|
|
88
123
|
applicableLegislation: readonly string[];
|
|
89
124
|
}>): HealthDcatResource;
|
|
125
|
+
/** Builds a governed Dataset Distribution without exposing the source records. */
|
|
126
|
+
export declare function buildHealthDcatDistribution(input: ControllerAuthority & Readonly<{
|
|
127
|
+
id: string;
|
|
128
|
+
title: string;
|
|
129
|
+
accessUrl: string;
|
|
130
|
+
format: string;
|
|
131
|
+
mediaType?: string;
|
|
132
|
+
accessRights: HealthDcatAccessRights;
|
|
133
|
+
healthDataAccessBodyIdentifier: string;
|
|
134
|
+
policyIdentifier?: string;
|
|
135
|
+
}>): HealthDcatResource;
|
|
136
|
+
/** Builds metadata for a host API that serves published Dataset descriptions. */
|
|
137
|
+
export declare function buildHealthDcatDataService(input: ControllerAuthority & Readonly<{
|
|
138
|
+
id: string;
|
|
139
|
+
title: string;
|
|
140
|
+
endpointUrl: string;
|
|
141
|
+
servesDatasetIdentifiers: readonly string[];
|
|
142
|
+
accessRights: HealthDcatAccessRights;
|
|
143
|
+
}>): HealthDcatResource;
|
|
144
|
+
/**
|
|
145
|
+
* Links one cohort Dataset to the ResearchStudy that generated it and creates
|
|
146
|
+
* the separate editorial CatalogRecord controlling external discoverability.
|
|
147
|
+
* ResearchSubject identifiers are deliberately absent from this projection.
|
|
148
|
+
*/
|
|
149
|
+
export declare function buildResearchStudyDatasetPublication(input: ControllerAuthority & Readonly<{
|
|
150
|
+
dataset: unknown;
|
|
151
|
+
researchStudyIdentifier: string;
|
|
152
|
+
catalogRecordIdentifier: string;
|
|
153
|
+
status: HealthDcatCatalogRecordStatus;
|
|
154
|
+
modifiedAt: string;
|
|
155
|
+
}>): Readonly<{
|
|
156
|
+
dataset: HealthDcatResource;
|
|
157
|
+
record: HealthDcatResource;
|
|
158
|
+
}>;
|
|
159
|
+
/** Controller-only editorial transition; archiving removes external discovery only. */
|
|
160
|
+
export declare function updateHealthDcatCatalogRecordStatus(input: ControllerAuthority & Readonly<{
|
|
161
|
+
record: unknown;
|
|
162
|
+
status: HealthDcatCatalogRecordStatus;
|
|
163
|
+
modifiedAt: string;
|
|
164
|
+
}>): HealthDcatResource;
|
|
165
|
+
/** True only for records eligible for an external marketplace search result. */
|
|
166
|
+
export declare function isHealthDcatCatalogRecordExternallyDiscoverable(record: unknown): boolean;
|
|
90
167
|
/** Creates an auditable tenant authorization for host-provided catalogue federation. */
|
|
91
168
|
export declare function buildHealthDcatFederationRequest(input: ControllerAuthority & Readonly<{
|
|
92
169
|
catalogIdentifier: string;
|
package/dist/health-dcat.js
CHANGED
|
@@ -21,6 +21,7 @@ export var ClaimsHealthDcatDataset;
|
|
|
21
21
|
ClaimsHealthDcatDataset["distribution"] = "dcat.distribution";
|
|
22
22
|
ClaimsHealthDcatDataset["source"] = "dct.source";
|
|
23
23
|
ClaimsHealthDcatDataset["provenance"] = "dct.provenance";
|
|
24
|
+
ClaimsHealthDcatDataset["wasGeneratedBy"] = "prov.wasGeneratedBy";
|
|
24
25
|
ClaimsHealthDcatDataset["personalData"] = "dpv.hasPersonalData";
|
|
25
26
|
ClaimsHealthDcatDataset["healthCategory"] = "healthdcatap.healthCategory";
|
|
26
27
|
ClaimsHealthDcatDataset["healthDataAccessBody"] = "healthdcatap.hdab";
|
|
@@ -41,17 +42,53 @@ export var ClaimsHealthDcatCatalog;
|
|
|
41
42
|
ClaimsHealthDcatCatalog["dataset"] = "dcat.dataset";
|
|
42
43
|
ClaimsHealthDcatCatalog["userSelected"] = "Catalog.user-selected";
|
|
43
44
|
})(ClaimsHealthDcatCatalog || (ClaimsHealthDcatCatalog = {}));
|
|
45
|
+
/** DCAT Catalogue Record editorial claims; status is distinct from data access rights. */
|
|
46
|
+
export var ClaimsHealthDcatCatalogRecord;
|
|
47
|
+
(function (ClaimsHealthDcatCatalogRecord) {
|
|
48
|
+
ClaimsHealthDcatCatalogRecord["identifier"] = "dct.identifier";
|
|
49
|
+
ClaimsHealthDcatCatalogRecord["primaryTopic"] = "foaf.primaryTopic";
|
|
50
|
+
ClaimsHealthDcatCatalogRecord["status"] = "adms.status";
|
|
51
|
+
ClaimsHealthDcatCatalogRecord["modified"] = "dct.modified";
|
|
52
|
+
ClaimsHealthDcatCatalogRecord["conformsTo"] = "dct.conformsTo";
|
|
53
|
+
ClaimsHealthDcatCatalogRecord["userSelected"] = "CatalogRecord.user-selected";
|
|
54
|
+
})(ClaimsHealthDcatCatalogRecord || (ClaimsHealthDcatCatalogRecord = {}));
|
|
55
|
+
/** DCAT Distribution claims used for downloadable or controlled-access representations. */
|
|
56
|
+
export var ClaimsHealthDcatDistribution;
|
|
57
|
+
(function (ClaimsHealthDcatDistribution) {
|
|
58
|
+
ClaimsHealthDcatDistribution["identifier"] = "dct.identifier";
|
|
59
|
+
ClaimsHealthDcatDistribution["title"] = "dct.title";
|
|
60
|
+
ClaimsHealthDcatDistribution["accessUrl"] = "dcat.accessURL";
|
|
61
|
+
ClaimsHealthDcatDistribution["format"] = "dct.format";
|
|
62
|
+
ClaimsHealthDcatDistribution["mediaType"] = "dcat.mediaType";
|
|
63
|
+
ClaimsHealthDcatDistribution["accessRights"] = "dct.accessRights";
|
|
64
|
+
ClaimsHealthDcatDistribution["policy"] = "odrl.hasPolicy";
|
|
65
|
+
ClaimsHealthDcatDistribution["userSelected"] = "Distribution.user-selected";
|
|
66
|
+
})(ClaimsHealthDcatDistribution || (ClaimsHealthDcatDistribution = {}));
|
|
67
|
+
/** DCAT DataService claims used for host APIs serving one or more Datasets. */
|
|
68
|
+
export var ClaimsHealthDcatDataService;
|
|
69
|
+
(function (ClaimsHealthDcatDataService) {
|
|
70
|
+
ClaimsHealthDcatDataService["identifier"] = "dct.identifier";
|
|
71
|
+
ClaimsHealthDcatDataService["title"] = "dct.title";
|
|
72
|
+
ClaimsHealthDcatDataService["publisher"] = "dct.publisher";
|
|
73
|
+
ClaimsHealthDcatDataService["endpointUrl"] = "dcat.endpointURL";
|
|
74
|
+
ClaimsHealthDcatDataService["servesDataset"] = "dcat.servesDataset";
|
|
75
|
+
ClaimsHealthDcatDataService["accessRights"] = "dct.accessRights";
|
|
76
|
+
ClaimsHealthDcatDataService["userSelected"] = "DataService.user-selected";
|
|
77
|
+
})(ClaimsHealthDcatDataService || (ClaimsHealthDcatDataService = {}));
|
|
44
78
|
/** Complete allowlist for product-persisted HealthDCAT flat claims. */
|
|
45
79
|
export const HealthDcatFlatClaimCatalog = Object.freeze({
|
|
46
80
|
Dataset: Object.freeze(Object.values(ClaimsHealthDcatDataset)),
|
|
47
81
|
Catalog: Object.freeze(Object.values(ClaimsHealthDcatCatalog)),
|
|
82
|
+
CatalogRecord: Object.freeze(Object.values(ClaimsHealthDcatCatalogRecord)),
|
|
83
|
+
Distribution: Object.freeze(Object.values(ClaimsHealthDcatDistribution)),
|
|
84
|
+
DataService: Object.freeze(Object.values(ClaimsHealthDcatDataService)),
|
|
48
85
|
});
|
|
49
86
|
/** Validates an indexed HealthDCAT resource and rejects nested or unknown claims. */
|
|
50
87
|
export function normalizeHealthDcatResource(candidate) {
|
|
51
88
|
if (!candidate || typeof candidate !== 'object')
|
|
52
89
|
throw new TypeError('health_dcat_resource_invalid');
|
|
53
90
|
const value = candidate;
|
|
54
|
-
if (
|
|
91
|
+
if (!['Dataset', 'Catalog', 'CatalogRecord', 'Distribution', 'DataService'].includes(String(value.resourceType)))
|
|
55
92
|
throw new TypeError('health_dcat_resource_invalid');
|
|
56
93
|
if (typeof value.id !== 'string' || !value.id.trim())
|
|
57
94
|
throw new TypeError('health_dcat_resource_invalid');
|
|
@@ -60,7 +97,8 @@ export function normalizeHealthDcatResource(candidate) {
|
|
|
60
97
|
const claims = value.meta.claims;
|
|
61
98
|
if (!claims || typeof claims !== 'object' || Array.isArray(claims))
|
|
62
99
|
throw new TypeError('health_dcat_resource_invalid');
|
|
63
|
-
const
|
|
100
|
+
const resourceType = value.resourceType;
|
|
101
|
+
const allowed = HealthDcatFlatClaimCatalog[resourceType];
|
|
64
102
|
const normalized = {};
|
|
65
103
|
for (const [claim, raw] of Object.entries(claims)) {
|
|
66
104
|
if (!allowed.includes(claim))
|
|
@@ -73,7 +111,7 @@ export function normalizeHealthDcatResource(candidate) {
|
|
|
73
111
|
throw new TypeError(`health_dcat_claim_value_invalid:${claim}`);
|
|
74
112
|
}
|
|
75
113
|
return Object.freeze({
|
|
76
|
-
resourceType
|
|
114
|
+
resourceType,
|
|
77
115
|
id: value.id,
|
|
78
116
|
meta: Object.freeze({ claims: Object.freeze(normalized) }),
|
|
79
117
|
});
|
|
@@ -90,6 +128,7 @@ const JsonLdClaimNames = Object.freeze({
|
|
|
90
128
|
[ClaimsHealthDcatDataset.distribution]: 'dcat:distribution',
|
|
91
129
|
[ClaimsHealthDcatDataset.source]: 'dct:source',
|
|
92
130
|
[ClaimsHealthDcatDataset.provenance]: 'dct:provenance',
|
|
131
|
+
[ClaimsHealthDcatDataset.wasGeneratedBy]: 'prov:wasGeneratedBy',
|
|
93
132
|
[ClaimsHealthDcatDataset.personalData]: 'dpv:hasPersonalData',
|
|
94
133
|
[ClaimsHealthDcatDataset.healthCategory]: 'healthdcatap:healthCategory',
|
|
95
134
|
[ClaimsHealthDcatDataset.healthDataAccessBody]: 'healthdcatap:hdab',
|
|
@@ -97,6 +136,16 @@ const JsonLdClaimNames = Object.freeze({
|
|
|
97
136
|
[ClaimsHealthDcatDataset.variables]: 'healthdcatap:hasVariables',
|
|
98
137
|
[ClaimsHealthDcatDataset.authorizationReference]: 'prov:qualifiedAttribution',
|
|
99
138
|
[ClaimsHealthDcatCatalog.dataset]: 'dcat:dataset',
|
|
139
|
+
[ClaimsHealthDcatCatalogRecord.primaryTopic]: 'foaf:primaryTopic',
|
|
140
|
+
[ClaimsHealthDcatCatalogRecord.status]: 'adms:status',
|
|
141
|
+
[ClaimsHealthDcatCatalogRecord.modified]: 'dct:modified',
|
|
142
|
+
[ClaimsHealthDcatCatalogRecord.conformsTo]: 'dct:conformsTo',
|
|
143
|
+
[ClaimsHealthDcatDistribution.accessUrl]: 'dcat:accessURL',
|
|
144
|
+
[ClaimsHealthDcatDistribution.format]: 'dct:format',
|
|
145
|
+
[ClaimsHealthDcatDistribution.mediaType]: 'dcat:mediaType',
|
|
146
|
+
[ClaimsHealthDcatDistribution.policy]: 'odrl:hasPolicy',
|
|
147
|
+
[ClaimsHealthDcatDataService.endpointUrl]: 'dcat:endpointURL',
|
|
148
|
+
[ClaimsHealthDcatDataService.servesDataset]: 'dcat:servesDataset',
|
|
100
149
|
});
|
|
101
150
|
/** Explicitly projects internal flat claims to DCAT/HealthDCAT JSON-LD. */
|
|
102
151
|
export function projectHealthDcatResourceToJsonLd(resource) {
|
|
@@ -110,9 +159,12 @@ export function projectHealthDcatResourceToJsonLd(resource) {
|
|
|
110
159
|
healthdcatap: 'http://healthdataportal.eu/ns/health#',
|
|
111
160
|
dpv: 'https://w3id.org/dpv#',
|
|
112
161
|
prov: 'http://www.w3.org/ns/prov#',
|
|
162
|
+
foaf: 'http://xmlns.com/foaf/0.1/',
|
|
163
|
+
adms: 'http://www.w3.org/ns/adms#',
|
|
164
|
+
odrl: 'http://www.w3.org/ns/odrl/2/',
|
|
113
165
|
}),
|
|
114
166
|
'@id': normalized.id,
|
|
115
|
-
'@type': normalized.resourceType
|
|
167
|
+
'@type': `dcat:${normalized.resourceType}`,
|
|
116
168
|
};
|
|
117
169
|
for (const [claim, value] of Object.entries(normalized.meta.claims)) {
|
|
118
170
|
const jsonLdName = JsonLdClaimNames[claim];
|
|
@@ -197,6 +249,111 @@ export function buildHealthDcatCatalog(input) {
|
|
|
197
249
|
};
|
|
198
250
|
return Object.freeze({ resourceType: 'Catalog', id: input.id, meta: Object.freeze({ claims: Object.freeze(claims) }) });
|
|
199
251
|
}
|
|
252
|
+
/** Builds a governed Dataset Distribution without exposing the source records. */
|
|
253
|
+
export function buildHealthDcatDistribution(input) {
|
|
254
|
+
assertController(input);
|
|
255
|
+
required(input.healthDataAccessBodyIdentifier, 'healthDataAccessBodyIdentifier');
|
|
256
|
+
const accessUrl = required(input.accessUrl, 'accessUrl');
|
|
257
|
+
if (!accessUrl.startsWith('https://'))
|
|
258
|
+
throw new TypeError('accessUrl must use HTTPS');
|
|
259
|
+
const claims = {
|
|
260
|
+
[ClaimsHealthDcatDistribution.identifier]: required(input.id, 'id'),
|
|
261
|
+
[ClaimsHealthDcatDistribution.title]: required(input.title, 'title'),
|
|
262
|
+
[ClaimsHealthDcatDistribution.accessUrl]: accessUrl,
|
|
263
|
+
[ClaimsHealthDcatDistribution.format]: required(input.format, 'format'),
|
|
264
|
+
[ClaimsHealthDcatDistribution.accessRights]: input.accessRights,
|
|
265
|
+
[ClaimsHealthDcatDistribution.userSelected]: true,
|
|
266
|
+
};
|
|
267
|
+
if (input.mediaType)
|
|
268
|
+
claims[ClaimsHealthDcatDistribution.mediaType] = required(input.mediaType, 'mediaType');
|
|
269
|
+
if (input.policyIdentifier)
|
|
270
|
+
claims[ClaimsHealthDcatDistribution.policy] = required(input.policyIdentifier, 'policyIdentifier');
|
|
271
|
+
return normalizeHealthDcatResource({ resourceType: 'Distribution', id: input.id, meta: { claims } });
|
|
272
|
+
}
|
|
273
|
+
/** Builds metadata for a host API that serves published Dataset descriptions. */
|
|
274
|
+
export function buildHealthDcatDataService(input) {
|
|
275
|
+
assertController(input);
|
|
276
|
+
const endpointUrl = required(input.endpointUrl, 'endpointUrl');
|
|
277
|
+
if (!endpointUrl.startsWith('https://'))
|
|
278
|
+
throw new TypeError('endpointUrl must use HTTPS');
|
|
279
|
+
if (!input.servesDatasetIdentifiers.length)
|
|
280
|
+
throw new TypeError('servesDatasetIdentifiers are required');
|
|
281
|
+
return normalizeHealthDcatResource({
|
|
282
|
+
resourceType: 'DataService', id: input.id, meta: { claims: {
|
|
283
|
+
[ClaimsHealthDcatDataService.identifier]: required(input.id, 'id'),
|
|
284
|
+
[ClaimsHealthDcatDataService.title]: required(input.title, 'title'),
|
|
285
|
+
[ClaimsHealthDcatDataService.publisher]: required(input.organizationIdentifier, 'organizationIdentifier'),
|
|
286
|
+
[ClaimsHealthDcatDataService.endpointUrl]: endpointUrl,
|
|
287
|
+
[ClaimsHealthDcatDataService.servesDataset]: Object.freeze([...input.servesDatasetIdentifiers]),
|
|
288
|
+
[ClaimsHealthDcatDataService.accessRights]: input.accessRights,
|
|
289
|
+
[ClaimsHealthDcatDataService.userSelected]: true,
|
|
290
|
+
} },
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Links one cohort Dataset to the ResearchStudy that generated it and creates
|
|
295
|
+
* the separate editorial CatalogRecord controlling external discoverability.
|
|
296
|
+
* ResearchSubject identifiers are deliberately absent from this projection.
|
|
297
|
+
*/
|
|
298
|
+
export function buildResearchStudyDatasetPublication(input) {
|
|
299
|
+
assertController(input);
|
|
300
|
+
const dataset = normalizeHealthDcatResource(input.dataset);
|
|
301
|
+
if (dataset.resourceType !== 'Dataset')
|
|
302
|
+
throw new TypeError('research_dataset_required');
|
|
303
|
+
const researchStudyIdentifier = required(input.researchStudyIdentifier, 'researchStudyIdentifier');
|
|
304
|
+
if (!researchStudyIdentifier.startsWith('ResearchStudy/'))
|
|
305
|
+
throw new TypeError('research_study_reference_invalid');
|
|
306
|
+
const modifiedAt = new Date(input.modifiedAt);
|
|
307
|
+
if (!Number.isFinite(modifiedAt.getTime()) || modifiedAt.toISOString() !== input.modifiedAt)
|
|
308
|
+
throw new TypeError('modifiedAt must be an ISO instant');
|
|
309
|
+
const linkedDataset = normalizeHealthDcatResource({
|
|
310
|
+
...dataset,
|
|
311
|
+
meta: { claims: { ...dataset.meta.claims, [ClaimsHealthDcatDataset.wasGeneratedBy]: researchStudyIdentifier } },
|
|
312
|
+
});
|
|
313
|
+
const recordId = required(input.catalogRecordIdentifier, 'catalogRecordIdentifier');
|
|
314
|
+
const record = normalizeHealthDcatResource({
|
|
315
|
+
resourceType: 'CatalogRecord',
|
|
316
|
+
id: recordId,
|
|
317
|
+
meta: { claims: {
|
|
318
|
+
[ClaimsHealthDcatCatalogRecord.identifier]: recordId,
|
|
319
|
+
[ClaimsHealthDcatCatalogRecord.primaryTopic]: `Dataset/${dataset.id}`,
|
|
320
|
+
[ClaimsHealthDcatCatalogRecord.status]: input.status,
|
|
321
|
+
[ClaimsHealthDcatCatalogRecord.modified]: input.modifiedAt,
|
|
322
|
+
[ClaimsHealthDcatCatalogRecord.conformsTo]: 'HealthDCAT-AP Release 7',
|
|
323
|
+
[ClaimsHealthDcatCatalogRecord.userSelected]: true,
|
|
324
|
+
} },
|
|
325
|
+
});
|
|
326
|
+
return Object.freeze({ dataset: linkedDataset, record });
|
|
327
|
+
}
|
|
328
|
+
/** Controller-only editorial transition; archiving removes external discovery only. */
|
|
329
|
+
export function updateHealthDcatCatalogRecordStatus(input) {
|
|
330
|
+
assertController(input);
|
|
331
|
+
const record = normalizeHealthDcatResource(input.record);
|
|
332
|
+
if (record.resourceType !== 'CatalogRecord')
|
|
333
|
+
throw new TypeError('health_dcat_catalog_record_required');
|
|
334
|
+
const timestamp = new Date(input.modifiedAt);
|
|
335
|
+
if (!Number.isFinite(timestamp.getTime()) || timestamp.toISOString() !== input.modifiedAt)
|
|
336
|
+
throw new TypeError('modifiedAt must be an ISO instant');
|
|
337
|
+
return normalizeHealthDcatResource({
|
|
338
|
+
...record,
|
|
339
|
+
meta: { claims: {
|
|
340
|
+
...record.meta.claims,
|
|
341
|
+
[ClaimsHealthDcatCatalogRecord.status]: input.status,
|
|
342
|
+
[ClaimsHealthDcatCatalogRecord.modified]: input.modifiedAt,
|
|
343
|
+
} },
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
/** True only for records eligible for an external marketplace search result. */
|
|
347
|
+
export function isHealthDcatCatalogRecordExternallyDiscoverable(record) {
|
|
348
|
+
try {
|
|
349
|
+
const normalized = normalizeHealthDcatResource(record);
|
|
350
|
+
return normalized.resourceType === 'CatalogRecord'
|
|
351
|
+
&& normalized.meta.claims[ClaimsHealthDcatCatalogRecord.status] === 'published';
|
|
352
|
+
}
|
|
353
|
+
catch {
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
200
357
|
/** Creates an auditable tenant authorization for host-provided catalogue federation. */
|
|
201
358
|
export function buildHealthDcatFederationRequest(input) {
|
|
202
359
|
assertController(input);
|