@openhi/constructs 0.0.138 → 0.0.140
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.
|
@@ -5405,6 +5405,89 @@ async function listAppointmentsOperation(params) {
|
|
|
5405
5405
|
);
|
|
5406
5406
|
}
|
|
5407
5407
|
|
|
5408
|
+
// src/data/search/engine/reference-predicate.ts
|
|
5409
|
+
function buildOpenHiResourceUrn(opts) {
|
|
5410
|
+
return `urn:ohi:${opts.tenantId}:${opts.workspaceId}:${opts.resourceType}:${opts.resourceId}`;
|
|
5411
|
+
}
|
|
5412
|
+
var REFERENCE_CONTAINMENT_SQL_FRAGMENT = "(resource @> :containmentRelative::jsonb OR resource @> :containmentUrn::jsonb)";
|
|
5413
|
+
function parseTypedReference(s) {
|
|
5414
|
+
const match = /^([A-Za-z][A-Za-z0-9_]*)\/([^\s/]+)$/.exec(s);
|
|
5415
|
+
if (!match) {
|
|
5416
|
+
return void 0;
|
|
5417
|
+
}
|
|
5418
|
+
return { resourceType: match[1], resourceId: match[2] };
|
|
5419
|
+
}
|
|
5420
|
+
function wrapReferenceInShape(shape, reference) {
|
|
5421
|
+
switch (shape.kind) {
|
|
5422
|
+
case "scalar":
|
|
5423
|
+
return { [shape.field]: { reference } };
|
|
5424
|
+
case "array-of-references":
|
|
5425
|
+
return { [shape.field]: [{ reference }] };
|
|
5426
|
+
case "array-of-objects":
|
|
5427
|
+
return { [shape.field]: [{ [shape.subfield]: { reference } }] };
|
|
5428
|
+
}
|
|
5429
|
+
}
|
|
5430
|
+
function buildReferenceContainmentPayload(params) {
|
|
5431
|
+
const parsed = parseTypedReference(params.reference);
|
|
5432
|
+
if (!parsed) {
|
|
5433
|
+
throw new Error(
|
|
5434
|
+
`Reference "${params.reference}" is not a valid typed reference (<ResourceType>/<id>).`
|
|
5435
|
+
);
|
|
5436
|
+
}
|
|
5437
|
+
const urn = buildOpenHiResourceUrn({
|
|
5438
|
+
tenantId: params.tenantId,
|
|
5439
|
+
workspaceId: params.workspaceId,
|
|
5440
|
+
resourceType: parsed.resourceType,
|
|
5441
|
+
resourceId: parsed.resourceId
|
|
5442
|
+
});
|
|
5443
|
+
return {
|
|
5444
|
+
containmentRelative: JSON.stringify(
|
|
5445
|
+
wrapReferenceInShape(params.shape, params.reference)
|
|
5446
|
+
),
|
|
5447
|
+
containmentUrn: JSON.stringify(wrapReferenceInShape(params.shape, urn))
|
|
5448
|
+
};
|
|
5449
|
+
}
|
|
5450
|
+
function jsonbPathToReferenceShape(jsonbPath) {
|
|
5451
|
+
const scalar = /^\$\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(jsonbPath);
|
|
5452
|
+
if (scalar) {
|
|
5453
|
+
return { kind: "scalar", field: scalar[1] };
|
|
5454
|
+
}
|
|
5455
|
+
const arrayOfRefs = /^\$\.([A-Za-z_][A-Za-z0-9_]*)\[\*\]$/.exec(jsonbPath);
|
|
5456
|
+
if (arrayOfRefs) {
|
|
5457
|
+
return { kind: "array-of-references", field: arrayOfRefs[1] };
|
|
5458
|
+
}
|
|
5459
|
+
const arrayOfObjs = /^\$\.([A-Za-z_][A-Za-z0-9_]*)\[\*\]\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(
|
|
5460
|
+
jsonbPath
|
|
5461
|
+
);
|
|
5462
|
+
if (arrayOfObjs) {
|
|
5463
|
+
return {
|
|
5464
|
+
kind: "array-of-objects",
|
|
5465
|
+
field: arrayOfObjs[1],
|
|
5466
|
+
subfield: arrayOfObjs[2]
|
|
5467
|
+
};
|
|
5468
|
+
}
|
|
5469
|
+
throw new Error(
|
|
5470
|
+
`Reference predicate cannot translate JSONPath "${jsonbPath}". Supported shapes: "$.field", "$.field[*]", "$.field[*].subfield".`
|
|
5471
|
+
);
|
|
5472
|
+
}
|
|
5473
|
+
function emitReferencePredicate(opts) {
|
|
5474
|
+
const shape = jsonbPathToReferenceShape(opts.jsonbPath);
|
|
5475
|
+
const { containmentRelative, containmentUrn } = buildReferenceContainmentPayload({
|
|
5476
|
+
shape,
|
|
5477
|
+
reference: opts.rawValue,
|
|
5478
|
+
tenantId: opts.context.tenantId,
|
|
5479
|
+
workspaceId: opts.context.workspaceId
|
|
5480
|
+
});
|
|
5481
|
+
const relName = `${opts.paramName}R`;
|
|
5482
|
+
const urnName = `${opts.paramName}U`;
|
|
5483
|
+
const sql = `(resource @> :${relName}::jsonb OR resource @> :${urnName}::jsonb)`;
|
|
5484
|
+
const params = [
|
|
5485
|
+
{ name: relName, value: containmentRelative },
|
|
5486
|
+
{ name: urnName, value: containmentUrn }
|
|
5487
|
+
];
|
|
5488
|
+
return { sql, params };
|
|
5489
|
+
}
|
|
5490
|
+
|
|
5408
5491
|
// src/data/postgres/data-api-postgres-query-runner.ts
|
|
5409
5492
|
import {
|
|
5410
5493
|
ExecuteStatementCommand,
|
|
@@ -5596,129 +5679,95 @@ function emitFieldMissingPredicate(opts) {
|
|
|
5596
5679
|
const sql = opts.missing ? `${extract} IS NULL` : `${extract} IS NOT NULL`;
|
|
5597
5680
|
return { sql, params: [] };
|
|
5598
5681
|
}
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5682
|
+
|
|
5683
|
+
// src/data/search/engine/string-predicate.ts
|
|
5684
|
+
var STRING_MODIFIERS = ["exact", "contains"];
|
|
5685
|
+
function isStringModifier(s) {
|
|
5686
|
+
return STRING_MODIFIERS.includes(s);
|
|
5602
5687
|
}
|
|
5603
|
-
function
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
case "gt":
|
|
5608
|
-
return `(${endExtract} IS NULL OR ${endExtract} > :${paramName})`;
|
|
5609
|
-
case "lt":
|
|
5610
|
-
return `(${startExtract} IS NULL OR ${startExtract} < :${paramName})`;
|
|
5611
|
-
case "ge":
|
|
5612
|
-
return `(${endExtract} IS NULL OR ${endExtract} >= :${paramName})`;
|
|
5613
|
-
case "le":
|
|
5614
|
-
return `(${startExtract} IS NULL OR ${startExtract} <= :${paramName})`;
|
|
5615
|
-
case "sa":
|
|
5616
|
-
return `(${startExtract} IS NOT NULL AND ${startExtract} > :${paramName})`;
|
|
5617
|
-
case "eb":
|
|
5618
|
-
return `(${endExtract} IS NOT NULL AND ${endExtract} < :${paramName})`;
|
|
5688
|
+
function jsonbPathToStringShape(jsonbPath) {
|
|
5689
|
+
const scalar = /^\$\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(jsonbPath);
|
|
5690
|
+
if (scalar) {
|
|
5691
|
+
return { kind: "scalar", field: scalar[1] };
|
|
5619
5692
|
}
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
return [];
|
|
5693
|
+
const arrayOfScalars = /^\$\.([A-Za-z_][A-Za-z0-9_]*)\[\*\]$/.exec(jsonbPath);
|
|
5694
|
+
if (arrayOfScalars) {
|
|
5695
|
+
return { kind: "array-of-scalars", field: arrayOfScalars[1] };
|
|
5624
5696
|
}
|
|
5625
|
-
const
|
|
5626
|
-
|
|
5627
|
-
const endExtract = flatJsonbExtract(opts.endPath);
|
|
5628
|
-
const fragments = constraints.map(
|
|
5629
|
-
(c, i) => buildIntervalSingleSql(
|
|
5630
|
-
c.prefix,
|
|
5631
|
-
startExtract,
|
|
5632
|
-
endExtract,
|
|
5633
|
-
intervalConstraintParamName(paramPrefix, i)
|
|
5634
|
-
)
|
|
5697
|
+
const arrayOfObjs = /^\$\.([A-Za-z_][A-Za-z0-9_]*)\[\*\]\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(
|
|
5698
|
+
jsonbPath
|
|
5635
5699
|
);
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
}
|
|
5646
|
-
var APPOINTMENT_DATE_SEARCH_PREFIXES = [
|
|
5647
|
-
"gt",
|
|
5648
|
-
"lt",
|
|
5649
|
-
"ge",
|
|
5650
|
-
"le",
|
|
5651
|
-
"sa",
|
|
5652
|
-
"eb"
|
|
5653
|
-
];
|
|
5654
|
-
function isAppointmentDateSearchPrefix(s) {
|
|
5655
|
-
return APPOINTMENT_DATE_SEARCH_PREFIXES.includes(
|
|
5656
|
-
s
|
|
5700
|
+
if (arrayOfObjs) {
|
|
5701
|
+
return {
|
|
5702
|
+
kind: "array-of-objects",
|
|
5703
|
+
field: arrayOfObjs[1],
|
|
5704
|
+
subfield: arrayOfObjs[2]
|
|
5705
|
+
};
|
|
5706
|
+
}
|
|
5707
|
+
throw new Error(
|
|
5708
|
+
`String predicate cannot translate JSONPath "${jsonbPath}". Supported shapes: "$.field", "$.field[*]", "$.field[*].subfield".`
|
|
5657
5709
|
);
|
|
5658
5710
|
}
|
|
5659
|
-
function
|
|
5660
|
-
return
|
|
5661
|
-
startPath: "$.start",
|
|
5662
|
-
endPath: "$.end",
|
|
5663
|
-
paramNamePrefix: "apptDateConstraint"
|
|
5664
|
-
});
|
|
5665
|
-
}
|
|
5666
|
-
function buildAppointmentDateSearchPredicateParams(constraints) {
|
|
5667
|
-
return buildIntervalDateSearchPredicateParams(constraints, {
|
|
5668
|
-
paramNamePrefix: "apptDateConstraint"
|
|
5669
|
-
});
|
|
5670
|
-
}
|
|
5671
|
-
|
|
5672
|
-
// src/data/search/engine/reference-predicate.ts
|
|
5673
|
-
function buildOpenHiResourceUrn(opts) {
|
|
5674
|
-
return `urn:ohi:${opts.tenantId}:${opts.workspaceId}:${opts.resourceType}:${opts.resourceId}`;
|
|
5711
|
+
function escapeLikePattern(value) {
|
|
5712
|
+
return value.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
5675
5713
|
}
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5714
|
+
function buildLikePattern(value, modifier) {
|
|
5715
|
+
const escaped = escapeLikePattern(value);
|
|
5716
|
+
switch (modifier) {
|
|
5717
|
+
case "exact":
|
|
5718
|
+
return escaped;
|
|
5719
|
+
case "contains":
|
|
5720
|
+
return `%${escaped}%`;
|
|
5721
|
+
case void 0:
|
|
5722
|
+
return `${escaped}%`;
|
|
5681
5723
|
}
|
|
5682
|
-
return { resourceType: match[1], resourceId: match[2] };
|
|
5683
5724
|
}
|
|
5684
|
-
function
|
|
5725
|
+
function buildIlikeExtractSql(shape, paramName) {
|
|
5685
5726
|
switch (shape.kind) {
|
|
5686
5727
|
case "scalar":
|
|
5687
|
-
return {
|
|
5688
|
-
case "array-of-
|
|
5689
|
-
return
|
|
5728
|
+
return `resource->>'${shape.field}' ILIKE :${paramName}`;
|
|
5729
|
+
case "array-of-scalars":
|
|
5730
|
+
return [
|
|
5731
|
+
"EXISTS (SELECT 1 FROM",
|
|
5732
|
+
`jsonb_array_elements_text(resource->'${shape.field}') AS s_elem(text_val)`,
|
|
5733
|
+
`WHERE s_elem.text_val ILIKE :${paramName})`
|
|
5734
|
+
].join(" ");
|
|
5690
5735
|
case "array-of-objects":
|
|
5691
|
-
return
|
|
5736
|
+
return [
|
|
5737
|
+
"EXISTS (SELECT 1 FROM",
|
|
5738
|
+
`jsonb_array_elements(resource->'${shape.field}') AS s_obj(obj)`,
|
|
5739
|
+
`WHERE s_obj.obj->>'${shape.subfield}' ILIKE :${paramName})`
|
|
5740
|
+
].join(" ");
|
|
5692
5741
|
}
|
|
5693
5742
|
}
|
|
5694
|
-
function
|
|
5695
|
-
const
|
|
5696
|
-
|
|
5743
|
+
function emitStringPredicate(opts) {
|
|
5744
|
+
const shape = jsonbPathToStringShape(opts.jsonbPath);
|
|
5745
|
+
const modifier = opts.modifier === void 0 ? void 0 : checkModifier(opts.modifier);
|
|
5746
|
+
const sql = buildIlikeExtractSql(shape, opts.paramName);
|
|
5747
|
+
const pattern = buildLikePattern(opts.rawValue, modifier);
|
|
5748
|
+
const params = [
|
|
5749
|
+
{ name: opts.paramName, value: pattern }
|
|
5750
|
+
];
|
|
5751
|
+
return { sql, params };
|
|
5752
|
+
}
|
|
5753
|
+
function checkModifier(modifier) {
|
|
5754
|
+
if (!isStringModifier(modifier)) {
|
|
5697
5755
|
throw new Error(
|
|
5698
|
-
`
|
|
5756
|
+
`String predicate does not support modifier ":${modifier}". Supported: ${STRING_MODIFIERS.map((m) => `:${m}`).join(", ")}.`
|
|
5699
5757
|
);
|
|
5700
5758
|
}
|
|
5701
|
-
|
|
5702
|
-
tenantId: params.tenantId,
|
|
5703
|
-
workspaceId: params.workspaceId,
|
|
5704
|
-
resourceType: parsed.resourceType,
|
|
5705
|
-
resourceId: parsed.resourceId
|
|
5706
|
-
});
|
|
5707
|
-
return {
|
|
5708
|
-
containmentRelative: JSON.stringify(
|
|
5709
|
-
wrapReferenceInShape(params.shape, params.reference)
|
|
5710
|
-
),
|
|
5711
|
-
containmentUrn: JSON.stringify(wrapReferenceInShape(params.shape, urn))
|
|
5712
|
-
};
|
|
5759
|
+
return modifier;
|
|
5713
5760
|
}
|
|
5714
|
-
|
|
5761
|
+
|
|
5762
|
+
// src/data/search/engine/token-predicate.ts
|
|
5763
|
+
function jsonbPathToTokenShape(jsonbPath) {
|
|
5715
5764
|
const scalar = /^\$\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(jsonbPath);
|
|
5716
5765
|
if (scalar) {
|
|
5717
5766
|
return { kind: "scalar", field: scalar[1] };
|
|
5718
5767
|
}
|
|
5719
|
-
const
|
|
5720
|
-
if (
|
|
5721
|
-
return { kind: "array-of-
|
|
5768
|
+
const arrayOfScalars = /^\$\.([A-Za-z_][A-Za-z0-9_]*)\[\*\]$/.exec(jsonbPath);
|
|
5769
|
+
if (arrayOfScalars) {
|
|
5770
|
+
return { kind: "array-of-scalars", field: arrayOfScalars[1] };
|
|
5722
5771
|
}
|
|
5723
5772
|
const arrayOfObjs = /^\$\.([A-Za-z_][A-Za-z0-9_]*)\[\*\]\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(
|
|
5724
5773
|
jsonbPath
|
|
@@ -5731,240 +5780,332 @@ function jsonbPathToReferenceShape(jsonbPath) {
|
|
|
5731
5780
|
};
|
|
5732
5781
|
}
|
|
5733
5782
|
throw new Error(
|
|
5734
|
-
`
|
|
5783
|
+
`Token predicate cannot translate JSONPath "${jsonbPath}". Supported shapes: "$.field", "$.field[*]", "$.field[*].subfield".`
|
|
5735
5784
|
);
|
|
5736
5785
|
}
|
|
5737
|
-
function
|
|
5738
|
-
const
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
}
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5786
|
+
function parseTokenValue(raw) {
|
|
5787
|
+
const idx = raw.indexOf("|");
|
|
5788
|
+
if (idx === -1) {
|
|
5789
|
+
return { code: raw };
|
|
5790
|
+
}
|
|
5791
|
+
const system = raw.slice(0, idx);
|
|
5792
|
+
const code = raw.slice(idx + 1);
|
|
5793
|
+
return system.length > 0 ? { system, code } : { code };
|
|
5794
|
+
}
|
|
5795
|
+
function wrapTokenInShape(shape, value) {
|
|
5796
|
+
switch (shape.kind) {
|
|
5797
|
+
case "scalar":
|
|
5798
|
+
return { [shape.field]: value };
|
|
5799
|
+
case "array-of-scalars":
|
|
5800
|
+
return { [shape.field]: [value] };
|
|
5801
|
+
case "array-of-objects":
|
|
5802
|
+
return { [shape.field]: [{ [shape.subfield]: value }] };
|
|
5803
|
+
}
|
|
5804
|
+
}
|
|
5805
|
+
function emitTokenPredicate(opts) {
|
|
5806
|
+
const shape = jsonbPathToTokenShape(opts.jsonbPath);
|
|
5807
|
+
const { code } = parseTokenValue(opts.rawValue);
|
|
5808
|
+
const payload = wrapTokenInShape(shape, code);
|
|
5809
|
+
const sql = `resource @> :${opts.paramName}::jsonb`;
|
|
5748
5810
|
const params = [
|
|
5749
|
-
{ name:
|
|
5750
|
-
{ name: urnName, value: containmentUrn }
|
|
5811
|
+
{ name: opts.paramName, value: JSON.stringify(payload) }
|
|
5751
5812
|
];
|
|
5752
5813
|
return { sql, params };
|
|
5753
5814
|
}
|
|
5754
5815
|
|
|
5755
|
-
// src/data/
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
);
|
|
5761
|
-
const lines = [
|
|
5762
|
-
"SELECT resource_id AS id, resource",
|
|
5763
|
-
"FROM resources",
|
|
5764
|
-
"WHERE tenant_id = :tenantId",
|
|
5765
|
-
" AND workspace_id = :workspaceId",
|
|
5766
|
-
" AND resource_type = 'Appointment'",
|
|
5767
|
-
" AND deleted_at IS NULL",
|
|
5768
|
-
` AND ${REFERENCE_CONTAINMENT_SQL_FRAGMENT}`
|
|
5769
|
-
];
|
|
5770
|
-
for (const fragment of datePredicates) {
|
|
5771
|
-
lines.push(` AND ${fragment}`);
|
|
5816
|
+
// src/data/search/engine/combinator.ts
|
|
5817
|
+
function parseQueryKey(key) {
|
|
5818
|
+
const idx = key.indexOf(":");
|
|
5819
|
+
if (idx === -1) {
|
|
5820
|
+
return { code: key, modifier: void 0 };
|
|
5772
5821
|
}
|
|
5773
|
-
|
|
5774
|
-
lines.push("LIMIT :limit;");
|
|
5775
|
-
return lines.join("\n");
|
|
5822
|
+
return { code: key.slice(0, idx), modifier: key.slice(idx + 1) };
|
|
5776
5823
|
}
|
|
5777
|
-
|
|
5778
|
-
const
|
|
5779
|
-
const
|
|
5780
|
-
const
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5824
|
+
function flattenQueryValues(raw) {
|
|
5825
|
+
const list = Array.isArray(raw) ? raw : [raw];
|
|
5826
|
+
const out = [];
|
|
5827
|
+
for (const v of list) {
|
|
5828
|
+
for (const piece of v.split(",")) {
|
|
5829
|
+
out.push(piece);
|
|
5830
|
+
}
|
|
5831
|
+
}
|
|
5832
|
+
return out;
|
|
5833
|
+
}
|
|
5834
|
+
function parseQueryEntries(query) {
|
|
5835
|
+
const entries = [];
|
|
5836
|
+
for (const [rawKey, rawValue] of Object.entries(query)) {
|
|
5837
|
+
if (rawValue === void 0) continue;
|
|
5838
|
+
const { code, modifier } = parseQueryKey(rawKey);
|
|
5839
|
+
entries.push({
|
|
5840
|
+
code,
|
|
5841
|
+
modifier,
|
|
5842
|
+
values: flattenQueryValues(rawValue)
|
|
5843
|
+
});
|
|
5844
|
+
}
|
|
5845
|
+
return entries;
|
|
5846
|
+
}
|
|
5847
|
+
function findParam(params, code) {
|
|
5848
|
+
return params.find((p) => p.code === code);
|
|
5849
|
+
}
|
|
5850
|
+
function checkModifierAllowed(modifier, param) {
|
|
5851
|
+
const universal = modifier === "missing" || modifier === "not";
|
|
5852
|
+
const stringNative = param.type === "string" && (modifier === "exact" || modifier === "contains");
|
|
5853
|
+
if (universal || stringNative) {
|
|
5854
|
+
if (param.modifiers && !param.modifiers.includes(modifier)) {
|
|
5855
|
+
throw new Error(
|
|
5856
|
+
`Modifier ":${modifier}" is not in the allow-list for param "${param.code}".`
|
|
5857
|
+
);
|
|
5858
|
+
}
|
|
5859
|
+
return;
|
|
5860
|
+
}
|
|
5861
|
+
throw new Error(
|
|
5862
|
+
`Modifier ":${modifier}" is not recognized for param "${param.code}" (type "${param.type}").`
|
|
5863
|
+
);
|
|
5864
|
+
}
|
|
5865
|
+
function emitOne(opts) {
|
|
5866
|
+
const { param, modifier, rawValue, paramName, context } = opts;
|
|
5867
|
+
if (modifier === "missing") {
|
|
5868
|
+
const missing = parseMissingValue(rawValue);
|
|
5869
|
+
return emitFieldMissingPredicate({
|
|
5870
|
+
jsonbPath: param.jsonbPath,
|
|
5871
|
+
missing
|
|
5872
|
+
});
|
|
5873
|
+
}
|
|
5874
|
+
const negate = modifier === "not";
|
|
5875
|
+
const effectiveModifier = negate ? void 0 : modifier;
|
|
5876
|
+
const inner = emitForType({
|
|
5877
|
+
paramType: param.type,
|
|
5878
|
+
jsonbPath: param.jsonbPath,
|
|
5879
|
+
rawValue,
|
|
5880
|
+
paramName,
|
|
5881
|
+
modifier: effectiveModifier,
|
|
5882
|
+
context
|
|
5792
5883
|
});
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5884
|
+
if (!negate) {
|
|
5885
|
+
return inner;
|
|
5886
|
+
}
|
|
5887
|
+
return { sql: `NOT (${inner.sql})`, params: inner.params };
|
|
5888
|
+
}
|
|
5889
|
+
function parseMissingValue(raw) {
|
|
5890
|
+
if (raw === "true") return true;
|
|
5891
|
+
if (raw === "false") return false;
|
|
5892
|
+
throw new Error(
|
|
5893
|
+
`:missing requires a value of "true" or "false"; received "${raw}".`
|
|
5894
|
+
);
|
|
5895
|
+
}
|
|
5896
|
+
function emitForType(opts) {
|
|
5897
|
+
switch (opts.paramType) {
|
|
5898
|
+
case "token":
|
|
5899
|
+
return emitTokenPredicate({
|
|
5900
|
+
jsonbPath: opts.jsonbPath,
|
|
5901
|
+
rawValue: opts.rawValue,
|
|
5902
|
+
paramName: opts.paramName,
|
|
5903
|
+
context: opts.context
|
|
5904
|
+
});
|
|
5905
|
+
case "date":
|
|
5906
|
+
return emitDatePredicate({
|
|
5907
|
+
jsonbPath: opts.jsonbPath,
|
|
5908
|
+
rawValue: opts.rawValue,
|
|
5909
|
+
paramName: opts.paramName,
|
|
5910
|
+
context: opts.context
|
|
5911
|
+
});
|
|
5912
|
+
case "reference":
|
|
5913
|
+
return emitReferencePredicate({
|
|
5914
|
+
jsonbPath: opts.jsonbPath,
|
|
5915
|
+
rawValue: opts.rawValue,
|
|
5916
|
+
paramName: opts.paramName,
|
|
5917
|
+
context: opts.context
|
|
5918
|
+
});
|
|
5919
|
+
case "string":
|
|
5920
|
+
return emitStringPredicate({
|
|
5921
|
+
jsonbPath: opts.jsonbPath,
|
|
5922
|
+
rawValue: opts.rawValue,
|
|
5923
|
+
paramName: opts.paramName,
|
|
5924
|
+
modifier: opts.modifier,
|
|
5925
|
+
context: opts.context
|
|
5926
|
+
});
|
|
5927
|
+
}
|
|
5928
|
+
}
|
|
5929
|
+
function combineSearchPredicates(opts) {
|
|
5930
|
+
const entries = parseQueryEntries(opts.query);
|
|
5931
|
+
const groupSqls = [];
|
|
5932
|
+
const allParams = [];
|
|
5933
|
+
let paramIdx = 0;
|
|
5934
|
+
for (const entry of entries) {
|
|
5935
|
+
const registered = findParam(opts.registeredParams, entry.code);
|
|
5936
|
+
if (!registered) {
|
|
5937
|
+
continue;
|
|
5808
5938
|
}
|
|
5809
|
-
|
|
5810
|
-
|
|
5939
|
+
if (entry.modifier !== void 0) {
|
|
5940
|
+
checkModifierAllowed(entry.modifier, registered);
|
|
5941
|
+
}
|
|
5942
|
+
if (entry.values.length === 0) {
|
|
5943
|
+
continue;
|
|
5944
|
+
}
|
|
5945
|
+
const fragmentSqls = [];
|
|
5946
|
+
let valueIdx = 0;
|
|
5947
|
+
for (const rawValue of entry.values) {
|
|
5948
|
+
const paramName = `p${paramIdx}v${valueIdx}`;
|
|
5949
|
+
const frag = emitOne({
|
|
5950
|
+
param: registered,
|
|
5951
|
+
modifier: entry.modifier,
|
|
5952
|
+
rawValue,
|
|
5953
|
+
paramName,
|
|
5954
|
+
context: opts.context
|
|
5955
|
+
});
|
|
5956
|
+
fragmentSqls.push(frag.sql);
|
|
5957
|
+
for (const p of frag.params) {
|
|
5958
|
+
allParams.push(p);
|
|
5959
|
+
}
|
|
5960
|
+
valueIdx++;
|
|
5961
|
+
}
|
|
5962
|
+
paramIdx++;
|
|
5963
|
+
if (fragmentSqls.length === 1) {
|
|
5964
|
+
groupSqls.push(fragmentSqls[0]);
|
|
5965
|
+
} else {
|
|
5966
|
+
groupSqls.push(`(${fragmentSqls.join(" OR ")})`);
|
|
5967
|
+
}
|
|
5968
|
+
}
|
|
5969
|
+
if (groupSqls.length === 0) {
|
|
5970
|
+
return { sql: "", params: [] };
|
|
5971
|
+
}
|
|
5972
|
+
return { sql: groupSqls.join(" AND "), params: allParams };
|
|
5811
5973
|
}
|
|
5812
5974
|
|
|
5813
|
-
// src/data/operations/
|
|
5814
|
-
var
|
|
5815
|
-
function
|
|
5816
|
-
const datePredicates = buildAppointmentDateSearchPredicateSql(
|
|
5817
|
-
opts.dateConstraints
|
|
5818
|
-
);
|
|
5975
|
+
// src/data/search/operations/generic-search-operation.ts
|
|
5976
|
+
var DEFAULT_LIMIT = 100;
|
|
5977
|
+
function buildGenericSearchSql(opts) {
|
|
5819
5978
|
const lines = [
|
|
5820
5979
|
"SELECT resource_id AS id, resource",
|
|
5821
5980
|
"FROM resources",
|
|
5822
5981
|
"WHERE tenant_id = :tenantId",
|
|
5823
5982
|
" AND workspace_id = :workspaceId",
|
|
5824
|
-
" AND resource_type =
|
|
5983
|
+
" AND resource_type = :resourceType",
|
|
5825
5984
|
" AND deleted_at IS NULL"
|
|
5826
5985
|
];
|
|
5827
|
-
|
|
5828
|
-
lines.push(` AND ${
|
|
5986
|
+
if (opts.combinedSql.length > 0) {
|
|
5987
|
+
lines.push(` AND (${opts.combinedSql})`);
|
|
5829
5988
|
}
|
|
5830
5989
|
lines.push("ORDER BY last_updated DESC");
|
|
5831
5990
|
lines.push("LIMIT :limit;");
|
|
5832
5991
|
return lines.join("\n");
|
|
5833
5992
|
}
|
|
5834
|
-
async function
|
|
5835
|
-
const {
|
|
5836
|
-
if (dateConstraints.length === 0) {
|
|
5837
|
-
throw new Error(
|
|
5838
|
-
"searchAppointmentsByDateOperation requires at least one dateConstraint"
|
|
5839
|
-
);
|
|
5840
|
-
}
|
|
5841
|
-
const { tenantId, workspaceId } = context;
|
|
5993
|
+
async function genericSearchOperation(params) {
|
|
5994
|
+
const { resourceType, tenantId, workspaceId, query, resolver } = params;
|
|
5842
5995
|
const runner = params.runner ?? getDefaultPostgresQueryRunner();
|
|
5843
|
-
const limit = params.limit ??
|
|
5844
|
-
const
|
|
5996
|
+
const limit = params.limit ?? DEFAULT_LIMIT;
|
|
5997
|
+
const registeredParams = resolver(resourceType, tenantId);
|
|
5998
|
+
const context = { tenantId, workspaceId, resourceType };
|
|
5999
|
+
const combined = combineSearchPredicates({
|
|
6000
|
+
query,
|
|
6001
|
+
registeredParams,
|
|
6002
|
+
context
|
|
6003
|
+
});
|
|
6004
|
+
const sql = buildGenericSearchSql({ combinedSql: combined.sql });
|
|
5845
6005
|
const queryParams = [
|
|
5846
6006
|
{ name: "tenantId", value: tenantId },
|
|
5847
6007
|
{ name: "workspaceId", value: workspaceId },
|
|
6008
|
+
{ name: "resourceType", value: resourceType },
|
|
5848
6009
|
{ name: "limit", value: limit },
|
|
5849
|
-
...
|
|
6010
|
+
...combined.params
|
|
5850
6011
|
];
|
|
5851
6012
|
const rows = await runner.query(sql, queryParams);
|
|
5852
6013
|
const entries = rows.map((row) => ({
|
|
5853
6014
|
id: row.id,
|
|
5854
|
-
resource: {
|
|
5855
|
-
...row.resource,
|
|
5856
|
-
id: row.id
|
|
5857
|
-
}
|
|
6015
|
+
resource: { ...row.resource, id: row.id }
|
|
5858
6016
|
}));
|
|
5859
6017
|
return { entries, total: entries.length };
|
|
5860
6018
|
}
|
|
5861
6019
|
|
|
5862
|
-
// src/data/
|
|
5863
|
-
var
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
}
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
}
|
|
5894
|
-
|
|
5895
|
-
participant: [
|
|
5896
|
-
{
|
|
5897
|
-
actor: {
|
|
5898
|
-
reference: buildOpenHiResourceUrn({
|
|
5899
|
-
tenantId,
|
|
5900
|
-
workspaceId,
|
|
5901
|
-
resourceType: "Patient",
|
|
5902
|
-
resourceId: patientId
|
|
5903
|
-
})
|
|
5904
|
-
}
|
|
5905
|
-
}
|
|
5906
|
-
]
|
|
5907
|
-
});
|
|
5908
|
-
const sql = buildSearchAppointmentsByPatientSql({ dateConstraints });
|
|
5909
|
-
const queryParams = [
|
|
5910
|
-
{ name: "tenantId", value: tenantId },
|
|
5911
|
-
{ name: "workspaceId", value: workspaceId },
|
|
5912
|
-
{ name: "containmentRelative", value: containmentRelative },
|
|
5913
|
-
{ name: "containmentUrn", value: containmentUrn },
|
|
5914
|
-
{ name: "limit", value: limit },
|
|
5915
|
-
...buildAppointmentDateSearchPredicateParams(dateConstraints)
|
|
5916
|
-
];
|
|
5917
|
-
const rows = await runner.query(sql, queryParams);
|
|
5918
|
-
const entries = rows.map((row) => ({
|
|
5919
|
-
id: row.id,
|
|
5920
|
-
resource: {
|
|
5921
|
-
...row.resource,
|
|
5922
|
-
id: row.id
|
|
5923
|
-
}
|
|
5924
|
-
}));
|
|
5925
|
-
return { entries, total: entries.length };
|
|
5926
|
-
}
|
|
6020
|
+
// src/data/search/registry/appointment-search-parameters.ts
|
|
6021
|
+
var APPOINTMENT_SEARCH_PARAMETERS = [
|
|
6022
|
+
{ code: "status", type: "token", jsonbPath: "$.status" },
|
|
6023
|
+
{ code: "date", type: "date", jsonbPath: "$.start" },
|
|
6024
|
+
{
|
|
6025
|
+
code: "patient",
|
|
6026
|
+
type: "reference",
|
|
6027
|
+
jsonbPath: "$.participant[*].actor"
|
|
6028
|
+
},
|
|
6029
|
+
{
|
|
6030
|
+
code: "actor",
|
|
6031
|
+
type: "reference",
|
|
6032
|
+
jsonbPath: "$.participant[*].actor"
|
|
6033
|
+
},
|
|
6034
|
+
{
|
|
6035
|
+
code: "practitioner",
|
|
6036
|
+
type: "reference",
|
|
6037
|
+
jsonbPath: "$.participant[*].actor"
|
|
6038
|
+
},
|
|
6039
|
+
{ code: "service-type", type: "token", jsonbPath: "$.serviceType[*]" },
|
|
6040
|
+
{
|
|
6041
|
+
code: "service-category",
|
|
6042
|
+
type: "token",
|
|
6043
|
+
jsonbPath: "$.serviceCategory[*]"
|
|
6044
|
+
},
|
|
6045
|
+
{ code: "specialty", type: "token", jsonbPath: "$.specialty[*]" },
|
|
6046
|
+
{
|
|
6047
|
+
code: "appointment-type",
|
|
6048
|
+
type: "token",
|
|
6049
|
+
jsonbPath: "$.appointmentType"
|
|
6050
|
+
},
|
|
6051
|
+
{ code: "slot", type: "reference", jsonbPath: "$.slot[*]" }
|
|
6052
|
+
];
|
|
5927
6053
|
|
|
5928
|
-
// src/data/
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
6054
|
+
// src/data/search/registry/patient-search-parameters.ts
|
|
6055
|
+
var PATIENT_SEARCH_PARAMETERS = [
|
|
6056
|
+
{ code: "gender", type: "token", jsonbPath: "$.gender" },
|
|
6057
|
+
{ code: "identifier", type: "token", jsonbPath: "$.identifier[*].value" },
|
|
6058
|
+
{ code: "birthdate", type: "date", jsonbPath: "$.birthDate" },
|
|
6059
|
+
{
|
|
6060
|
+
code: "name",
|
|
6061
|
+
type: "string",
|
|
6062
|
+
jsonbPath: "$.name[*].text",
|
|
6063
|
+
modifiers: ["exact", "contains", "missing", "not"]
|
|
6064
|
+
},
|
|
6065
|
+
{
|
|
6066
|
+
code: "family",
|
|
6067
|
+
type: "string",
|
|
6068
|
+
jsonbPath: "$.name[*].family",
|
|
6069
|
+
modifiers: ["exact", "contains", "missing", "not"]
|
|
6070
|
+
},
|
|
6071
|
+
{
|
|
6072
|
+
code: "given",
|
|
6073
|
+
type: "string",
|
|
6074
|
+
jsonbPath: "$.name[*].given",
|
|
6075
|
+
modifiers: ["exact", "contains", "missing", "not"]
|
|
6076
|
+
},
|
|
6077
|
+
{ code: "active", type: "token", jsonbPath: "$.active" },
|
|
6078
|
+
{ code: "deceased", type: "token", jsonbPath: "$.deceasedBoolean" },
|
|
6079
|
+
{
|
|
6080
|
+
code: "general-practitioner",
|
|
6081
|
+
type: "reference",
|
|
6082
|
+
jsonbPath: "$.generalPractitioner[*]"
|
|
6083
|
+
},
|
|
6084
|
+
{
|
|
6085
|
+
code: "organization",
|
|
6086
|
+
type: "reference",
|
|
6087
|
+
jsonbPath: "$.managingOrganization"
|
|
5933
6088
|
}
|
|
5934
|
-
|
|
5935
|
-
|
|
6089
|
+
];
|
|
6090
|
+
|
|
6091
|
+
// src/data/search/registry/resolver.ts
|
|
6092
|
+
var STATIC_SEARCH_PARAMETER_MAP = {
|
|
6093
|
+
Appointment: APPOINTMENT_SEARCH_PARAMETERS,
|
|
6094
|
+
Patient: PATIENT_SEARCH_PARAMETERS
|
|
6095
|
+
};
|
|
6096
|
+
var defaultSearchParameterResolver = (resourceType, _tenantId) => STATIC_SEARCH_PARAMETER_MAP[resourceType] ?? [];
|
|
6097
|
+
function getRegisteredSearchParameters(resourceType) {
|
|
6098
|
+
return STATIC_SEARCH_PARAMETER_MAP[resourceType] ?? [];
|
|
5936
6099
|
}
|
|
5937
|
-
|
|
5938
|
-
|
|
6100
|
+
|
|
6101
|
+
// src/data/rest-api/routes/data/appointment/appointment-list-route.ts
|
|
6102
|
+
var APPOINTMENT_RESOURCE_TYPE = "Appointment";
|
|
6103
|
+
function stripModifier(key) {
|
|
6104
|
+
const idx = key.indexOf(":");
|
|
6105
|
+
return idx === -1 ? key : key.slice(0, idx);
|
|
5939
6106
|
}
|
|
5940
|
-
function
|
|
5941
|
-
|
|
5942
|
-
if (raw === void 0) {
|
|
5943
|
-
return [];
|
|
5944
|
-
}
|
|
5945
|
-
const values = Array.isArray(raw) ? raw : [raw];
|
|
5946
|
-
const out = [];
|
|
5947
|
-
for (const v of values) {
|
|
5948
|
-
if (typeof v !== "string") {
|
|
5949
|
-
return { error: "Each ?date= value must be a string." };
|
|
5950
|
-
}
|
|
5951
|
-
const trimmed = v.trim();
|
|
5952
|
-
if (trimmed === "") {
|
|
5953
|
-
return { error: "?date= value must not be empty." };
|
|
5954
|
-
}
|
|
5955
|
-
const prefix = trimmed.slice(0, 2);
|
|
5956
|
-
const datetime = trimmed.slice(2);
|
|
5957
|
-
if (!isAppointmentDateSearchPrefix(prefix)) {
|
|
5958
|
-
return {
|
|
5959
|
-
error: `Unsupported ?date= prefix in "${trimmed}". Supported prefixes: ${APPOINTMENT_DATE_SEARCH_PREFIXES.join(", ")}.`
|
|
5960
|
-
};
|
|
5961
|
-
}
|
|
5962
|
-
if (datetime === "" || Number.isNaN(Date.parse(datetime))) {
|
|
5963
|
-
return { error: `Invalid datetime in ?date=${trimmed}.` };
|
|
5964
|
-
}
|
|
5965
|
-
out.push({ prefix, value: datetime });
|
|
5966
|
-
}
|
|
5967
|
-
return out;
|
|
6107
|
+
function isResultParameter(key) {
|
|
6108
|
+
return key.startsWith("_");
|
|
5968
6109
|
}
|
|
5969
6110
|
function sendInvalidSearch400(res, diagnostics) {
|
|
5970
6111
|
return res.status(400).json({
|
|
@@ -5972,95 +6113,93 @@ function sendInvalidSearch400(res, diagnostics) {
|
|
|
5972
6113
|
issue: [{ severity: "error", code: "invalid", diagnostics }]
|
|
5973
6114
|
});
|
|
5974
6115
|
}
|
|
6116
|
+
function extractSearchParamKeys(query) {
|
|
6117
|
+
const out = [];
|
|
6118
|
+
for (const rawKey of Object.keys(query)) {
|
|
6119
|
+
if (isResultParameter(rawKey)) {
|
|
6120
|
+
continue;
|
|
6121
|
+
}
|
|
6122
|
+
out.push({ rawKey, code: stripModifier(rawKey) });
|
|
6123
|
+
}
|
|
6124
|
+
return out;
|
|
6125
|
+
}
|
|
6126
|
+
function buildUnknownParamDiagnostics(unknownCodes) {
|
|
6127
|
+
const validCodes = getRegisteredSearchParameters(APPOINTMENT_RESOURCE_TYPE).map((p) => p.code).sort().join(", ");
|
|
6128
|
+
const codes = unknownCodes.join(", ");
|
|
6129
|
+
const isPlural = unknownCodes.length !== 1;
|
|
6130
|
+
return [
|
|
6131
|
+
`Unknown search ${isPlural ? "parameters" : "parameter"} for Appointment: ${codes}.`,
|
|
6132
|
+
`Valid codes: ${validCodes}.`
|
|
6133
|
+
].join(" ");
|
|
6134
|
+
}
|
|
6135
|
+
function findMalformedReference(query, searchParamKeys) {
|
|
6136
|
+
const referenceCodes = new Set(
|
|
6137
|
+
getRegisteredSearchParameters(APPOINTMENT_RESOURCE_TYPE).filter((p) => p.type === "reference").map((p) => p.code)
|
|
6138
|
+
);
|
|
6139
|
+
for (const { rawKey, code } of searchParamKeys) {
|
|
6140
|
+
if (!referenceCodes.has(code)) {
|
|
6141
|
+
continue;
|
|
6142
|
+
}
|
|
6143
|
+
const raw = query[rawKey];
|
|
6144
|
+
const values = typeof raw === "string" ? raw.split(",") : Array.isArray(raw) ? raw.flatMap((v) => v.split(",")) : [];
|
|
6145
|
+
for (const v of values) {
|
|
6146
|
+
const trimmed = v.trim();
|
|
6147
|
+
if (trimmed.length === 0) {
|
|
6148
|
+
continue;
|
|
6149
|
+
}
|
|
6150
|
+
if (parseTypedReference(trimmed) === void 0) {
|
|
6151
|
+
return { rawKey, value: trimmed };
|
|
6152
|
+
}
|
|
6153
|
+
}
|
|
6154
|
+
}
|
|
6155
|
+
return void 0;
|
|
6156
|
+
}
|
|
5975
6157
|
async function listAppointmentsRoute(req, res) {
|
|
5976
|
-
const
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
if (
|
|
5980
|
-
return
|
|
6158
|
+
const searchParamKeys = extractSearchParamKeys(
|
|
6159
|
+
req.query
|
|
6160
|
+
);
|
|
6161
|
+
if (searchParamKeys.length === 0) {
|
|
6162
|
+
return handleListRoute({
|
|
6163
|
+
req,
|
|
6164
|
+
res,
|
|
6165
|
+
basePath: BASE_PATH.APPOINTMENT,
|
|
6166
|
+
listOperation: listAppointmentsOperation,
|
|
6167
|
+
errorLogContext: "GET /Appointment list error:"
|
|
6168
|
+
});
|
|
5981
6169
|
}
|
|
5982
|
-
const
|
|
5983
|
-
|
|
6170
|
+
const registered = getRegisteredSearchParameters(APPOINTMENT_RESOURCE_TYPE);
|
|
6171
|
+
const validCodes = new Set(registered.map((p) => p.code));
|
|
6172
|
+
const unknownCodes = searchParamKeys.map((k) => k.code).filter((code) => !validCodes.has(code));
|
|
6173
|
+
if (unknownCodes.length > 0) {
|
|
5984
6174
|
return sendInvalidSearch400(
|
|
5985
6175
|
res,
|
|
5986
|
-
|
|
6176
|
+
buildUnknownParamDiagnostics([...new Set(unknownCodes)])
|
|
5987
6177
|
);
|
|
5988
6178
|
}
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
const result = await searchAppointmentsByActorOperation({
|
|
5999
|
-
context: ctx,
|
|
6000
|
-
actorReference: actorRef,
|
|
6001
|
-
dateConstraints
|
|
6002
|
-
});
|
|
6003
|
-
const bundle = buildSearchsetBundle(
|
|
6004
|
-
BASE_PATH.APPOINTMENT,
|
|
6005
|
-
result.entries
|
|
6006
|
-
);
|
|
6007
|
-
return res.json(bundle);
|
|
6008
|
-
} catch (err) {
|
|
6009
|
-
return sendOperationOutcome500(
|
|
6010
|
-
res,
|
|
6011
|
-
err,
|
|
6012
|
-
"GET /Appointment?actor= search error:"
|
|
6013
|
-
);
|
|
6014
|
-
}
|
|
6015
|
-
}
|
|
6016
|
-
if (patientId !== void 0) {
|
|
6017
|
-
const ctx = req.openhiContext;
|
|
6018
|
-
try {
|
|
6019
|
-
const result = await searchAppointmentsByPatientOperation({
|
|
6020
|
-
context: ctx,
|
|
6021
|
-
patientId,
|
|
6022
|
-
dateConstraints
|
|
6023
|
-
});
|
|
6024
|
-
const bundle = buildSearchsetBundle(
|
|
6025
|
-
BASE_PATH.APPOINTMENT,
|
|
6026
|
-
result.entries
|
|
6027
|
-
);
|
|
6028
|
-
return res.json(bundle);
|
|
6029
|
-
} catch (err) {
|
|
6030
|
-
return sendOperationOutcome500(
|
|
6031
|
-
res,
|
|
6032
|
-
err,
|
|
6033
|
-
"GET /Appointment?patient= search error:"
|
|
6034
|
-
);
|
|
6035
|
-
}
|
|
6179
|
+
const malformedRef = findMalformedReference(
|
|
6180
|
+
req.query,
|
|
6181
|
+
searchParamKeys
|
|
6182
|
+
);
|
|
6183
|
+
if (malformedRef !== void 0) {
|
|
6184
|
+
return sendInvalidSearch400(
|
|
6185
|
+
res,
|
|
6186
|
+
`?${malformedRef.rawKey} must be a typed reference like "Practitioner/<id>"; got "${malformedRef.value}".`
|
|
6187
|
+
);
|
|
6036
6188
|
}
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
return sendOperationOutcome500(
|
|
6051
|
-
res,
|
|
6052
|
-
err,
|
|
6053
|
-
"GET /Appointment?date= search error:"
|
|
6054
|
-
);
|
|
6055
|
-
}
|
|
6189
|
+
const ctx = req.openhiContext;
|
|
6190
|
+
try {
|
|
6191
|
+
const result = await genericSearchOperation({
|
|
6192
|
+
resourceType: APPOINTMENT_RESOURCE_TYPE,
|
|
6193
|
+
tenantId: ctx.tenantId,
|
|
6194
|
+
workspaceId: ctx.workspaceId,
|
|
6195
|
+
query: req.query,
|
|
6196
|
+
resolver: defaultSearchParameterResolver
|
|
6197
|
+
});
|
|
6198
|
+
const bundle = buildSearchsetBundle(BASE_PATH.APPOINTMENT, result.entries);
|
|
6199
|
+
return res.json(bundle);
|
|
6200
|
+
} catch (err) {
|
|
6201
|
+
return sendOperationOutcome500(res, err, "GET /Appointment search error:");
|
|
6056
6202
|
}
|
|
6057
|
-
return handleListRoute({
|
|
6058
|
-
req,
|
|
6059
|
-
res,
|
|
6060
|
-
basePath: BASE_PATH.APPOINTMENT,
|
|
6061
|
-
listOperation: listAppointmentsOperation,
|
|
6062
|
-
errorLogContext: "GET /Appointment list error:"
|
|
6063
|
-
});
|
|
6064
6203
|
}
|
|
6065
6204
|
|
|
6066
6205
|
// src/data/operations/data/appointment/appointment-update-operation.ts
|
|
@@ -13620,7 +13759,7 @@ function buildPeriodSearchPredicateParams(constraints) {
|
|
|
13620
13759
|
}
|
|
13621
13760
|
|
|
13622
13761
|
// src/data/operations/data/encounter/encounter-search-by-date-operation.ts
|
|
13623
|
-
var
|
|
13762
|
+
var DEFAULT_LIMIT2 = 100;
|
|
13624
13763
|
function buildSearchEncountersByDateSql(opts) {
|
|
13625
13764
|
const periodPredicates = buildPeriodSearchPredicateSql(
|
|
13626
13765
|
opts.periodConstraints
|
|
@@ -13649,7 +13788,7 @@ async function searchEncountersByDateOperation(params) {
|
|
|
13649
13788
|
}
|
|
13650
13789
|
const { tenantId, workspaceId } = context;
|
|
13651
13790
|
const runner = params.runner ?? getDefaultPostgresQueryRunner();
|
|
13652
|
-
const limit = params.limit ??
|
|
13791
|
+
const limit = params.limit ?? DEFAULT_LIMIT2;
|
|
13653
13792
|
const sql = buildSearchEncountersByDateSql({ periodConstraints });
|
|
13654
13793
|
const queryParams = [
|
|
13655
13794
|
{ name: "tenantId", value: tenantId },
|
|
@@ -13669,7 +13808,7 @@ async function searchEncountersByDateOperation(params) {
|
|
|
13669
13808
|
}
|
|
13670
13809
|
|
|
13671
13810
|
// src/data/operations/data/encounter/encounter-search-by-participant-operation.ts
|
|
13672
|
-
var
|
|
13811
|
+
var DEFAULT_LIMIT3 = 100;
|
|
13673
13812
|
function buildSearchEncountersByParticipantSql(opts) {
|
|
13674
13813
|
const periodPredicates = buildPeriodSearchPredicateSql(
|
|
13675
13814
|
opts?.periodConstraints ?? []
|
|
@@ -13695,7 +13834,7 @@ async function searchEncountersByParticipantOperation(params) {
|
|
|
13695
13834
|
const periodConstraints = params.periodConstraints ?? [];
|
|
13696
13835
|
const { tenantId, workspaceId } = context;
|
|
13697
13836
|
const runner = params.runner ?? getDefaultPostgresQueryRunner();
|
|
13698
|
-
const limit = params.limit ??
|
|
13837
|
+
const limit = params.limit ?? DEFAULT_LIMIT3;
|
|
13699
13838
|
const { containmentRelative, containmentUrn } = buildReferenceContainmentPayload({
|
|
13700
13839
|
shape: {
|
|
13701
13840
|
kind: "array-of-objects",
|
|
@@ -13727,7 +13866,7 @@ async function searchEncountersByParticipantOperation(params) {
|
|
|
13727
13866
|
}
|
|
13728
13867
|
|
|
13729
13868
|
// src/data/operations/data/encounter/encounter-search-by-patient-operation.ts
|
|
13730
|
-
var
|
|
13869
|
+
var DEFAULT_LIMIT4 = 100;
|
|
13731
13870
|
function buildSearchEncountersByPatientSql(opts) {
|
|
13732
13871
|
const periodPredicates = buildPeriodSearchPredicateSql(
|
|
13733
13872
|
opts?.periodConstraints ?? []
|
|
@@ -13754,7 +13893,7 @@ async function searchEncountersByPatientOperation(params) {
|
|
|
13754
13893
|
const periodConstraints = params.periodConstraints ?? [];
|
|
13755
13894
|
const { tenantId, workspaceId } = context;
|
|
13756
13895
|
const runner = params.runner ?? getDefaultPostgresQueryRunner();
|
|
13757
|
-
const limit = params.limit ??
|
|
13896
|
+
const limit = params.limit ?? DEFAULT_LIMIT4;
|
|
13758
13897
|
const containmentRelative = JSON.stringify({
|
|
13759
13898
|
subject: { reference: `Patient/${patientId}` }
|
|
13760
13899
|
});
|
|
@@ -13789,7 +13928,7 @@ async function searchEncountersByPatientOperation(params) {
|
|
|
13789
13928
|
}
|
|
13790
13929
|
|
|
13791
13930
|
// src/data/rest-api/routes/data/encounter/encounter-list-route.ts
|
|
13792
|
-
function
|
|
13931
|
+
function singleStringQueryParam(req, name) {
|
|
13793
13932
|
const v = req.query[name];
|
|
13794
13933
|
if (typeof v !== "string") {
|
|
13795
13934
|
return void 0;
|
|
@@ -13797,7 +13936,7 @@ function singleStringQueryParam2(req, name) {
|
|
|
13797
13936
|
const trimmed = v.trim();
|
|
13798
13937
|
return trimmed === "" ? void 0 : trimmed;
|
|
13799
13938
|
}
|
|
13800
|
-
function
|
|
13939
|
+
function isError(v) {
|
|
13801
13940
|
return v.error !== void 0;
|
|
13802
13941
|
}
|
|
13803
13942
|
function parseEncounterDateConstraints(req) {
|
|
@@ -13836,10 +13975,10 @@ function sendInvalidSearch4002(res, diagnostics) {
|
|
|
13836
13975
|
});
|
|
13837
13976
|
}
|
|
13838
13977
|
async function listEncountersRoute(req, res) {
|
|
13839
|
-
const patientId =
|
|
13840
|
-
const participantRef =
|
|
13978
|
+
const patientId = singleStringQueryParam(req, "patient");
|
|
13979
|
+
const participantRef = singleStringQueryParam(req, "participant");
|
|
13841
13980
|
const parsed = parseEncounterDateConstraints(req);
|
|
13842
|
-
if (
|
|
13981
|
+
if (isError(parsed)) {
|
|
13843
13982
|
return sendInvalidSearch4002(res, parsed.error);
|
|
13844
13983
|
}
|
|
13845
13984
|
const periodConstraints = parsed;
|
|
@@ -25412,560 +25551,177 @@ async function listOrganizationAffiliationsRoute(req, res) {
|
|
|
25412
25551
|
return handleListRoute({
|
|
25413
25552
|
req,
|
|
25414
25553
|
res,
|
|
25415
|
-
basePath: BASE_PATH.ORGANIZATIONAFFILIATION,
|
|
25416
|
-
listOperation: listOrganizationAffiliationsOperation,
|
|
25417
|
-
errorLogContext: "GET /OrganizationAffiliation list error:"
|
|
25418
|
-
});
|
|
25419
|
-
}
|
|
25420
|
-
|
|
25421
|
-
// src/data/operations/data/organizationaffiliation/organizationaffiliation-update-operation.ts
|
|
25422
|
-
async function updateOrganizationAffiliationOperation(params) {
|
|
25423
|
-
const { context, id, body, tableName } = params;
|
|
25424
|
-
const { tenantId, workspaceId, date, actorId, actorName } = context;
|
|
25425
|
-
const service = getDynamoDataService(tableName);
|
|
25426
|
-
return updateDataEntityById(
|
|
25427
|
-
service.entities.organizationaffiliation,
|
|
25428
|
-
tenantId,
|
|
25429
|
-
workspaceId,
|
|
25430
|
-
id,
|
|
25431
|
-
"OrganizationAffiliation",
|
|
25432
|
-
context,
|
|
25433
|
-
(existingResourceStr) => buildUpdatedResourceWithAudit(
|
|
25434
|
-
body,
|
|
25435
|
-
id,
|
|
25436
|
-
date,
|
|
25437
|
-
actorId,
|
|
25438
|
-
actorName,
|
|
25439
|
-
existingResourceStr,
|
|
25440
|
-
"OrganizationAffiliation"
|
|
25441
|
-
)
|
|
25442
|
-
);
|
|
25443
|
-
}
|
|
25444
|
-
|
|
25445
|
-
// src/data/rest-api/routes/data/organizationaffiliation/organizationaffiliation-update-route.ts
|
|
25446
|
-
async function updateOrganizationAffiliationRoute(req, res) {
|
|
25447
|
-
const bodyResult = requireJsonBodyAs(req, res);
|
|
25448
|
-
if ("errorResponse" in bodyResult) return bodyResult.errorResponse;
|
|
25449
|
-
const id = String(req.params.id);
|
|
25450
|
-
const ctx = req.openhiContext;
|
|
25451
|
-
const body = bodyResult.body;
|
|
25452
|
-
const resource = {
|
|
25453
|
-
...body,
|
|
25454
|
-
resourceType: "OrganizationAffiliation",
|
|
25455
|
-
id
|
|
25456
|
-
};
|
|
25457
|
-
try {
|
|
25458
|
-
const result = await updateOrganizationAffiliationOperation({
|
|
25459
|
-
context: ctx,
|
|
25460
|
-
id,
|
|
25461
|
-
body: resource
|
|
25462
|
-
});
|
|
25463
|
-
return res.json(result.resource);
|
|
25464
|
-
} catch (err) {
|
|
25465
|
-
const status = domainErrorToHttpStatus(err);
|
|
25466
|
-
if (status === 404) {
|
|
25467
|
-
const diagnostics = err instanceof NotFoundError ? err.message : `OrganizationAffiliation ${id} not found`;
|
|
25468
|
-
return sendOperationOutcome404(res, diagnostics);
|
|
25469
|
-
}
|
|
25470
|
-
return sendOperationOutcome500(
|
|
25471
|
-
res,
|
|
25472
|
-
err,
|
|
25473
|
-
"PUT OrganizationAffiliation error:"
|
|
25474
|
-
);
|
|
25475
|
-
}
|
|
25476
|
-
}
|
|
25477
|
-
|
|
25478
|
-
// src/data/rest-api/routes/data/organizationaffiliation/organizationaffiliation.ts
|
|
25479
|
-
var router107 = express107.Router();
|
|
25480
|
-
router107.get("/", listOrganizationAffiliationsRoute);
|
|
25481
|
-
router107.get("/:id", getOrganizationAffiliationByIdRoute);
|
|
25482
|
-
router107.post("/", createOrganizationAffiliationRoute);
|
|
25483
|
-
router107.put("/:id", updateOrganizationAffiliationRoute);
|
|
25484
|
-
router107.delete("/:id", deleteOrganizationAffiliationRoute);
|
|
25485
|
-
|
|
25486
|
-
// src/data/rest-api/routes/data/patient/patient.ts
|
|
25487
|
-
import express108 from "express";
|
|
25488
|
-
|
|
25489
|
-
// src/data/rest-api/routes/data/patient/patient-create-route.ts
|
|
25490
|
-
async function createPatientRoute(req, res) {
|
|
25491
|
-
const bodyResult = requireJsonBodyAs(req, res);
|
|
25492
|
-
if ("errorResponse" in bodyResult) return bodyResult.errorResponse;
|
|
25493
|
-
const ctx = req.openhiContext;
|
|
25494
|
-
const body = bodyResult.body;
|
|
25495
|
-
const patient = {
|
|
25496
|
-
...body,
|
|
25497
|
-
resourceType: "Patient"
|
|
25498
|
-
};
|
|
25499
|
-
try {
|
|
25500
|
-
const result = await createPatientOperation({
|
|
25501
|
-
context: ctx,
|
|
25502
|
-
body: patient
|
|
25503
|
-
});
|
|
25504
|
-
return res.status(201).location(`${BASE_PATH.PATIENT}/${result.id}`).json(result.resource);
|
|
25505
|
-
} catch (err) {
|
|
25506
|
-
return sendOperationOutcome500(res, err, "POST Patient error:");
|
|
25507
|
-
}
|
|
25508
|
-
}
|
|
25509
|
-
|
|
25510
|
-
// src/data/operations/data/patient/patient-delete-operation.ts
|
|
25511
|
-
async function deletePatientOperation(params) {
|
|
25512
|
-
const { context, id, tableName } = params;
|
|
25513
|
-
const { tenantId, workspaceId } = context;
|
|
25514
|
-
const service = getDynamoDataService(tableName);
|
|
25515
|
-
await deleteDataEntityById(
|
|
25516
|
-
service.entities.patient,
|
|
25517
|
-
tenantId,
|
|
25518
|
-
workspaceId,
|
|
25519
|
-
id
|
|
25520
|
-
);
|
|
25521
|
-
}
|
|
25522
|
-
|
|
25523
|
-
// src/data/rest-api/routes/data/patient/patient-delete-route.ts
|
|
25524
|
-
async function deletePatientRoute(req, res) {
|
|
25525
|
-
const id = String(req.params.id);
|
|
25526
|
-
const ctx = req.openhiContext;
|
|
25527
|
-
try {
|
|
25528
|
-
await deletePatientOperation({ context: ctx, id });
|
|
25529
|
-
return res.status(204).send();
|
|
25530
|
-
} catch (err) {
|
|
25531
|
-
return sendOperationOutcome500(res, err, "DELETE Patient error:");
|
|
25532
|
-
}
|
|
25533
|
-
}
|
|
25534
|
-
|
|
25535
|
-
// src/data/operations/data/patient/patient-get-by-id-operation.ts
|
|
25536
|
-
async function getPatientByIdOperation(params) {
|
|
25537
|
-
const { context, id, tableName } = params;
|
|
25538
|
-
const { tenantId, workspaceId } = context;
|
|
25539
|
-
const service = getDynamoDataService(tableName);
|
|
25540
|
-
return getDataEntityById(
|
|
25541
|
-
service.entities.patient,
|
|
25542
|
-
tenantId,
|
|
25543
|
-
workspaceId,
|
|
25544
|
-
id,
|
|
25545
|
-
"Patient"
|
|
25546
|
-
);
|
|
25547
|
-
}
|
|
25548
|
-
|
|
25549
|
-
// src/data/rest-api/routes/data/patient/patient-get-by-id-route.ts
|
|
25550
|
-
async function getPatientByIdRoute(req, res) {
|
|
25551
|
-
const id = String(req.params.id);
|
|
25552
|
-
const ctx = req.openhiContext;
|
|
25553
|
-
try {
|
|
25554
|
-
const result = await getPatientByIdOperation({ context: ctx, id });
|
|
25555
|
-
return res.json(result.resource);
|
|
25556
|
-
} catch (err) {
|
|
25557
|
-
const status = domainErrorToHttpStatus(err);
|
|
25558
|
-
if (status === 404) {
|
|
25559
|
-
const diagnostics = err instanceof NotFoundError ? err.message : `Patient ${id} not found`;
|
|
25560
|
-
return sendOperationOutcome404(res, diagnostics);
|
|
25561
|
-
}
|
|
25562
|
-
return sendOperationOutcome500(res, err, "GET Patient error:");
|
|
25563
|
-
}
|
|
25564
|
-
}
|
|
25565
|
-
|
|
25566
|
-
// src/data/operations/data/patient/patient-list-operation.ts
|
|
25567
|
-
async function listPatientsOperation(params) {
|
|
25568
|
-
const { context, tableName, mode } = params;
|
|
25569
|
-
const { tenantId, workspaceId } = context;
|
|
25570
|
-
const service = getDynamoDataService(tableName);
|
|
25571
|
-
return listDataEntitiesByWorkspace(
|
|
25572
|
-
service.entities.patient,
|
|
25573
|
-
tenantId,
|
|
25574
|
-
workspaceId,
|
|
25575
|
-
mode
|
|
25576
|
-
);
|
|
25577
|
-
}
|
|
25578
|
-
|
|
25579
|
-
// src/data/search/engine/string-predicate.ts
|
|
25580
|
-
var STRING_MODIFIERS = ["exact", "contains"];
|
|
25581
|
-
function isStringModifier(s) {
|
|
25582
|
-
return STRING_MODIFIERS.includes(s);
|
|
25583
|
-
}
|
|
25584
|
-
function jsonbPathToStringShape(jsonbPath) {
|
|
25585
|
-
const scalar = /^\$\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(jsonbPath);
|
|
25586
|
-
if (scalar) {
|
|
25587
|
-
return { kind: "scalar", field: scalar[1] };
|
|
25588
|
-
}
|
|
25589
|
-
const arrayOfScalars = /^\$\.([A-Za-z_][A-Za-z0-9_]*)\[\*\]$/.exec(jsonbPath);
|
|
25590
|
-
if (arrayOfScalars) {
|
|
25591
|
-
return { kind: "array-of-scalars", field: arrayOfScalars[1] };
|
|
25592
|
-
}
|
|
25593
|
-
const arrayOfObjs = /^\$\.([A-Za-z_][A-Za-z0-9_]*)\[\*\]\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(
|
|
25594
|
-
jsonbPath
|
|
25595
|
-
);
|
|
25596
|
-
if (arrayOfObjs) {
|
|
25597
|
-
return {
|
|
25598
|
-
kind: "array-of-objects",
|
|
25599
|
-
field: arrayOfObjs[1],
|
|
25600
|
-
subfield: arrayOfObjs[2]
|
|
25601
|
-
};
|
|
25602
|
-
}
|
|
25603
|
-
throw new Error(
|
|
25604
|
-
`String predicate cannot translate JSONPath "${jsonbPath}". Supported shapes: "$.field", "$.field[*]", "$.field[*].subfield".`
|
|
25605
|
-
);
|
|
25606
|
-
}
|
|
25607
|
-
function escapeLikePattern(value) {
|
|
25608
|
-
return value.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
25609
|
-
}
|
|
25610
|
-
function buildLikePattern(value, modifier) {
|
|
25611
|
-
const escaped = escapeLikePattern(value);
|
|
25612
|
-
switch (modifier) {
|
|
25613
|
-
case "exact":
|
|
25614
|
-
return escaped;
|
|
25615
|
-
case "contains":
|
|
25616
|
-
return `%${escaped}%`;
|
|
25617
|
-
case void 0:
|
|
25618
|
-
return `${escaped}%`;
|
|
25619
|
-
}
|
|
25620
|
-
}
|
|
25621
|
-
function buildIlikeExtractSql(shape, paramName) {
|
|
25622
|
-
switch (shape.kind) {
|
|
25623
|
-
case "scalar":
|
|
25624
|
-
return `resource->>'${shape.field}' ILIKE :${paramName}`;
|
|
25625
|
-
case "array-of-scalars":
|
|
25626
|
-
return [
|
|
25627
|
-
"EXISTS (SELECT 1 FROM",
|
|
25628
|
-
`jsonb_array_elements_text(resource->'${shape.field}') AS s_elem(text_val)`,
|
|
25629
|
-
`WHERE s_elem.text_val ILIKE :${paramName})`
|
|
25630
|
-
].join(" ");
|
|
25631
|
-
case "array-of-objects":
|
|
25632
|
-
return [
|
|
25633
|
-
"EXISTS (SELECT 1 FROM",
|
|
25634
|
-
`jsonb_array_elements(resource->'${shape.field}') AS s_obj(obj)`,
|
|
25635
|
-
`WHERE s_obj.obj->>'${shape.subfield}' ILIKE :${paramName})`
|
|
25636
|
-
].join(" ");
|
|
25637
|
-
}
|
|
25638
|
-
}
|
|
25639
|
-
function emitStringPredicate(opts) {
|
|
25640
|
-
const shape = jsonbPathToStringShape(opts.jsonbPath);
|
|
25641
|
-
const modifier = opts.modifier === void 0 ? void 0 : checkModifier(opts.modifier);
|
|
25642
|
-
const sql = buildIlikeExtractSql(shape, opts.paramName);
|
|
25643
|
-
const pattern = buildLikePattern(opts.rawValue, modifier);
|
|
25644
|
-
const params = [
|
|
25645
|
-
{ name: opts.paramName, value: pattern }
|
|
25646
|
-
];
|
|
25647
|
-
return { sql, params };
|
|
25648
|
-
}
|
|
25649
|
-
function checkModifier(modifier) {
|
|
25650
|
-
if (!isStringModifier(modifier)) {
|
|
25651
|
-
throw new Error(
|
|
25652
|
-
`String predicate does not support modifier ":${modifier}". Supported: ${STRING_MODIFIERS.map((m) => `:${m}`).join(", ")}.`
|
|
25653
|
-
);
|
|
25654
|
-
}
|
|
25655
|
-
return modifier;
|
|
25656
|
-
}
|
|
25657
|
-
|
|
25658
|
-
// src/data/search/engine/token-predicate.ts
|
|
25659
|
-
function jsonbPathToTokenShape(jsonbPath) {
|
|
25660
|
-
const scalar = /^\$\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(jsonbPath);
|
|
25661
|
-
if (scalar) {
|
|
25662
|
-
return { kind: "scalar", field: scalar[1] };
|
|
25663
|
-
}
|
|
25664
|
-
const arrayOfScalars = /^\$\.([A-Za-z_][A-Za-z0-9_]*)\[\*\]$/.exec(jsonbPath);
|
|
25665
|
-
if (arrayOfScalars) {
|
|
25666
|
-
return { kind: "array-of-scalars", field: arrayOfScalars[1] };
|
|
25667
|
-
}
|
|
25668
|
-
const arrayOfObjs = /^\$\.([A-Za-z_][A-Za-z0-9_]*)\[\*\]\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(
|
|
25669
|
-
jsonbPath
|
|
25670
|
-
);
|
|
25671
|
-
if (arrayOfObjs) {
|
|
25672
|
-
return {
|
|
25673
|
-
kind: "array-of-objects",
|
|
25674
|
-
field: arrayOfObjs[1],
|
|
25675
|
-
subfield: arrayOfObjs[2]
|
|
25676
|
-
};
|
|
25677
|
-
}
|
|
25678
|
-
throw new Error(
|
|
25679
|
-
`Token predicate cannot translate JSONPath "${jsonbPath}". Supported shapes: "$.field", "$.field[*]", "$.field[*].subfield".`
|
|
25680
|
-
);
|
|
25681
|
-
}
|
|
25682
|
-
function parseTokenValue(raw) {
|
|
25683
|
-
const idx = raw.indexOf("|");
|
|
25684
|
-
if (idx === -1) {
|
|
25685
|
-
return { code: raw };
|
|
25686
|
-
}
|
|
25687
|
-
const system = raw.slice(0, idx);
|
|
25688
|
-
const code = raw.slice(idx + 1);
|
|
25689
|
-
return system.length > 0 ? { system, code } : { code };
|
|
25690
|
-
}
|
|
25691
|
-
function wrapTokenInShape(shape, value) {
|
|
25692
|
-
switch (shape.kind) {
|
|
25693
|
-
case "scalar":
|
|
25694
|
-
return { [shape.field]: value };
|
|
25695
|
-
case "array-of-scalars":
|
|
25696
|
-
return { [shape.field]: [value] };
|
|
25697
|
-
case "array-of-objects":
|
|
25698
|
-
return { [shape.field]: [{ [shape.subfield]: value }] };
|
|
25699
|
-
}
|
|
25700
|
-
}
|
|
25701
|
-
function emitTokenPredicate(opts) {
|
|
25702
|
-
const shape = jsonbPathToTokenShape(opts.jsonbPath);
|
|
25703
|
-
const { code } = parseTokenValue(opts.rawValue);
|
|
25704
|
-
const payload = wrapTokenInShape(shape, code);
|
|
25705
|
-
const sql = `resource @> :${opts.paramName}::jsonb`;
|
|
25706
|
-
const params = [
|
|
25707
|
-
{ name: opts.paramName, value: JSON.stringify(payload) }
|
|
25708
|
-
];
|
|
25709
|
-
return { sql, params };
|
|
25710
|
-
}
|
|
25711
|
-
|
|
25712
|
-
// src/data/search/engine/combinator.ts
|
|
25713
|
-
function parseQueryKey(key) {
|
|
25714
|
-
const idx = key.indexOf(":");
|
|
25715
|
-
if (idx === -1) {
|
|
25716
|
-
return { code: key, modifier: void 0 };
|
|
25717
|
-
}
|
|
25718
|
-
return { code: key.slice(0, idx), modifier: key.slice(idx + 1) };
|
|
25719
|
-
}
|
|
25720
|
-
function flattenQueryValues(raw) {
|
|
25721
|
-
const list = Array.isArray(raw) ? raw : [raw];
|
|
25722
|
-
const out = [];
|
|
25723
|
-
for (const v of list) {
|
|
25724
|
-
for (const piece of v.split(",")) {
|
|
25725
|
-
out.push(piece);
|
|
25726
|
-
}
|
|
25727
|
-
}
|
|
25728
|
-
return out;
|
|
25729
|
-
}
|
|
25730
|
-
function parseQueryEntries(query) {
|
|
25731
|
-
const entries = [];
|
|
25732
|
-
for (const [rawKey, rawValue] of Object.entries(query)) {
|
|
25733
|
-
if (rawValue === void 0) continue;
|
|
25734
|
-
const { code, modifier } = parseQueryKey(rawKey);
|
|
25735
|
-
entries.push({
|
|
25736
|
-
code,
|
|
25737
|
-
modifier,
|
|
25738
|
-
values: flattenQueryValues(rawValue)
|
|
25739
|
-
});
|
|
25740
|
-
}
|
|
25741
|
-
return entries;
|
|
25742
|
-
}
|
|
25743
|
-
function findParam(params, code) {
|
|
25744
|
-
return params.find((p) => p.code === code);
|
|
25745
|
-
}
|
|
25746
|
-
function checkModifierAllowed(modifier, param) {
|
|
25747
|
-
const universal = modifier === "missing" || modifier === "not";
|
|
25748
|
-
const stringNative = param.type === "string" && (modifier === "exact" || modifier === "contains");
|
|
25749
|
-
if (universal || stringNative) {
|
|
25750
|
-
if (param.modifiers && !param.modifiers.includes(modifier)) {
|
|
25751
|
-
throw new Error(
|
|
25752
|
-
`Modifier ":${modifier}" is not in the allow-list for param "${param.code}".`
|
|
25753
|
-
);
|
|
25754
|
-
}
|
|
25755
|
-
return;
|
|
25756
|
-
}
|
|
25757
|
-
throw new Error(
|
|
25758
|
-
`Modifier ":${modifier}" is not recognized for param "${param.code}" (type "${param.type}").`
|
|
25759
|
-
);
|
|
25760
|
-
}
|
|
25761
|
-
function emitOne(opts) {
|
|
25762
|
-
const { param, modifier, rawValue, paramName, context } = opts;
|
|
25763
|
-
if (modifier === "missing") {
|
|
25764
|
-
const missing = parseMissingValue(rawValue);
|
|
25765
|
-
return emitFieldMissingPredicate({
|
|
25766
|
-
jsonbPath: param.jsonbPath,
|
|
25767
|
-
missing
|
|
25768
|
-
});
|
|
25769
|
-
}
|
|
25770
|
-
const negate = modifier === "not";
|
|
25771
|
-
const effectiveModifier = negate ? void 0 : modifier;
|
|
25772
|
-
const inner = emitForType({
|
|
25773
|
-
paramType: param.type,
|
|
25774
|
-
jsonbPath: param.jsonbPath,
|
|
25775
|
-
rawValue,
|
|
25776
|
-
paramName,
|
|
25777
|
-
modifier: effectiveModifier,
|
|
25778
|
-
context
|
|
25779
|
-
});
|
|
25780
|
-
if (!negate) {
|
|
25781
|
-
return inner;
|
|
25782
|
-
}
|
|
25783
|
-
return { sql: `NOT (${inner.sql})`, params: inner.params };
|
|
25784
|
-
}
|
|
25785
|
-
function parseMissingValue(raw) {
|
|
25786
|
-
if (raw === "true") return true;
|
|
25787
|
-
if (raw === "false") return false;
|
|
25788
|
-
throw new Error(
|
|
25789
|
-
`:missing requires a value of "true" or "false"; received "${raw}".`
|
|
25790
|
-
);
|
|
25791
|
-
}
|
|
25792
|
-
function emitForType(opts) {
|
|
25793
|
-
switch (opts.paramType) {
|
|
25794
|
-
case "token":
|
|
25795
|
-
return emitTokenPredicate({
|
|
25796
|
-
jsonbPath: opts.jsonbPath,
|
|
25797
|
-
rawValue: opts.rawValue,
|
|
25798
|
-
paramName: opts.paramName,
|
|
25799
|
-
context: opts.context
|
|
25800
|
-
});
|
|
25801
|
-
case "date":
|
|
25802
|
-
return emitDatePredicate({
|
|
25803
|
-
jsonbPath: opts.jsonbPath,
|
|
25804
|
-
rawValue: opts.rawValue,
|
|
25805
|
-
paramName: opts.paramName,
|
|
25806
|
-
context: opts.context
|
|
25807
|
-
});
|
|
25808
|
-
case "reference":
|
|
25809
|
-
return emitReferencePredicate({
|
|
25810
|
-
jsonbPath: opts.jsonbPath,
|
|
25811
|
-
rawValue: opts.rawValue,
|
|
25812
|
-
paramName: opts.paramName,
|
|
25813
|
-
context: opts.context
|
|
25814
|
-
});
|
|
25815
|
-
case "string":
|
|
25816
|
-
return emitStringPredicate({
|
|
25817
|
-
jsonbPath: opts.jsonbPath,
|
|
25818
|
-
rawValue: opts.rawValue,
|
|
25819
|
-
paramName: opts.paramName,
|
|
25820
|
-
modifier: opts.modifier,
|
|
25821
|
-
context: opts.context
|
|
25822
|
-
});
|
|
25823
|
-
}
|
|
25824
|
-
}
|
|
25825
|
-
function combineSearchPredicates(opts) {
|
|
25826
|
-
const entries = parseQueryEntries(opts.query);
|
|
25827
|
-
const groupSqls = [];
|
|
25828
|
-
const allParams = [];
|
|
25829
|
-
let paramIdx = 0;
|
|
25830
|
-
for (const entry of entries) {
|
|
25831
|
-
const registered = findParam(opts.registeredParams, entry.code);
|
|
25832
|
-
if (!registered) {
|
|
25833
|
-
continue;
|
|
25834
|
-
}
|
|
25835
|
-
if (entry.modifier !== void 0) {
|
|
25836
|
-
checkModifierAllowed(entry.modifier, registered);
|
|
25837
|
-
}
|
|
25838
|
-
if (entry.values.length === 0) {
|
|
25839
|
-
continue;
|
|
25840
|
-
}
|
|
25841
|
-
const fragmentSqls = [];
|
|
25842
|
-
let valueIdx = 0;
|
|
25843
|
-
for (const rawValue of entry.values) {
|
|
25844
|
-
const paramName = `p${paramIdx}v${valueIdx}`;
|
|
25845
|
-
const frag = emitOne({
|
|
25846
|
-
param: registered,
|
|
25847
|
-
modifier: entry.modifier,
|
|
25848
|
-
rawValue,
|
|
25849
|
-
paramName,
|
|
25850
|
-
context: opts.context
|
|
25851
|
-
});
|
|
25852
|
-
fragmentSqls.push(frag.sql);
|
|
25853
|
-
for (const p of frag.params) {
|
|
25854
|
-
allParams.push(p);
|
|
25855
|
-
}
|
|
25856
|
-
valueIdx++;
|
|
25857
|
-
}
|
|
25858
|
-
paramIdx++;
|
|
25859
|
-
if (fragmentSqls.length === 1) {
|
|
25860
|
-
groupSqls.push(fragmentSqls[0]);
|
|
25861
|
-
} else {
|
|
25862
|
-
groupSqls.push(`(${fragmentSqls.join(" OR ")})`);
|
|
25554
|
+
basePath: BASE_PATH.ORGANIZATIONAFFILIATION,
|
|
25555
|
+
listOperation: listOrganizationAffiliationsOperation,
|
|
25556
|
+
errorLogContext: "GET /OrganizationAffiliation list error:"
|
|
25557
|
+
});
|
|
25558
|
+
}
|
|
25559
|
+
|
|
25560
|
+
// src/data/operations/data/organizationaffiliation/organizationaffiliation-update-operation.ts
|
|
25561
|
+
async function updateOrganizationAffiliationOperation(params) {
|
|
25562
|
+
const { context, id, body, tableName } = params;
|
|
25563
|
+
const { tenantId, workspaceId, date, actorId, actorName } = context;
|
|
25564
|
+
const service = getDynamoDataService(tableName);
|
|
25565
|
+
return updateDataEntityById(
|
|
25566
|
+
service.entities.organizationaffiliation,
|
|
25567
|
+
tenantId,
|
|
25568
|
+
workspaceId,
|
|
25569
|
+
id,
|
|
25570
|
+
"OrganizationAffiliation",
|
|
25571
|
+
context,
|
|
25572
|
+
(existingResourceStr) => buildUpdatedResourceWithAudit(
|
|
25573
|
+
body,
|
|
25574
|
+
id,
|
|
25575
|
+
date,
|
|
25576
|
+
actorId,
|
|
25577
|
+
actorName,
|
|
25578
|
+
existingResourceStr,
|
|
25579
|
+
"OrganizationAffiliation"
|
|
25580
|
+
)
|
|
25581
|
+
);
|
|
25582
|
+
}
|
|
25583
|
+
|
|
25584
|
+
// src/data/rest-api/routes/data/organizationaffiliation/organizationaffiliation-update-route.ts
|
|
25585
|
+
async function updateOrganizationAffiliationRoute(req, res) {
|
|
25586
|
+
const bodyResult = requireJsonBodyAs(req, res);
|
|
25587
|
+
if ("errorResponse" in bodyResult) return bodyResult.errorResponse;
|
|
25588
|
+
const id = String(req.params.id);
|
|
25589
|
+
const ctx = req.openhiContext;
|
|
25590
|
+
const body = bodyResult.body;
|
|
25591
|
+
const resource = {
|
|
25592
|
+
...body,
|
|
25593
|
+
resourceType: "OrganizationAffiliation",
|
|
25594
|
+
id
|
|
25595
|
+
};
|
|
25596
|
+
try {
|
|
25597
|
+
const result = await updateOrganizationAffiliationOperation({
|
|
25598
|
+
context: ctx,
|
|
25599
|
+
id,
|
|
25600
|
+
body: resource
|
|
25601
|
+
});
|
|
25602
|
+
return res.json(result.resource);
|
|
25603
|
+
} catch (err) {
|
|
25604
|
+
const status = domainErrorToHttpStatus(err);
|
|
25605
|
+
if (status === 404) {
|
|
25606
|
+
const diagnostics = err instanceof NotFoundError ? err.message : `OrganizationAffiliation ${id} not found`;
|
|
25607
|
+
return sendOperationOutcome404(res, diagnostics);
|
|
25863
25608
|
}
|
|
25609
|
+
return sendOperationOutcome500(
|
|
25610
|
+
res,
|
|
25611
|
+
err,
|
|
25612
|
+
"PUT OrganizationAffiliation error:"
|
|
25613
|
+
);
|
|
25864
25614
|
}
|
|
25865
|
-
|
|
25866
|
-
|
|
25615
|
+
}
|
|
25616
|
+
|
|
25617
|
+
// src/data/rest-api/routes/data/organizationaffiliation/organizationaffiliation.ts
|
|
25618
|
+
var router107 = express107.Router();
|
|
25619
|
+
router107.get("/", listOrganizationAffiliationsRoute);
|
|
25620
|
+
router107.get("/:id", getOrganizationAffiliationByIdRoute);
|
|
25621
|
+
router107.post("/", createOrganizationAffiliationRoute);
|
|
25622
|
+
router107.put("/:id", updateOrganizationAffiliationRoute);
|
|
25623
|
+
router107.delete("/:id", deleteOrganizationAffiliationRoute);
|
|
25624
|
+
|
|
25625
|
+
// src/data/rest-api/routes/data/patient/patient.ts
|
|
25626
|
+
import express108 from "express";
|
|
25627
|
+
|
|
25628
|
+
// src/data/rest-api/routes/data/patient/patient-create-route.ts
|
|
25629
|
+
async function createPatientRoute(req, res) {
|
|
25630
|
+
const bodyResult = requireJsonBodyAs(req, res);
|
|
25631
|
+
if ("errorResponse" in bodyResult) return bodyResult.errorResponse;
|
|
25632
|
+
const ctx = req.openhiContext;
|
|
25633
|
+
const body = bodyResult.body;
|
|
25634
|
+
const patient = {
|
|
25635
|
+
...body,
|
|
25636
|
+
resourceType: "Patient"
|
|
25637
|
+
};
|
|
25638
|
+
try {
|
|
25639
|
+
const result = await createPatientOperation({
|
|
25640
|
+
context: ctx,
|
|
25641
|
+
body: patient
|
|
25642
|
+
});
|
|
25643
|
+
return res.status(201).location(`${BASE_PATH.PATIENT}/${result.id}`).json(result.resource);
|
|
25644
|
+
} catch (err) {
|
|
25645
|
+
return sendOperationOutcome500(res, err, "POST Patient error:");
|
|
25867
25646
|
}
|
|
25868
|
-
return { sql: groupSqls.join(" AND "), params: allParams };
|
|
25869
25647
|
}
|
|
25870
25648
|
|
|
25871
|
-
// src/data/
|
|
25872
|
-
|
|
25873
|
-
|
|
25874
|
-
const
|
|
25875
|
-
|
|
25876
|
-
|
|
25877
|
-
|
|
25878
|
-
|
|
25879
|
-
|
|
25880
|
-
|
|
25881
|
-
|
|
25882
|
-
|
|
25883
|
-
|
|
25649
|
+
// src/data/operations/data/patient/patient-delete-operation.ts
|
|
25650
|
+
async function deletePatientOperation(params) {
|
|
25651
|
+
const { context, id, tableName } = params;
|
|
25652
|
+
const { tenantId, workspaceId } = context;
|
|
25653
|
+
const service = getDynamoDataService(tableName);
|
|
25654
|
+
await deleteDataEntityById(
|
|
25655
|
+
service.entities.patient,
|
|
25656
|
+
tenantId,
|
|
25657
|
+
workspaceId,
|
|
25658
|
+
id
|
|
25659
|
+
);
|
|
25660
|
+
}
|
|
25661
|
+
|
|
25662
|
+
// src/data/rest-api/routes/data/patient/patient-delete-route.ts
|
|
25663
|
+
async function deletePatientRoute(req, res) {
|
|
25664
|
+
const id = String(req.params.id);
|
|
25665
|
+
const ctx = req.openhiContext;
|
|
25666
|
+
try {
|
|
25667
|
+
await deletePatientOperation({ context: ctx, id });
|
|
25668
|
+
return res.status(204).send();
|
|
25669
|
+
} catch (err) {
|
|
25670
|
+
return sendOperationOutcome500(res, err, "DELETE Patient error:");
|
|
25884
25671
|
}
|
|
25885
|
-
lines.push("ORDER BY last_updated DESC");
|
|
25886
|
-
lines.push("LIMIT :limit;");
|
|
25887
|
-
return lines.join("\n");
|
|
25888
25672
|
}
|
|
25889
|
-
|
|
25890
|
-
|
|
25891
|
-
|
|
25892
|
-
const
|
|
25893
|
-
const
|
|
25894
|
-
const
|
|
25895
|
-
|
|
25896
|
-
|
|
25897
|
-
|
|
25898
|
-
|
|
25899
|
-
|
|
25900
|
-
|
|
25901
|
-
|
|
25902
|
-
{ name: "tenantId", value: tenantId },
|
|
25903
|
-
{ name: "workspaceId", value: workspaceId },
|
|
25904
|
-
{ name: "resourceType", value: resourceType },
|
|
25905
|
-
{ name: "limit", value: limit },
|
|
25906
|
-
...combined.params
|
|
25907
|
-
];
|
|
25908
|
-
const rows = await runner.query(sql, queryParams);
|
|
25909
|
-
const entries = rows.map((row) => ({
|
|
25910
|
-
id: row.id,
|
|
25911
|
-
resource: { ...row.resource, id: row.id }
|
|
25912
|
-
}));
|
|
25913
|
-
return { entries, total: entries.length };
|
|
25673
|
+
|
|
25674
|
+
// src/data/operations/data/patient/patient-get-by-id-operation.ts
|
|
25675
|
+
async function getPatientByIdOperation(params) {
|
|
25676
|
+
const { context, id, tableName } = params;
|
|
25677
|
+
const { tenantId, workspaceId } = context;
|
|
25678
|
+
const service = getDynamoDataService(tableName);
|
|
25679
|
+
return getDataEntityById(
|
|
25680
|
+
service.entities.patient,
|
|
25681
|
+
tenantId,
|
|
25682
|
+
workspaceId,
|
|
25683
|
+
id,
|
|
25684
|
+
"Patient"
|
|
25685
|
+
);
|
|
25914
25686
|
}
|
|
25915
25687
|
|
|
25916
|
-
// src/data/
|
|
25917
|
-
|
|
25918
|
-
|
|
25919
|
-
|
|
25920
|
-
{
|
|
25921
|
-
|
|
25922
|
-
|
|
25923
|
-
|
|
25924
|
-
|
|
25925
|
-
|
|
25926
|
-
|
|
25927
|
-
|
|
25928
|
-
|
|
25929
|
-
|
|
25930
|
-
jsonbPath: "$.name[*].family",
|
|
25931
|
-
modifiers: ["exact", "contains", "missing", "not"]
|
|
25932
|
-
},
|
|
25933
|
-
{
|
|
25934
|
-
code: "given",
|
|
25935
|
-
type: "string",
|
|
25936
|
-
jsonbPath: "$.name[*].given",
|
|
25937
|
-
modifiers: ["exact", "contains", "missing", "not"]
|
|
25938
|
-
},
|
|
25939
|
-
{ code: "active", type: "token", jsonbPath: "$.active" },
|
|
25940
|
-
{ code: "deceased", type: "token", jsonbPath: "$.deceasedBoolean" },
|
|
25941
|
-
{
|
|
25942
|
-
code: "general-practitioner",
|
|
25943
|
-
type: "reference",
|
|
25944
|
-
jsonbPath: "$.generalPractitioner[*]"
|
|
25945
|
-
},
|
|
25946
|
-
{
|
|
25947
|
-
code: "organization",
|
|
25948
|
-
type: "reference",
|
|
25949
|
-
jsonbPath: "$.managingOrganization"
|
|
25688
|
+
// src/data/rest-api/routes/data/patient/patient-get-by-id-route.ts
|
|
25689
|
+
async function getPatientByIdRoute(req, res) {
|
|
25690
|
+
const id = String(req.params.id);
|
|
25691
|
+
const ctx = req.openhiContext;
|
|
25692
|
+
try {
|
|
25693
|
+
const result = await getPatientByIdOperation({ context: ctx, id });
|
|
25694
|
+
return res.json(result.resource);
|
|
25695
|
+
} catch (err) {
|
|
25696
|
+
const status = domainErrorToHttpStatus(err);
|
|
25697
|
+
if (status === 404) {
|
|
25698
|
+
const diagnostics = err instanceof NotFoundError ? err.message : `Patient ${id} not found`;
|
|
25699
|
+
return sendOperationOutcome404(res, diagnostics);
|
|
25700
|
+
}
|
|
25701
|
+
return sendOperationOutcome500(res, err, "GET Patient error:");
|
|
25950
25702
|
}
|
|
25951
|
-
|
|
25703
|
+
}
|
|
25952
25704
|
|
|
25953
|
-
// src/data/
|
|
25954
|
-
|
|
25955
|
-
|
|
25956
|
-
};
|
|
25957
|
-
|
|
25958
|
-
|
|
25959
|
-
|
|
25705
|
+
// src/data/operations/data/patient/patient-list-operation.ts
|
|
25706
|
+
async function listPatientsOperation(params) {
|
|
25707
|
+
const { context, tableName, mode } = params;
|
|
25708
|
+
const { tenantId, workspaceId } = context;
|
|
25709
|
+
const service = getDynamoDataService(tableName);
|
|
25710
|
+
return listDataEntitiesByWorkspace(
|
|
25711
|
+
service.entities.patient,
|
|
25712
|
+
tenantId,
|
|
25713
|
+
workspaceId,
|
|
25714
|
+
mode
|
|
25715
|
+
);
|
|
25960
25716
|
}
|
|
25961
25717
|
|
|
25962
25718
|
// src/data/rest-api/routes/data/patient/patient-list-route.ts
|
|
25963
25719
|
var PATIENT_RESOURCE_TYPE = "Patient";
|
|
25964
|
-
function
|
|
25720
|
+
function stripModifier2(key) {
|
|
25965
25721
|
const idx = key.indexOf(":");
|
|
25966
25722
|
return idx === -1 ? key : key.slice(0, idx);
|
|
25967
25723
|
}
|
|
25968
|
-
function
|
|
25724
|
+
function isResultParameter2(key) {
|
|
25969
25725
|
return key.startsWith("_");
|
|
25970
25726
|
}
|
|
25971
25727
|
function sendInvalidSearch4003(res, diagnostics) {
|
|
@@ -25974,17 +25730,17 @@ function sendInvalidSearch4003(res, diagnostics) {
|
|
|
25974
25730
|
issue: [{ severity: "error", code: "invalid", diagnostics }]
|
|
25975
25731
|
});
|
|
25976
25732
|
}
|
|
25977
|
-
function
|
|
25733
|
+
function extractSearchParamKeys2(query) {
|
|
25978
25734
|
const out = [];
|
|
25979
25735
|
for (const rawKey of Object.keys(query)) {
|
|
25980
|
-
if (
|
|
25736
|
+
if (isResultParameter2(rawKey)) {
|
|
25981
25737
|
continue;
|
|
25982
25738
|
}
|
|
25983
|
-
out.push({ rawKey, code:
|
|
25739
|
+
out.push({ rawKey, code: stripModifier2(rawKey) });
|
|
25984
25740
|
}
|
|
25985
25741
|
return out;
|
|
25986
25742
|
}
|
|
25987
|
-
function
|
|
25743
|
+
function buildUnknownParamDiagnostics2(unknownCodes) {
|
|
25988
25744
|
const validCodes = getRegisteredSearchParameters(PATIENT_RESOURCE_TYPE).map((p) => p.code).sort().join(", ");
|
|
25989
25745
|
const codes = unknownCodes.join(", ");
|
|
25990
25746
|
const isPlural = unknownCodes.length !== 1;
|
|
@@ -25993,7 +25749,7 @@ function buildUnknownParamDiagnostics(unknownCodes) {
|
|
|
25993
25749
|
`Valid codes: ${validCodes}.`
|
|
25994
25750
|
].join(" ");
|
|
25995
25751
|
}
|
|
25996
|
-
function
|
|
25752
|
+
function findMalformedReference2(query, searchParamKeys) {
|
|
25997
25753
|
const referenceCodes = new Set(
|
|
25998
25754
|
getRegisteredSearchParameters(PATIENT_RESOURCE_TYPE).filter((p) => p.type === "reference").map((p) => p.code)
|
|
25999
25755
|
);
|
|
@@ -26016,7 +25772,7 @@ function findMalformedReference(query, searchParamKeys) {
|
|
|
26016
25772
|
return void 0;
|
|
26017
25773
|
}
|
|
26018
25774
|
async function listPatientsRoute(req, res) {
|
|
26019
|
-
const searchParamKeys =
|
|
25775
|
+
const searchParamKeys = extractSearchParamKeys2(
|
|
26020
25776
|
req.query
|
|
26021
25777
|
);
|
|
26022
25778
|
if (searchParamKeys.length === 0) {
|
|
@@ -26034,10 +25790,10 @@ async function listPatientsRoute(req, res) {
|
|
|
26034
25790
|
if (unknownCodes.length > 0) {
|
|
26035
25791
|
return sendInvalidSearch4003(
|
|
26036
25792
|
res,
|
|
26037
|
-
|
|
25793
|
+
buildUnknownParamDiagnostics2([...new Set(unknownCodes)])
|
|
26038
25794
|
);
|
|
26039
25795
|
}
|
|
26040
|
-
const malformedRef =
|
|
25796
|
+
const malformedRef = findMalformedReference2(
|
|
26041
25797
|
req.query,
|
|
26042
25798
|
searchParamKeys
|
|
26043
25799
|
);
|
|
@@ -29845,7 +29601,7 @@ async function listSchedulesOperation(params) {
|
|
|
29845
29601
|
}
|
|
29846
29602
|
|
|
29847
29603
|
// src/data/operations/data/schedule/schedule-search-by-actor-operation.ts
|
|
29848
|
-
var
|
|
29604
|
+
var DEFAULT_LIMIT5 = 100;
|
|
29849
29605
|
function buildSearchSchedulesByActorSql() {
|
|
29850
29606
|
return [
|
|
29851
29607
|
"SELECT resource_id AS id, resource",
|
|
@@ -29863,7 +29619,7 @@ async function searchSchedulesByActorOperation(params) {
|
|
|
29863
29619
|
const { context, actorReference } = params;
|
|
29864
29620
|
const { tenantId, workspaceId } = context;
|
|
29865
29621
|
const runner = params.runner ?? getDefaultPostgresQueryRunner();
|
|
29866
|
-
const limit = params.limit ??
|
|
29622
|
+
const limit = params.limit ?? DEFAULT_LIMIT5;
|
|
29867
29623
|
const { containmentRelative, containmentUrn } = buildReferenceContainmentPayload({
|
|
29868
29624
|
shape: { kind: "array-of-references", field: "actor" },
|
|
29869
29625
|
reference: actorReference,
|
|
@@ -29889,7 +29645,7 @@ async function searchSchedulesByActorOperation(params) {
|
|
|
29889
29645
|
}
|
|
29890
29646
|
|
|
29891
29647
|
// src/data/rest-api/routes/data/schedule/schedule-list-route.ts
|
|
29892
|
-
function
|
|
29648
|
+
function singleStringQueryParam2(req, name) {
|
|
29893
29649
|
const v = req.query[name];
|
|
29894
29650
|
if (typeof v !== "string") {
|
|
29895
29651
|
return void 0;
|
|
@@ -29904,7 +29660,7 @@ function sendInvalidSearch4004(res, diagnostics) {
|
|
|
29904
29660
|
});
|
|
29905
29661
|
}
|
|
29906
29662
|
async function listSchedulesRoute(req, res) {
|
|
29907
|
-
const actorRef =
|
|
29663
|
+
const actorRef = singleStringQueryParam2(req, "actor");
|
|
29908
29664
|
if (actorRef !== void 0) {
|
|
29909
29665
|
if (parseTypedReference(actorRef) === void 0) {
|
|
29910
29666
|
return sendInvalidSearch4004(
|
|
@@ -33609,7 +33365,7 @@ async function listTasksOperation(params) {
|
|
|
33609
33365
|
}
|
|
33610
33366
|
|
|
33611
33367
|
// src/data/operations/data/task/task-search-by-owner-operation.ts
|
|
33612
|
-
var
|
|
33368
|
+
var DEFAULT_LIMIT6 = 100;
|
|
33613
33369
|
function buildSearchTasksByOwnerSql() {
|
|
33614
33370
|
return [
|
|
33615
33371
|
"SELECT resource_id AS id, resource",
|
|
@@ -33627,7 +33383,7 @@ async function searchTasksByOwnerOperation(params) {
|
|
|
33627
33383
|
const { context, ownerReference } = params;
|
|
33628
33384
|
const { tenantId, workspaceId } = context;
|
|
33629
33385
|
const runner = params.runner ?? getDefaultPostgresQueryRunner();
|
|
33630
|
-
const limit = params.limit ??
|
|
33386
|
+
const limit = params.limit ?? DEFAULT_LIMIT6;
|
|
33631
33387
|
const { containmentRelative, containmentUrn } = buildReferenceContainmentPayload({
|
|
33632
33388
|
shape: { kind: "scalar", field: "owner" },
|
|
33633
33389
|
reference: ownerReference,
|
|
@@ -33653,7 +33409,7 @@ async function searchTasksByOwnerOperation(params) {
|
|
|
33653
33409
|
}
|
|
33654
33410
|
|
|
33655
33411
|
// src/data/operations/data/task/task-search-by-requester-operation.ts
|
|
33656
|
-
var
|
|
33412
|
+
var DEFAULT_LIMIT7 = 100;
|
|
33657
33413
|
function buildSearchTasksByRequesterSql() {
|
|
33658
33414
|
return [
|
|
33659
33415
|
"SELECT resource_id AS id, resource",
|
|
@@ -33671,7 +33427,7 @@ async function searchTasksByRequesterOperation(params) {
|
|
|
33671
33427
|
const { context, requesterReference } = params;
|
|
33672
33428
|
const { tenantId, workspaceId } = context;
|
|
33673
33429
|
const runner = params.runner ?? getDefaultPostgresQueryRunner();
|
|
33674
|
-
const limit = params.limit ??
|
|
33430
|
+
const limit = params.limit ?? DEFAULT_LIMIT7;
|
|
33675
33431
|
const { containmentRelative, containmentUrn } = buildReferenceContainmentPayload({
|
|
33676
33432
|
shape: { kind: "scalar", field: "requester" },
|
|
33677
33433
|
reference: requesterReference,
|
|
@@ -33697,7 +33453,7 @@ async function searchTasksByRequesterOperation(params) {
|
|
|
33697
33453
|
}
|
|
33698
33454
|
|
|
33699
33455
|
// src/data/rest-api/routes/data/task/task-list-route.ts
|
|
33700
|
-
function
|
|
33456
|
+
function singleStringQueryParam3(req, name) {
|
|
33701
33457
|
const v = req.query[name];
|
|
33702
33458
|
if (typeof v !== "string") {
|
|
33703
33459
|
return void 0;
|
|
@@ -33712,8 +33468,8 @@ function sendInvalidSearch4005(res, diagnostics) {
|
|
|
33712
33468
|
});
|
|
33713
33469
|
}
|
|
33714
33470
|
async function listTasksRoute(req, res) {
|
|
33715
|
-
const ownerRef =
|
|
33716
|
-
const requesterRef =
|
|
33471
|
+
const ownerRef = singleStringQueryParam3(req, "owner");
|
|
33472
|
+
const requesterRef = singleStringQueryParam3(req, "requester");
|
|
33717
33473
|
if (ownerRef !== void 0 && requesterRef !== void 0) {
|
|
33718
33474
|
return sendInvalidSearch4005(
|
|
33719
33475
|
res,
|