@medplum/fhir-router 2.0.21 → 2.0.23
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/dist/cjs/index.cjs +463 -299
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.min.cjs +1 -1
- package/dist/cjs/index.min.cjs.map +1 -1
- package/dist/esm/index.min.mjs +1 -1
- package/dist/esm/index.min.mjs.map +1 -1
- package/dist/esm/index.mjs +464 -300
- package/dist/esm/index.mjs.map +1 -1
- package/dist/types/batch.d.ts +0 -1
- package/dist/types/fhirrouter.d.ts +6 -1
- package/dist/types/graphql/graphql.d.ts +14 -0
- package/dist/types/graphql/index.d.ts +1 -0
- package/dist/types/graphql/input-types.d.ts +2 -0
- package/dist/types/graphql/output-types.d.ts +4 -0
- package/dist/types/graphql/utils.d.ts +46 -0
- package/dist/types/repo.d.ts +0 -15
- package/package.json +1 -1
- package/dist/types/graphql.d.ts +0 -10
package/dist/esm/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { OperationOutcomeError, badRequest, normalizeOperationOutcome, parseSearchUrl, getReferenceString, resolveId, getStatus, isOk, allOk,
|
|
1
|
+
import { OperationOutcomeError, badRequest, normalizeOperationOutcome, parseSearchUrl, getReferenceString, resolveId, getStatus, isOk, allOk, Operator, parseSearchRequest, getSearchParameters, getResourceTypeSchema, isResourceType, getElementDefinition, buildTypeName, capitalize, getResourceTypes, isResourceTypeSchema, isLowerCase, globalSchema, toJsBoolean, evalFhirPathTyped, toTypedValue, LRUCache, forbidden, DEFAULT_SEARCH_COUNT, notFound, created, deepClone, matchesSearchRequest, evalFhirPath } from '@medplum/core';
|
|
2
2
|
import DataLoader from 'dataloader';
|
|
3
3
|
import { applyPatch } from 'rfc6902';
|
|
4
4
|
|
|
@@ -6,7 +6,6 @@ import { applyPatch } from 'rfc6902';
|
|
|
6
6
|
* Processes a FHIR batch request.
|
|
7
7
|
*
|
|
8
8
|
* See: https://www.hl7.org/fhir/http.html#transaction
|
|
9
|
-
*
|
|
10
9
|
* @param router The FHIR router.
|
|
11
10
|
* @param repo The FHIR repository.
|
|
12
11
|
* @param bundle The input bundle.
|
|
@@ -34,8 +33,6 @@ class BatchProcessor {
|
|
|
34
33
|
}
|
|
35
34
|
/**
|
|
36
35
|
* Processes a FHIR batch request.
|
|
37
|
-
* @param repo The FHIR repository.
|
|
38
|
-
* @param bundle The input bundle.
|
|
39
36
|
* @returns The bundle response.
|
|
40
37
|
*/
|
|
41
38
|
async processBatch() {
|
|
@@ -13376,131 +13373,202 @@ const typeCache = {
|
|
|
13376
13373
|
'http://hl7.org/fhirpath/System.String': GraphQLString,
|
|
13377
13374
|
'http://hl7.org/fhirpath/System.Time': GraphQLString,
|
|
13378
13375
|
};
|
|
13376
|
+
function parseSearchArgs(resourceType, source, args) {
|
|
13377
|
+
let referenceFilter = undefined;
|
|
13378
|
+
if (source) {
|
|
13379
|
+
// _reference is a required field for reverse lookup searches
|
|
13380
|
+
// The GraphQL parser will validate that it is there.
|
|
13381
|
+
const reference = args['_reference'];
|
|
13382
|
+
delete args['_reference'];
|
|
13383
|
+
referenceFilter = {
|
|
13384
|
+
code: reference,
|
|
13385
|
+
operator: Operator.EQUALS,
|
|
13386
|
+
value: getReferenceString(source),
|
|
13387
|
+
};
|
|
13388
|
+
}
|
|
13389
|
+
// Reverse the transform of dashes to underscores, back to dashes
|
|
13390
|
+
args = Object.fromEntries(Object.entries(args).map(([key, value]) => [graphQLFieldToFhirParam(key), value]));
|
|
13391
|
+
// Parse the search request
|
|
13392
|
+
const searchRequest = parseSearchRequest(resourceType, args);
|
|
13393
|
+
// If a reverse lookup filter was specified,
|
|
13394
|
+
// add it to the search request.
|
|
13395
|
+
if (referenceFilter) {
|
|
13396
|
+
const existingFilters = searchRequest.filters || [];
|
|
13397
|
+
searchRequest.filters = [referenceFilter, ...existingFilters];
|
|
13398
|
+
}
|
|
13399
|
+
return searchRequest;
|
|
13400
|
+
}
|
|
13401
|
+
function graphQLFieldToFhirParam(code) {
|
|
13402
|
+
return code.startsWith('_') ? code : code.replaceAll('_', '-');
|
|
13403
|
+
}
|
|
13404
|
+
function fhirParamToGraphQLField(code) {
|
|
13405
|
+
return code.replaceAll('-', '_');
|
|
13406
|
+
}
|
|
13379
13407
|
/**
|
|
13380
|
-
*
|
|
13381
|
-
*
|
|
13382
|
-
* The
|
|
13408
|
+
* GraphQL data loader for search requests.
|
|
13409
|
+
* The field name should always end with "List" (i.e., "Patient" search uses "PatientList").
|
|
13410
|
+
* The search args should be FHIR search parameters.
|
|
13411
|
+
* @param source The source/root. This should always be null for our top level readers.
|
|
13412
|
+
* @param args The GraphQL search arguments.
|
|
13413
|
+
* @param ctx The GraphQL context.
|
|
13414
|
+
* @param info The GraphQL resolve info. This includes the schema, and additional field details.
|
|
13415
|
+
* @returns Promise to read the resoures for the query.
|
|
13383
13416
|
*/
|
|
13384
|
-
|
|
13417
|
+
async function resolveBySearch(source, args, ctx, info) {
|
|
13418
|
+
const fieldName = info.fieldName;
|
|
13419
|
+
const resourceType = fieldName.substring(0, fieldName.length - 'List'.length);
|
|
13420
|
+
const searchRequest = parseSearchArgs(resourceType, source, args);
|
|
13421
|
+
const bundle = await ctx.repo.search(searchRequest);
|
|
13422
|
+
return bundle.entry?.map((e) => e.resource);
|
|
13423
|
+
}
|
|
13424
|
+
function buildSearchArgs(resourceType) {
|
|
13425
|
+
const args = {
|
|
13426
|
+
_count: {
|
|
13427
|
+
type: GraphQLInt,
|
|
13428
|
+
description: 'Specify how many elements to return from a repeating list.',
|
|
13429
|
+
},
|
|
13430
|
+
_offset: {
|
|
13431
|
+
type: GraphQLInt,
|
|
13432
|
+
description: 'Specify the offset to start at for a repeating element.',
|
|
13433
|
+
},
|
|
13434
|
+
_sort: {
|
|
13435
|
+
type: GraphQLString,
|
|
13436
|
+
description: 'Specify the sort order by comma-separated list of sort rules in priority order.',
|
|
13437
|
+
},
|
|
13438
|
+
_id: {
|
|
13439
|
+
type: GraphQLString,
|
|
13440
|
+
description: 'Select resources based on the logical id of the resource.',
|
|
13441
|
+
},
|
|
13442
|
+
_lastUpdated: {
|
|
13443
|
+
type: GraphQLString,
|
|
13444
|
+
description: 'Select resources based on the last time they were changed.',
|
|
13445
|
+
},
|
|
13446
|
+
};
|
|
13447
|
+
const searchParams = getSearchParameters(resourceType);
|
|
13448
|
+
if (searchParams) {
|
|
13449
|
+
for (const [code, searchParam] of Object.entries(searchParams)) {
|
|
13450
|
+
// GraphQL does not support dashes in argument names
|
|
13451
|
+
// So convert dashes to underscores
|
|
13452
|
+
args[fhirParamToGraphQLField(code)] = {
|
|
13453
|
+
type: GraphQLString,
|
|
13454
|
+
description: searchParam.description,
|
|
13455
|
+
};
|
|
13456
|
+
}
|
|
13457
|
+
}
|
|
13458
|
+
return args;
|
|
13459
|
+
}
|
|
13385
13460
|
/**
|
|
13386
|
-
*
|
|
13387
|
-
*
|
|
13461
|
+
* Returns the depth of the GraphQL node in a query.
|
|
13462
|
+
* We use "selections" as the representation of depth.
|
|
13463
|
+
* As a rough approximation, it's the number of indentations in a well formatted query.
|
|
13464
|
+
* @param path The GraphQL node path.
|
|
13465
|
+
* @returns The "depth" of the node.
|
|
13388
13466
|
*/
|
|
13389
|
-
|
|
13467
|
+
function getDepth(path) {
|
|
13468
|
+
return path.filter((p) => p === 'selections').length;
|
|
13469
|
+
}
|
|
13390
13470
|
/**
|
|
13391
|
-
*
|
|
13392
|
-
*
|
|
13393
|
-
*
|
|
13471
|
+
* Returns true if the field is requested in the GraphQL query.
|
|
13472
|
+
* @param info The GraphQL resolve info. This includes the field name.
|
|
13473
|
+
* @param fieldName The field name to check.
|
|
13474
|
+
* @returns True if the field is requested in the GraphQL query.
|
|
13394
13475
|
*/
|
|
13395
|
-
|
|
13396
|
-
|
|
13397
|
-
|
|
13398
|
-
|
|
13399
|
-
}
|
|
13400
|
-
let document;
|
|
13401
|
-
try {
|
|
13402
|
-
document = parse(query);
|
|
13403
|
-
}
|
|
13404
|
-
catch (err) {
|
|
13405
|
-
return [badRequest('GraphQL syntax error.')];
|
|
13406
|
-
}
|
|
13407
|
-
const schema = getRootSchema();
|
|
13408
|
-
const validationRules = [...specifiedRules, MaxDepthRule];
|
|
13409
|
-
const validationErrors = validate(schema, document, validationRules);
|
|
13410
|
-
if (validationErrors.length > 0) {
|
|
13411
|
-
return [invalidRequest(validationErrors)];
|
|
13412
|
-
}
|
|
13413
|
-
const introspection = isIntrospectionQuery(query);
|
|
13414
|
-
if (introspection) {
|
|
13415
|
-
return [forbidden];
|
|
13416
|
-
}
|
|
13417
|
-
const dataLoader = new DataLoader((keys) => repo.readReferences(keys));
|
|
13418
|
-
let result = introspection && introspectionResults.get(query);
|
|
13419
|
-
if (!result) {
|
|
13420
|
-
result = await execute({
|
|
13421
|
-
schema,
|
|
13422
|
-
document,
|
|
13423
|
-
contextValue: { repo, dataLoader },
|
|
13424
|
-
operationName,
|
|
13425
|
-
variableValues: variables,
|
|
13426
|
-
});
|
|
13427
|
-
}
|
|
13428
|
-
return [allOk, result];
|
|
13476
|
+
function isFieldRequested(info, fieldName) {
|
|
13477
|
+
return info.fieldNodes.some((fieldNode) => fieldNode.selectionSet?.selections.some((selection) => {
|
|
13478
|
+
return selection.kind === 'Field' && selection.name.value === fieldName;
|
|
13479
|
+
}));
|
|
13429
13480
|
}
|
|
13430
13481
|
/**
|
|
13431
|
-
* Returns
|
|
13432
|
-
*
|
|
13433
|
-
*
|
|
13434
|
-
*
|
|
13435
|
-
* See: https://graphql.org/learn/introspection/
|
|
13436
|
-
*
|
|
13437
|
-
* @param query The GraphQL query.
|
|
13438
|
-
* @returns True if the query is an introspection query.
|
|
13482
|
+
* Returns an OperationOutcome for GraphQL errors.
|
|
13483
|
+
* @param errors Array of GraphQL errors.
|
|
13484
|
+
* @returns OperationOutcome with the GraphQL errors as OperationOutcome issues.
|
|
13439
13485
|
*/
|
|
13440
|
-
function
|
|
13441
|
-
return
|
|
13486
|
+
function invalidRequest(errors) {
|
|
13487
|
+
return {
|
|
13488
|
+
resourceType: 'OperationOutcome',
|
|
13489
|
+
issue: errors.map((error) => ({
|
|
13490
|
+
severity: 'error',
|
|
13491
|
+
code: 'invalid',
|
|
13492
|
+
details: { text: error.message },
|
|
13493
|
+
})),
|
|
13494
|
+
};
|
|
13442
13495
|
}
|
|
13443
|
-
|
|
13444
|
-
|
|
13445
|
-
|
|
13496
|
+
|
|
13497
|
+
const inputTypeCache = {
|
|
13498
|
+
...typeCache,
|
|
13499
|
+
};
|
|
13500
|
+
function getGraphQLInputType(inputType, nameSuffix) {
|
|
13501
|
+
let result = inputTypeCache[inputType];
|
|
13502
|
+
if (!result) {
|
|
13503
|
+
result = buildGraphQLInputType(inputType, nameSuffix);
|
|
13504
|
+
inputTypeCache[inputType] = result;
|
|
13446
13505
|
}
|
|
13447
|
-
return
|
|
13506
|
+
return result;
|
|
13448
13507
|
}
|
|
13449
|
-
function
|
|
13450
|
-
|
|
13451
|
-
|
|
13452
|
-
|
|
13453
|
-
|
|
13454
|
-
|
|
13455
|
-
|
|
13508
|
+
function buildGraphQLInputType(resourceType, nameSuffix) {
|
|
13509
|
+
const schema = getResourceTypeSchema(resourceType);
|
|
13510
|
+
return new GraphQLInputObjectType({
|
|
13511
|
+
name: resourceType + nameSuffix,
|
|
13512
|
+
description: schema.description,
|
|
13513
|
+
fields: () => buildGraphQLInputFields(resourceType, nameSuffix),
|
|
13514
|
+
});
|
|
13515
|
+
}
|
|
13516
|
+
function buildGraphQLInputFields(resourceType, nameSuffix) {
|
|
13456
13517
|
const fields = {};
|
|
13457
|
-
|
|
13458
|
-
|
|
13459
|
-
|
|
13460
|
-
|
|
13461
|
-
type:
|
|
13462
|
-
args: {
|
|
13463
|
-
id: {
|
|
13464
|
-
type: new GraphQLNonNull(GraphQLID),
|
|
13465
|
-
description: resourceType + ' ID',
|
|
13466
|
-
},
|
|
13467
|
-
},
|
|
13468
|
-
resolve: resolveById,
|
|
13469
|
-
};
|
|
13470
|
-
// Search resource by search parameters
|
|
13471
|
-
fields[resourceType + 'List'] = {
|
|
13472
|
-
type: new GraphQLList(graphQLType),
|
|
13473
|
-
args: buildSearchArgs(resourceType),
|
|
13474
|
-
resolve: resolveBySearch,
|
|
13475
|
-
};
|
|
13476
|
-
// FHIR GraphQL Connection API
|
|
13477
|
-
fields[resourceType + 'Connection'] = {
|
|
13478
|
-
type: buildConnectionType(resourceType, graphQLType),
|
|
13479
|
-
args: buildSearchArgs(resourceType),
|
|
13480
|
-
resolve: resolveByConnectionApi,
|
|
13518
|
+
// Add resourceType field for root resource
|
|
13519
|
+
if (isResourceType(resourceType)) {
|
|
13520
|
+
const propertyFieldConfig = {
|
|
13521
|
+
description: 'The type of resource',
|
|
13522
|
+
type: GraphQLString,
|
|
13481
13523
|
};
|
|
13524
|
+
fields['resourceType'] = propertyFieldConfig;
|
|
13482
13525
|
}
|
|
13483
|
-
|
|
13484
|
-
|
|
13485
|
-
|
|
13486
|
-
|
|
13487
|
-
|
|
13488
|
-
|
|
13526
|
+
buildInputPropertyFields(resourceType, fields, nameSuffix);
|
|
13527
|
+
return fields;
|
|
13528
|
+
}
|
|
13529
|
+
function buildInputPropertyFields(resourceType, fields, nameSuffix) {
|
|
13530
|
+
const schema = getResourceTypeSchema(resourceType);
|
|
13531
|
+
const properties = schema.properties;
|
|
13532
|
+
for (const key of Object.keys(properties)) {
|
|
13533
|
+
const elementDefinition = getElementDefinition(resourceType, key);
|
|
13534
|
+
for (const type of elementDefinition.type) {
|
|
13535
|
+
buildInputPropertyField(fields, key, elementDefinition, type, nameSuffix);
|
|
13536
|
+
}
|
|
13537
|
+
}
|
|
13538
|
+
}
|
|
13539
|
+
function buildInputPropertyField(fields, key, elementDefinition, elementDefinitionType, nameSuffix) {
|
|
13540
|
+
let typeName = elementDefinitionType.code;
|
|
13541
|
+
if (typeName === 'Element' || typeName === 'BackboneElement') {
|
|
13542
|
+
typeName = buildTypeName(elementDefinition.path?.split('.'));
|
|
13543
|
+
}
|
|
13544
|
+
const fieldConfig = {
|
|
13545
|
+
description: elementDefinition.short,
|
|
13546
|
+
type: getGraphQLInputType(typeName, nameSuffix),
|
|
13547
|
+
};
|
|
13548
|
+
if (elementDefinition.max === '*') {
|
|
13549
|
+
fieldConfig.type = new GraphQLList(getGraphQLInputType(typeName, nameSuffix));
|
|
13550
|
+
}
|
|
13551
|
+
const propertyName = key.replace('[x]', capitalize(elementDefinitionType.code));
|
|
13552
|
+
fields[propertyName] = fieldConfig;
|
|
13489
13553
|
}
|
|
13490
|
-
|
|
13491
|
-
|
|
13554
|
+
|
|
13555
|
+
const outputTypeCache = {
|
|
13556
|
+
...typeCache,
|
|
13557
|
+
};
|
|
13558
|
+
function getGraphQLOutputType(inputType) {
|
|
13559
|
+
let result = outputTypeCache[inputType];
|
|
13492
13560
|
if (!result) {
|
|
13493
|
-
result =
|
|
13494
|
-
|
|
13561
|
+
result = buildGraphQLOutputType(inputType);
|
|
13562
|
+
outputTypeCache[inputType] = result;
|
|
13495
13563
|
}
|
|
13496
13564
|
return result;
|
|
13497
13565
|
}
|
|
13498
|
-
function
|
|
13566
|
+
function buildGraphQLOutputType(resourceType) {
|
|
13499
13567
|
if (resourceType === 'ResourceList') {
|
|
13500
13568
|
return new GraphQLUnionType({
|
|
13501
13569
|
name: 'ResourceList',
|
|
13502
13570
|
types: () => getResourceTypes()
|
|
13503
|
-
.map(
|
|
13571
|
+
.map(getGraphQLOutputType)
|
|
13504
13572
|
.filter((t) => !!t),
|
|
13505
13573
|
resolveType: resolveTypeByReference,
|
|
13506
13574
|
});
|
|
@@ -13509,16 +13577,16 @@ function buildGraphQLType(resourceType) {
|
|
|
13509
13577
|
return new GraphQLObjectType({
|
|
13510
13578
|
name: resourceType,
|
|
13511
13579
|
description: schema.description,
|
|
13512
|
-
fields: () =>
|
|
13580
|
+
fields: () => buildGraphQLOutputFields(resourceType),
|
|
13513
13581
|
});
|
|
13514
13582
|
}
|
|
13515
|
-
function
|
|
13583
|
+
function buildGraphQLOutputFields(resourceType) {
|
|
13516
13584
|
const fields = {};
|
|
13517
|
-
|
|
13585
|
+
buildOutputPropertyFields(resourceType, fields);
|
|
13518
13586
|
buildReverseLookupFields(resourceType, fields);
|
|
13519
13587
|
return fields;
|
|
13520
13588
|
}
|
|
13521
|
-
function
|
|
13589
|
+
function buildOutputPropertyFields(resourceType, fields) {
|
|
13522
13590
|
const schema = getResourceTypeSchema(resourceType);
|
|
13523
13591
|
const properties = schema.properties;
|
|
13524
13592
|
if (isResourceTypeSchema(schema)) {
|
|
@@ -13530,25 +13598,25 @@ function buildPropertyFields(resourceType, fields) {
|
|
|
13530
13598
|
if (resourceType === 'Reference') {
|
|
13531
13599
|
fields.resource = {
|
|
13532
13600
|
description: 'Reference',
|
|
13533
|
-
type:
|
|
13601
|
+
type: getGraphQLOutputType('ResourceList'),
|
|
13534
13602
|
resolve: resolveByReference,
|
|
13535
13603
|
};
|
|
13536
13604
|
}
|
|
13537
13605
|
for (const key of Object.keys(properties)) {
|
|
13538
13606
|
const elementDefinition = getElementDefinition(resourceType, key);
|
|
13539
13607
|
for (const type of elementDefinition.type) {
|
|
13540
|
-
|
|
13608
|
+
buildOutputPropertyField(fields, key, elementDefinition, type);
|
|
13541
13609
|
}
|
|
13542
13610
|
}
|
|
13543
13611
|
}
|
|
13544
|
-
function
|
|
13612
|
+
function buildOutputPropertyField(fields, key, elementDefinition, elementDefinitionType) {
|
|
13545
13613
|
let typeName = elementDefinitionType.code;
|
|
13546
13614
|
if (typeName === 'Element' || typeName === 'BackboneElement') {
|
|
13547
13615
|
typeName = buildTypeName(elementDefinition.path?.split('.'));
|
|
13548
13616
|
}
|
|
13549
13617
|
const fieldConfig = {
|
|
13550
13618
|
description: elementDefinition.short,
|
|
13551
|
-
type:
|
|
13619
|
+
type: getOutputPropertyType(elementDefinition, typeName),
|
|
13552
13620
|
resolve: resolveField,
|
|
13553
13621
|
};
|
|
13554
13622
|
if (elementDefinition.max === '*') {
|
|
@@ -13567,7 +13635,6 @@ function buildPropertyField(fields, key, elementDefinition, elementDefinitionTyp
|
|
|
13567
13635
|
* 4. All properties of the list element type.
|
|
13568
13636
|
*
|
|
13569
13637
|
* See: https://hl7.org/fhir/R4/graphql.html#list
|
|
13570
|
-
*
|
|
13571
13638
|
* @param fieldTypeName The type name of the field.
|
|
13572
13639
|
* @returns The arguments for the field.
|
|
13573
13640
|
*/
|
|
@@ -13652,13 +13719,12 @@ function buildListPropertyFieldArg(fieldArgs, fieldKey, elementDefinition, eleme
|
|
|
13652
13719
|
* (except that the "id" argument is prohibited here as nonsensical).
|
|
13653
13720
|
*
|
|
13654
13721
|
* See: https://www.hl7.org/fhir/graphql.html#reverse
|
|
13655
|
-
*
|
|
13656
13722
|
* @param resourceType The resource type to build fields for.
|
|
13657
13723
|
* @param fields The fields object to add fields to.
|
|
13658
13724
|
*/
|
|
13659
13725
|
function buildReverseLookupFields(resourceType, fields) {
|
|
13660
13726
|
for (const childResourceType of getResourceTypes()) {
|
|
13661
|
-
const childGraphQLType =
|
|
13727
|
+
const childGraphQLType = getGraphQLOutputType(childResourceType);
|
|
13662
13728
|
const childSearchParams = getSearchParameters(childResourceType);
|
|
13663
13729
|
const enumValues = {};
|
|
13664
13730
|
let count = 0;
|
|
@@ -13688,49 +13754,236 @@ function buildReverseLookupFields(resourceType, fields) {
|
|
|
13688
13754
|
}
|
|
13689
13755
|
}
|
|
13690
13756
|
}
|
|
13691
|
-
function
|
|
13692
|
-
const
|
|
13693
|
-
_count: {
|
|
13694
|
-
type: GraphQLInt,
|
|
13695
|
-
description: 'Specify how many elements to return from a repeating list.',
|
|
13696
|
-
},
|
|
13697
|
-
_offset: {
|
|
13698
|
-
type: GraphQLInt,
|
|
13699
|
-
description: 'Specify the offset to start at for a repeating element.',
|
|
13700
|
-
},
|
|
13701
|
-
_sort: {
|
|
13702
|
-
type: GraphQLString,
|
|
13703
|
-
description: 'Specify the sort order by comma-separated list of sort rules in priority order.',
|
|
13704
|
-
},
|
|
13705
|
-
_id: {
|
|
13706
|
-
type: GraphQLString,
|
|
13707
|
-
description: 'Select resources based on the logical id of the resource.',
|
|
13708
|
-
},
|
|
13709
|
-
_lastUpdated: {
|
|
13710
|
-
type: GraphQLString,
|
|
13711
|
-
description: 'Select resources based on the last time they were changed.',
|
|
13712
|
-
},
|
|
13713
|
-
};
|
|
13714
|
-
const searchParams = getSearchParameters(resourceType);
|
|
13715
|
-
if (searchParams) {
|
|
13716
|
-
for (const [code, searchParam] of Object.entries(searchParams)) {
|
|
13717
|
-
// GraphQL does not support dashes in argument names
|
|
13718
|
-
// So convert dashes to underscores
|
|
13719
|
-
args[fhirParamToGraphQLField(code)] = {
|
|
13720
|
-
type: GraphQLString,
|
|
13721
|
-
description: searchParam.description,
|
|
13722
|
-
};
|
|
13723
|
-
}
|
|
13724
|
-
}
|
|
13725
|
-
return args;
|
|
13726
|
-
}
|
|
13727
|
-
function getPropertyType(elementDefinition, typeName) {
|
|
13728
|
-
const graphqlType = getGraphQLType(typeName);
|
|
13757
|
+
function getOutputPropertyType(elementDefinition, typeName) {
|
|
13758
|
+
const graphqlType = getGraphQLOutputType(typeName);
|
|
13729
13759
|
if (elementDefinition.max === '*') {
|
|
13730
13760
|
return new GraphQLList(graphqlType);
|
|
13731
13761
|
}
|
|
13732
13762
|
return graphqlType;
|
|
13733
13763
|
}
|
|
13764
|
+
/**
|
|
13765
|
+
* GraphQL resolver for fields.
|
|
13766
|
+
* In the common case, this is just a matter of returning the field value from the source object.
|
|
13767
|
+
* If the field is a list and the user specifies list arguments, then we can apply those arguments here.
|
|
13768
|
+
* @param source The source. This is the object that contains the field.
|
|
13769
|
+
* @param args The GraphQL search arguments.
|
|
13770
|
+
* @param _ctx The GraphQL context.
|
|
13771
|
+
* @param info The GraphQL resolve info. This includes the field name.
|
|
13772
|
+
* @returns Promise to read the resoure for the query.
|
|
13773
|
+
*/
|
|
13774
|
+
async function resolveField(source, args, _ctx, info) {
|
|
13775
|
+
const fieldValue = source?.[info.fieldName];
|
|
13776
|
+
if (!args || !fieldValue) {
|
|
13777
|
+
return fieldValue;
|
|
13778
|
+
}
|
|
13779
|
+
const { _offset, _count, fhirpath, ...rest } = args;
|
|
13780
|
+
let array = fieldValue;
|
|
13781
|
+
for (const [key, value] of Object.entries(rest)) {
|
|
13782
|
+
array = array.filter((item) => item[key] === value);
|
|
13783
|
+
}
|
|
13784
|
+
if (fhirpath) {
|
|
13785
|
+
array = array.filter((item) => toJsBoolean(evalFhirPathTyped(fhirpath, [toTypedValue(item)])));
|
|
13786
|
+
}
|
|
13787
|
+
if (_offset) {
|
|
13788
|
+
array = array.slice(_offset);
|
|
13789
|
+
}
|
|
13790
|
+
if (_count) {
|
|
13791
|
+
array = array.slice(0, _count);
|
|
13792
|
+
}
|
|
13793
|
+
return array;
|
|
13794
|
+
}
|
|
13795
|
+
/**
|
|
13796
|
+
* GraphQL data loader for Reference requests.
|
|
13797
|
+
* This is a special data loader for following Reference objects.
|
|
13798
|
+
* @param source The source/root. This should always be null for our top level readers.
|
|
13799
|
+
* @param _args The GraphQL search arguments.
|
|
13800
|
+
* @param ctx The GraphQL context.
|
|
13801
|
+
* @returns Promise to read the resoure(s) for the query.
|
|
13802
|
+
*/
|
|
13803
|
+
async function resolveByReference(source, _args, ctx) {
|
|
13804
|
+
try {
|
|
13805
|
+
return await ctx.dataLoader.load(source);
|
|
13806
|
+
}
|
|
13807
|
+
catch (err) {
|
|
13808
|
+
throw new OperationOutcomeError(normalizeOperationOutcome(err), err);
|
|
13809
|
+
}
|
|
13810
|
+
}
|
|
13811
|
+
/**
|
|
13812
|
+
* GraphQL type resolver for resources.
|
|
13813
|
+
* When loading a resource via reference, GraphQL needs to know the type of the resource.
|
|
13814
|
+
* @param resource The loaded resource.
|
|
13815
|
+
* @returns The GraphQL type of the resource.
|
|
13816
|
+
*/
|
|
13817
|
+
function resolveTypeByReference(resource) {
|
|
13818
|
+
const resourceType = resource?.resourceType;
|
|
13819
|
+
if (!resourceType) {
|
|
13820
|
+
return undefined;
|
|
13821
|
+
}
|
|
13822
|
+
return getGraphQLOutputType(resourceType).name;
|
|
13823
|
+
}
|
|
13824
|
+
|
|
13825
|
+
/**
|
|
13826
|
+
* Cache of "introspection" query results.
|
|
13827
|
+
* Common case is the standard schema query from GraphiQL and Insomnia.
|
|
13828
|
+
* The result is big and somewhat computationally expensive.
|
|
13829
|
+
*/
|
|
13830
|
+
const introspectionResults = new LRUCache();
|
|
13831
|
+
/**
|
|
13832
|
+
* Cached GraphQL schema.
|
|
13833
|
+
* This should be initialized at server startup.
|
|
13834
|
+
*/
|
|
13835
|
+
let rootSchema;
|
|
13836
|
+
/**
|
|
13837
|
+
* Handles FHIR GraphQL requests.
|
|
13838
|
+
*
|
|
13839
|
+
* See: https://www.hl7.org/fhir/graphql.html
|
|
13840
|
+
* @param req The request details.
|
|
13841
|
+
* @param repo The current user FHIR repository.
|
|
13842
|
+
* @param router The router for router options.
|
|
13843
|
+
* @returns The response.
|
|
13844
|
+
*/
|
|
13845
|
+
async function graphqlHandler(req, repo, router) {
|
|
13846
|
+
const { query, operationName, variables } = req.body;
|
|
13847
|
+
if (!query) {
|
|
13848
|
+
return [badRequest('Must provide query.')];
|
|
13849
|
+
}
|
|
13850
|
+
let document;
|
|
13851
|
+
try {
|
|
13852
|
+
document = parse(query);
|
|
13853
|
+
}
|
|
13854
|
+
catch (err) {
|
|
13855
|
+
return [badRequest('GraphQL syntax error.')];
|
|
13856
|
+
}
|
|
13857
|
+
const schema = getRootSchema();
|
|
13858
|
+
const validationRules = [...specifiedRules, MaxDepthRule];
|
|
13859
|
+
const validationErrors = validate(schema, document, validationRules);
|
|
13860
|
+
if (validationErrors.length > 0) {
|
|
13861
|
+
return [invalidRequest(validationErrors)];
|
|
13862
|
+
}
|
|
13863
|
+
const introspection = isIntrospectionQuery(query);
|
|
13864
|
+
if (introspection && !router.options?.introspectionEnabled) {
|
|
13865
|
+
return [forbidden];
|
|
13866
|
+
}
|
|
13867
|
+
const dataLoader = new DataLoader((keys) => repo.readReferences(keys));
|
|
13868
|
+
let result = introspection && introspectionResults.get(query);
|
|
13869
|
+
if (!result) {
|
|
13870
|
+
result = await execute({
|
|
13871
|
+
schema,
|
|
13872
|
+
document,
|
|
13873
|
+
contextValue: { repo, dataLoader },
|
|
13874
|
+
operationName,
|
|
13875
|
+
variableValues: variables,
|
|
13876
|
+
});
|
|
13877
|
+
}
|
|
13878
|
+
return [allOk, result];
|
|
13879
|
+
}
|
|
13880
|
+
/**
|
|
13881
|
+
* Returns true if the query is a GraphQL introspection query.
|
|
13882
|
+
*
|
|
13883
|
+
* Introspection queries ask for the schema, which is expensive.
|
|
13884
|
+
*
|
|
13885
|
+
* See: https://graphql.org/learn/introspection/
|
|
13886
|
+
* @param query The GraphQL query.
|
|
13887
|
+
* @returns True if the query is an introspection query.
|
|
13888
|
+
*/
|
|
13889
|
+
function isIntrospectionQuery(query) {
|
|
13890
|
+
return query.includes('query IntrospectionQuery') || query.includes('__schema') || query.includes('__type');
|
|
13891
|
+
}
|
|
13892
|
+
function getRootSchema() {
|
|
13893
|
+
if (!rootSchema) {
|
|
13894
|
+
rootSchema = buildRootSchema();
|
|
13895
|
+
}
|
|
13896
|
+
return rootSchema;
|
|
13897
|
+
}
|
|
13898
|
+
function buildRootSchema() {
|
|
13899
|
+
// First, create placeholder types
|
|
13900
|
+
// We need this first for circular dependencies
|
|
13901
|
+
for (const resourceType of getResourceTypes()) {
|
|
13902
|
+
outputTypeCache[resourceType] = buildGraphQLOutputType(resourceType);
|
|
13903
|
+
}
|
|
13904
|
+
// Next, fill in all of the type properties
|
|
13905
|
+
const fields = {};
|
|
13906
|
+
const mutationFields = {};
|
|
13907
|
+
for (const resourceType of getResourceTypes()) {
|
|
13908
|
+
const graphQLOutputType = getGraphQLOutputType(resourceType);
|
|
13909
|
+
// Get resource by ID
|
|
13910
|
+
fields[resourceType] = {
|
|
13911
|
+
type: graphQLOutputType,
|
|
13912
|
+
args: {
|
|
13913
|
+
id: {
|
|
13914
|
+
type: new GraphQLNonNull(GraphQLID),
|
|
13915
|
+
description: resourceType + ' ID',
|
|
13916
|
+
},
|
|
13917
|
+
},
|
|
13918
|
+
resolve: resolveById,
|
|
13919
|
+
};
|
|
13920
|
+
// Search resource by search parameters
|
|
13921
|
+
fields[resourceType + 'List'] = {
|
|
13922
|
+
type: new GraphQLList(graphQLOutputType),
|
|
13923
|
+
args: buildSearchArgs(resourceType),
|
|
13924
|
+
resolve: resolveBySearch,
|
|
13925
|
+
};
|
|
13926
|
+
// FHIR GraphQL Connection API
|
|
13927
|
+
fields[resourceType + 'Connection'] = {
|
|
13928
|
+
type: buildConnectionType(resourceType, graphQLOutputType),
|
|
13929
|
+
args: buildSearchArgs(resourceType),
|
|
13930
|
+
resolve: resolveByConnectionApi,
|
|
13931
|
+
};
|
|
13932
|
+
// Mutation API
|
|
13933
|
+
mutationFields[resourceType + 'Create'] = {
|
|
13934
|
+
type: graphQLOutputType,
|
|
13935
|
+
args: buildCreateArgs(resourceType),
|
|
13936
|
+
resolve: resolveByCreate,
|
|
13937
|
+
};
|
|
13938
|
+
mutationFields[resourceType + 'Update'] = {
|
|
13939
|
+
type: graphQLOutputType,
|
|
13940
|
+
args: buildUpdateArgs(resourceType),
|
|
13941
|
+
resolve: resolveByUpdate,
|
|
13942
|
+
};
|
|
13943
|
+
mutationFields[resourceType + 'Delete'] = {
|
|
13944
|
+
type: graphQLOutputType,
|
|
13945
|
+
args: {
|
|
13946
|
+
id: {
|
|
13947
|
+
type: new GraphQLNonNull(GraphQLID),
|
|
13948
|
+
description: resourceType + ' ID',
|
|
13949
|
+
},
|
|
13950
|
+
},
|
|
13951
|
+
resolve: resolveByDelete,
|
|
13952
|
+
};
|
|
13953
|
+
}
|
|
13954
|
+
return new GraphQLSchema({
|
|
13955
|
+
query: new GraphQLObjectType({
|
|
13956
|
+
name: 'QueryType',
|
|
13957
|
+
fields,
|
|
13958
|
+
}),
|
|
13959
|
+
mutation: new GraphQLObjectType({
|
|
13960
|
+
name: 'MutationType',
|
|
13961
|
+
fields: mutationFields,
|
|
13962
|
+
}),
|
|
13963
|
+
});
|
|
13964
|
+
}
|
|
13965
|
+
function buildCreateArgs(resourceType) {
|
|
13966
|
+
const args = {
|
|
13967
|
+
res: {
|
|
13968
|
+
type: getGraphQLInputType(resourceType, 'Create'),
|
|
13969
|
+
description: resourceType + ' Create',
|
|
13970
|
+
},
|
|
13971
|
+
};
|
|
13972
|
+
return args;
|
|
13973
|
+
}
|
|
13974
|
+
function buildUpdateArgs(resourceType) {
|
|
13975
|
+
const args = {
|
|
13976
|
+
id: {
|
|
13977
|
+
type: new GraphQLNonNull(GraphQLID),
|
|
13978
|
+
description: resourceType + ' ID',
|
|
13979
|
+
},
|
|
13980
|
+
res: {
|
|
13981
|
+
type: getGraphQLInputType(resourceType, 'Update'),
|
|
13982
|
+
description: resourceType + ' Update',
|
|
13983
|
+
},
|
|
13984
|
+
};
|
|
13985
|
+
return args;
|
|
13986
|
+
}
|
|
13734
13987
|
function buildConnectionType(resourceType, resourceGraphQLType) {
|
|
13735
13988
|
return new GraphQLObjectType({
|
|
13736
13989
|
name: resourceType + 'Connection',
|
|
@@ -13764,25 +14017,6 @@ function buildConnectionType(resourceType, resourceGraphQLType) {
|
|
|
13764
14017
|
* @param ctx The GraphQL context.
|
|
13765
14018
|
* @param info The GraphQL resolve info. This includes the schema, and additional field details.
|
|
13766
14019
|
* @returns Promise to read the resoures for the query.
|
|
13767
|
-
* @implements {GraphQLFieldResolver}
|
|
13768
|
-
*/
|
|
13769
|
-
async function resolveBySearch(source, args, ctx, info) {
|
|
13770
|
-
const fieldName = info.fieldName;
|
|
13771
|
-
const resourceType = fieldName.substring(0, fieldName.length - 'List'.length);
|
|
13772
|
-
const searchRequest = parseSearchArgs(resourceType, source, args);
|
|
13773
|
-
const bundle = await ctx.repo.search(searchRequest);
|
|
13774
|
-
return bundle.entry?.map((e) => e.resource);
|
|
13775
|
-
}
|
|
13776
|
-
/**
|
|
13777
|
-
* GraphQL data loader for search requests.
|
|
13778
|
-
* The field name should always end with "List" (i.e., "Patient" search uses "PatientList").
|
|
13779
|
-
* The search args should be FHIR search parameters.
|
|
13780
|
-
* @param source The source/root. This should always be null for our top level readers.
|
|
13781
|
-
* @param args The GraphQL search arguments.
|
|
13782
|
-
* @param ctx The GraphQL context.
|
|
13783
|
-
* @param info The GraphQL resolve info. This includes the schema, and additional field details.
|
|
13784
|
-
* @returns Promise to read the resoures for the query.
|
|
13785
|
-
* @implements {GraphQLFieldResolver}
|
|
13786
14020
|
*/
|
|
13787
14021
|
async function resolveByConnectionApi(source, args, ctx, info) {
|
|
13788
14022
|
const fieldName = info.fieldName;
|
|
@@ -13812,7 +14046,6 @@ async function resolveByConnectionApi(source, args, ctx, info) {
|
|
|
13812
14046
|
* @param ctx The GraphQL context.
|
|
13813
14047
|
* @param info The GraphQL resolve info. This includes the schema, and additional field details.
|
|
13814
14048
|
* @returns Promise to read the resoure for the query.
|
|
13815
|
-
* @implements {GraphQLFieldResolver}
|
|
13816
14049
|
*/
|
|
13817
14050
|
async function resolveById(_source, args, ctx, info) {
|
|
13818
14051
|
try {
|
|
@@ -13823,98 +14056,66 @@ async function resolveById(_source, args, ctx, info) {
|
|
|
13823
14056
|
}
|
|
13824
14057
|
}
|
|
13825
14058
|
/**
|
|
13826
|
-
* GraphQL
|
|
13827
|
-
*
|
|
13828
|
-
*
|
|
13829
|
-
* @param
|
|
13830
|
-
* @param
|
|
13831
|
-
* @
|
|
13832
|
-
* @
|
|
13833
|
-
|
|
13834
|
-
async function resolveByReference(source, _args, ctx) {
|
|
13835
|
-
try {
|
|
13836
|
-
return await ctx.dataLoader.load(source);
|
|
13837
|
-
}
|
|
13838
|
-
catch (err) {
|
|
13839
|
-
throw new OperationOutcomeError(normalizeOperationOutcome(err), err);
|
|
13840
|
-
}
|
|
13841
|
-
}
|
|
13842
|
-
/**
|
|
13843
|
-
* GraphQL type resolver for resources.
|
|
13844
|
-
* When loading a resource via reference, GraphQL needs to know the type of the resource.
|
|
13845
|
-
* @param resource The loaded resource.
|
|
13846
|
-
* @returns The GraphQL type of the resource.
|
|
13847
|
-
* @implements {GraphQLTypeResolver}
|
|
14059
|
+
* GraphQL resolver function for create requests.
|
|
14060
|
+
* The field name should end with "Create" (i.e., "PatientCreate" for updating a Patient).
|
|
14061
|
+
* The args should include the data to be created for the specified resource type.
|
|
14062
|
+
* @param _source The source/root object. In the case of creates, this is typically not used and is thus ignored.
|
|
14063
|
+
* @param args The GraphQL arguments, containing the new data for the resource.
|
|
14064
|
+
* @param ctx The GraphQL context. This includes the repository where resources are stored.
|
|
14065
|
+
* @param info The GraphQL resolve info. This includes the schema, field details, and other query-specific information.
|
|
14066
|
+
* @returns A Promise that resolves to the created resource, or undefined if the resource could not be found or updated.
|
|
13848
14067
|
*/
|
|
13849
|
-
function
|
|
13850
|
-
const
|
|
13851
|
-
|
|
13852
|
-
|
|
14068
|
+
async function resolveByCreate(_source, args, ctx, info) {
|
|
14069
|
+
const fieldName = info.fieldName;
|
|
14070
|
+
// 'Create.length'=== 6 && 'Update.length' === 6
|
|
14071
|
+
const resourceType = fieldName.substring(0, fieldName.length - 6);
|
|
14072
|
+
const resourceArgs = args.res;
|
|
14073
|
+
if (resourceArgs.resourceType !== resourceType) {
|
|
14074
|
+
return [badRequest('Invalid resourceType')];
|
|
14075
|
+
}
|
|
14076
|
+
const resource = await ctx.repo.createResource(resourceArgs);
|
|
14077
|
+
return resource;
|
|
14078
|
+
}
|
|
14079
|
+
// Mutation Resolvers
|
|
14080
|
+
/**
|
|
14081
|
+
* GraphQL resolver function for update requests.
|
|
14082
|
+
* The field name should end with "Update" (i.e., "PatientUpdate" for updating a Patient).
|
|
14083
|
+
* The args should include the data to be updated for the specified resource type.
|
|
14084
|
+
* @param _source The source/root object. In the case of updates, this is typically not used and is thus ignored.
|
|
14085
|
+
* @param args The GraphQL arguments, containing the new data for the resource.
|
|
14086
|
+
* @param ctx The GraphQL context. This includes the repository where resources are stored.
|
|
14087
|
+
* @param info The GraphQL resolve info. This includes the schema, field details, and other query-specific information.
|
|
14088
|
+
* @returns A Promise that resolves to the updated resource, or undefined if the resource could not be found or updated.
|
|
14089
|
+
*/
|
|
14090
|
+
async function resolveByUpdate(_source, args, ctx, info) {
|
|
14091
|
+
const fieldName = info.fieldName;
|
|
14092
|
+
// 'Create.length'=== 6 && 'Update.length' === 6
|
|
14093
|
+
const resourceType = fieldName.substring(0, fieldName.length - 6);
|
|
14094
|
+
const resourceArgs = args.res;
|
|
14095
|
+
const resourceId = args.id;
|
|
14096
|
+
if (resourceArgs.resourceType !== resourceType) {
|
|
14097
|
+
return [badRequest('Invalid resourceType')];
|
|
14098
|
+
}
|
|
14099
|
+
if (resourceId !== resourceArgs.id) {
|
|
14100
|
+
return [badRequest('Incorrect ID')];
|
|
13853
14101
|
}
|
|
13854
|
-
|
|
14102
|
+
const resource = await ctx.repo.updateResource(resourceArgs);
|
|
14103
|
+
return resource;
|
|
13855
14104
|
}
|
|
13856
14105
|
/**
|
|
13857
|
-
* GraphQL resolver for
|
|
13858
|
-
*
|
|
13859
|
-
*
|
|
13860
|
-
* @param
|
|
13861
|
-
* @param args The GraphQL
|
|
13862
|
-
* @param
|
|
13863
|
-
* @param info The GraphQL resolve info.
|
|
13864
|
-
* @returns Promise
|
|
13865
|
-
* @implements {GraphQLFieldResolver}
|
|
14106
|
+
* GraphQL resolver function for delete requests.
|
|
14107
|
+
* The field name should end with "Delete" (e.g., "PatientDelete" for deleting a Patient).
|
|
14108
|
+
* The args should include the ID of the resource to be deleted.
|
|
14109
|
+
* @param _source The source/root object. In the case of deletions, this is typically not used and is thus ignored.
|
|
14110
|
+
* @param args The GraphQL arguments, containing the ID of the resource to be deleted.
|
|
14111
|
+
* @param ctx The GraphQL context. This includes the repository where resources are stored.
|
|
14112
|
+
* @param info The GraphQL resolve info. This includes the schema, field details, and other query-specific information.
|
|
14113
|
+
* @returns A Promise that resolves when the resource has been deleted. No value is returned.
|
|
13866
14114
|
*/
|
|
13867
|
-
async function
|
|
13868
|
-
const
|
|
13869
|
-
|
|
13870
|
-
|
|
13871
|
-
}
|
|
13872
|
-
const { _offset, _count, fhirpath, ...rest } = args;
|
|
13873
|
-
let array = fieldValue;
|
|
13874
|
-
for (const [key, value] of Object.entries(rest)) {
|
|
13875
|
-
array = array.filter((item) => item[key] === value);
|
|
13876
|
-
}
|
|
13877
|
-
if (fhirpath) {
|
|
13878
|
-
array = array.filter((item) => toJsBoolean(evalFhirPathTyped(fhirpath, [toTypedValue(item)])));
|
|
13879
|
-
}
|
|
13880
|
-
if (_offset) {
|
|
13881
|
-
array = array.slice(_offset);
|
|
13882
|
-
}
|
|
13883
|
-
if (_count) {
|
|
13884
|
-
array = array.slice(0, _count);
|
|
13885
|
-
}
|
|
13886
|
-
return array;
|
|
13887
|
-
}
|
|
13888
|
-
function parseSearchArgs(resourceType, source, args) {
|
|
13889
|
-
let referenceFilter = undefined;
|
|
13890
|
-
if (source) {
|
|
13891
|
-
// _reference is a required field for reverse lookup searches
|
|
13892
|
-
// The GraphQL parser will validate that it is there.
|
|
13893
|
-
const reference = args['_reference'];
|
|
13894
|
-
delete args['_reference'];
|
|
13895
|
-
referenceFilter = {
|
|
13896
|
-
code: reference,
|
|
13897
|
-
operator: Operator.EQUALS,
|
|
13898
|
-
value: getReferenceString(source),
|
|
13899
|
-
};
|
|
13900
|
-
}
|
|
13901
|
-
// Reverse the transform of dashes to underscores, back to dashes
|
|
13902
|
-
args = Object.fromEntries(Object.entries(args).map(([key, value]) => [graphQLFieldToFhirParam(key), value]));
|
|
13903
|
-
// Parse the search request
|
|
13904
|
-
const searchRequest = parseSearchRequest(resourceType, args);
|
|
13905
|
-
// If a reverse lookup filter was specified,
|
|
13906
|
-
// add it to the search request.
|
|
13907
|
-
if (referenceFilter) {
|
|
13908
|
-
const existingFilters = searchRequest.filters || [];
|
|
13909
|
-
searchRequest.filters = [referenceFilter, ...existingFilters];
|
|
13910
|
-
}
|
|
13911
|
-
return searchRequest;
|
|
13912
|
-
}
|
|
13913
|
-
function fhirParamToGraphQLField(code) {
|
|
13914
|
-
return code.replaceAll('-', '_');
|
|
13915
|
-
}
|
|
13916
|
-
function graphQLFieldToFhirParam(code) {
|
|
13917
|
-
return code.startsWith('_') ? code : code.replaceAll('_', '-');
|
|
14115
|
+
async function resolveByDelete(_source, args, ctx, info) {
|
|
14116
|
+
const fieldName = info.fieldName;
|
|
14117
|
+
const resourceType = fieldName.substring(0, fieldName.length - 'Delete'.length);
|
|
14118
|
+
await ctx.repo.deleteResource(resourceType, args.id);
|
|
13918
14119
|
}
|
|
13919
14120
|
/**
|
|
13920
14121
|
* Custom GraphQL rule that enforces max depth constraint.
|
|
@@ -13941,42 +14142,6 @@ const MaxDepthRule = (context) => ({
|
|
|
13941
14142
|
}
|
|
13942
14143
|
},
|
|
13943
14144
|
});
|
|
13944
|
-
/**
|
|
13945
|
-
* Returns the depth of the GraphQL node in a query.
|
|
13946
|
-
* We use "selections" as the representation of depth.
|
|
13947
|
-
* As a rough approximation, it's the number of indentations in a well formatted query.
|
|
13948
|
-
* @param path The GraphQL node path.
|
|
13949
|
-
* @returns The "depth" of the node.
|
|
13950
|
-
*/
|
|
13951
|
-
function getDepth(path) {
|
|
13952
|
-
return path.filter((p) => p === 'selections').length;
|
|
13953
|
-
}
|
|
13954
|
-
/**
|
|
13955
|
-
* Returns true if the field is requested in the GraphQL query.
|
|
13956
|
-
* @param info The GraphQL resolve info. This includes the field name.
|
|
13957
|
-
* @param fieldName The field name to check.
|
|
13958
|
-
* @returns True if the field is requested in the GraphQL query.
|
|
13959
|
-
*/
|
|
13960
|
-
function isFieldRequested(info, fieldName) {
|
|
13961
|
-
return info.fieldNodes.some((fieldNode) => fieldNode.selectionSet?.selections.some((selection) => {
|
|
13962
|
-
return selection.kind === 'Field' && selection.name.value === fieldName;
|
|
13963
|
-
}));
|
|
13964
|
-
}
|
|
13965
|
-
/**
|
|
13966
|
-
* Returns an OperationOutcome for GraphQL errors.
|
|
13967
|
-
* @param errors Array of GraphQL errors.
|
|
13968
|
-
* @returns OperationOutcome with the GraphQL errors as OperationOutcome issues.
|
|
13969
|
-
*/
|
|
13970
|
-
function invalidRequest(errors) {
|
|
13971
|
-
return {
|
|
13972
|
-
resourceType: 'OperationOutcome',
|
|
13973
|
-
issue: errors.map((error) => ({
|
|
13974
|
-
severity: 'error',
|
|
13975
|
-
code: 'invalid',
|
|
13976
|
-
details: { text: error.message },
|
|
13977
|
-
})),
|
|
13978
|
-
};
|
|
13979
|
-
}
|
|
13980
14145
|
|
|
13981
14146
|
class Router {
|
|
13982
14147
|
constructor() {
|
|
@@ -14109,8 +14274,9 @@ async function patchResource(req, repo) {
|
|
|
14109
14274
|
return [allOk, resource];
|
|
14110
14275
|
}
|
|
14111
14276
|
class FhirRouter {
|
|
14112
|
-
constructor() {
|
|
14277
|
+
constructor(options = {}) {
|
|
14113
14278
|
this.router = new Router();
|
|
14279
|
+
this.options = options;
|
|
14114
14280
|
this.router.add('POST', '', batch);
|
|
14115
14281
|
this.router.add('GET', ':resourceType', search);
|
|
14116
14282
|
this.router.add('POST', ':resourceType/_search', searchByPost);
|
|
@@ -14148,7 +14314,6 @@ class BaseRepository {
|
|
|
14148
14314
|
* The return value is the resource, if available; otherwise, undefined.
|
|
14149
14315
|
*
|
|
14150
14316
|
* See FHIR search for full details: https://www.hl7.org/fhir/search.html
|
|
14151
|
-
*
|
|
14152
14317
|
* @param searchRequest The FHIR search request.
|
|
14153
14318
|
* @returns Promise to the first search result or undefined.
|
|
14154
14319
|
*/
|
|
@@ -14164,7 +14329,6 @@ class BaseRepository {
|
|
|
14164
14329
|
* The return value is an array of resources.
|
|
14165
14330
|
*
|
|
14166
14331
|
* See FHIR search for full details: https://www.hl7.org/fhir/search.html
|
|
14167
|
-
*
|
|
14168
14332
|
* @param searchRequest The FHIR search request.
|
|
14169
14333
|
* @returns Promise to the array of search results.
|
|
14170
14334
|
*/
|