@medplum/core 2.0.18 → 2.0.20
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 +1 -1
- package/dist/cjs/index.cjs +724 -577
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.min.cjs +1 -1
- package/dist/esm/client.mjs +34 -3
- package/dist/esm/client.mjs.map +1 -1
- package/dist/esm/filter/parse.mjs +54 -1
- package/dist/esm/filter/parse.mjs.map +1 -1
- package/dist/esm/filter/types.mjs.map +1 -1
- package/dist/esm/hl7.mjs +23 -1
- package/dist/esm/hl7.mjs.map +1 -1
- package/dist/esm/index.min.mjs +1 -1
- package/dist/esm/index.mjs +3 -2
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/search/details.mjs +20 -15
- package/dist/esm/search/details.mjs.map +1 -1
- package/dist/esm/sftp.mjs +25 -0
- package/dist/esm/sftp.mjs.map +1 -0
- package/dist/esm/utils.mjs +13 -1
- package/dist/esm/utils.mjs.map +1 -1
- package/dist/types/client.d.ts +15 -1
- package/dist/types/config.d.ts +45 -0
- package/dist/types/filter/types.d.ts +3 -2
- package/dist/types/hl7.d.ts +12 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/search/details.d.ts +1 -0
- package/dist/types/sftp.d.ts +9 -0
- package/dist/types/utils.d.ts +16 -0
- package/package.json +1 -1
package/dist/cjs/index.cjs
CHANGED
|
@@ -1098,6 +1098,18 @@
|
|
|
1098
1098
|
}
|
|
1099
1099
|
return Math.round(a * Math.pow(10, precision));
|
|
1100
1100
|
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Finds the first resource in the input array that matches the specified code and system.
|
|
1103
|
+
* @param resources - The array of resources to search.
|
|
1104
|
+
* @param code - The code to search for.
|
|
1105
|
+
* @param system - The system to search for.
|
|
1106
|
+
* @returns The first resource in the input array that matches the specified code and system, or undefined if no such resource is found.
|
|
1107
|
+
*/
|
|
1108
|
+
function findResourceByCode(resources, code, system) {
|
|
1109
|
+
return resources.find((r) => typeof code === 'string'
|
|
1110
|
+
? getCodeBySystem(r.code || {}, system) === code
|
|
1111
|
+
: getCodeBySystem(r.code || {}, system) === getCodeBySystem(code, system));
|
|
1112
|
+
}
|
|
1101
1113
|
|
|
1102
1114
|
/**
|
|
1103
1115
|
* Returns a cryptographically secure random string.
|
|
@@ -6404,7 +6416,8 @@
|
|
|
6404
6416
|
const globalSchema = baseSchema;
|
|
6405
6417
|
|
|
6406
6418
|
// PKCE auth based on:
|
|
6407
|
-
|
|
6419
|
+
// https://aws.amazon.com/blogs/security/how-to-add-authentication-single-page-web-application-with-amazon-cognito-oauth2-implementation/
|
|
6420
|
+
const MEDPLUM_VERSION = "2.0.20-effeb76c" ;
|
|
6408
6421
|
const DEFAULT_BASE_URL = 'https://api.medplum.com/';
|
|
6409
6422
|
const DEFAULT_RESOURCE_CACHE_SIZE = 1000;
|
|
6410
6423
|
const DEFAULT_CACHE_TIME = 60000; // 60 seconds
|
|
@@ -6575,6 +6588,13 @@
|
|
|
6575
6588
|
url = url.toString();
|
|
6576
6589
|
this.requestCache?.delete(url);
|
|
6577
6590
|
}
|
|
6591
|
+
/**
|
|
6592
|
+
* Invalidates all cached values and flushes the cache.
|
|
6593
|
+
* @category Caching
|
|
6594
|
+
*/
|
|
6595
|
+
invalidateAll() {
|
|
6596
|
+
this.requestCache?.clear();
|
|
6597
|
+
}
|
|
6578
6598
|
/**
|
|
6579
6599
|
* Invalidates all cached search results or cached requests for the given resourceType.
|
|
6580
6600
|
* @category Caching
|
|
@@ -7906,6 +7926,26 @@
|
|
|
7906
7926
|
const response = await this.fetch(url.toString(), options);
|
|
7907
7927
|
return response.blob();
|
|
7908
7928
|
}
|
|
7929
|
+
/**
|
|
7930
|
+
* Upload media to the server and create a Media instance for the uploaded content.
|
|
7931
|
+
* @param contents The contents of the media file, as a string, Uint8Array, File, or Blob.
|
|
7932
|
+
* @param contentType The media type of the content
|
|
7933
|
+
* @param filename The name of the file to be uploaded, or undefined if not applicable
|
|
7934
|
+
* @param additionalFields Additional fields for Media
|
|
7935
|
+
* @returns Promise that resolves to the created Media
|
|
7936
|
+
*/
|
|
7937
|
+
async uploadMedia(contents, contentType, filename, additionalFields) {
|
|
7938
|
+
const binary = await this.createBinary(contents, filename, contentType);
|
|
7939
|
+
return this.createResource({
|
|
7940
|
+
...additionalFields,
|
|
7941
|
+
resourceType: 'Media',
|
|
7942
|
+
content: {
|
|
7943
|
+
contentType: contentType,
|
|
7944
|
+
url: 'Binary/' + binary.id,
|
|
7945
|
+
title: filename,
|
|
7946
|
+
},
|
|
7947
|
+
});
|
|
7948
|
+
}
|
|
7909
7949
|
//
|
|
7910
7950
|
// Private helpers
|
|
7911
7951
|
//
|
|
@@ -7981,7 +8021,10 @@
|
|
|
7981
8021
|
return undefined;
|
|
7982
8022
|
}
|
|
7983
8023
|
if (response.status === 404) {
|
|
7984
|
-
|
|
8024
|
+
const contentType = response.headers.get('content-type');
|
|
8025
|
+
if (!contentType?.includes('application/fhir+json')) {
|
|
8026
|
+
throw new OperationOutcomeError(notFound);
|
|
8027
|
+
}
|
|
7985
8028
|
}
|
|
7986
8029
|
let obj = undefined;
|
|
7987
8030
|
try {
|
|
@@ -11394,157 +11437,572 @@
|
|
|
11394
11437
|
return new StructureMapParser(parser).parse();
|
|
11395
11438
|
}
|
|
11396
11439
|
|
|
11397
|
-
const
|
|
11398
|
-
function tokenize(str) {
|
|
11399
|
-
return new Tokenizer(str, FHIRPATH_KEYWORDS, MAPPING_LANGUAGE_OPERATORS, {
|
|
11400
|
-
dateTimeLiterals: true,
|
|
11401
|
-
symbolRegex: /[^\s\])]/,
|
|
11402
|
-
}).tokenize();
|
|
11403
|
-
}
|
|
11404
|
-
|
|
11405
|
-
// See: https://hl7.org/fhir/search_filter.html
|
|
11440
|
+
const DEFAULT_SEARCH_COUNT = 20;
|
|
11406
11441
|
/**
|
|
11407
|
-
*
|
|
11442
|
+
* Search operators.
|
|
11443
|
+
* These operators represent "modifiers" and "prefixes" in FHIR search.
|
|
11444
|
+
* See: https://www.hl7.org/fhir/search.html
|
|
11408
11445
|
*/
|
|
11409
|
-
|
|
11410
|
-
|
|
11411
|
-
|
|
11412
|
-
|
|
11413
|
-
|
|
11414
|
-
|
|
11415
|
-
|
|
11446
|
+
exports.Operator = void 0;
|
|
11447
|
+
(function (Operator) {
|
|
11448
|
+
Operator["EQUALS"] = "eq";
|
|
11449
|
+
Operator["NOT_EQUALS"] = "ne";
|
|
11450
|
+
// Numbers
|
|
11451
|
+
Operator["GREATER_THAN"] = "gt";
|
|
11452
|
+
Operator["LESS_THAN"] = "lt";
|
|
11453
|
+
Operator["GREATER_THAN_OR_EQUALS"] = "ge";
|
|
11454
|
+
Operator["LESS_THAN_OR_EQUALS"] = "le";
|
|
11455
|
+
// Dates
|
|
11456
|
+
Operator["STARTS_AFTER"] = "sa";
|
|
11457
|
+
Operator["ENDS_BEFORE"] = "eb";
|
|
11458
|
+
Operator["APPROXIMATELY"] = "ap";
|
|
11459
|
+
// String
|
|
11460
|
+
Operator["CONTAINS"] = "contains";
|
|
11461
|
+
Operator["EXACT"] = "exact";
|
|
11462
|
+
// Token
|
|
11463
|
+
Operator["TEXT"] = "text";
|
|
11464
|
+
Operator["NOT"] = "not";
|
|
11465
|
+
Operator["ABOVE"] = "above";
|
|
11466
|
+
Operator["BELOW"] = "below";
|
|
11467
|
+
Operator["IN"] = "in";
|
|
11468
|
+
Operator["NOT_IN"] = "not-in";
|
|
11469
|
+
Operator["OF_TYPE"] = "of-type";
|
|
11470
|
+
// All
|
|
11471
|
+
Operator["MISSING"] = "missing";
|
|
11472
|
+
// Reference
|
|
11473
|
+
Operator["IDENTIFIER"] = "identifier";
|
|
11474
|
+
// _include and _revinclude
|
|
11475
|
+
Operator["ITERATE"] = "iterate";
|
|
11476
|
+
})(exports.Operator || (exports.Operator = {}));
|
|
11416
11477
|
/**
|
|
11417
|
-
*
|
|
11418
|
-
*
|
|
11478
|
+
* Parameter names may specify a modifier as a suffix.
|
|
11479
|
+
* The modifiers are separated from the parameter name by a colon.
|
|
11480
|
+
* See: https://www.hl7.org/fhir/search.html#modifiers
|
|
11419
11481
|
*/
|
|
11420
|
-
|
|
11421
|
-
|
|
11422
|
-
|
|
11423
|
-
|
|
11424
|
-
|
|
11482
|
+
const MODIFIER_OPERATORS = {
|
|
11483
|
+
contains: exports.Operator.CONTAINS,
|
|
11484
|
+
exact: exports.Operator.EXACT,
|
|
11485
|
+
above: exports.Operator.ABOVE,
|
|
11486
|
+
below: exports.Operator.BELOW,
|
|
11487
|
+
text: exports.Operator.TEXT,
|
|
11488
|
+
not: exports.Operator.NOT,
|
|
11489
|
+
in: exports.Operator.IN,
|
|
11490
|
+
'not-in': exports.Operator.NOT_IN,
|
|
11491
|
+
'of-type': exports.Operator.OF_TYPE,
|
|
11492
|
+
missing: exports.Operator.MISSING,
|
|
11493
|
+
identifier: exports.Operator.IDENTIFIER,
|
|
11494
|
+
iterate: exports.Operator.ITERATE,
|
|
11495
|
+
};
|
|
11425
11496
|
/**
|
|
11426
|
-
*
|
|
11427
|
-
*
|
|
11497
|
+
* For the ordered parameter types of number, date, and quantity,
|
|
11498
|
+
* a prefix to the parameter value may be used to control the nature
|
|
11499
|
+
* of the matching.
|
|
11500
|
+
* See: https://www.hl7.org/fhir/search.html#prefix
|
|
11428
11501
|
*/
|
|
11429
|
-
|
|
11430
|
-
|
|
11431
|
-
|
|
11432
|
-
|
|
11433
|
-
|
|
11434
|
-
|
|
11435
|
-
|
|
11436
|
-
|
|
11437
|
-
|
|
11438
|
-
|
|
11439
|
-
|
|
11440
|
-
|
|
11441
|
-
|
|
11442
|
-
|
|
11443
|
-
|
|
11444
|
-
|
|
11445
|
-
|
|
11446
|
-
|
|
11447
|
-
|
|
11448
|
-
|
|
11449
|
-
|
|
11450
|
-
|
|
11451
|
-
|
|
11452
|
-
|
|
11502
|
+
const PREFIX_OPERATORS = {
|
|
11503
|
+
eq: exports.Operator.EQUALS,
|
|
11504
|
+
ne: exports.Operator.NOT_EQUALS,
|
|
11505
|
+
lt: exports.Operator.LESS_THAN,
|
|
11506
|
+
le: exports.Operator.LESS_THAN_OR_EQUALS,
|
|
11507
|
+
gt: exports.Operator.GREATER_THAN,
|
|
11508
|
+
ge: exports.Operator.GREATER_THAN_OR_EQUALS,
|
|
11509
|
+
sa: exports.Operator.STARTS_AFTER,
|
|
11510
|
+
eb: exports.Operator.ENDS_BEFORE,
|
|
11511
|
+
ap: exports.Operator.APPROXIMATELY,
|
|
11512
|
+
};
|
|
11513
|
+
/**
|
|
11514
|
+
* Parses a search URL into a search request.
|
|
11515
|
+
* @param resourceType The FHIR resource type.
|
|
11516
|
+
* @param query The collection of query string parameters.
|
|
11517
|
+
* @returns A parsed SearchRequest.
|
|
11518
|
+
*/
|
|
11519
|
+
function parseSearchRequest(resourceType, query) {
|
|
11520
|
+
const queryArray = [];
|
|
11521
|
+
for (const [key, value] of Object.entries(query)) {
|
|
11522
|
+
if (Array.isArray(value)) {
|
|
11523
|
+
for (let i = 0; i < value.length; i++) {
|
|
11524
|
+
queryArray.push([key, value[i]]);
|
|
11525
|
+
}
|
|
11453
11526
|
}
|
|
11454
11527
|
else {
|
|
11455
|
-
|
|
11456
|
-
}
|
|
11457
|
-
const next = this.parser.peek()?.value;
|
|
11458
|
-
if (next === 'and' || next === 'or') {
|
|
11459
|
-
this.parser.consume('Symbol', next);
|
|
11460
|
-
return new FhirFilterConnective(next, result, this.parse());
|
|
11528
|
+
queryArray.push([key, value || '']);
|
|
11461
11529
|
}
|
|
11462
|
-
return result;
|
|
11463
11530
|
}
|
|
11531
|
+
return parseSearchImpl(resourceType, queryArray);
|
|
11464
11532
|
}
|
|
11465
|
-
const fhirPathParserBuilder = initFhirPathParserBuilder();
|
|
11466
11533
|
/**
|
|
11467
|
-
* Parses a
|
|
11468
|
-
* @param
|
|
11469
|
-
* @returns
|
|
11534
|
+
* Parses a search URL into a search request.
|
|
11535
|
+
* @param url The search URL.
|
|
11536
|
+
* @returns A parsed SearchRequest.
|
|
11470
11537
|
*/
|
|
11471
|
-
function
|
|
11472
|
-
const
|
|
11473
|
-
|
|
11474
|
-
return new FilterParameterParser(parser).parse();
|
|
11538
|
+
function parseSearchUrl(url) {
|
|
11539
|
+
const resourceType = url.pathname.split('/').filter(Boolean).pop();
|
|
11540
|
+
return parseSearchImpl(resourceType, url.searchParams.entries());
|
|
11475
11541
|
}
|
|
11476
|
-
|
|
11477
11542
|
/**
|
|
11478
|
-
*
|
|
11479
|
-
*
|
|
11480
|
-
*
|
|
11481
|
-
* https://hl7-definition.caristix.com/v2/HL7v2.6/Fields/MSH.1
|
|
11482
|
-
*
|
|
11483
|
-
* MSH-2:
|
|
11484
|
-
* https://hl7-definition.caristix.com/v2/HL7v2.6/Fields/MSH.2
|
|
11485
|
-
*
|
|
11486
|
-
* See this tutorial on MSH, and why it's a bad idea to use anything other than the default values:
|
|
11487
|
-
* https://www.hl7soup.com/HL7TutorialMSH.html
|
|
11543
|
+
* Parses a URL string into a SearchRequest.
|
|
11544
|
+
* @param url The URL to parse.
|
|
11545
|
+
* @returns Parsed search definition.
|
|
11488
11546
|
*/
|
|
11489
|
-
|
|
11490
|
-
|
|
11491
|
-
this.segmentSeparator = segmentSeparator;
|
|
11492
|
-
this.fieldSeparator = fieldSeparator;
|
|
11493
|
-
this.componentSeparator = componentSeparator;
|
|
11494
|
-
this.repetitionSeparator = repetitionSeparator;
|
|
11495
|
-
this.escapeCharacter = escapeCharacter;
|
|
11496
|
-
this.subcomponentSeparator = subcomponentSeparator;
|
|
11497
|
-
}
|
|
11498
|
-
/**
|
|
11499
|
-
* Returns the MSH-2 field value based on the configured separators.
|
|
11500
|
-
* @returns The HL7 MSH-2 field value.
|
|
11501
|
-
*/
|
|
11502
|
-
getMsh2() {
|
|
11503
|
-
return (this.fieldSeparator +
|
|
11504
|
-
this.componentSeparator +
|
|
11505
|
-
this.repetitionSeparator +
|
|
11506
|
-
this.escapeCharacter +
|
|
11507
|
-
this.subcomponentSeparator);
|
|
11508
|
-
}
|
|
11547
|
+
function parseSearchDefinition(url) {
|
|
11548
|
+
return parseSearchUrl(new URL(url, 'https://example.com/'));
|
|
11509
11549
|
}
|
|
11510
|
-
|
|
11511
|
-
|
|
11512
|
-
|
|
11513
|
-
|
|
11514
|
-
|
|
11515
|
-
|
|
11516
|
-
* Creates a new HL7 message.
|
|
11517
|
-
* @param segments The HL7 segments.
|
|
11518
|
-
* @param context Optional HL7 parsing context.
|
|
11519
|
-
*/
|
|
11520
|
-
constructor(segments, context = new Hl7Context()) {
|
|
11521
|
-
this.context = context;
|
|
11522
|
-
this.segments = segments;
|
|
11550
|
+
function parseSearchImpl(resourceType, query) {
|
|
11551
|
+
const searchRequest = {
|
|
11552
|
+
resourceType,
|
|
11553
|
+
};
|
|
11554
|
+
for (const [key, value] of query) {
|
|
11555
|
+
parseKeyValue(searchRequest, key, value);
|
|
11523
11556
|
}
|
|
11524
|
-
|
|
11525
|
-
|
|
11526
|
-
|
|
11527
|
-
|
|
11528
|
-
|
|
11529
|
-
|
|
11530
|
-
|
|
11531
|
-
|
|
11532
|
-
|
|
11533
|
-
return this.segments.find((s) => s.name === index);
|
|
11557
|
+
return searchRequest;
|
|
11558
|
+
}
|
|
11559
|
+
function parseKeyValue(searchRequest, key, value) {
|
|
11560
|
+
let code;
|
|
11561
|
+
let modifier;
|
|
11562
|
+
const colonIndex = key.indexOf(':');
|
|
11563
|
+
if (colonIndex >= 0) {
|
|
11564
|
+
code = key.substring(0, colonIndex);
|
|
11565
|
+
modifier = key.substring(colonIndex + 1);
|
|
11534
11566
|
}
|
|
11535
|
-
|
|
11536
|
-
|
|
11537
|
-
|
|
11538
|
-
* @returns An array of HL7 segments with the specified name.
|
|
11539
|
-
*/
|
|
11540
|
-
getAll(name) {
|
|
11541
|
-
return this.segments.filter((s) => s.name === name);
|
|
11567
|
+
else {
|
|
11568
|
+
code = key;
|
|
11569
|
+
modifier = '';
|
|
11542
11570
|
}
|
|
11543
|
-
|
|
11544
|
-
|
|
11545
|
-
|
|
11546
|
-
|
|
11547
|
-
|
|
11571
|
+
switch (code) {
|
|
11572
|
+
case '_sort':
|
|
11573
|
+
parseSortRule(searchRequest, value);
|
|
11574
|
+
break;
|
|
11575
|
+
case '_count':
|
|
11576
|
+
searchRequest.count = parseInt(value);
|
|
11577
|
+
break;
|
|
11578
|
+
case '_offset':
|
|
11579
|
+
searchRequest.offset = parseInt(value);
|
|
11580
|
+
break;
|
|
11581
|
+
case '_total':
|
|
11582
|
+
searchRequest.total = value;
|
|
11583
|
+
break;
|
|
11584
|
+
case '_summary':
|
|
11585
|
+
searchRequest.total = 'estimate';
|
|
11586
|
+
searchRequest.count = 0;
|
|
11587
|
+
break;
|
|
11588
|
+
case '_include': {
|
|
11589
|
+
const target = parseIncludeTarget(value);
|
|
11590
|
+
if (modifier === 'iterate') {
|
|
11591
|
+
target.modifier = exports.Operator.ITERATE;
|
|
11592
|
+
}
|
|
11593
|
+
if (searchRequest.include) {
|
|
11594
|
+
searchRequest.include.push(target);
|
|
11595
|
+
}
|
|
11596
|
+
else {
|
|
11597
|
+
searchRequest.include = [target];
|
|
11598
|
+
}
|
|
11599
|
+
break;
|
|
11600
|
+
}
|
|
11601
|
+
case '_revinclude': {
|
|
11602
|
+
const target = parseIncludeTarget(value);
|
|
11603
|
+
if (modifier === 'iterate') {
|
|
11604
|
+
target.modifier = exports.Operator.ITERATE;
|
|
11605
|
+
}
|
|
11606
|
+
if (searchRequest.revInclude) {
|
|
11607
|
+
searchRequest.revInclude.push(target);
|
|
11608
|
+
}
|
|
11609
|
+
else {
|
|
11610
|
+
searchRequest.revInclude = [target];
|
|
11611
|
+
}
|
|
11612
|
+
break;
|
|
11613
|
+
}
|
|
11614
|
+
case '_fields':
|
|
11615
|
+
searchRequest.fields = value.split(',');
|
|
11616
|
+
break;
|
|
11617
|
+
default: {
|
|
11618
|
+
const param = globalSchema.types[searchRequest.resourceType]?.searchParams?.[code];
|
|
11619
|
+
if (param) {
|
|
11620
|
+
parseParameter(searchRequest, param, modifier, value);
|
|
11621
|
+
}
|
|
11622
|
+
else {
|
|
11623
|
+
parseUnknownParameter(searchRequest, code, modifier, value);
|
|
11624
|
+
}
|
|
11625
|
+
}
|
|
11626
|
+
}
|
|
11627
|
+
}
|
|
11628
|
+
function parseSortRule(searchRequest, value) {
|
|
11629
|
+
for (const field of value.split(',')) {
|
|
11630
|
+
let code;
|
|
11631
|
+
let descending = false;
|
|
11632
|
+
if (field.startsWith('-')) {
|
|
11633
|
+
code = field.substring(1);
|
|
11634
|
+
descending = true;
|
|
11635
|
+
}
|
|
11636
|
+
else {
|
|
11637
|
+
code = field;
|
|
11638
|
+
}
|
|
11639
|
+
if (!searchRequest.sortRules) {
|
|
11640
|
+
searchRequest.sortRules = [];
|
|
11641
|
+
}
|
|
11642
|
+
searchRequest.sortRules.push({ code, descending });
|
|
11643
|
+
}
|
|
11644
|
+
}
|
|
11645
|
+
function parseParameter(searchRequest, searchParam, modifier, value) {
|
|
11646
|
+
if (modifier === 'missing') {
|
|
11647
|
+
addFilter(searchRequest, {
|
|
11648
|
+
code: searchParam.code,
|
|
11649
|
+
operator: exports.Operator.MISSING,
|
|
11650
|
+
value,
|
|
11651
|
+
});
|
|
11652
|
+
return;
|
|
11653
|
+
}
|
|
11654
|
+
switch (searchParam.type) {
|
|
11655
|
+
case 'number':
|
|
11656
|
+
case 'date':
|
|
11657
|
+
parsePrefixType(searchRequest, searchParam, value);
|
|
11658
|
+
break;
|
|
11659
|
+
case 'reference':
|
|
11660
|
+
case 'string':
|
|
11661
|
+
case 'token':
|
|
11662
|
+
case 'uri':
|
|
11663
|
+
parseModifierType(searchRequest, searchParam, modifier, value);
|
|
11664
|
+
break;
|
|
11665
|
+
case 'quantity':
|
|
11666
|
+
parseQuantity(searchRequest, searchParam, value);
|
|
11667
|
+
break;
|
|
11668
|
+
}
|
|
11669
|
+
}
|
|
11670
|
+
function parsePrefixType(searchRequest, param, input) {
|
|
11671
|
+
const { operator, value } = parsePrefix(input);
|
|
11672
|
+
addFilter(searchRequest, {
|
|
11673
|
+
code: param.code,
|
|
11674
|
+
operator,
|
|
11675
|
+
value,
|
|
11676
|
+
});
|
|
11677
|
+
}
|
|
11678
|
+
function parseModifierType(searchRequest, param, modifier, value) {
|
|
11679
|
+
addFilter(searchRequest, {
|
|
11680
|
+
code: param.code,
|
|
11681
|
+
operator: parseModifier(modifier),
|
|
11682
|
+
value,
|
|
11683
|
+
});
|
|
11684
|
+
}
|
|
11685
|
+
function parseQuantity(searchRequest, param, input) {
|
|
11686
|
+
const [prefixNumber, unitSystem, unitCode] = input.split('|');
|
|
11687
|
+
const { operator, value } = parsePrefix(prefixNumber);
|
|
11688
|
+
addFilter(searchRequest, {
|
|
11689
|
+
code: param.code,
|
|
11690
|
+
operator,
|
|
11691
|
+
value,
|
|
11692
|
+
unitSystem,
|
|
11693
|
+
unitCode,
|
|
11694
|
+
});
|
|
11695
|
+
}
|
|
11696
|
+
function parseUnknownParameter(searchRequest, code, modifier, value) {
|
|
11697
|
+
let operator = exports.Operator.EQUALS;
|
|
11698
|
+
if (modifier) {
|
|
11699
|
+
operator = modifier;
|
|
11700
|
+
}
|
|
11701
|
+
else if (value.length >= 2) {
|
|
11702
|
+
const prefix = value.substring(0, 2);
|
|
11703
|
+
if (prefix in PREFIX_OPERATORS) {
|
|
11704
|
+
if (value.length === 2 || value.at(2)?.match(/\d/)) {
|
|
11705
|
+
operator = prefix;
|
|
11706
|
+
value = value.substring(prefix.length);
|
|
11707
|
+
}
|
|
11708
|
+
}
|
|
11709
|
+
}
|
|
11710
|
+
addFilter(searchRequest, {
|
|
11711
|
+
code,
|
|
11712
|
+
operator,
|
|
11713
|
+
value,
|
|
11714
|
+
});
|
|
11715
|
+
}
|
|
11716
|
+
function parsePrefix(input) {
|
|
11717
|
+
const prefix = input.substring(0, 2);
|
|
11718
|
+
const prefixOperator = PREFIX_OPERATORS[prefix];
|
|
11719
|
+
if (prefixOperator) {
|
|
11720
|
+
return { operator: prefixOperator, value: input.substring(2) };
|
|
11721
|
+
}
|
|
11722
|
+
return { operator: exports.Operator.EQUALS, value: input };
|
|
11723
|
+
}
|
|
11724
|
+
function parseModifier(modifier) {
|
|
11725
|
+
return MODIFIER_OPERATORS[modifier] || exports.Operator.EQUALS;
|
|
11726
|
+
}
|
|
11727
|
+
function parseIncludeTarget(input) {
|
|
11728
|
+
const parts = input.split(':');
|
|
11729
|
+
parts.forEach((p) => {
|
|
11730
|
+
if (p === '*') {
|
|
11731
|
+
throw new OperationOutcomeError(badRequest(`'*' is not supported as a value for search inclusion parameters`));
|
|
11732
|
+
}
|
|
11733
|
+
});
|
|
11734
|
+
if (parts.length === 1) {
|
|
11735
|
+
// Full wildcard, not currently supported
|
|
11736
|
+
throw new OperationOutcomeError(badRequest(`Invalid include value '${input}': must be of the form ResourceType:search-parameter`));
|
|
11737
|
+
}
|
|
11738
|
+
else if (parts.length === 2) {
|
|
11739
|
+
return {
|
|
11740
|
+
resourceType: parts[0],
|
|
11741
|
+
searchParam: parts[1],
|
|
11742
|
+
};
|
|
11743
|
+
}
|
|
11744
|
+
else if (parts.length === 3) {
|
|
11745
|
+
return {
|
|
11746
|
+
resourceType: parts[0],
|
|
11747
|
+
searchParam: parts[1],
|
|
11748
|
+
targetType: parts[2],
|
|
11749
|
+
};
|
|
11750
|
+
}
|
|
11751
|
+
else {
|
|
11752
|
+
throw new OperationOutcomeError(badRequest(`Invalid include value '${input}'`));
|
|
11753
|
+
}
|
|
11754
|
+
}
|
|
11755
|
+
function addFilter(searchRequest, filter) {
|
|
11756
|
+
if (searchRequest.filters) {
|
|
11757
|
+
searchRequest.filters.push(filter);
|
|
11758
|
+
}
|
|
11759
|
+
else {
|
|
11760
|
+
searchRequest.filters = [filter];
|
|
11761
|
+
}
|
|
11762
|
+
}
|
|
11763
|
+
/**
|
|
11764
|
+
* Formats a search definition object into a query string.
|
|
11765
|
+
* Note: The return value does not include the resource type.
|
|
11766
|
+
* @param {!SearchRequest} definition The search definition.
|
|
11767
|
+
* @returns Formatted URL.
|
|
11768
|
+
*/
|
|
11769
|
+
function formatSearchQuery(definition) {
|
|
11770
|
+
const params = [];
|
|
11771
|
+
if (definition.fields) {
|
|
11772
|
+
params.push('_fields=' + definition.fields.join(','));
|
|
11773
|
+
}
|
|
11774
|
+
if (definition.filters) {
|
|
11775
|
+
definition.filters.forEach((filter) => params.push(formatFilter(filter)));
|
|
11776
|
+
}
|
|
11777
|
+
if (definition.sortRules && definition.sortRules.length > 0) {
|
|
11778
|
+
params.push(formatSortRules(definition.sortRules));
|
|
11779
|
+
}
|
|
11780
|
+
if (definition.offset !== undefined) {
|
|
11781
|
+
params.push('_offset=' + definition.offset);
|
|
11782
|
+
}
|
|
11783
|
+
if (definition.count !== undefined) {
|
|
11784
|
+
params.push('_count=' + definition.count);
|
|
11785
|
+
}
|
|
11786
|
+
if (definition.total !== undefined) {
|
|
11787
|
+
params.push('_total=' + definition.total);
|
|
11788
|
+
}
|
|
11789
|
+
if (params.length === 0) {
|
|
11790
|
+
return '';
|
|
11791
|
+
}
|
|
11792
|
+
params.sort();
|
|
11793
|
+
return '?' + params.join('&');
|
|
11794
|
+
}
|
|
11795
|
+
function formatFilter(filter) {
|
|
11796
|
+
const modifier = filter.operator in MODIFIER_OPERATORS ? ':' + filter.operator : '';
|
|
11797
|
+
const prefix = filter.operator !== exports.Operator.EQUALS && filter.operator in PREFIX_OPERATORS ? filter.operator : '';
|
|
11798
|
+
return `${filter.code}${modifier}=${prefix}${encodeURIComponent(filter.value)}`;
|
|
11799
|
+
}
|
|
11800
|
+
function formatSortRules(sortRules) {
|
|
11801
|
+
return '_sort=' + sortRules.map((sr) => (sr.descending ? '-' + sr.code : sr.code)).join(',');
|
|
11802
|
+
}
|
|
11803
|
+
|
|
11804
|
+
const MAPPING_LANGUAGE_OPERATORS = [...FHIRPATH_OPERATORS, 'eq', 'ne', 'co'];
|
|
11805
|
+
function tokenize(str) {
|
|
11806
|
+
return new Tokenizer(str, FHIRPATH_KEYWORDS, MAPPING_LANGUAGE_OPERATORS, {
|
|
11807
|
+
dateTimeLiterals: true,
|
|
11808
|
+
symbolRegex: /[^\s\])]/,
|
|
11809
|
+
}).tokenize();
|
|
11810
|
+
}
|
|
11811
|
+
|
|
11812
|
+
// See: https://hl7.org/fhir/search_filter.html
|
|
11813
|
+
/**
|
|
11814
|
+
* The FhirFilterComparison class represents a comparison expression.
|
|
11815
|
+
*/
|
|
11816
|
+
class FhirFilterComparison {
|
|
11817
|
+
constructor(path, operator, value) {
|
|
11818
|
+
this.path = path;
|
|
11819
|
+
this.operator = operator;
|
|
11820
|
+
this.value = value;
|
|
11821
|
+
}
|
|
11822
|
+
}
|
|
11823
|
+
/**
|
|
11824
|
+
* The FhirFilterNegation class represents a negation expression.
|
|
11825
|
+
* It contains a single child expression.
|
|
11826
|
+
*/
|
|
11827
|
+
class FhirFilterNegation {
|
|
11828
|
+
constructor(child) {
|
|
11829
|
+
this.child = child;
|
|
11830
|
+
}
|
|
11831
|
+
}
|
|
11832
|
+
/**
|
|
11833
|
+
* The FhirFilterConnective class represents a connective expression.
|
|
11834
|
+
* It contains a list of child expressions.
|
|
11835
|
+
*/
|
|
11836
|
+
class FhirFilterConnective {
|
|
11837
|
+
constructor(keyword, left, right) {
|
|
11838
|
+
this.keyword = keyword;
|
|
11839
|
+
this.left = left;
|
|
11840
|
+
this.right = right;
|
|
11841
|
+
}
|
|
11842
|
+
}
|
|
11843
|
+
|
|
11844
|
+
/**
|
|
11845
|
+
* The operatorMap maps FHIR _filter operators to Medplum search operators.
|
|
11846
|
+
* See _filter operators: https://www.hl7.org/fhir/search_filter.html#ops
|
|
11847
|
+
*/
|
|
11848
|
+
const operatorMap = {
|
|
11849
|
+
// eq - an item in the set has an equal value
|
|
11850
|
+
eq: exports.Operator.EQUALS,
|
|
11851
|
+
// ne - An item in the set has an unequal value
|
|
11852
|
+
ne: exports.Operator.NOT_EQUALS,
|
|
11853
|
+
// co - An item in the set contains this value
|
|
11854
|
+
co: exports.Operator.CONTAINS,
|
|
11855
|
+
// sw - An item in the set starts with this value
|
|
11856
|
+
sw: undefined,
|
|
11857
|
+
// ew - An item in the set ends with this value
|
|
11858
|
+
ew: undefined,
|
|
11859
|
+
// gt / lt / ge / le - A value in the set is (greater than, less than, greater or equal, less or equal) the given value
|
|
11860
|
+
gt: exports.Operator.GREATER_THAN,
|
|
11861
|
+
lt: exports.Operator.LESS_THAN,
|
|
11862
|
+
ge: exports.Operator.GREATER_THAN_OR_EQUALS,
|
|
11863
|
+
le: exports.Operator.LESS_THAN_OR_EQUALS,
|
|
11864
|
+
// ap - A value in the set is approximately the same as this value.
|
|
11865
|
+
// Note that the recommended value for the approximation is 10% of the stated value (or for a date, 10% of the gap between now and the date), but systems may choose other values where appropriate
|
|
11866
|
+
ap: exports.Operator.APPROXIMATELY,
|
|
11867
|
+
// sa - The value starts after the specified value
|
|
11868
|
+
sa: exports.Operator.STARTS_AFTER,
|
|
11869
|
+
// eb - The value ends before the specified value
|
|
11870
|
+
eb: exports.Operator.ENDS_BEFORE,
|
|
11871
|
+
// pr - The set is empty or not (value is false or true)
|
|
11872
|
+
pr: exports.Operator.MISSING,
|
|
11873
|
+
// po - True if a (implied) date period in the set overlaps with the implied period in the value
|
|
11874
|
+
po: undefined,
|
|
11875
|
+
// ss - True if the value subsumes a concept in the set
|
|
11876
|
+
ss: undefined,
|
|
11877
|
+
// sb - True if the value is subsumed by a concept in the set
|
|
11878
|
+
sb: undefined,
|
|
11879
|
+
// in - True if one of the concepts is in the nominated value set by URI, either a relative, literal or logical vs
|
|
11880
|
+
in: exports.Operator.IN,
|
|
11881
|
+
// ni - True if none of the concepts are in the nominated value set by URI, either a relative, literal or logical vs
|
|
11882
|
+
ni: exports.Operator.NOT_IN,
|
|
11883
|
+
// re - True if one of the references in set points to the given URL
|
|
11884
|
+
re: undefined,
|
|
11885
|
+
// identifier - True if the identifier is in the identifier set (Medplum extension)
|
|
11886
|
+
identifier: exports.Operator.IDENTIFIER,
|
|
11887
|
+
};
|
|
11888
|
+
function getOperator(value) {
|
|
11889
|
+
const operator = operatorMap[value];
|
|
11890
|
+
if (!operator) {
|
|
11891
|
+
throw new OperationOutcomeError(badRequest('Invalid operator: ' + value));
|
|
11892
|
+
}
|
|
11893
|
+
return operator;
|
|
11894
|
+
}
|
|
11895
|
+
class FilterParameterParser {
|
|
11896
|
+
constructor(parser) {
|
|
11897
|
+
this.parser = parser;
|
|
11898
|
+
}
|
|
11899
|
+
parse() {
|
|
11900
|
+
let result;
|
|
11901
|
+
if (this.parser.peek()?.value === '(') {
|
|
11902
|
+
this.parser.consume('(');
|
|
11903
|
+
result = this.parse();
|
|
11904
|
+
this.parser.consume(')');
|
|
11905
|
+
}
|
|
11906
|
+
else if (this.parser.peek()?.value === 'not') {
|
|
11907
|
+
this.parser.consume('Symbol', 'not');
|
|
11908
|
+
this.parser.consume('(');
|
|
11909
|
+
result = new FhirFilterNegation(this.parse());
|
|
11910
|
+
this.parser.consume(')');
|
|
11911
|
+
}
|
|
11912
|
+
else {
|
|
11913
|
+
result = new FhirFilterComparison(this.parser.consume('Symbol').value, getOperator(this.parser.consume('Symbol').value), this.parser.consume().value);
|
|
11914
|
+
}
|
|
11915
|
+
const next = this.parser.peek()?.value;
|
|
11916
|
+
if (next === 'and' || next === 'or') {
|
|
11917
|
+
this.parser.consume('Symbol', next);
|
|
11918
|
+
return new FhirFilterConnective(next, result, this.parse());
|
|
11919
|
+
}
|
|
11920
|
+
return result;
|
|
11921
|
+
}
|
|
11922
|
+
}
|
|
11923
|
+
const fhirPathParserBuilder = initFhirPathParserBuilder();
|
|
11924
|
+
/**
|
|
11925
|
+
* Parses a FHIR _filter parameter expression into an AST.
|
|
11926
|
+
* @param input The FHIR _filter parameter expression.
|
|
11927
|
+
* @returns The AST representing the filters.
|
|
11928
|
+
*/
|
|
11929
|
+
function parseFilterParameter(input) {
|
|
11930
|
+
const parser = fhirPathParserBuilder.construct(tokenize(input));
|
|
11931
|
+
parser.removeComments();
|
|
11932
|
+
return new FilterParameterParser(parser).parse();
|
|
11933
|
+
}
|
|
11934
|
+
|
|
11935
|
+
/**
|
|
11936
|
+
* The Hl7Context class represents the parsing context for an HL7 message.
|
|
11937
|
+
*
|
|
11938
|
+
* MSH-1:
|
|
11939
|
+
* https://hl7-definition.caristix.com/v2/HL7v2.6/Fields/MSH.1
|
|
11940
|
+
*
|
|
11941
|
+
* MSH-2:
|
|
11942
|
+
* https://hl7-definition.caristix.com/v2/HL7v2.6/Fields/MSH.2
|
|
11943
|
+
*
|
|
11944
|
+
* See this tutorial on MSH, and why it's a bad idea to use anything other than the default values:
|
|
11945
|
+
* https://www.hl7soup.com/HL7TutorialMSH.html
|
|
11946
|
+
*/
|
|
11947
|
+
class Hl7Context {
|
|
11948
|
+
constructor(segmentSeparator = '\r', fieldSeparator = '|', componentSeparator = '^', repetitionSeparator = '~', escapeCharacter = '\\', subcomponentSeparator = '&') {
|
|
11949
|
+
this.segmentSeparator = segmentSeparator;
|
|
11950
|
+
this.fieldSeparator = fieldSeparator;
|
|
11951
|
+
this.componentSeparator = componentSeparator;
|
|
11952
|
+
this.repetitionSeparator = repetitionSeparator;
|
|
11953
|
+
this.escapeCharacter = escapeCharacter;
|
|
11954
|
+
this.subcomponentSeparator = subcomponentSeparator;
|
|
11955
|
+
}
|
|
11956
|
+
/**
|
|
11957
|
+
* Returns the MSH-2 field value based on the configured separators.
|
|
11958
|
+
* @returns The HL7 MSH-2 field value.
|
|
11959
|
+
*/
|
|
11960
|
+
getMsh2() {
|
|
11961
|
+
return (this.fieldSeparator +
|
|
11962
|
+
this.componentSeparator +
|
|
11963
|
+
this.repetitionSeparator +
|
|
11964
|
+
this.escapeCharacter +
|
|
11965
|
+
this.subcomponentSeparator);
|
|
11966
|
+
}
|
|
11967
|
+
}
|
|
11968
|
+
/**
|
|
11969
|
+
* The Hl7Message class represents one HL7 message.
|
|
11970
|
+
* A message is a collection of segments.
|
|
11971
|
+
*/
|
|
11972
|
+
class Hl7Message {
|
|
11973
|
+
/**
|
|
11974
|
+
* Creates a new HL7 message.
|
|
11975
|
+
* @param segments The HL7 segments.
|
|
11976
|
+
* @param context Optional HL7 parsing context.
|
|
11977
|
+
*/
|
|
11978
|
+
constructor(segments, context = new Hl7Context()) {
|
|
11979
|
+
this.context = context;
|
|
11980
|
+
this.segments = segments;
|
|
11981
|
+
}
|
|
11982
|
+
/**
|
|
11983
|
+
* Returns an HL7 segment by index or by name.
|
|
11984
|
+
* @param index The HL7 segment index or name.
|
|
11985
|
+
* @returns The HL7 segment if found; otherwise, undefined.
|
|
11986
|
+
*/
|
|
11987
|
+
get(index) {
|
|
11988
|
+
if (typeof index === 'number') {
|
|
11989
|
+
return this.segments[index];
|
|
11990
|
+
}
|
|
11991
|
+
return this.segments.find((s) => s.name === index);
|
|
11992
|
+
}
|
|
11993
|
+
/**
|
|
11994
|
+
* Returns all HL7 segments of a given name.
|
|
11995
|
+
* @param name The HL7 segment name.
|
|
11996
|
+
* @returns An array of HL7 segments with the specified name.
|
|
11997
|
+
*/
|
|
11998
|
+
getAll(name) {
|
|
11999
|
+
return this.segments.filter((s) => s.name === name);
|
|
12000
|
+
}
|
|
12001
|
+
/**
|
|
12002
|
+
* Returns the HL7 message as a string.
|
|
12003
|
+
* @returns The HL7 message as a string.
|
|
12004
|
+
*/
|
|
12005
|
+
toString() {
|
|
11548
12006
|
return this.segments.map((s) => s.toString()).join(this.context.segmentSeparator);
|
|
11549
12007
|
}
|
|
11550
12008
|
/**
|
|
@@ -11689,6 +12147,28 @@
|
|
|
11689
12147
|
return new Hl7Field(text.split(context.repetitionSeparator).map((r) => r.split(context.componentSeparator)), context);
|
|
11690
12148
|
}
|
|
11691
12149
|
}
|
|
12150
|
+
/**
|
|
12151
|
+
* Returns a formatted string representing the date in ISO-8601 format.
|
|
12152
|
+
* @param hl7Date Date string.
|
|
12153
|
+
* @param options Optional configuration Object
|
|
12154
|
+
* @returns
|
|
12155
|
+
*/
|
|
12156
|
+
function parseHl7Date(hl7Date, options) {
|
|
12157
|
+
if (!hl7Date) {
|
|
12158
|
+
return undefined;
|
|
12159
|
+
}
|
|
12160
|
+
options = { ...{ seconds: true, tzOffset: 'Z' }, ...options };
|
|
12161
|
+
const year = Number.parseInt(hl7Date.substring(0, 4));
|
|
12162
|
+
const month = Number.parseInt(hl7Date.substring(4, 6));
|
|
12163
|
+
const date = Number.parseInt(hl7Date.substring(6, 8));
|
|
12164
|
+
const hours = Number.parseInt(hl7Date.substring(8, 10));
|
|
12165
|
+
const minutes = Number.parseInt(hl7Date.substring(10, 12));
|
|
12166
|
+
const seconds = options.seconds ? Number.parseInt(hl7Date.substring(12, 14)) : 0;
|
|
12167
|
+
return `${pad2(year)}-${pad2(month)}-${pad2(date)}T${pad2(hours)}:${pad2(minutes)}:${pad2(seconds)}.000${options.tzOffset}`;
|
|
12168
|
+
}
|
|
12169
|
+
function pad2(n) {
|
|
12170
|
+
return n.toString().padStart(2, '0');
|
|
12171
|
+
}
|
|
11692
12172
|
|
|
11693
12173
|
/*
|
|
11694
12174
|
* This file provides schema validation utilities for FHIR JSON objects.
|
|
@@ -12112,6 +12592,7 @@
|
|
|
12112
12592
|
SearchParameterType["QUANTITY"] = "QUANTITY";
|
|
12113
12593
|
SearchParameterType["TEXT"] = "TEXT";
|
|
12114
12594
|
SearchParameterType["REFERENCE"] = "REFERENCE";
|
|
12595
|
+
SearchParameterType["CANONICAL"] = "CANONICAL";
|
|
12115
12596
|
SearchParameterType["DATE"] = "DATE";
|
|
12116
12597
|
SearchParameterType["DATETIME"] = "DATETIME";
|
|
12117
12598
|
SearchParameterType["PERIOD"] = "PERIOD";
|
|
@@ -12154,474 +12635,114 @@
|
|
|
12154
12635
|
// In the future, explore returning multiple column definitions
|
|
12155
12636
|
return { columnName, type: exports.SearchParameterType.TEXT };
|
|
12156
12637
|
}
|
|
12157
|
-
|
|
12158
|
-
let
|
|
12159
|
-
let
|
|
12160
|
-
let
|
|
12161
|
-
let
|
|
12162
|
-
|
|
12163
|
-
|
|
12164
|
-
|
|
12165
|
-
if (
|
|
12166
|
-
|
|
12167
|
-
|
|
12168
|
-
if (elementDefinition.max === '*') {
|
|
12169
|
-
array = true;
|
|
12170
|
-
}
|
|
12171
|
-
propertyType = elementDefinition.type?.[0].code;
|
|
12172
|
-
if (!propertyType) {
|
|
12173
|
-
// This happens when one of parent properties uses contentReference
|
|
12174
|
-
// In the future, explore following the reference
|
|
12175
|
-
return { columnName, type: defaultType, array };
|
|
12176
|
-
}
|
|
12177
|
-
if (i < expression.length - 1) {
|
|
12178
|
-
if (isBackboneElement(propertyType)) {
|
|
12179
|
-
baseType = buildTypeName(elementDefinition.path?.split('.'));
|
|
12180
|
-
}
|
|
12181
|
-
else {
|
|
12182
|
-
baseType = propertyType;
|
|
12183
|
-
}
|
|
12184
|
-
}
|
|
12185
|
-
}
|
|
12186
|
-
const type = getSearchParameterType(searchParam, propertyType);
|
|
12187
|
-
const result = { columnName, type, elementDefinition, array };
|
|
12188
|
-
setSearchParamterDetails(resourceType, code, result);
|
|
12189
|
-
return result;
|
|
12190
|
-
}
|
|
12191
|
-
function isBackboneElement(propertyType) {
|
|
12192
|
-
return propertyType === 'Element' || propertyType === 'BackboneElement';
|
|
12193
|
-
}
|
|
12194
|
-
/**
|
|
12195
|
-
* Converts a hyphen-delimited code to camelCase string.
|
|
12196
|
-
* @param code The search parameter code.
|
|
12197
|
-
* @returns The SQL column name.
|
|
12198
|
-
*/
|
|
12199
|
-
function convertCodeToColumnName(code) {
|
|
12200
|
-
return code.split('-').reduce((result, word, index) => result + (index ? capitalize(word) : word), '');
|
|
12201
|
-
}
|
|
12202
|
-
function getSearchParameterType(searchParam, propertyType) {
|
|
12203
|
-
let type = exports.SearchParameterType.TEXT;
|
|
12204
|
-
switch (searchParam.type) {
|
|
12205
|
-
case 'date':
|
|
12206
|
-
if (propertyType === exports.PropertyType.dateTime || propertyType === exports.PropertyType.instant) {
|
|
12207
|
-
type = exports.SearchParameterType.DATETIME;
|
|
12208
|
-
}
|
|
12209
|
-
else {
|
|
12210
|
-
type = exports.SearchParameterType.DATE;
|
|
12211
|
-
}
|
|
12212
|
-
break;
|
|
12213
|
-
case 'number':
|
|
12214
|
-
type = exports.SearchParameterType.NUMBER;
|
|
12215
|
-
break;
|
|
12216
|
-
case 'quantity':
|
|
12217
|
-
type = exports.SearchParameterType.QUANTITY;
|
|
12218
|
-
break;
|
|
12219
|
-
case 'reference':
|
|
12220
|
-
type = exports.SearchParameterType.REFERENCE;
|
|
12221
|
-
break;
|
|
12222
|
-
case 'token':
|
|
12223
|
-
if (propertyType === 'boolean') {
|
|
12224
|
-
type = exports.SearchParameterType.BOOLEAN;
|
|
12225
|
-
}
|
|
12226
|
-
break;
|
|
12227
|
-
}
|
|
12228
|
-
return type;
|
|
12229
|
-
}
|
|
12230
|
-
function getExpressionForResourceType(resourceType, expression) {
|
|
12231
|
-
const expressions = expression.split(' | ');
|
|
12232
|
-
for (const e of expressions) {
|
|
12233
|
-
if (isIgnoredExpression(e)) {
|
|
12234
|
-
continue;
|
|
12235
|
-
}
|
|
12236
|
-
const simplified = simplifyExpression(e);
|
|
12237
|
-
if (simplified.startsWith(resourceType + '.')) {
|
|
12238
|
-
return simplified;
|
|
12239
|
-
}
|
|
12240
|
-
}
|
|
12241
|
-
return undefined;
|
|
12242
|
-
}
|
|
12243
|
-
function isIgnoredExpression(input) {
|
|
12244
|
-
return input.includes(' as Period') || input.includes(' as SampledDate');
|
|
12245
|
-
}
|
|
12246
|
-
function simplifyExpression(input) {
|
|
12247
|
-
let result = input.trim();
|
|
12248
|
-
if (result.startsWith('(') && result.endsWith(')')) {
|
|
12249
|
-
result = result.substring(1, result.length - 1);
|
|
12250
|
-
}
|
|
12251
|
-
if (result.includes('[0]')) {
|
|
12252
|
-
result = result.replaceAll('[0]', '');
|
|
12253
|
-
}
|
|
12254
|
-
const stopStrings = [' != ', ' as ', '.as(', '.exists(', '.resolve(', '.where('];
|
|
12255
|
-
for (const stopString of stopStrings) {
|
|
12256
|
-
if (result.includes(stopString)) {
|
|
12257
|
-
result = result.substring(0, result.indexOf(stopString));
|
|
12258
|
-
}
|
|
12259
|
-
}
|
|
12260
|
-
return result;
|
|
12261
|
-
}
|
|
12262
|
-
|
|
12263
|
-
const DEFAULT_SEARCH_COUNT = 20;
|
|
12264
|
-
/**
|
|
12265
|
-
* Search operators.
|
|
12266
|
-
* These operators represent "modifiers" and "prefixes" in FHIR search.
|
|
12267
|
-
* See: https://www.hl7.org/fhir/search.html
|
|
12268
|
-
*/
|
|
12269
|
-
exports.Operator = void 0;
|
|
12270
|
-
(function (Operator) {
|
|
12271
|
-
Operator["EQUALS"] = "eq";
|
|
12272
|
-
Operator["NOT_EQUALS"] = "ne";
|
|
12273
|
-
// Numbers
|
|
12274
|
-
Operator["GREATER_THAN"] = "gt";
|
|
12275
|
-
Operator["LESS_THAN"] = "lt";
|
|
12276
|
-
Operator["GREATER_THAN_OR_EQUALS"] = "ge";
|
|
12277
|
-
Operator["LESS_THAN_OR_EQUALS"] = "le";
|
|
12278
|
-
// Dates
|
|
12279
|
-
Operator["STARTS_AFTER"] = "sa";
|
|
12280
|
-
Operator["ENDS_BEFORE"] = "eb";
|
|
12281
|
-
Operator["APPROXIMATELY"] = "ap";
|
|
12282
|
-
// String
|
|
12283
|
-
Operator["CONTAINS"] = "contains";
|
|
12284
|
-
Operator["EXACT"] = "exact";
|
|
12285
|
-
// Token
|
|
12286
|
-
Operator["TEXT"] = "text";
|
|
12287
|
-
Operator["NOT"] = "not";
|
|
12288
|
-
Operator["ABOVE"] = "above";
|
|
12289
|
-
Operator["BELOW"] = "below";
|
|
12290
|
-
Operator["IN"] = "in";
|
|
12291
|
-
Operator["NOT_IN"] = "not-in";
|
|
12292
|
-
Operator["OF_TYPE"] = "of-type";
|
|
12293
|
-
// All
|
|
12294
|
-
Operator["MISSING"] = "missing";
|
|
12295
|
-
// Reference
|
|
12296
|
-
Operator["IDENTIFIER"] = "identifier";
|
|
12297
|
-
// _include and _revinclude
|
|
12298
|
-
Operator["ITERATE"] = "iterate";
|
|
12299
|
-
})(exports.Operator || (exports.Operator = {}));
|
|
12300
|
-
/**
|
|
12301
|
-
* Parameter names may specify a modifier as a suffix.
|
|
12302
|
-
* The modifiers are separated from the parameter name by a colon.
|
|
12303
|
-
* See: https://www.hl7.org/fhir/search.html#modifiers
|
|
12304
|
-
*/
|
|
12305
|
-
const MODIFIER_OPERATORS = {
|
|
12306
|
-
contains: exports.Operator.CONTAINS,
|
|
12307
|
-
exact: exports.Operator.EXACT,
|
|
12308
|
-
above: exports.Operator.ABOVE,
|
|
12309
|
-
below: exports.Operator.BELOW,
|
|
12310
|
-
text: exports.Operator.TEXT,
|
|
12311
|
-
not: exports.Operator.NOT,
|
|
12312
|
-
in: exports.Operator.IN,
|
|
12313
|
-
'not-in': exports.Operator.NOT_IN,
|
|
12314
|
-
'of-type': exports.Operator.OF_TYPE,
|
|
12315
|
-
missing: exports.Operator.MISSING,
|
|
12316
|
-
identifier: exports.Operator.IDENTIFIER,
|
|
12317
|
-
iterate: exports.Operator.ITERATE,
|
|
12318
|
-
};
|
|
12319
|
-
/**
|
|
12320
|
-
* For the ordered parameter types of number, date, and quantity,
|
|
12321
|
-
* a prefix to the parameter value may be used to control the nature
|
|
12322
|
-
* of the matching.
|
|
12323
|
-
* See: https://www.hl7.org/fhir/search.html#prefix
|
|
12324
|
-
*/
|
|
12325
|
-
const PREFIX_OPERATORS = {
|
|
12326
|
-
eq: exports.Operator.EQUALS,
|
|
12327
|
-
ne: exports.Operator.NOT_EQUALS,
|
|
12328
|
-
lt: exports.Operator.LESS_THAN,
|
|
12329
|
-
le: exports.Operator.LESS_THAN_OR_EQUALS,
|
|
12330
|
-
gt: exports.Operator.GREATER_THAN,
|
|
12331
|
-
ge: exports.Operator.GREATER_THAN_OR_EQUALS,
|
|
12332
|
-
sa: exports.Operator.STARTS_AFTER,
|
|
12333
|
-
eb: exports.Operator.ENDS_BEFORE,
|
|
12334
|
-
ap: exports.Operator.APPROXIMATELY,
|
|
12335
|
-
};
|
|
12336
|
-
/**
|
|
12337
|
-
* Parses a search URL into a search request.
|
|
12338
|
-
* @param resourceType The FHIR resource type.
|
|
12339
|
-
* @param query The collection of query string parameters.
|
|
12340
|
-
* @returns A parsed SearchRequest.
|
|
12341
|
-
*/
|
|
12342
|
-
function parseSearchRequest(resourceType, query) {
|
|
12343
|
-
const queryArray = [];
|
|
12344
|
-
for (const [key, value] of Object.entries(query)) {
|
|
12345
|
-
if (Array.isArray(value)) {
|
|
12346
|
-
for (let i = 0; i < value.length; i++) {
|
|
12347
|
-
queryArray.push([key, value[i]]);
|
|
12348
|
-
}
|
|
12349
|
-
}
|
|
12350
|
-
else {
|
|
12351
|
-
queryArray.push([key, value || '']);
|
|
12352
|
-
}
|
|
12353
|
-
}
|
|
12354
|
-
return parseSearchImpl(resourceType, queryArray);
|
|
12355
|
-
}
|
|
12356
|
-
/**
|
|
12357
|
-
* Parses a search URL into a search request.
|
|
12358
|
-
* @param url The search URL.
|
|
12359
|
-
* @returns A parsed SearchRequest.
|
|
12360
|
-
*/
|
|
12361
|
-
function parseSearchUrl(url) {
|
|
12362
|
-
const resourceType = url.pathname.split('/').filter(Boolean).pop();
|
|
12363
|
-
return parseSearchImpl(resourceType, url.searchParams.entries());
|
|
12364
|
-
}
|
|
12365
|
-
/**
|
|
12366
|
-
* Parses a URL string into a SearchRequest.
|
|
12367
|
-
* @param url The URL to parse.
|
|
12368
|
-
* @returns Parsed search definition.
|
|
12369
|
-
*/
|
|
12370
|
-
function parseSearchDefinition(url) {
|
|
12371
|
-
return parseSearchUrl(new URL(url, 'https://example.com/'));
|
|
12372
|
-
}
|
|
12373
|
-
function parseSearchImpl(resourceType, query) {
|
|
12374
|
-
const searchRequest = {
|
|
12375
|
-
resourceType,
|
|
12376
|
-
};
|
|
12377
|
-
for (const [key, value] of query) {
|
|
12378
|
-
parseKeyValue(searchRequest, key, value);
|
|
12379
|
-
}
|
|
12380
|
-
return searchRequest;
|
|
12381
|
-
}
|
|
12382
|
-
function parseKeyValue(searchRequest, key, value) {
|
|
12383
|
-
let code;
|
|
12384
|
-
let modifier;
|
|
12385
|
-
const colonIndex = key.indexOf(':');
|
|
12386
|
-
if (colonIndex >= 0) {
|
|
12387
|
-
code = key.substring(0, colonIndex);
|
|
12388
|
-
modifier = key.substring(colonIndex + 1);
|
|
12389
|
-
}
|
|
12390
|
-
else {
|
|
12391
|
-
code = key;
|
|
12392
|
-
modifier = '';
|
|
12393
|
-
}
|
|
12394
|
-
switch (code) {
|
|
12395
|
-
case '_sort':
|
|
12396
|
-
parseSortRule(searchRequest, value);
|
|
12397
|
-
break;
|
|
12398
|
-
case '_count':
|
|
12399
|
-
searchRequest.count = parseInt(value);
|
|
12400
|
-
break;
|
|
12401
|
-
case '_offset':
|
|
12402
|
-
searchRequest.offset = parseInt(value);
|
|
12403
|
-
break;
|
|
12404
|
-
case '_total':
|
|
12405
|
-
searchRequest.total = value;
|
|
12406
|
-
break;
|
|
12407
|
-
case '_summary':
|
|
12408
|
-
searchRequest.total = 'estimate';
|
|
12409
|
-
searchRequest.count = 0;
|
|
12410
|
-
break;
|
|
12411
|
-
case '_include': {
|
|
12412
|
-
const target = parseIncludeTarget(value);
|
|
12413
|
-
if (modifier === 'iterate') {
|
|
12414
|
-
target.modifier = exports.Operator.ITERATE;
|
|
12415
|
-
}
|
|
12416
|
-
if (searchRequest.include) {
|
|
12417
|
-
searchRequest.include.push(target);
|
|
12418
|
-
}
|
|
12419
|
-
else {
|
|
12420
|
-
searchRequest.include = [target];
|
|
12421
|
-
}
|
|
12422
|
-
break;
|
|
12638
|
+
let baseType = resourceType;
|
|
12639
|
+
let elementDefinition = undefined;
|
|
12640
|
+
let propertyType = undefined;
|
|
12641
|
+
let array = false;
|
|
12642
|
+
for (let i = 1; i < expression.length; i++) {
|
|
12643
|
+
let propertyName = expression[i];
|
|
12644
|
+
let hasArrayIndex = false;
|
|
12645
|
+
const arrayIndexMatch = /\[\d+\]$/.exec(propertyName);
|
|
12646
|
+
if (arrayIndexMatch) {
|
|
12647
|
+
propertyName = propertyName.substring(0, propertyName.length - arrayIndexMatch[0].length);
|
|
12648
|
+
hasArrayIndex = true;
|
|
12423
12649
|
}
|
|
12424
|
-
|
|
12425
|
-
|
|
12426
|
-
|
|
12427
|
-
target.modifier = exports.Operator.ITERATE;
|
|
12428
|
-
}
|
|
12429
|
-
if (searchRequest.revInclude) {
|
|
12430
|
-
searchRequest.revInclude.push(target);
|
|
12431
|
-
}
|
|
12432
|
-
else {
|
|
12433
|
-
searchRequest.revInclude = [target];
|
|
12434
|
-
}
|
|
12435
|
-
break;
|
|
12650
|
+
elementDefinition = getElementDefinition(baseType, propertyName);
|
|
12651
|
+
if (!elementDefinition) {
|
|
12652
|
+
throw new Error(`Element definition not found for ${resourceType} ${searchParam.code}`);
|
|
12436
12653
|
}
|
|
12437
|
-
|
|
12438
|
-
|
|
12439
|
-
|
|
12440
|
-
|
|
12441
|
-
|
|
12442
|
-
|
|
12443
|
-
|
|
12654
|
+
if (elementDefinition.max !== '0' && elementDefinition.max !== '1' && !hasArrayIndex) {
|
|
12655
|
+
array = true;
|
|
12656
|
+
}
|
|
12657
|
+
// "code" is only missing when using "contentReference"
|
|
12658
|
+
// "contentReference" is handled above in "getElementDefinition"
|
|
12659
|
+
propertyType = elementDefinition.type?.[0].code;
|
|
12660
|
+
if (i < expression.length - 1) {
|
|
12661
|
+
if (isBackboneElement(propertyType)) {
|
|
12662
|
+
baseType = buildTypeName(elementDefinition.path?.split('.'));
|
|
12444
12663
|
}
|
|
12445
12664
|
else {
|
|
12446
|
-
|
|
12665
|
+
baseType = propertyType;
|
|
12447
12666
|
}
|
|
12448
12667
|
}
|
|
12449
12668
|
}
|
|
12669
|
+
const type = getSearchParameterType(searchParam, propertyType);
|
|
12670
|
+
const result = { columnName, type, elementDefinition, array };
|
|
12671
|
+
setSearchParamterDetails(resourceType, code, result);
|
|
12672
|
+
return result;
|
|
12450
12673
|
}
|
|
12451
|
-
function
|
|
12452
|
-
|
|
12453
|
-
let code;
|
|
12454
|
-
let descending = false;
|
|
12455
|
-
if (field.startsWith('-')) {
|
|
12456
|
-
code = field.substring(1);
|
|
12457
|
-
descending = true;
|
|
12458
|
-
}
|
|
12459
|
-
else {
|
|
12460
|
-
code = field;
|
|
12461
|
-
}
|
|
12462
|
-
if (!searchRequest.sortRules) {
|
|
12463
|
-
searchRequest.sortRules = [];
|
|
12464
|
-
}
|
|
12465
|
-
searchRequest.sortRules.push({ code, descending });
|
|
12466
|
-
}
|
|
12674
|
+
function isBackboneElement(propertyType) {
|
|
12675
|
+
return propertyType === 'Element' || propertyType === 'BackboneElement';
|
|
12467
12676
|
}
|
|
12468
|
-
|
|
12469
|
-
|
|
12470
|
-
|
|
12471
|
-
|
|
12472
|
-
|
|
12473
|
-
|
|
12474
|
-
|
|
12475
|
-
|
|
12476
|
-
|
|
12677
|
+
/**
|
|
12678
|
+
* Converts a hyphen-delimited code to camelCase string.
|
|
12679
|
+
* @param code The search parameter code.
|
|
12680
|
+
* @returns The SQL column name.
|
|
12681
|
+
*/
|
|
12682
|
+
function convertCodeToColumnName(code) {
|
|
12683
|
+
return code.split('-').reduce((result, word, index) => result + (index ? capitalize(word) : word), '');
|
|
12684
|
+
}
|
|
12685
|
+
function getSearchParameterType(searchParam, propertyType) {
|
|
12686
|
+
let type = exports.SearchParameterType.TEXT;
|
|
12477
12687
|
switch (searchParam.type) {
|
|
12478
|
-
case 'number':
|
|
12479
12688
|
case 'date':
|
|
12480
|
-
|
|
12689
|
+
if (propertyType === exports.PropertyType.date) {
|
|
12690
|
+
type = exports.SearchParameterType.DATE;
|
|
12691
|
+
}
|
|
12692
|
+
else {
|
|
12693
|
+
type = exports.SearchParameterType.DATETIME;
|
|
12694
|
+
}
|
|
12481
12695
|
break;
|
|
12482
|
-
case '
|
|
12483
|
-
|
|
12484
|
-
case 'token':
|
|
12485
|
-
case 'uri':
|
|
12486
|
-
parseModifierType(searchRequest, searchParam, modifier, value);
|
|
12696
|
+
case 'number':
|
|
12697
|
+
type = exports.SearchParameterType.NUMBER;
|
|
12487
12698
|
break;
|
|
12488
12699
|
case 'quantity':
|
|
12489
|
-
|
|
12700
|
+
type = exports.SearchParameterType.QUANTITY;
|
|
12490
12701
|
break;
|
|
12491
|
-
|
|
12492
|
-
|
|
12493
|
-
|
|
12494
|
-
const { operator, value } = parsePrefix(input);
|
|
12495
|
-
addFilter(searchRequest, {
|
|
12496
|
-
code: param.code,
|
|
12497
|
-
operator,
|
|
12498
|
-
value,
|
|
12499
|
-
});
|
|
12500
|
-
}
|
|
12501
|
-
function parseModifierType(searchRequest, param, modifier, value) {
|
|
12502
|
-
addFilter(searchRequest, {
|
|
12503
|
-
code: param.code,
|
|
12504
|
-
operator: parseModifier(modifier),
|
|
12505
|
-
value,
|
|
12506
|
-
});
|
|
12507
|
-
}
|
|
12508
|
-
function parseQuantity(searchRequest, param, input) {
|
|
12509
|
-
const [prefixNumber, unitSystem, unitCode] = input.split('|');
|
|
12510
|
-
const { operator, value } = parsePrefix(prefixNumber);
|
|
12511
|
-
addFilter(searchRequest, {
|
|
12512
|
-
code: param.code,
|
|
12513
|
-
operator,
|
|
12514
|
-
value,
|
|
12515
|
-
unitSystem,
|
|
12516
|
-
unitCode,
|
|
12517
|
-
});
|
|
12518
|
-
}
|
|
12519
|
-
function parseUnknownParameter(searchRequest, code, modifier, value) {
|
|
12520
|
-
let operator = exports.Operator.EQUALS;
|
|
12521
|
-
if (modifier) {
|
|
12522
|
-
operator = modifier;
|
|
12523
|
-
}
|
|
12524
|
-
else if (value.length >= 2) {
|
|
12525
|
-
const prefix = value.substring(0, 2);
|
|
12526
|
-
if (prefix in PREFIX_OPERATORS) {
|
|
12527
|
-
if (value.length === 2 || value.at(2)?.match(/\d/)) {
|
|
12528
|
-
operator = prefix;
|
|
12529
|
-
value = value.substring(prefix.length);
|
|
12702
|
+
case 'reference':
|
|
12703
|
+
if (propertyType === exports.PropertyType.canonical) {
|
|
12704
|
+
type = exports.SearchParameterType.CANONICAL;
|
|
12530
12705
|
}
|
|
12531
|
-
|
|
12532
|
-
|
|
12533
|
-
|
|
12534
|
-
|
|
12535
|
-
|
|
12536
|
-
|
|
12537
|
-
|
|
12538
|
-
|
|
12539
|
-
|
|
12540
|
-
const prefix = input.substring(0, 2);
|
|
12541
|
-
const prefixOperator = PREFIX_OPERATORS[prefix];
|
|
12542
|
-
if (prefixOperator) {
|
|
12543
|
-
return { operator: prefixOperator, value: input.substring(2) };
|
|
12706
|
+
else {
|
|
12707
|
+
type = exports.SearchParameterType.REFERENCE;
|
|
12708
|
+
}
|
|
12709
|
+
break;
|
|
12710
|
+
case 'token':
|
|
12711
|
+
if (propertyType === 'boolean') {
|
|
12712
|
+
type = exports.SearchParameterType.BOOLEAN;
|
|
12713
|
+
}
|
|
12714
|
+
break;
|
|
12544
12715
|
}
|
|
12545
|
-
return
|
|
12546
|
-
}
|
|
12547
|
-
function parseModifier(modifier) {
|
|
12548
|
-
return MODIFIER_OPERATORS[modifier] || exports.Operator.EQUALS;
|
|
12716
|
+
return type;
|
|
12549
12717
|
}
|
|
12550
|
-
function
|
|
12551
|
-
const
|
|
12552
|
-
|
|
12553
|
-
if (
|
|
12554
|
-
|
|
12718
|
+
function getExpressionForResourceType(resourceType, expression) {
|
|
12719
|
+
const expressions = expression.split(' | ');
|
|
12720
|
+
for (const e of expressions) {
|
|
12721
|
+
if (isIgnoredExpression(e)) {
|
|
12722
|
+
continue;
|
|
12723
|
+
}
|
|
12724
|
+
const simplified = simplifyExpression(e);
|
|
12725
|
+
if (simplified.startsWith(resourceType + '.')) {
|
|
12726
|
+
return simplified;
|
|
12555
12727
|
}
|
|
12556
|
-
});
|
|
12557
|
-
if (parts.length === 1) {
|
|
12558
|
-
// Full wildcard, not currently supported
|
|
12559
|
-
throw new OperationOutcomeError(badRequest(`Invalid include value '${input}': must be of the form ResourceType:search-parameter`));
|
|
12560
|
-
}
|
|
12561
|
-
else if (parts.length === 2) {
|
|
12562
|
-
return {
|
|
12563
|
-
resourceType: parts[0],
|
|
12564
|
-
searchParam: parts[1],
|
|
12565
|
-
};
|
|
12566
|
-
}
|
|
12567
|
-
else if (parts.length === 3) {
|
|
12568
|
-
return {
|
|
12569
|
-
resourceType: parts[0],
|
|
12570
|
-
searchParam: parts[1],
|
|
12571
|
-
targetType: parts[2],
|
|
12572
|
-
};
|
|
12573
|
-
}
|
|
12574
|
-
else {
|
|
12575
|
-
throw new OperationOutcomeError(badRequest(`Invalid include value '${input}'`));
|
|
12576
12728
|
}
|
|
12729
|
+
return undefined;
|
|
12577
12730
|
}
|
|
12578
|
-
function
|
|
12579
|
-
|
|
12580
|
-
searchRequest.filters.push(filter);
|
|
12581
|
-
}
|
|
12582
|
-
else {
|
|
12583
|
-
searchRequest.filters = [filter];
|
|
12584
|
-
}
|
|
12731
|
+
function isIgnoredExpression(input) {
|
|
12732
|
+
return input.includes(' as Period') || input.includes(' as SampledDate');
|
|
12585
12733
|
}
|
|
12586
|
-
|
|
12587
|
-
|
|
12588
|
-
|
|
12589
|
-
|
|
12590
|
-
* @returns Formatted URL.
|
|
12591
|
-
*/
|
|
12592
|
-
function formatSearchQuery(definition) {
|
|
12593
|
-
const params = [];
|
|
12594
|
-
if (definition.fields) {
|
|
12595
|
-
params.push('_fields=' + definition.fields.join(','));
|
|
12596
|
-
}
|
|
12597
|
-
if (definition.filters) {
|
|
12598
|
-
definition.filters.forEach((filter) => params.push(formatFilter(filter)));
|
|
12599
|
-
}
|
|
12600
|
-
if (definition.sortRules && definition.sortRules.length > 0) {
|
|
12601
|
-
params.push(formatSortRules(definition.sortRules));
|
|
12602
|
-
}
|
|
12603
|
-
if (definition.offset !== undefined) {
|
|
12604
|
-
params.push('_offset=' + definition.offset);
|
|
12605
|
-
}
|
|
12606
|
-
if (definition.count !== undefined) {
|
|
12607
|
-
params.push('_count=' + definition.count);
|
|
12608
|
-
}
|
|
12609
|
-
if (definition.total !== undefined) {
|
|
12610
|
-
params.push('_total=' + definition.total);
|
|
12734
|
+
function simplifyExpression(input) {
|
|
12735
|
+
let result = input.trim();
|
|
12736
|
+
if (result.startsWith('(') && result.endsWith(')')) {
|
|
12737
|
+
result = result.substring(1, result.length - 1);
|
|
12611
12738
|
}
|
|
12612
|
-
|
|
12613
|
-
|
|
12739
|
+
const stopStrings = [' != ', ' as ', '.as(', '.exists(', '.resolve(', '.where('];
|
|
12740
|
+
for (const stopString of stopStrings) {
|
|
12741
|
+
if (result.includes(stopString)) {
|
|
12742
|
+
result = result.substring(0, result.indexOf(stopString));
|
|
12743
|
+
}
|
|
12614
12744
|
}
|
|
12615
|
-
|
|
12616
|
-
return '?' + params.join('&');
|
|
12617
|
-
}
|
|
12618
|
-
function formatFilter(filter) {
|
|
12619
|
-
const modifier = filter.operator in MODIFIER_OPERATORS ? ':' + filter.operator : '';
|
|
12620
|
-
const prefix = filter.operator !== exports.Operator.EQUALS && filter.operator in PREFIX_OPERATORS ? filter.operator : '';
|
|
12621
|
-
return `${filter.code}${modifier}=${prefix}${encodeURIComponent(filter.value)}`;
|
|
12622
|
-
}
|
|
12623
|
-
function formatSortRules(sortRules) {
|
|
12624
|
-
return '_sort=' + sortRules.map((sr) => (sr.descending ? '-' + sr.code : sr.code)).join(',');
|
|
12745
|
+
return result;
|
|
12625
12746
|
}
|
|
12626
12747
|
|
|
12627
12748
|
/**
|
|
@@ -12779,6 +12900,29 @@
|
|
|
12779
12900
|
return operator === exports.Operator.NOT_EQUALS || operator === exports.Operator.NOT;
|
|
12780
12901
|
}
|
|
12781
12902
|
|
|
12903
|
+
/**
|
|
12904
|
+
* Reads data from a Readable stream and returns a Promise that resolves with a Buffer containing all the data.
|
|
12905
|
+
* @param stream - The Readable stream to read from.
|
|
12906
|
+
* @returns A Promise that resolves with a Buffer containing all the data from the Readable stream.
|
|
12907
|
+
*/
|
|
12908
|
+
function streamToBuffer(stream) {
|
|
12909
|
+
const chunks = [];
|
|
12910
|
+
return new Promise((resolve, reject) => {
|
|
12911
|
+
stream.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
|
|
12912
|
+
stream.on('error', (err) => {
|
|
12913
|
+
console.error(err.message);
|
|
12914
|
+
stream.destroy();
|
|
12915
|
+
reject(err);
|
|
12916
|
+
});
|
|
12917
|
+
stream.on('end', () => {
|
|
12918
|
+
resolve(Buffer.concat(chunks));
|
|
12919
|
+
});
|
|
12920
|
+
stream.on('close', () => {
|
|
12921
|
+
stream.destroy();
|
|
12922
|
+
});
|
|
12923
|
+
});
|
|
12924
|
+
}
|
|
12925
|
+
|
|
12782
12926
|
exports.AndAtom = AndAtom;
|
|
12783
12927
|
exports.ArithemticOperatorAtom = ArithemticOperatorAtom;
|
|
12784
12928
|
exports.AsAtom = AsAtom;
|
|
@@ -12852,6 +12996,7 @@
|
|
|
12852
12996
|
exports.fhirPathNot = fhirPathNot;
|
|
12853
12997
|
exports.findObservationInterval = findObservationInterval;
|
|
12854
12998
|
exports.findObservationReferenceRange = findObservationReferenceRange;
|
|
12999
|
+
exports.findResourceByCode = findResourceByCode;
|
|
12855
13000
|
exports.forbidden = forbidden;
|
|
12856
13001
|
exports.formatAddress = formatAddress;
|
|
12857
13002
|
exports.formatCodeableConcept = formatCodeableConcept;
|
|
@@ -12922,6 +13067,7 @@
|
|
|
12922
13067
|
exports.operationOutcomeToString = operationOutcomeToString;
|
|
12923
13068
|
exports.parseFhirPath = parseFhirPath;
|
|
12924
13069
|
exports.parseFilterParameter = parseFilterParameter;
|
|
13070
|
+
exports.parseHl7Date = parseHl7Date;
|
|
12925
13071
|
exports.parseJWTPayload = parseJWTPayload;
|
|
12926
13072
|
exports.parseMappingLanguage = parseMappingLanguage;
|
|
12927
13073
|
exports.parseSearchDefinition = parseSearchDefinition;
|
|
@@ -12936,6 +13082,7 @@
|
|
|
12936
13082
|
exports.removeDuplicates = removeDuplicates;
|
|
12937
13083
|
exports.resolveId = resolveId;
|
|
12938
13084
|
exports.setCodeBySystem = setCodeBySystem;
|
|
13085
|
+
exports.streamToBuffer = streamToBuffer;
|
|
12939
13086
|
exports.stringify = stringify;
|
|
12940
13087
|
exports.toJsBoolean = toJsBoolean;
|
|
12941
13088
|
exports.toTypedValue = toTypedValue;
|