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