@medplum/core 2.0.18 → 2.0.19
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 +806 -665
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.min.cjs +1 -1
- package/dist/esm/client.mjs +27 -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 +11 -5
- 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 +10 -1
- package/dist/types/filter/types.d.ts +3 -2
- package/dist/types/hl7.d.ts +12 -0
- package/dist/types/index.d.ts +1 -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.19-40e6e27d" ;
|
|
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
|
|
@@ -7906,6 +7919,26 @@
|
|
|
7906
7919
|
const response = await this.fetch(url.toString(), options);
|
|
7907
7920
|
return response.blob();
|
|
7908
7921
|
}
|
|
7922
|
+
/**
|
|
7923
|
+
* Upload media to the server and create a Media instance for the uploaded content.
|
|
7924
|
+
* @param contents The contents of the media file, as a string, Uint8Array, File, or Blob.
|
|
7925
|
+
* @param contentType The media type of the content
|
|
7926
|
+
* @param filename The name of the file to be uploaded, or undefined if not applicable
|
|
7927
|
+
* @param additionalFields Additional fields for Media
|
|
7928
|
+
* @returns Promise that resolves to the created Media
|
|
7929
|
+
*/
|
|
7930
|
+
async uploadMedia(contents, contentType, filename, additionalFields) {
|
|
7931
|
+
const binary = await this.createBinary(contents, filename, contentType);
|
|
7932
|
+
return this.createResource({
|
|
7933
|
+
...additionalFields,
|
|
7934
|
+
resourceType: 'Media',
|
|
7935
|
+
content: {
|
|
7936
|
+
contentType: contentType,
|
|
7937
|
+
url: 'Binary/' + binary.id,
|
|
7938
|
+
title: filename,
|
|
7939
|
+
},
|
|
7940
|
+
});
|
|
7941
|
+
}
|
|
7909
7942
|
//
|
|
7910
7943
|
// Private helpers
|
|
7911
7944
|
//
|
|
@@ -7981,7 +8014,10 @@
|
|
|
7981
8014
|
return undefined;
|
|
7982
8015
|
}
|
|
7983
8016
|
if (response.status === 404) {
|
|
7984
|
-
|
|
8017
|
+
const contentType = response.headers.get('content-type');
|
|
8018
|
+
if (!contentType?.includes('application/fhir+json')) {
|
|
8019
|
+
throw new OperationOutcomeError(notFound);
|
|
8020
|
+
}
|
|
7985
8021
|
}
|
|
7986
8022
|
let obj = undefined;
|
|
7987
8023
|
try {
|
|
@@ -11394,162 +11430,577 @@
|
|
|
11394
11430
|
return new StructureMapParser(parser).parse();
|
|
11395
11431
|
}
|
|
11396
11432
|
|
|
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
|
|
11433
|
+
const DEFAULT_SEARCH_COUNT = 20;
|
|
11406
11434
|
/**
|
|
11407
|
-
*
|
|
11435
|
+
* Search operators.
|
|
11436
|
+
* These operators represent "modifiers" and "prefixes" in FHIR search.
|
|
11437
|
+
* See: https://www.hl7.org/fhir/search.html
|
|
11408
11438
|
*/
|
|
11409
|
-
|
|
11410
|
-
|
|
11411
|
-
|
|
11412
|
-
|
|
11413
|
-
|
|
11414
|
-
|
|
11415
|
-
|
|
11439
|
+
exports.Operator = void 0;
|
|
11440
|
+
(function (Operator) {
|
|
11441
|
+
Operator["EQUALS"] = "eq";
|
|
11442
|
+
Operator["NOT_EQUALS"] = "ne";
|
|
11443
|
+
// Numbers
|
|
11444
|
+
Operator["GREATER_THAN"] = "gt";
|
|
11445
|
+
Operator["LESS_THAN"] = "lt";
|
|
11446
|
+
Operator["GREATER_THAN_OR_EQUALS"] = "ge";
|
|
11447
|
+
Operator["LESS_THAN_OR_EQUALS"] = "le";
|
|
11448
|
+
// Dates
|
|
11449
|
+
Operator["STARTS_AFTER"] = "sa";
|
|
11450
|
+
Operator["ENDS_BEFORE"] = "eb";
|
|
11451
|
+
Operator["APPROXIMATELY"] = "ap";
|
|
11452
|
+
// String
|
|
11453
|
+
Operator["CONTAINS"] = "contains";
|
|
11454
|
+
Operator["EXACT"] = "exact";
|
|
11455
|
+
// Token
|
|
11456
|
+
Operator["TEXT"] = "text";
|
|
11457
|
+
Operator["NOT"] = "not";
|
|
11458
|
+
Operator["ABOVE"] = "above";
|
|
11459
|
+
Operator["BELOW"] = "below";
|
|
11460
|
+
Operator["IN"] = "in";
|
|
11461
|
+
Operator["NOT_IN"] = "not-in";
|
|
11462
|
+
Operator["OF_TYPE"] = "of-type";
|
|
11463
|
+
// All
|
|
11464
|
+
Operator["MISSING"] = "missing";
|
|
11465
|
+
// Reference
|
|
11466
|
+
Operator["IDENTIFIER"] = "identifier";
|
|
11467
|
+
// _include and _revinclude
|
|
11468
|
+
Operator["ITERATE"] = "iterate";
|
|
11469
|
+
})(exports.Operator || (exports.Operator = {}));
|
|
11416
11470
|
/**
|
|
11417
|
-
*
|
|
11418
|
-
*
|
|
11471
|
+
* Parameter names may specify a modifier as a suffix.
|
|
11472
|
+
* The modifiers are separated from the parameter name by a colon.
|
|
11473
|
+
* See: https://www.hl7.org/fhir/search.html#modifiers
|
|
11419
11474
|
*/
|
|
11420
|
-
|
|
11421
|
-
|
|
11422
|
-
|
|
11423
|
-
|
|
11424
|
-
|
|
11475
|
+
const MODIFIER_OPERATORS = {
|
|
11476
|
+
contains: exports.Operator.CONTAINS,
|
|
11477
|
+
exact: exports.Operator.EXACT,
|
|
11478
|
+
above: exports.Operator.ABOVE,
|
|
11479
|
+
below: exports.Operator.BELOW,
|
|
11480
|
+
text: exports.Operator.TEXT,
|
|
11481
|
+
not: exports.Operator.NOT,
|
|
11482
|
+
in: exports.Operator.IN,
|
|
11483
|
+
'not-in': exports.Operator.NOT_IN,
|
|
11484
|
+
'of-type': exports.Operator.OF_TYPE,
|
|
11485
|
+
missing: exports.Operator.MISSING,
|
|
11486
|
+
identifier: exports.Operator.IDENTIFIER,
|
|
11487
|
+
iterate: exports.Operator.ITERATE,
|
|
11488
|
+
};
|
|
11425
11489
|
/**
|
|
11426
|
-
*
|
|
11427
|
-
*
|
|
11490
|
+
* For the ordered parameter types of number, date, and quantity,
|
|
11491
|
+
* a prefix to the parameter value may be used to control the nature
|
|
11492
|
+
* of the matching.
|
|
11493
|
+
* See: https://www.hl7.org/fhir/search.html#prefix
|
|
11428
11494
|
*/
|
|
11429
|
-
|
|
11430
|
-
|
|
11431
|
-
|
|
11432
|
-
|
|
11433
|
-
|
|
11434
|
-
|
|
11435
|
-
|
|
11436
|
-
|
|
11437
|
-
|
|
11438
|
-
|
|
11439
|
-
|
|
11440
|
-
|
|
11441
|
-
|
|
11442
|
-
|
|
11443
|
-
|
|
11444
|
-
|
|
11445
|
-
|
|
11446
|
-
|
|
11447
|
-
|
|
11448
|
-
|
|
11449
|
-
|
|
11450
|
-
|
|
11451
|
-
|
|
11452
|
-
|
|
11495
|
+
const PREFIX_OPERATORS = {
|
|
11496
|
+
eq: exports.Operator.EQUALS,
|
|
11497
|
+
ne: exports.Operator.NOT_EQUALS,
|
|
11498
|
+
lt: exports.Operator.LESS_THAN,
|
|
11499
|
+
le: exports.Operator.LESS_THAN_OR_EQUALS,
|
|
11500
|
+
gt: exports.Operator.GREATER_THAN,
|
|
11501
|
+
ge: exports.Operator.GREATER_THAN_OR_EQUALS,
|
|
11502
|
+
sa: exports.Operator.STARTS_AFTER,
|
|
11503
|
+
eb: exports.Operator.ENDS_BEFORE,
|
|
11504
|
+
ap: exports.Operator.APPROXIMATELY,
|
|
11505
|
+
};
|
|
11506
|
+
/**
|
|
11507
|
+
* Parses a search URL into a search request.
|
|
11508
|
+
* @param resourceType The FHIR resource type.
|
|
11509
|
+
* @param query The collection of query string parameters.
|
|
11510
|
+
* @returns A parsed SearchRequest.
|
|
11511
|
+
*/
|
|
11512
|
+
function parseSearchRequest(resourceType, query) {
|
|
11513
|
+
const queryArray = [];
|
|
11514
|
+
for (const [key, value] of Object.entries(query)) {
|
|
11515
|
+
if (Array.isArray(value)) {
|
|
11516
|
+
for (let i = 0; i < value.length; i++) {
|
|
11517
|
+
queryArray.push([key, value[i]]);
|
|
11518
|
+
}
|
|
11453
11519
|
}
|
|
11454
11520
|
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());
|
|
11521
|
+
queryArray.push([key, value || '']);
|
|
11461
11522
|
}
|
|
11462
|
-
return result;
|
|
11463
11523
|
}
|
|
11524
|
+
return parseSearchImpl(resourceType, queryArray);
|
|
11464
11525
|
}
|
|
11465
|
-
const fhirPathParserBuilder = initFhirPathParserBuilder();
|
|
11466
11526
|
/**
|
|
11467
|
-
* Parses a
|
|
11468
|
-
* @param
|
|
11469
|
-
* @returns
|
|
11527
|
+
* Parses a search URL into a search request.
|
|
11528
|
+
* @param url The search URL.
|
|
11529
|
+
* @returns A parsed SearchRequest.
|
|
11470
11530
|
*/
|
|
11471
|
-
function
|
|
11472
|
-
const
|
|
11473
|
-
|
|
11474
|
-
return new FilterParameterParser(parser).parse();
|
|
11531
|
+
function parseSearchUrl(url) {
|
|
11532
|
+
const resourceType = url.pathname.split('/').filter(Boolean).pop();
|
|
11533
|
+
return parseSearchImpl(resourceType, url.searchParams.entries());
|
|
11475
11534
|
}
|
|
11476
|
-
|
|
11477
11535
|
/**
|
|
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
|
|
11536
|
+
* Parses a URL string into a SearchRequest.
|
|
11537
|
+
* @param url The URL to parse.
|
|
11538
|
+
* @returns Parsed search definition.
|
|
11488
11539
|
*/
|
|
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
|
-
}
|
|
11540
|
+
function parseSearchDefinition(url) {
|
|
11541
|
+
return parseSearchUrl(new URL(url, 'https://example.com/'));
|
|
11509
11542
|
}
|
|
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;
|
|
11523
|
-
}
|
|
11524
|
-
/**
|
|
11525
|
-
* Returns an HL7 segment by index or by name.
|
|
11526
|
-
* @param index The HL7 segment index or name.
|
|
11527
|
-
* @returns The HL7 segment if found; otherwise, undefined.
|
|
11528
|
-
*/
|
|
11529
|
-
get(index) {
|
|
11530
|
-
if (typeof index === 'number') {
|
|
11531
|
-
return this.segments[index];
|
|
11532
|
-
}
|
|
11533
|
-
return this.segments.find((s) => s.name === index);
|
|
11543
|
+
function parseSearchImpl(resourceType, query) {
|
|
11544
|
+
const searchRequest = {
|
|
11545
|
+
resourceType,
|
|
11546
|
+
};
|
|
11547
|
+
for (const [key, value] of query) {
|
|
11548
|
+
parseKeyValue(searchRequest, key, value);
|
|
11534
11549
|
}
|
|
11535
|
-
|
|
11536
|
-
|
|
11537
|
-
|
|
11538
|
-
|
|
11539
|
-
|
|
11540
|
-
|
|
11541
|
-
|
|
11550
|
+
return searchRequest;
|
|
11551
|
+
}
|
|
11552
|
+
function parseKeyValue(searchRequest, key, value) {
|
|
11553
|
+
let code;
|
|
11554
|
+
let modifier;
|
|
11555
|
+
const colonIndex = key.indexOf(':');
|
|
11556
|
+
if (colonIndex >= 0) {
|
|
11557
|
+
code = key.substring(0, colonIndex);
|
|
11558
|
+
modifier = key.substring(colonIndex + 1);
|
|
11542
11559
|
}
|
|
11543
|
-
|
|
11544
|
-
|
|
11545
|
-
|
|
11546
|
-
*/
|
|
11547
|
-
toString() {
|
|
11548
|
-
return this.segments.map((s) => s.toString()).join(this.context.segmentSeparator);
|
|
11560
|
+
else {
|
|
11561
|
+
code = key;
|
|
11562
|
+
modifier = '';
|
|
11549
11563
|
}
|
|
11550
|
-
|
|
11551
|
-
|
|
11552
|
-
|
|
11564
|
+
switch (code) {
|
|
11565
|
+
case '_sort':
|
|
11566
|
+
parseSortRule(searchRequest, value);
|
|
11567
|
+
break;
|
|
11568
|
+
case '_count':
|
|
11569
|
+
searchRequest.count = parseInt(value);
|
|
11570
|
+
break;
|
|
11571
|
+
case '_offset':
|
|
11572
|
+
searchRequest.offset = parseInt(value);
|
|
11573
|
+
break;
|
|
11574
|
+
case '_total':
|
|
11575
|
+
searchRequest.total = value;
|
|
11576
|
+
break;
|
|
11577
|
+
case '_summary':
|
|
11578
|
+
searchRequest.total = 'estimate';
|
|
11579
|
+
searchRequest.count = 0;
|
|
11580
|
+
break;
|
|
11581
|
+
case '_include': {
|
|
11582
|
+
const target = parseIncludeTarget(value);
|
|
11583
|
+
if (modifier === 'iterate') {
|
|
11584
|
+
target.modifier = exports.Operator.ITERATE;
|
|
11585
|
+
}
|
|
11586
|
+
if (searchRequest.include) {
|
|
11587
|
+
searchRequest.include.push(target);
|
|
11588
|
+
}
|
|
11589
|
+
else {
|
|
11590
|
+
searchRequest.include = [target];
|
|
11591
|
+
}
|
|
11592
|
+
break;
|
|
11593
|
+
}
|
|
11594
|
+
case '_revinclude': {
|
|
11595
|
+
const target = parseIncludeTarget(value);
|
|
11596
|
+
if (modifier === 'iterate') {
|
|
11597
|
+
target.modifier = exports.Operator.ITERATE;
|
|
11598
|
+
}
|
|
11599
|
+
if (searchRequest.revInclude) {
|
|
11600
|
+
searchRequest.revInclude.push(target);
|
|
11601
|
+
}
|
|
11602
|
+
else {
|
|
11603
|
+
searchRequest.revInclude = [target];
|
|
11604
|
+
}
|
|
11605
|
+
break;
|
|
11606
|
+
}
|
|
11607
|
+
case '_fields':
|
|
11608
|
+
searchRequest.fields = value.split(',');
|
|
11609
|
+
break;
|
|
11610
|
+
default: {
|
|
11611
|
+
const param = globalSchema.types[searchRequest.resourceType]?.searchParams?.[code];
|
|
11612
|
+
if (param) {
|
|
11613
|
+
parseParameter(searchRequest, param, modifier, value);
|
|
11614
|
+
}
|
|
11615
|
+
else {
|
|
11616
|
+
parseUnknownParameter(searchRequest, code, modifier, value);
|
|
11617
|
+
}
|
|
11618
|
+
}
|
|
11619
|
+
}
|
|
11620
|
+
}
|
|
11621
|
+
function parseSortRule(searchRequest, value) {
|
|
11622
|
+
for (const field of value.split(',')) {
|
|
11623
|
+
let code;
|
|
11624
|
+
let descending = false;
|
|
11625
|
+
if (field.startsWith('-')) {
|
|
11626
|
+
code = field.substring(1);
|
|
11627
|
+
descending = true;
|
|
11628
|
+
}
|
|
11629
|
+
else {
|
|
11630
|
+
code = field;
|
|
11631
|
+
}
|
|
11632
|
+
if (!searchRequest.sortRules) {
|
|
11633
|
+
searchRequest.sortRules = [];
|
|
11634
|
+
}
|
|
11635
|
+
searchRequest.sortRules.push({ code, descending });
|
|
11636
|
+
}
|
|
11637
|
+
}
|
|
11638
|
+
function parseParameter(searchRequest, searchParam, modifier, value) {
|
|
11639
|
+
if (modifier === 'missing') {
|
|
11640
|
+
addFilter(searchRequest, {
|
|
11641
|
+
code: searchParam.code,
|
|
11642
|
+
operator: exports.Operator.MISSING,
|
|
11643
|
+
value,
|
|
11644
|
+
});
|
|
11645
|
+
return;
|
|
11646
|
+
}
|
|
11647
|
+
switch (searchParam.type) {
|
|
11648
|
+
case 'number':
|
|
11649
|
+
case 'date':
|
|
11650
|
+
parsePrefixType(searchRequest, searchParam, value);
|
|
11651
|
+
break;
|
|
11652
|
+
case 'reference':
|
|
11653
|
+
case 'string':
|
|
11654
|
+
case 'token':
|
|
11655
|
+
case 'uri':
|
|
11656
|
+
parseModifierType(searchRequest, searchParam, modifier, value);
|
|
11657
|
+
break;
|
|
11658
|
+
case 'quantity':
|
|
11659
|
+
parseQuantity(searchRequest, searchParam, value);
|
|
11660
|
+
break;
|
|
11661
|
+
}
|
|
11662
|
+
}
|
|
11663
|
+
function parsePrefixType(searchRequest, param, input) {
|
|
11664
|
+
const { operator, value } = parsePrefix(input);
|
|
11665
|
+
addFilter(searchRequest, {
|
|
11666
|
+
code: param.code,
|
|
11667
|
+
operator,
|
|
11668
|
+
value,
|
|
11669
|
+
});
|
|
11670
|
+
}
|
|
11671
|
+
function parseModifierType(searchRequest, param, modifier, value) {
|
|
11672
|
+
addFilter(searchRequest, {
|
|
11673
|
+
code: param.code,
|
|
11674
|
+
operator: parseModifier(modifier),
|
|
11675
|
+
value,
|
|
11676
|
+
});
|
|
11677
|
+
}
|
|
11678
|
+
function parseQuantity(searchRequest, param, input) {
|
|
11679
|
+
const [prefixNumber, unitSystem, unitCode] = input.split('|');
|
|
11680
|
+
const { operator, value } = parsePrefix(prefixNumber);
|
|
11681
|
+
addFilter(searchRequest, {
|
|
11682
|
+
code: param.code,
|
|
11683
|
+
operator,
|
|
11684
|
+
value,
|
|
11685
|
+
unitSystem,
|
|
11686
|
+
unitCode,
|
|
11687
|
+
});
|
|
11688
|
+
}
|
|
11689
|
+
function parseUnknownParameter(searchRequest, code, modifier, value) {
|
|
11690
|
+
let operator = exports.Operator.EQUALS;
|
|
11691
|
+
if (modifier) {
|
|
11692
|
+
operator = modifier;
|
|
11693
|
+
}
|
|
11694
|
+
else if (value.length >= 2) {
|
|
11695
|
+
const prefix = value.substring(0, 2);
|
|
11696
|
+
if (prefix in PREFIX_OPERATORS) {
|
|
11697
|
+
if (value.length === 2 || value.at(2)?.match(/\d/)) {
|
|
11698
|
+
operator = prefix;
|
|
11699
|
+
value = value.substring(prefix.length);
|
|
11700
|
+
}
|
|
11701
|
+
}
|
|
11702
|
+
}
|
|
11703
|
+
addFilter(searchRequest, {
|
|
11704
|
+
code,
|
|
11705
|
+
operator,
|
|
11706
|
+
value,
|
|
11707
|
+
});
|
|
11708
|
+
}
|
|
11709
|
+
function parsePrefix(input) {
|
|
11710
|
+
const prefix = input.substring(0, 2);
|
|
11711
|
+
const prefixOperator = PREFIX_OPERATORS[prefix];
|
|
11712
|
+
if (prefixOperator) {
|
|
11713
|
+
return { operator: prefixOperator, value: input.substring(2) };
|
|
11714
|
+
}
|
|
11715
|
+
return { operator: exports.Operator.EQUALS, value: input };
|
|
11716
|
+
}
|
|
11717
|
+
function parseModifier(modifier) {
|
|
11718
|
+
return MODIFIER_OPERATORS[modifier] || exports.Operator.EQUALS;
|
|
11719
|
+
}
|
|
11720
|
+
function parseIncludeTarget(input) {
|
|
11721
|
+
const parts = input.split(':');
|
|
11722
|
+
parts.forEach((p) => {
|
|
11723
|
+
if (p === '*') {
|
|
11724
|
+
throw new OperationOutcomeError(badRequest(`'*' is not supported as a value for search inclusion parameters`));
|
|
11725
|
+
}
|
|
11726
|
+
});
|
|
11727
|
+
if (parts.length === 1) {
|
|
11728
|
+
// Full wildcard, not currently supported
|
|
11729
|
+
throw new OperationOutcomeError(badRequest(`Invalid include value '${input}': must be of the form ResourceType:search-parameter`));
|
|
11730
|
+
}
|
|
11731
|
+
else if (parts.length === 2) {
|
|
11732
|
+
return {
|
|
11733
|
+
resourceType: parts[0],
|
|
11734
|
+
searchParam: parts[1],
|
|
11735
|
+
};
|
|
11736
|
+
}
|
|
11737
|
+
else if (parts.length === 3) {
|
|
11738
|
+
return {
|
|
11739
|
+
resourceType: parts[0],
|
|
11740
|
+
searchParam: parts[1],
|
|
11741
|
+
targetType: parts[2],
|
|
11742
|
+
};
|
|
11743
|
+
}
|
|
11744
|
+
else {
|
|
11745
|
+
throw new OperationOutcomeError(badRequest(`Invalid include value '${input}'`));
|
|
11746
|
+
}
|
|
11747
|
+
}
|
|
11748
|
+
function addFilter(searchRequest, filter) {
|
|
11749
|
+
if (searchRequest.filters) {
|
|
11750
|
+
searchRequest.filters.push(filter);
|
|
11751
|
+
}
|
|
11752
|
+
else {
|
|
11753
|
+
searchRequest.filters = [filter];
|
|
11754
|
+
}
|
|
11755
|
+
}
|
|
11756
|
+
/**
|
|
11757
|
+
* Formats a search definition object into a query string.
|
|
11758
|
+
* Note: The return value does not include the resource type.
|
|
11759
|
+
* @param {!SearchRequest} definition The search definition.
|
|
11760
|
+
* @returns Formatted URL.
|
|
11761
|
+
*/
|
|
11762
|
+
function formatSearchQuery(definition) {
|
|
11763
|
+
const params = [];
|
|
11764
|
+
if (definition.fields) {
|
|
11765
|
+
params.push('_fields=' + definition.fields.join(','));
|
|
11766
|
+
}
|
|
11767
|
+
if (definition.filters) {
|
|
11768
|
+
definition.filters.forEach((filter) => params.push(formatFilter(filter)));
|
|
11769
|
+
}
|
|
11770
|
+
if (definition.sortRules && definition.sortRules.length > 0) {
|
|
11771
|
+
params.push(formatSortRules(definition.sortRules));
|
|
11772
|
+
}
|
|
11773
|
+
if (definition.offset !== undefined) {
|
|
11774
|
+
params.push('_offset=' + definition.offset);
|
|
11775
|
+
}
|
|
11776
|
+
if (definition.count !== undefined) {
|
|
11777
|
+
params.push('_count=' + definition.count);
|
|
11778
|
+
}
|
|
11779
|
+
if (definition.total !== undefined) {
|
|
11780
|
+
params.push('_total=' + definition.total);
|
|
11781
|
+
}
|
|
11782
|
+
if (params.length === 0) {
|
|
11783
|
+
return '';
|
|
11784
|
+
}
|
|
11785
|
+
params.sort();
|
|
11786
|
+
return '?' + params.join('&');
|
|
11787
|
+
}
|
|
11788
|
+
function formatFilter(filter) {
|
|
11789
|
+
const modifier = filter.operator in MODIFIER_OPERATORS ? ':' + filter.operator : '';
|
|
11790
|
+
const prefix = filter.operator !== exports.Operator.EQUALS && filter.operator in PREFIX_OPERATORS ? filter.operator : '';
|
|
11791
|
+
return `${filter.code}${modifier}=${prefix}${encodeURIComponent(filter.value)}`;
|
|
11792
|
+
}
|
|
11793
|
+
function formatSortRules(sortRules) {
|
|
11794
|
+
return '_sort=' + sortRules.map((sr) => (sr.descending ? '-' + sr.code : sr.code)).join(',');
|
|
11795
|
+
}
|
|
11796
|
+
|
|
11797
|
+
const MAPPING_LANGUAGE_OPERATORS = [...FHIRPATH_OPERATORS, 'eq', 'ne', 'co'];
|
|
11798
|
+
function tokenize(str) {
|
|
11799
|
+
return new Tokenizer(str, FHIRPATH_KEYWORDS, MAPPING_LANGUAGE_OPERATORS, {
|
|
11800
|
+
dateTimeLiterals: true,
|
|
11801
|
+
symbolRegex: /[^\s\])]/,
|
|
11802
|
+
}).tokenize();
|
|
11803
|
+
}
|
|
11804
|
+
|
|
11805
|
+
// See: https://hl7.org/fhir/search_filter.html
|
|
11806
|
+
/**
|
|
11807
|
+
* The FhirFilterComparison class represents a comparison expression.
|
|
11808
|
+
*/
|
|
11809
|
+
class FhirFilterComparison {
|
|
11810
|
+
constructor(path, operator, value) {
|
|
11811
|
+
this.path = path;
|
|
11812
|
+
this.operator = operator;
|
|
11813
|
+
this.value = value;
|
|
11814
|
+
}
|
|
11815
|
+
}
|
|
11816
|
+
/**
|
|
11817
|
+
* The FhirFilterNegation class represents a negation expression.
|
|
11818
|
+
* It contains a single child expression.
|
|
11819
|
+
*/
|
|
11820
|
+
class FhirFilterNegation {
|
|
11821
|
+
constructor(child) {
|
|
11822
|
+
this.child = child;
|
|
11823
|
+
}
|
|
11824
|
+
}
|
|
11825
|
+
/**
|
|
11826
|
+
* The FhirFilterConnective class represents a connective expression.
|
|
11827
|
+
* It contains a list of child expressions.
|
|
11828
|
+
*/
|
|
11829
|
+
class FhirFilterConnective {
|
|
11830
|
+
constructor(keyword, left, right) {
|
|
11831
|
+
this.keyword = keyword;
|
|
11832
|
+
this.left = left;
|
|
11833
|
+
this.right = right;
|
|
11834
|
+
}
|
|
11835
|
+
}
|
|
11836
|
+
|
|
11837
|
+
/**
|
|
11838
|
+
* The operatorMap maps FHIR _filter operators to Medplum search operators.
|
|
11839
|
+
* See _filter operators: https://www.hl7.org/fhir/search_filter.html#ops
|
|
11840
|
+
*/
|
|
11841
|
+
const operatorMap = {
|
|
11842
|
+
// eq - an item in the set has an equal value
|
|
11843
|
+
eq: exports.Operator.EQUALS,
|
|
11844
|
+
// ne - An item in the set has an unequal value
|
|
11845
|
+
ne: exports.Operator.NOT_EQUALS,
|
|
11846
|
+
// co - An item in the set contains this value
|
|
11847
|
+
co: exports.Operator.CONTAINS,
|
|
11848
|
+
// sw - An item in the set starts with this value
|
|
11849
|
+
sw: undefined,
|
|
11850
|
+
// ew - An item in the set ends with this value
|
|
11851
|
+
ew: undefined,
|
|
11852
|
+
// gt / lt / ge / le - A value in the set is (greater than, less than, greater or equal, less or equal) the given value
|
|
11853
|
+
gt: exports.Operator.GREATER_THAN,
|
|
11854
|
+
lt: exports.Operator.LESS_THAN,
|
|
11855
|
+
ge: exports.Operator.GREATER_THAN_OR_EQUALS,
|
|
11856
|
+
le: exports.Operator.LESS_THAN_OR_EQUALS,
|
|
11857
|
+
// ap - A value in the set is approximately the same as this value.
|
|
11858
|
+
// 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
|
|
11859
|
+
ap: exports.Operator.APPROXIMATELY,
|
|
11860
|
+
// sa - The value starts after the specified value
|
|
11861
|
+
sa: exports.Operator.STARTS_AFTER,
|
|
11862
|
+
// eb - The value ends before the specified value
|
|
11863
|
+
eb: exports.Operator.ENDS_BEFORE,
|
|
11864
|
+
// pr - The set is empty or not (value is false or true)
|
|
11865
|
+
pr: exports.Operator.MISSING,
|
|
11866
|
+
// po - True if a (implied) date period in the set overlaps with the implied period in the value
|
|
11867
|
+
po: undefined,
|
|
11868
|
+
// ss - True if the value subsumes a concept in the set
|
|
11869
|
+
ss: undefined,
|
|
11870
|
+
// sb - True if the value is subsumed by a concept in the set
|
|
11871
|
+
sb: undefined,
|
|
11872
|
+
// in - True if one of the concepts is in the nominated value set by URI, either a relative, literal or logical vs
|
|
11873
|
+
in: exports.Operator.IN,
|
|
11874
|
+
// ni - True if none of the concepts are in the nominated value set by URI, either a relative, literal or logical vs
|
|
11875
|
+
ni: exports.Operator.NOT_IN,
|
|
11876
|
+
// re - True if one of the references in set points to the given URL
|
|
11877
|
+
re: undefined,
|
|
11878
|
+
// identifier - True if the identifier is in the identifier set (Medplum extension)
|
|
11879
|
+
identifier: exports.Operator.IDENTIFIER,
|
|
11880
|
+
};
|
|
11881
|
+
function getOperator(value) {
|
|
11882
|
+
const operator = operatorMap[value];
|
|
11883
|
+
if (!operator) {
|
|
11884
|
+
throw new OperationOutcomeError(badRequest('Invalid operator: ' + value));
|
|
11885
|
+
}
|
|
11886
|
+
return operator;
|
|
11887
|
+
}
|
|
11888
|
+
class FilterParameterParser {
|
|
11889
|
+
constructor(parser) {
|
|
11890
|
+
this.parser = parser;
|
|
11891
|
+
}
|
|
11892
|
+
parse() {
|
|
11893
|
+
let result;
|
|
11894
|
+
if (this.parser.peek()?.value === '(') {
|
|
11895
|
+
this.parser.consume('(');
|
|
11896
|
+
result = this.parse();
|
|
11897
|
+
this.parser.consume(')');
|
|
11898
|
+
}
|
|
11899
|
+
else if (this.parser.peek()?.value === 'not') {
|
|
11900
|
+
this.parser.consume('Symbol', 'not');
|
|
11901
|
+
this.parser.consume('(');
|
|
11902
|
+
result = new FhirFilterNegation(this.parse());
|
|
11903
|
+
this.parser.consume(')');
|
|
11904
|
+
}
|
|
11905
|
+
else {
|
|
11906
|
+
result = new FhirFilterComparison(this.parser.consume('Symbol').value, getOperator(this.parser.consume('Symbol').value), this.parser.consume().value);
|
|
11907
|
+
}
|
|
11908
|
+
const next = this.parser.peek()?.value;
|
|
11909
|
+
if (next === 'and' || next === 'or') {
|
|
11910
|
+
this.parser.consume('Symbol', next);
|
|
11911
|
+
return new FhirFilterConnective(next, result, this.parse());
|
|
11912
|
+
}
|
|
11913
|
+
return result;
|
|
11914
|
+
}
|
|
11915
|
+
}
|
|
11916
|
+
const fhirPathParserBuilder = initFhirPathParserBuilder();
|
|
11917
|
+
/**
|
|
11918
|
+
* Parses a FHIR _filter parameter expression into an AST.
|
|
11919
|
+
* @param input The FHIR _filter parameter expression.
|
|
11920
|
+
* @returns The AST representing the filters.
|
|
11921
|
+
*/
|
|
11922
|
+
function parseFilterParameter(input) {
|
|
11923
|
+
const parser = fhirPathParserBuilder.construct(tokenize(input));
|
|
11924
|
+
parser.removeComments();
|
|
11925
|
+
return new FilterParameterParser(parser).parse();
|
|
11926
|
+
}
|
|
11927
|
+
|
|
11928
|
+
/**
|
|
11929
|
+
* The Hl7Context class represents the parsing context for an HL7 message.
|
|
11930
|
+
*
|
|
11931
|
+
* MSH-1:
|
|
11932
|
+
* https://hl7-definition.caristix.com/v2/HL7v2.6/Fields/MSH.1
|
|
11933
|
+
*
|
|
11934
|
+
* MSH-2:
|
|
11935
|
+
* https://hl7-definition.caristix.com/v2/HL7v2.6/Fields/MSH.2
|
|
11936
|
+
*
|
|
11937
|
+
* See this tutorial on MSH, and why it's a bad idea to use anything other than the default values:
|
|
11938
|
+
* https://www.hl7soup.com/HL7TutorialMSH.html
|
|
11939
|
+
*/
|
|
11940
|
+
class Hl7Context {
|
|
11941
|
+
constructor(segmentSeparator = '\r', fieldSeparator = '|', componentSeparator = '^', repetitionSeparator = '~', escapeCharacter = '\\', subcomponentSeparator = '&') {
|
|
11942
|
+
this.segmentSeparator = segmentSeparator;
|
|
11943
|
+
this.fieldSeparator = fieldSeparator;
|
|
11944
|
+
this.componentSeparator = componentSeparator;
|
|
11945
|
+
this.repetitionSeparator = repetitionSeparator;
|
|
11946
|
+
this.escapeCharacter = escapeCharacter;
|
|
11947
|
+
this.subcomponentSeparator = subcomponentSeparator;
|
|
11948
|
+
}
|
|
11949
|
+
/**
|
|
11950
|
+
* Returns the MSH-2 field value based on the configured separators.
|
|
11951
|
+
* @returns The HL7 MSH-2 field value.
|
|
11952
|
+
*/
|
|
11953
|
+
getMsh2() {
|
|
11954
|
+
return (this.fieldSeparator +
|
|
11955
|
+
this.componentSeparator +
|
|
11956
|
+
this.repetitionSeparator +
|
|
11957
|
+
this.escapeCharacter +
|
|
11958
|
+
this.subcomponentSeparator);
|
|
11959
|
+
}
|
|
11960
|
+
}
|
|
11961
|
+
/**
|
|
11962
|
+
* The Hl7Message class represents one HL7 message.
|
|
11963
|
+
* A message is a collection of segments.
|
|
11964
|
+
*/
|
|
11965
|
+
class Hl7Message {
|
|
11966
|
+
/**
|
|
11967
|
+
* Creates a new HL7 message.
|
|
11968
|
+
* @param segments The HL7 segments.
|
|
11969
|
+
* @param context Optional HL7 parsing context.
|
|
11970
|
+
*/
|
|
11971
|
+
constructor(segments, context = new Hl7Context()) {
|
|
11972
|
+
this.context = context;
|
|
11973
|
+
this.segments = segments;
|
|
11974
|
+
}
|
|
11975
|
+
/**
|
|
11976
|
+
* Returns an HL7 segment by index or by name.
|
|
11977
|
+
* @param index The HL7 segment index or name.
|
|
11978
|
+
* @returns The HL7 segment if found; otherwise, undefined.
|
|
11979
|
+
*/
|
|
11980
|
+
get(index) {
|
|
11981
|
+
if (typeof index === 'number') {
|
|
11982
|
+
return this.segments[index];
|
|
11983
|
+
}
|
|
11984
|
+
return this.segments.find((s) => s.name === index);
|
|
11985
|
+
}
|
|
11986
|
+
/**
|
|
11987
|
+
* Returns all HL7 segments of a given name.
|
|
11988
|
+
* @param name The HL7 segment name.
|
|
11989
|
+
* @returns An array of HL7 segments with the specified name.
|
|
11990
|
+
*/
|
|
11991
|
+
getAll(name) {
|
|
11992
|
+
return this.segments.filter((s) => s.name === name);
|
|
11993
|
+
}
|
|
11994
|
+
/**
|
|
11995
|
+
* Returns the HL7 message as a string.
|
|
11996
|
+
* @returns The HL7 message as a string.
|
|
11997
|
+
*/
|
|
11998
|
+
toString() {
|
|
11999
|
+
return this.segments.map((s) => s.toString()).join(this.context.segmentSeparator);
|
|
12000
|
+
}
|
|
12001
|
+
/**
|
|
12002
|
+
* Returns an HL7 "ACK" (acknowledgement) message for this message.
|
|
12003
|
+
* @returns The HL7 "ACK" message.
|
|
11553
12004
|
*/
|
|
11554
12005
|
buildAck() {
|
|
11555
12006
|
const now = new Date();
|
|
@@ -11689,6 +12140,28 @@
|
|
|
11689
12140
|
return new Hl7Field(text.split(context.repetitionSeparator).map((r) => r.split(context.componentSeparator)), context);
|
|
11690
12141
|
}
|
|
11691
12142
|
}
|
|
12143
|
+
/**
|
|
12144
|
+
* Returns a formatted string representing the date in ISO-8601 format.
|
|
12145
|
+
* @param hl7Date Date string.
|
|
12146
|
+
* @param options Optional configuration Object
|
|
12147
|
+
* @returns
|
|
12148
|
+
*/
|
|
12149
|
+
function parseHl7Date(hl7Date, options) {
|
|
12150
|
+
if (!hl7Date) {
|
|
12151
|
+
return undefined;
|
|
12152
|
+
}
|
|
12153
|
+
options = { ...{ seconds: true, tzOffset: 'Z' }, ...options };
|
|
12154
|
+
const year = Number.parseInt(hl7Date.substring(0, 4));
|
|
12155
|
+
const month = Number.parseInt(hl7Date.substring(4, 6));
|
|
12156
|
+
const date = Number.parseInt(hl7Date.substring(6, 8));
|
|
12157
|
+
const hours = Number.parseInt(hl7Date.substring(8, 10));
|
|
12158
|
+
const minutes = Number.parseInt(hl7Date.substring(10, 12));
|
|
12159
|
+
const seconds = options.seconds ? Number.parseInt(hl7Date.substring(12, 14)) : 0;
|
|
12160
|
+
return `${pad2(year)}-${pad2(month)}-${pad2(date)}T${pad2(hours)}:${pad2(minutes)}:${pad2(seconds)}.000${options.tzOffset}`;
|
|
12161
|
+
}
|
|
12162
|
+
function pad2(n) {
|
|
12163
|
+
return n.toString().padStart(2, '0');
|
|
12164
|
+
}
|
|
11692
12165
|
|
|
11693
12166
|
/*
|
|
11694
12167
|
* This file provides schema validation utilities for FHIR JSON objects.
|
|
@@ -12051,577 +12524,219 @@
|
|
|
12051
12524
|
if (Array.isArray(typedPropertyValue)) {
|
|
12052
12525
|
// At present, there are no choice types that are arrays in the FHIR spec
|
|
12053
12526
|
// Leaving this here to make TypeScript happy, and in case that changes
|
|
12054
|
-
typedPropertyValue = typedPropertyValue[0];
|
|
12055
|
-
}
|
|
12056
|
-
if (typedPropertyValue && key === basePropertyName + capitalize(typedPropertyValue.type)) {
|
|
12057
|
-
return true;
|
|
12058
|
-
}
|
|
12059
|
-
}
|
|
12060
|
-
return false;
|
|
12061
|
-
}
|
|
12062
|
-
/**
|
|
12063
|
-
* Recursively checks for null values in an object.
|
|
12064
|
-
*
|
|
12065
|
-
* Note that "null" is a special value in JSON that is not allowed in FHIR.
|
|
12066
|
-
*
|
|
12067
|
-
* @param value Input value of any type.
|
|
12068
|
-
* @param path Path string to the value for OperationOutcome.
|
|
12069
|
-
* @param issues Output list of issues.
|
|
12070
|
-
*/
|
|
12071
|
-
function checkForNull(value, path, issues) {
|
|
12072
|
-
if (value === null) {
|
|
12073
|
-
issues.push(createStructureIssue(path, 'Invalid null value'));
|
|
12074
|
-
}
|
|
12075
|
-
else if (Array.isArray(value)) {
|
|
12076
|
-
checkArrayForNull(value, path, issues);
|
|
12077
|
-
}
|
|
12078
|
-
else if (typeof value === 'object') {
|
|
12079
|
-
checkObjectForNull(value, path, issues);
|
|
12080
|
-
}
|
|
12081
|
-
}
|
|
12082
|
-
function checkArrayForNull(array, path, issues) {
|
|
12083
|
-
for (let i = 0; i < array.length; i++) {
|
|
12084
|
-
if (array[i] === undefined) {
|
|
12085
|
-
issues.push(createStructureIssue(`${path}[${i}]`, 'Invalid undefined value'));
|
|
12086
|
-
}
|
|
12087
|
-
else {
|
|
12088
|
-
checkForNull(array[i], `${path}[${i}]`, issues);
|
|
12089
|
-
}
|
|
12090
|
-
}
|
|
12091
|
-
}
|
|
12092
|
-
function checkObjectForNull(obj, path, issues) {
|
|
12093
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
12094
|
-
checkForNull(value, `${path}${path ? '.' : ''}${key}`, issues);
|
|
12095
|
-
}
|
|
12096
|
-
}
|
|
12097
|
-
function createStructureIssue(expression, details) {
|
|
12098
|
-
return {
|
|
12099
|
-
severity: 'error',
|
|
12100
|
-
code: 'structure',
|
|
12101
|
-
details: {
|
|
12102
|
-
text: details,
|
|
12103
|
-
},
|
|
12104
|
-
expression: [expression],
|
|
12105
|
-
};
|
|
12106
|
-
}
|
|
12107
|
-
|
|
12108
|
-
exports.SearchParameterType = void 0;
|
|
12109
|
-
(function (SearchParameterType) {
|
|
12110
|
-
SearchParameterType["BOOLEAN"] = "BOOLEAN";
|
|
12111
|
-
SearchParameterType["NUMBER"] = "NUMBER";
|
|
12112
|
-
SearchParameterType["QUANTITY"] = "QUANTITY";
|
|
12113
|
-
SearchParameterType["TEXT"] = "TEXT";
|
|
12114
|
-
SearchParameterType["REFERENCE"] = "REFERENCE";
|
|
12115
|
-
SearchParameterType["DATE"] = "DATE";
|
|
12116
|
-
SearchParameterType["DATETIME"] = "DATETIME";
|
|
12117
|
-
SearchParameterType["PERIOD"] = "PERIOD";
|
|
12118
|
-
SearchParameterType["UUID"] = "UUID";
|
|
12119
|
-
})(exports.SearchParameterType || (exports.SearchParameterType = {}));
|
|
12120
|
-
/**
|
|
12121
|
-
* Returns the type details of a SearchParameter.
|
|
12122
|
-
*
|
|
12123
|
-
* The SearchParameter resource has a "type" parameter, but that is missing some critical information.
|
|
12124
|
-
*
|
|
12125
|
-
* For example:
|
|
12126
|
-
* 1) The "date" type includes "date", "datetime", and "period".
|
|
12127
|
-
* 2) The "token" type includes enums and booleans.
|
|
12128
|
-
* 3) Arrays/multiple values are not reflected at all.
|
|
12129
|
-
*
|
|
12130
|
-
* @param resourceType The root resource type.
|
|
12131
|
-
* @param searchParam The search parameter.
|
|
12132
|
-
* @returns The search parameter type details.
|
|
12133
|
-
*/
|
|
12134
|
-
function getSearchParameterDetails(resourceType, searchParam) {
|
|
12135
|
-
let result = globalSchema.types[resourceType]?.searchParamsDetails?.[searchParam.code];
|
|
12136
|
-
if (!result) {
|
|
12137
|
-
result = buildSearchParamterDetails(resourceType, searchParam);
|
|
12138
|
-
}
|
|
12139
|
-
return result;
|
|
12140
|
-
}
|
|
12141
|
-
function setSearchParamterDetails(resourceType, code, details) {
|
|
12142
|
-
const typeSchema = globalSchema.types[resourceType];
|
|
12143
|
-
if (!typeSchema.searchParamsDetails) {
|
|
12144
|
-
typeSchema.searchParamsDetails = {};
|
|
12145
|
-
}
|
|
12146
|
-
typeSchema.searchParamsDetails[code] = details;
|
|
12147
|
-
}
|
|
12148
|
-
function buildSearchParamterDetails(resourceType, searchParam) {
|
|
12149
|
-
const code = searchParam.code;
|
|
12150
|
-
const columnName = convertCodeToColumnName(code);
|
|
12151
|
-
const expression = getExpressionForResourceType(resourceType, searchParam.expression)?.split('.');
|
|
12152
|
-
if (!expression) {
|
|
12153
|
-
// This happens on compound types
|
|
12154
|
-
// In the future, explore returning multiple column definitions
|
|
12155
|
-
return { columnName, type: exports.SearchParameterType.TEXT };
|
|
12156
|
-
}
|
|
12157
|
-
const defaultType = getSearchParameterType(searchParam);
|
|
12158
|
-
let baseType = resourceType;
|
|
12159
|
-
let elementDefinition = undefined;
|
|
12160
|
-
let propertyType = undefined;
|
|
12161
|
-
let array = false;
|
|
12162
|
-
for (let i = 1; i < expression.length; i++) {
|
|
12163
|
-
const propertyName = expression[i];
|
|
12164
|
-
elementDefinition = getElementDefinition(baseType, propertyName);
|
|
12165
|
-
if (!elementDefinition) {
|
|
12166
|
-
throw new Error(`Element definition not found for ${resourceType} ${searchParam.code}`);
|
|
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;
|
|
12527
|
+
typedPropertyValue = typedPropertyValue[0];
|
|
12528
|
+
}
|
|
12529
|
+
if (typedPropertyValue && key === basePropertyName + capitalize(typedPropertyValue.type)) {
|
|
12530
|
+
return true;
|
|
12239
12531
|
}
|
|
12240
12532
|
}
|
|
12241
|
-
return
|
|
12242
|
-
}
|
|
12243
|
-
function isIgnoredExpression(input) {
|
|
12244
|
-
return input.includes(' as Period') || input.includes(' as SampledDate');
|
|
12533
|
+
return false;
|
|
12245
12534
|
}
|
|
12246
|
-
|
|
12247
|
-
|
|
12248
|
-
|
|
12249
|
-
|
|
12535
|
+
/**
|
|
12536
|
+
* Recursively checks for null values in an object.
|
|
12537
|
+
*
|
|
12538
|
+
* Note that "null" is a special value in JSON that is not allowed in FHIR.
|
|
12539
|
+
*
|
|
12540
|
+
* @param value Input value of any type.
|
|
12541
|
+
* @param path Path string to the value for OperationOutcome.
|
|
12542
|
+
* @param issues Output list of issues.
|
|
12543
|
+
*/
|
|
12544
|
+
function checkForNull(value, path, issues) {
|
|
12545
|
+
if (value === null) {
|
|
12546
|
+
issues.push(createStructureIssue(path, 'Invalid null value'));
|
|
12250
12547
|
}
|
|
12251
|
-
if (
|
|
12252
|
-
|
|
12548
|
+
else if (Array.isArray(value)) {
|
|
12549
|
+
checkArrayForNull(value, path, issues);
|
|
12253
12550
|
}
|
|
12254
|
-
|
|
12255
|
-
|
|
12256
|
-
if (result.includes(stopString)) {
|
|
12257
|
-
result = result.substring(0, result.indexOf(stopString));
|
|
12258
|
-
}
|
|
12551
|
+
else if (typeof value === 'object') {
|
|
12552
|
+
checkObjectForNull(value, path, issues);
|
|
12259
12553
|
}
|
|
12260
|
-
return result;
|
|
12261
12554
|
}
|
|
12262
|
-
|
|
12263
|
-
|
|
12264
|
-
|
|
12265
|
-
|
|
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
|
-
}
|
|
12555
|
+
function checkArrayForNull(array, path, issues) {
|
|
12556
|
+
for (let i = 0; i < array.length; i++) {
|
|
12557
|
+
if (array[i] === undefined) {
|
|
12558
|
+
issues.push(createStructureIssue(`${path}[${i}]`, 'Invalid undefined value'));
|
|
12349
12559
|
}
|
|
12350
12560
|
else {
|
|
12351
|
-
|
|
12561
|
+
checkForNull(array[i], `${path}[${i}]`, issues);
|
|
12352
12562
|
}
|
|
12353
12563
|
}
|
|
12354
|
-
return parseSearchImpl(resourceType, queryArray);
|
|
12355
12564
|
}
|
|
12356
|
-
|
|
12357
|
-
|
|
12358
|
-
|
|
12359
|
-
|
|
12360
|
-
|
|
12361
|
-
function
|
|
12362
|
-
|
|
12363
|
-
|
|
12565
|
+
function checkObjectForNull(obj, path, issues) {
|
|
12566
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
12567
|
+
checkForNull(value, `${path}${path ? '.' : ''}${key}`, issues);
|
|
12568
|
+
}
|
|
12569
|
+
}
|
|
12570
|
+
function createStructureIssue(expression, details) {
|
|
12571
|
+
return {
|
|
12572
|
+
severity: 'error',
|
|
12573
|
+
code: 'structure',
|
|
12574
|
+
details: {
|
|
12575
|
+
text: details,
|
|
12576
|
+
},
|
|
12577
|
+
expression: [expression],
|
|
12578
|
+
};
|
|
12364
12579
|
}
|
|
12580
|
+
|
|
12581
|
+
exports.SearchParameterType = void 0;
|
|
12582
|
+
(function (SearchParameterType) {
|
|
12583
|
+
SearchParameterType["BOOLEAN"] = "BOOLEAN";
|
|
12584
|
+
SearchParameterType["NUMBER"] = "NUMBER";
|
|
12585
|
+
SearchParameterType["QUANTITY"] = "QUANTITY";
|
|
12586
|
+
SearchParameterType["TEXT"] = "TEXT";
|
|
12587
|
+
SearchParameterType["REFERENCE"] = "REFERENCE";
|
|
12588
|
+
SearchParameterType["CANONICAL"] = "CANONICAL";
|
|
12589
|
+
SearchParameterType["DATE"] = "DATE";
|
|
12590
|
+
SearchParameterType["DATETIME"] = "DATETIME";
|
|
12591
|
+
SearchParameterType["PERIOD"] = "PERIOD";
|
|
12592
|
+
SearchParameterType["UUID"] = "UUID";
|
|
12593
|
+
})(exports.SearchParameterType || (exports.SearchParameterType = {}));
|
|
12365
12594
|
/**
|
|
12366
|
-
*
|
|
12367
|
-
*
|
|
12368
|
-
*
|
|
12595
|
+
* Returns the type details of a SearchParameter.
|
|
12596
|
+
*
|
|
12597
|
+
* The SearchParameter resource has a "type" parameter, but that is missing some critical information.
|
|
12598
|
+
*
|
|
12599
|
+
* For example:
|
|
12600
|
+
* 1) The "date" type includes "date", "datetime", and "period".
|
|
12601
|
+
* 2) The "token" type includes enums and booleans.
|
|
12602
|
+
* 3) Arrays/multiple values are not reflected at all.
|
|
12603
|
+
*
|
|
12604
|
+
* @param resourceType The root resource type.
|
|
12605
|
+
* @param searchParam The search parameter.
|
|
12606
|
+
* @returns The search parameter type details.
|
|
12369
12607
|
*/
|
|
12370
|
-
function
|
|
12371
|
-
|
|
12372
|
-
|
|
12373
|
-
|
|
12374
|
-
const searchRequest = {
|
|
12375
|
-
resourceType,
|
|
12376
|
-
};
|
|
12377
|
-
for (const [key, value] of query) {
|
|
12378
|
-
parseKeyValue(searchRequest, key, value);
|
|
12608
|
+
function getSearchParameterDetails(resourceType, searchParam) {
|
|
12609
|
+
let result = globalSchema.types[resourceType]?.searchParamsDetails?.[searchParam.code];
|
|
12610
|
+
if (!result) {
|
|
12611
|
+
result = buildSearchParamterDetails(resourceType, searchParam);
|
|
12379
12612
|
}
|
|
12380
|
-
return
|
|
12613
|
+
return result;
|
|
12381
12614
|
}
|
|
12382
|
-
function
|
|
12383
|
-
|
|
12384
|
-
|
|
12385
|
-
|
|
12386
|
-
if (colonIndex >= 0) {
|
|
12387
|
-
code = key.substring(0, colonIndex);
|
|
12388
|
-
modifier = key.substring(colonIndex + 1);
|
|
12615
|
+
function setSearchParamterDetails(resourceType, code, details) {
|
|
12616
|
+
const typeSchema = globalSchema.types[resourceType];
|
|
12617
|
+
if (!typeSchema.searchParamsDetails) {
|
|
12618
|
+
typeSchema.searchParamsDetails = {};
|
|
12389
12619
|
}
|
|
12390
|
-
|
|
12391
|
-
|
|
12392
|
-
|
|
12620
|
+
typeSchema.searchParamsDetails[code] = details;
|
|
12621
|
+
}
|
|
12622
|
+
function buildSearchParamterDetails(resourceType, searchParam) {
|
|
12623
|
+
const code = searchParam.code;
|
|
12624
|
+
const columnName = convertCodeToColumnName(code);
|
|
12625
|
+
const expression = getExpressionForResourceType(resourceType, searchParam.expression)?.split('.');
|
|
12626
|
+
if (!expression) {
|
|
12627
|
+
// This happens on compound types
|
|
12628
|
+
// In the future, explore returning multiple column definitions
|
|
12629
|
+
return { columnName, type: exports.SearchParameterType.TEXT };
|
|
12393
12630
|
}
|
|
12394
|
-
|
|
12395
|
-
|
|
12396
|
-
|
|
12397
|
-
|
|
12398
|
-
|
|
12399
|
-
|
|
12400
|
-
|
|
12401
|
-
|
|
12402
|
-
|
|
12403
|
-
|
|
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;
|
|
12631
|
+
const defaultType = getSearchParameterType(searchParam);
|
|
12632
|
+
let baseType = resourceType;
|
|
12633
|
+
let elementDefinition = undefined;
|
|
12634
|
+
let propertyType = undefined;
|
|
12635
|
+
let array = false;
|
|
12636
|
+
for (let i = 1; i < expression.length; i++) {
|
|
12637
|
+
const propertyName = expression[i];
|
|
12638
|
+
elementDefinition = getElementDefinition(baseType, propertyName);
|
|
12639
|
+
if (!elementDefinition) {
|
|
12640
|
+
throw new Error(`Element definition not found for ${resourceType} ${searchParam.code}`);
|
|
12423
12641
|
}
|
|
12424
|
-
|
|
12425
|
-
|
|
12426
|
-
if (modifier === 'iterate') {
|
|
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;
|
|
12642
|
+
if (elementDefinition.max !== '0' && elementDefinition.max !== '1') {
|
|
12643
|
+
array = true;
|
|
12436
12644
|
}
|
|
12437
|
-
|
|
12438
|
-
|
|
12439
|
-
|
|
12440
|
-
|
|
12441
|
-
|
|
12442
|
-
|
|
12443
|
-
|
|
12645
|
+
propertyType = elementDefinition.type?.[0].code;
|
|
12646
|
+
if (!propertyType) {
|
|
12647
|
+
// This happens when one of parent properties uses contentReference
|
|
12648
|
+
// In the future, explore following the reference
|
|
12649
|
+
return { columnName, type: defaultType, array };
|
|
12650
|
+
}
|
|
12651
|
+
if (i < expression.length - 1) {
|
|
12652
|
+
if (isBackboneElement(propertyType)) {
|
|
12653
|
+
baseType = buildTypeName(elementDefinition.path?.split('.'));
|
|
12444
12654
|
}
|
|
12445
12655
|
else {
|
|
12446
|
-
|
|
12656
|
+
baseType = propertyType;
|
|
12447
12657
|
}
|
|
12448
12658
|
}
|
|
12449
12659
|
}
|
|
12660
|
+
const type = getSearchParameterType(searchParam, propertyType);
|
|
12661
|
+
const result = { columnName, type, elementDefinition, array };
|
|
12662
|
+
setSearchParamterDetails(resourceType, code, result);
|
|
12663
|
+
return result;
|
|
12450
12664
|
}
|
|
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
|
-
}
|
|
12665
|
+
function isBackboneElement(propertyType) {
|
|
12666
|
+
return propertyType === 'Element' || propertyType === 'BackboneElement';
|
|
12467
12667
|
}
|
|
12468
|
-
|
|
12469
|
-
|
|
12470
|
-
|
|
12471
|
-
|
|
12472
|
-
|
|
12473
|
-
|
|
12474
|
-
|
|
12475
|
-
|
|
12476
|
-
|
|
12668
|
+
/**
|
|
12669
|
+
* Converts a hyphen-delimited code to camelCase string.
|
|
12670
|
+
* @param code The search parameter code.
|
|
12671
|
+
* @returns The SQL column name.
|
|
12672
|
+
*/
|
|
12673
|
+
function convertCodeToColumnName(code) {
|
|
12674
|
+
return code.split('-').reduce((result, word, index) => result + (index ? capitalize(word) : word), '');
|
|
12675
|
+
}
|
|
12676
|
+
function getSearchParameterType(searchParam, propertyType) {
|
|
12677
|
+
let type = exports.SearchParameterType.TEXT;
|
|
12477
12678
|
switch (searchParam.type) {
|
|
12478
|
-
case 'number':
|
|
12479
12679
|
case 'date':
|
|
12480
|
-
|
|
12680
|
+
if (propertyType === exports.PropertyType.date) {
|
|
12681
|
+
type = exports.SearchParameterType.DATE;
|
|
12682
|
+
}
|
|
12683
|
+
else {
|
|
12684
|
+
type = exports.SearchParameterType.DATETIME;
|
|
12685
|
+
}
|
|
12481
12686
|
break;
|
|
12482
|
-
case '
|
|
12483
|
-
|
|
12484
|
-
case 'token':
|
|
12485
|
-
case 'uri':
|
|
12486
|
-
parseModifierType(searchRequest, searchParam, modifier, value);
|
|
12687
|
+
case 'number':
|
|
12688
|
+
type = exports.SearchParameterType.NUMBER;
|
|
12487
12689
|
break;
|
|
12488
12690
|
case 'quantity':
|
|
12489
|
-
|
|
12691
|
+
type = exports.SearchParameterType.QUANTITY;
|
|
12490
12692
|
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);
|
|
12693
|
+
case 'reference':
|
|
12694
|
+
if (propertyType === exports.PropertyType.canonical) {
|
|
12695
|
+
type = exports.SearchParameterType.CANONICAL;
|
|
12530
12696
|
}
|
|
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) };
|
|
12697
|
+
else {
|
|
12698
|
+
type = exports.SearchParameterType.REFERENCE;
|
|
12699
|
+
}
|
|
12700
|
+
break;
|
|
12701
|
+
case 'token':
|
|
12702
|
+
if (propertyType === 'boolean') {
|
|
12703
|
+
type = exports.SearchParameterType.BOOLEAN;
|
|
12704
|
+
}
|
|
12705
|
+
break;
|
|
12544
12706
|
}
|
|
12545
|
-
return
|
|
12546
|
-
}
|
|
12547
|
-
function parseModifier(modifier) {
|
|
12548
|
-
return MODIFIER_OPERATORS[modifier] || exports.Operator.EQUALS;
|
|
12707
|
+
return type;
|
|
12549
12708
|
}
|
|
12550
|
-
function
|
|
12551
|
-
const
|
|
12552
|
-
|
|
12553
|
-
if (
|
|
12554
|
-
|
|
12709
|
+
function getExpressionForResourceType(resourceType, expression) {
|
|
12710
|
+
const expressions = expression.split(' | ');
|
|
12711
|
+
for (const e of expressions) {
|
|
12712
|
+
if (isIgnoredExpression(e)) {
|
|
12713
|
+
continue;
|
|
12714
|
+
}
|
|
12715
|
+
const simplified = simplifyExpression(e);
|
|
12716
|
+
if (simplified.startsWith(resourceType + '.')) {
|
|
12717
|
+
return simplified;
|
|
12555
12718
|
}
|
|
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
12719
|
}
|
|
12720
|
+
return undefined;
|
|
12577
12721
|
}
|
|
12578
|
-
function
|
|
12579
|
-
|
|
12580
|
-
searchRequest.filters.push(filter);
|
|
12581
|
-
}
|
|
12582
|
-
else {
|
|
12583
|
-
searchRequest.filters = [filter];
|
|
12584
|
-
}
|
|
12722
|
+
function isIgnoredExpression(input) {
|
|
12723
|
+
return input.includes(' as Period') || input.includes(' as SampledDate');
|
|
12585
12724
|
}
|
|
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);
|
|
12725
|
+
function simplifyExpression(input) {
|
|
12726
|
+
let result = input.trim();
|
|
12727
|
+
if (result.startsWith('(') && result.endsWith(')')) {
|
|
12728
|
+
result = result.substring(1, result.length - 1);
|
|
12608
12729
|
}
|
|
12609
|
-
if (
|
|
12610
|
-
|
|
12730
|
+
if (result.includes('[0]')) {
|
|
12731
|
+
result = result.replaceAll('[0]', '');
|
|
12611
12732
|
}
|
|
12612
|
-
|
|
12613
|
-
|
|
12733
|
+
const stopStrings = [' != ', ' as ', '.as(', '.exists(', '.resolve(', '.where('];
|
|
12734
|
+
for (const stopString of stopStrings) {
|
|
12735
|
+
if (result.includes(stopString)) {
|
|
12736
|
+
result = result.substring(0, result.indexOf(stopString));
|
|
12737
|
+
}
|
|
12614
12738
|
}
|
|
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(',');
|
|
12739
|
+
return result;
|
|
12625
12740
|
}
|
|
12626
12741
|
|
|
12627
12742
|
/**
|
|
@@ -12779,6 +12894,29 @@
|
|
|
12779
12894
|
return operator === exports.Operator.NOT_EQUALS || operator === exports.Operator.NOT;
|
|
12780
12895
|
}
|
|
12781
12896
|
|
|
12897
|
+
/**
|
|
12898
|
+
* Reads data from a Readable stream and returns a Promise that resolves with a Buffer containing all the data.
|
|
12899
|
+
* @param stream - The Readable stream to read from.
|
|
12900
|
+
* @returns A Promise that resolves with a Buffer containing all the data from the Readable stream.
|
|
12901
|
+
*/
|
|
12902
|
+
function streamToBuffer(stream) {
|
|
12903
|
+
const chunks = [];
|
|
12904
|
+
return new Promise((resolve, reject) => {
|
|
12905
|
+
stream.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
|
|
12906
|
+
stream.on('error', (err) => {
|
|
12907
|
+
console.error(err.message);
|
|
12908
|
+
stream.destroy();
|
|
12909
|
+
reject(err);
|
|
12910
|
+
});
|
|
12911
|
+
stream.on('end', () => {
|
|
12912
|
+
resolve(Buffer.concat(chunks));
|
|
12913
|
+
});
|
|
12914
|
+
stream.on('close', () => {
|
|
12915
|
+
stream.destroy();
|
|
12916
|
+
});
|
|
12917
|
+
});
|
|
12918
|
+
}
|
|
12919
|
+
|
|
12782
12920
|
exports.AndAtom = AndAtom;
|
|
12783
12921
|
exports.ArithemticOperatorAtom = ArithemticOperatorAtom;
|
|
12784
12922
|
exports.AsAtom = AsAtom;
|
|
@@ -12852,6 +12990,7 @@
|
|
|
12852
12990
|
exports.fhirPathNot = fhirPathNot;
|
|
12853
12991
|
exports.findObservationInterval = findObservationInterval;
|
|
12854
12992
|
exports.findObservationReferenceRange = findObservationReferenceRange;
|
|
12993
|
+
exports.findResourceByCode = findResourceByCode;
|
|
12855
12994
|
exports.forbidden = forbidden;
|
|
12856
12995
|
exports.formatAddress = formatAddress;
|
|
12857
12996
|
exports.formatCodeableConcept = formatCodeableConcept;
|
|
@@ -12922,6 +13061,7 @@
|
|
|
12922
13061
|
exports.operationOutcomeToString = operationOutcomeToString;
|
|
12923
13062
|
exports.parseFhirPath = parseFhirPath;
|
|
12924
13063
|
exports.parseFilterParameter = parseFilterParameter;
|
|
13064
|
+
exports.parseHl7Date = parseHl7Date;
|
|
12925
13065
|
exports.parseJWTPayload = parseJWTPayload;
|
|
12926
13066
|
exports.parseMappingLanguage = parseMappingLanguage;
|
|
12927
13067
|
exports.parseSearchDefinition = parseSearchDefinition;
|
|
@@ -12936,6 +13076,7 @@
|
|
|
12936
13076
|
exports.removeDuplicates = removeDuplicates;
|
|
12937
13077
|
exports.resolveId = resolveId;
|
|
12938
13078
|
exports.setCodeBySystem = setCodeBySystem;
|
|
13079
|
+
exports.streamToBuffer = streamToBuffer;
|
|
12939
13080
|
exports.stringify = stringify;
|
|
12940
13081
|
exports.toJsBoolean = toJsBoolean;
|
|
12941
13082
|
exports.toTypedValue = toTypedValue;
|