@openhi/constructs 0.0.140 → 0.0.141

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.
@@ -5637,13 +5637,17 @@ function parseDateSearchValue(raw) {
5637
5637
  return { prefix: "eq", value: raw };
5638
5638
  }
5639
5639
  function flatJsonbExtract(jsonbPath) {
5640
- const match = /^\$\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(jsonbPath);
5641
- if (!match) {
5642
- throw new Error(
5643
- `Generic date predicate requires a flat top-level JSONPath like "$.fieldName"; received "${jsonbPath}".`
5644
- );
5640
+ const topLevel = /^\$\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(jsonbPath);
5641
+ if (topLevel) {
5642
+ return `resource->>'${topLevel[1]}'`;
5643
+ }
5644
+ const nested = /^\$\.([A-Za-z_][A-Za-z0-9_]*)\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(jsonbPath);
5645
+ if (nested) {
5646
+ return `resource->'${nested[1]}'->>'${nested[2]}'`;
5645
5647
  }
5646
- return `resource->>'${match[1]}'`;
5648
+ throw new Error(
5649
+ `Generic date predicate requires a scalar JSONPath like "$.fieldName" or "$.field.subfield"; received "${jsonbPath}".`
5650
+ );
5647
5651
  }
5648
5652
  function emitDatePredicate(opts) {
5649
5653
  const { jsonbPath, rawValue, paramName } = opts;
@@ -6051,6 +6055,53 @@ var APPOINTMENT_SEARCH_PARAMETERS = [
6051
6055
  { code: "slot", type: "reference", jsonbPath: "$.slot[*]" }
6052
6056
  ];
6053
6057
 
6058
+ // src/data/search/registry/encounter-search-parameters.ts
6059
+ var ENCOUNTER_SEARCH_PARAMETERS = [
6060
+ { code: "status", type: "token", jsonbPath: "$.status" },
6061
+ { code: "class", type: "token", jsonbPath: "$.class" },
6062
+ { code: "type", type: "token", jsonbPath: "$.type[*]" },
6063
+ { code: "subject", type: "reference", jsonbPath: "$.subject" },
6064
+ { code: "patient", type: "reference", jsonbPath: "$.subject" },
6065
+ {
6066
+ code: "participant",
6067
+ type: "reference",
6068
+ jsonbPath: "$.participant[*].individual"
6069
+ },
6070
+ { code: "date", type: "date", jsonbPath: "$.period.start" },
6071
+ {
6072
+ code: "service-provider",
6073
+ type: "reference",
6074
+ jsonbPath: "$.serviceProvider"
6075
+ },
6076
+ { code: "appointment", type: "reference", jsonbPath: "$.appointment[*]" },
6077
+ {
6078
+ code: "episode-of-care",
6079
+ type: "reference",
6080
+ jsonbPath: "$.episodeOfCare[*]"
6081
+ }
6082
+ ];
6083
+
6084
+ // src/data/search/registry/observation-search-parameters.ts
6085
+ var OBSERVATION_SEARCH_PARAMETERS = [
6086
+ { code: "status", type: "token", jsonbPath: "$.status" },
6087
+ { code: "category", type: "token", jsonbPath: "$.category[*]" },
6088
+ { code: "code", type: "token", jsonbPath: "$.code" },
6089
+ { code: "subject", type: "reference", jsonbPath: "$.subject" },
6090
+ { code: "patient", type: "reference", jsonbPath: "$.subject" },
6091
+ { code: "encounter", type: "reference", jsonbPath: "$.encounter" },
6092
+ { code: "performer", type: "reference", jsonbPath: "$.performer[*]" },
6093
+ { code: "date", type: "date", jsonbPath: "$.effectiveDateTime" },
6094
+ {
6095
+ code: "value-string",
6096
+ type: "string",
6097
+ jsonbPath: "$.valueString",
6098
+ modifiers: ["exact", "contains", "missing", "not"]
6099
+ },
6100
+ { code: "identifier", type: "token", jsonbPath: "$.identifier[*]" },
6101
+ { code: "based-on", type: "reference", jsonbPath: "$.basedOn[*]" },
6102
+ { code: "part-of", type: "reference", jsonbPath: "$.partOf[*]" }
6103
+ ];
6104
+
6054
6105
  // src/data/search/registry/patient-search-parameters.ts
6055
6106
  var PATIENT_SEARCH_PARAMETERS = [
6056
6107
  { code: "gender", type: "token", jsonbPath: "$.gender" },
@@ -6088,10 +6139,39 @@ var PATIENT_SEARCH_PARAMETERS = [
6088
6139
  }
6089
6140
  ];
6090
6141
 
6142
+ // src/data/search/registry/procedure-search-parameters.ts
6143
+ var PROCEDURE_SEARCH_PARAMETERS = [
6144
+ { code: "status", type: "token", jsonbPath: "$.status" },
6145
+ { code: "category", type: "token", jsonbPath: "$.category" },
6146
+ { code: "code", type: "token", jsonbPath: "$.code" },
6147
+ { code: "subject", type: "reference", jsonbPath: "$.subject" },
6148
+ { code: "patient", type: "reference", jsonbPath: "$.subject" },
6149
+ { code: "encounter", type: "reference", jsonbPath: "$.encounter" },
6150
+ {
6151
+ code: "performer",
6152
+ type: "reference",
6153
+ jsonbPath: "$.performer[*].actor"
6154
+ },
6155
+ { code: "date", type: "date", jsonbPath: "$.performedDateTime" },
6156
+ { code: "location", type: "reference", jsonbPath: "$.location" },
6157
+ { code: "identifier", type: "token", jsonbPath: "$.identifier[*]" },
6158
+ { code: "based-on", type: "reference", jsonbPath: "$.basedOn[*]" },
6159
+ { code: "part-of", type: "reference", jsonbPath: "$.partOf[*]" },
6160
+ { code: "reason-code", type: "token", jsonbPath: "$.reasonCode[*]" },
6161
+ {
6162
+ code: "reason-reference",
6163
+ type: "reference",
6164
+ jsonbPath: "$.reasonReference[*]"
6165
+ }
6166
+ ];
6167
+
6091
6168
  // src/data/search/registry/resolver.ts
6092
6169
  var STATIC_SEARCH_PARAMETER_MAP = {
6093
6170
  Appointment: APPOINTMENT_SEARCH_PARAMETERS,
6094
- Patient: PATIENT_SEARCH_PARAMETERS
6171
+ Encounter: ENCOUNTER_SEARCH_PARAMETERS,
6172
+ Observation: OBSERVATION_SEARCH_PARAMETERS,
6173
+ Patient: PATIENT_SEARCH_PARAMETERS,
6174
+ Procedure: PROCEDURE_SEARCH_PARAMETERS
6095
6175
  };
6096
6176
  var defaultSearchParameterResolver = (resourceType, _tenantId) => STATIC_SEARCH_PARAMETER_MAP[resourceType] ?? [];
6097
6177
  function getRegisteredSearchParameters(resourceType) {
@@ -13707,353 +13787,108 @@ async function listEncountersOperation(params) {
13707
13787
  );
13708
13788
  }
13709
13789
 
13710
- // src/data/operations/data/encounter/encounter-period-search-predicate.ts
13711
- var PERIOD_SEARCH_PREFIXES = [
13712
- "gt",
13713
- "lt",
13714
- "ge",
13715
- "le",
13716
- "sa",
13717
- "eb"
13718
- ];
13719
- function isPeriodSearchPrefix(s) {
13720
- return PERIOD_SEARCH_PREFIXES.includes(s);
13721
- }
13722
- var PERIOD_START = "resource->'period'->>'start'";
13723
- var PERIOD_END = "resource->'period'->>'end'";
13724
- var HAS_ANY_BOUND_GUARD = `(${PERIOD_START} IS NOT NULL OR ${PERIOD_END} IS NOT NULL)`;
13725
- function buildSinglePredicateSql(prefix, paramName) {
13726
- switch (prefix) {
13727
- case "gt":
13728
- return `(${PERIOD_END} IS NULL OR ${PERIOD_END} > :${paramName})`;
13729
- case "lt":
13730
- return `(${PERIOD_START} IS NULL OR ${PERIOD_START} < :${paramName})`;
13731
- case "ge":
13732
- return `(${PERIOD_END} IS NULL OR ${PERIOD_END} >= :${paramName})`;
13733
- case "le":
13734
- return `(${PERIOD_START} IS NULL OR ${PERIOD_START} <= :${paramName})`;
13735
- case "sa":
13736
- return `(${PERIOD_START} IS NOT NULL AND ${PERIOD_START} > :${paramName})`;
13737
- case "eb":
13738
- return `(${PERIOD_END} IS NOT NULL AND ${PERIOD_END} < :${paramName})`;
13739
- }
13740
- }
13741
- function periodConstraintParamName(index) {
13742
- return `periodConstraint${index}`;
13743
- }
13744
- function buildPeriodSearchPredicateSql(constraints) {
13745
- if (constraints.length === 0) {
13746
- return [];
13747
- }
13748
- const fragments = constraints.map(
13749
- (c, i) => buildSinglePredicateSql(c.prefix, periodConstraintParamName(i))
13750
- );
13751
- fragments.push(HAS_ANY_BOUND_GUARD);
13752
- return fragments;
13753
- }
13754
- function buildPeriodSearchPredicateParams(constraints) {
13755
- return constraints.map((c, i) => ({
13756
- name: periodConstraintParamName(i),
13757
- value: c.value
13758
- }));
13759
- }
13760
-
13761
- // src/data/operations/data/encounter/encounter-search-by-date-operation.ts
13762
- var DEFAULT_LIMIT2 = 100;
13763
- function buildSearchEncountersByDateSql(opts) {
13764
- const periodPredicates = buildPeriodSearchPredicateSql(
13765
- opts.periodConstraints
13766
- );
13767
- const lines = [
13768
- "SELECT resource_id AS id, resource",
13769
- "FROM resources",
13770
- "WHERE tenant_id = :tenantId",
13771
- " AND workspace_id = :workspaceId",
13772
- " AND resource_type = 'Encounter'",
13773
- " AND deleted_at IS NULL"
13774
- ];
13775
- for (const fragment of periodPredicates) {
13776
- lines.push(` AND ${fragment}`);
13777
- }
13778
- lines.push("ORDER BY last_updated DESC");
13779
- lines.push("LIMIT :limit;");
13780
- return lines.join("\n");
13781
- }
13782
- async function searchEncountersByDateOperation(params) {
13783
- const { context, periodConstraints } = params;
13784
- if (periodConstraints.length === 0) {
13785
- throw new Error(
13786
- "searchEncountersByDateOperation requires at least one periodConstraint"
13787
- );
13788
- }
13789
- const { tenantId, workspaceId } = context;
13790
- const runner = params.runner ?? getDefaultPostgresQueryRunner();
13791
- const limit = params.limit ?? DEFAULT_LIMIT2;
13792
- const sql = buildSearchEncountersByDateSql({ periodConstraints });
13793
- const queryParams = [
13794
- { name: "tenantId", value: tenantId },
13795
- { name: "workspaceId", value: workspaceId },
13796
- { name: "limit", value: limit },
13797
- ...buildPeriodSearchPredicateParams(periodConstraints)
13798
- ];
13799
- const rows = await runner.query(sql, queryParams);
13800
- const entries = rows.map((row) => ({
13801
- id: row.id,
13802
- resource: {
13803
- ...row.resource,
13804
- id: row.id
13805
- }
13806
- }));
13807
- return { entries, total: entries.length };
13790
+ // src/data/rest-api/routes/data/encounter/encounter-list-route.ts
13791
+ var ENCOUNTER_RESOURCE_TYPE = "Encounter";
13792
+ function stripModifier2(key) {
13793
+ const idx = key.indexOf(":");
13794
+ return idx === -1 ? key : key.slice(0, idx);
13808
13795
  }
13809
-
13810
- // src/data/operations/data/encounter/encounter-search-by-participant-operation.ts
13811
- var DEFAULT_LIMIT3 = 100;
13812
- function buildSearchEncountersByParticipantSql(opts) {
13813
- const periodPredicates = buildPeriodSearchPredicateSql(
13814
- opts?.periodConstraints ?? []
13815
- );
13816
- const lines = [
13817
- "SELECT resource_id AS id, resource",
13818
- "FROM resources",
13819
- "WHERE tenant_id = :tenantId",
13820
- " AND workspace_id = :workspaceId",
13821
- " AND resource_type = 'Encounter'",
13822
- " AND deleted_at IS NULL",
13823
- ` AND ${REFERENCE_CONTAINMENT_SQL_FRAGMENT}`
13824
- ];
13825
- for (const fragment of periodPredicates) {
13826
- lines.push(` AND ${fragment}`);
13827
- }
13828
- lines.push("ORDER BY last_updated DESC");
13829
- lines.push("LIMIT :limit;");
13830
- return lines.join("\n");
13796
+ function isResultParameter2(key) {
13797
+ return key.startsWith("_");
13831
13798
  }
13832
- async function searchEncountersByParticipantOperation(params) {
13833
- const { context, participantReference } = params;
13834
- const periodConstraints = params.periodConstraints ?? [];
13835
- const { tenantId, workspaceId } = context;
13836
- const runner = params.runner ?? getDefaultPostgresQueryRunner();
13837
- const limit = params.limit ?? DEFAULT_LIMIT3;
13838
- const { containmentRelative, containmentUrn } = buildReferenceContainmentPayload({
13839
- shape: {
13840
- kind: "array-of-objects",
13841
- field: "participant",
13842
- subfield: "individual"
13843
- },
13844
- reference: participantReference,
13845
- tenantId,
13846
- workspaceId
13799
+ function sendInvalidSearch4002(res, diagnostics) {
13800
+ return res.status(400).json({
13801
+ resourceType: "OperationOutcome",
13802
+ issue: [{ severity: "error", code: "invalid", diagnostics }]
13847
13803
  });
13848
- const sql = buildSearchEncountersByParticipantSql({ periodConstraints });
13849
- const queryParams = [
13850
- { name: "tenantId", value: tenantId },
13851
- { name: "workspaceId", value: workspaceId },
13852
- { name: "containmentRelative", value: containmentRelative },
13853
- { name: "containmentUrn", value: containmentUrn },
13854
- { name: "limit", value: limit },
13855
- ...buildPeriodSearchPredicateParams(periodConstraints)
13856
- ];
13857
- const rows = await runner.query(sql, queryParams);
13858
- const entries = rows.map((row) => ({
13859
- id: row.id,
13860
- resource: {
13861
- ...row.resource,
13862
- id: row.id
13863
- }
13864
- }));
13865
- return { entries, total: entries.length };
13866
13804
  }
13867
-
13868
- // src/data/operations/data/encounter/encounter-search-by-patient-operation.ts
13869
- var DEFAULT_LIMIT4 = 100;
13870
- function buildSearchEncountersByPatientSql(opts) {
13871
- const periodPredicates = buildPeriodSearchPredicateSql(
13872
- opts?.periodConstraints ?? []
13873
- );
13874
- const lines = [
13875
- "SELECT resource_id AS id, resource",
13876
- "FROM resources",
13877
- "WHERE tenant_id = :tenantId",
13878
- " AND workspace_id = :workspaceId",
13879
- " AND resource_type = 'Encounter'",
13880
- " AND deleted_at IS NULL",
13881
- " AND (resource @> :containmentRelative::jsonb",
13882
- " OR resource @> :containmentUrn::jsonb)"
13883
- ];
13884
- for (const fragment of periodPredicates) {
13885
- lines.push(` AND ${fragment}`);
13886
- }
13887
- lines.push("ORDER BY last_updated DESC");
13888
- lines.push("LIMIT :limit;");
13889
- return lines.join("\n");
13890
- }
13891
- async function searchEncountersByPatientOperation(params) {
13892
- const { context, patientId } = params;
13893
- const periodConstraints = params.periodConstraints ?? [];
13894
- const { tenantId, workspaceId } = context;
13895
- const runner = params.runner ?? getDefaultPostgresQueryRunner();
13896
- const limit = params.limit ?? DEFAULT_LIMIT4;
13897
- const containmentRelative = JSON.stringify({
13898
- subject: { reference: `Patient/${patientId}` }
13899
- });
13900
- const containmentUrn = JSON.stringify({
13901
- subject: {
13902
- reference: buildOpenHiResourceUrn({
13903
- tenantId,
13904
- workspaceId,
13905
- resourceType: "Patient",
13906
- resourceId: patientId
13907
- })
13908
- }
13909
- });
13910
- const sql = buildSearchEncountersByPatientSql({ periodConstraints });
13911
- const queryParams = [
13912
- { name: "tenantId", value: tenantId },
13913
- { name: "workspaceId", value: workspaceId },
13914
- { name: "containmentRelative", value: containmentRelative },
13915
- { name: "containmentUrn", value: containmentUrn },
13916
- { name: "limit", value: limit },
13917
- ...buildPeriodSearchPredicateParams(periodConstraints)
13918
- ];
13919
- const rows = await runner.query(sql, queryParams);
13920
- const entries = rows.map((row) => ({
13921
- id: row.id,
13922
- resource: {
13923
- ...row.resource,
13924
- id: row.id
13805
+ function extractSearchParamKeys2(query) {
13806
+ const out = [];
13807
+ for (const rawKey of Object.keys(query)) {
13808
+ if (isResultParameter2(rawKey)) {
13809
+ continue;
13925
13810
  }
13926
- }));
13927
- return { entries, total: entries.length };
13928
- }
13929
-
13930
- // src/data/rest-api/routes/data/encounter/encounter-list-route.ts
13931
- function singleStringQueryParam(req, name) {
13932
- const v = req.query[name];
13933
- if (typeof v !== "string") {
13934
- return void 0;
13811
+ out.push({ rawKey, code: stripModifier2(rawKey) });
13935
13812
  }
13936
- const trimmed = v.trim();
13937
- return trimmed === "" ? void 0 : trimmed;
13813
+ return out;
13938
13814
  }
13939
- function isError(v) {
13940
- return v.error !== void 0;
13815
+ function buildUnknownParamDiagnostics2(unknownCodes) {
13816
+ const validCodes = getRegisteredSearchParameters(ENCOUNTER_RESOURCE_TYPE).map((p) => p.code).sort().join(", ");
13817
+ const codes = unknownCodes.join(", ");
13818
+ const isPlural = unknownCodes.length !== 1;
13819
+ return [
13820
+ `Unknown search ${isPlural ? "parameters" : "parameter"} for Encounter: ${codes}.`,
13821
+ `Valid codes: ${validCodes}.`
13822
+ ].join(" ");
13941
13823
  }
13942
- function parseEncounterDateConstraints(req) {
13943
- const raw = req.query.date;
13944
- if (raw === void 0) {
13945
- return [];
13946
- }
13947
- const values = Array.isArray(raw) ? raw : [raw];
13948
- const out = [];
13949
- for (const v of values) {
13950
- if (typeof v !== "string") {
13951
- return { error: "Each ?date= value must be a string." };
13952
- }
13953
- const trimmed = v.trim();
13954
- if (trimmed === "") {
13955
- return { error: "?date= value must not be empty." };
13956
- }
13957
- const prefix = trimmed.slice(0, 2);
13958
- const datetime = trimmed.slice(2);
13959
- if (!isPeriodSearchPrefix(prefix)) {
13960
- return {
13961
- error: `Unsupported ?date= prefix in "${trimmed}". Supported prefixes: ${PERIOD_SEARCH_PREFIXES.join(", ")}.`
13962
- };
13824
+ function findMalformedReference2(query, searchParamKeys) {
13825
+ const referenceCodes = new Set(
13826
+ getRegisteredSearchParameters(ENCOUNTER_RESOURCE_TYPE).filter((p) => p.type === "reference").map((p) => p.code)
13827
+ );
13828
+ for (const { rawKey, code } of searchParamKeys) {
13829
+ if (!referenceCodes.has(code)) {
13830
+ continue;
13963
13831
  }
13964
- if (datetime === "" || Number.isNaN(Date.parse(datetime))) {
13965
- return { error: `Invalid datetime in ?date=${trimmed}.` };
13832
+ const raw = query[rawKey];
13833
+ const values = typeof raw === "string" ? raw.split(",") : Array.isArray(raw) ? raw.flatMap((v) => v.split(",")) : [];
13834
+ for (const v of values) {
13835
+ const trimmed = v.trim();
13836
+ if (trimmed.length === 0) {
13837
+ continue;
13838
+ }
13839
+ if (parseTypedReference(trimmed) === void 0) {
13840
+ return { rawKey, value: trimmed };
13841
+ }
13966
13842
  }
13967
- out.push({ prefix, value: datetime });
13968
13843
  }
13969
- return out;
13970
- }
13971
- function sendInvalidSearch4002(res, diagnostics) {
13972
- return res.status(400).json({
13973
- resourceType: "OperationOutcome",
13974
- issue: [{ severity: "error", code: "invalid", diagnostics }]
13975
- });
13844
+ return void 0;
13976
13845
  }
13977
13846
  async function listEncountersRoute(req, res) {
13978
- const patientId = singleStringQueryParam(req, "patient");
13979
- const participantRef = singleStringQueryParam(req, "participant");
13980
- const parsed = parseEncounterDateConstraints(req);
13981
- if (isError(parsed)) {
13982
- return sendInvalidSearch4002(res, parsed.error);
13983
- }
13984
- const periodConstraints = parsed;
13985
- if (patientId !== void 0 && participantRef !== void 0) {
13847
+ const searchParamKeys = extractSearchParamKeys2(
13848
+ req.query
13849
+ );
13850
+ if (searchParamKeys.length === 0) {
13851
+ return handleListRoute({
13852
+ req,
13853
+ res,
13854
+ basePath: BASE_PATH.ENCOUNTER,
13855
+ listOperation: listEncountersOperation,
13856
+ errorLogContext: "GET /Encounter list error:"
13857
+ });
13858
+ }
13859
+ const registered = getRegisteredSearchParameters(ENCOUNTER_RESOURCE_TYPE);
13860
+ const validCodes = new Set(registered.map((p) => p.code));
13861
+ const unknownCodes = searchParamKeys.map((k) => k.code).filter((code) => !validCodes.has(code));
13862
+ if (unknownCodes.length > 0) {
13986
13863
  return sendInvalidSearch4002(
13987
13864
  res,
13988
- "?patient= and ?participant= cannot be combined on the same request."
13865
+ buildUnknownParamDiagnostics2([...new Set(unknownCodes)])
13989
13866
  );
13990
13867
  }
13991
- if (participantRef !== void 0) {
13992
- if (parseTypedReference(participantRef) === void 0) {
13993
- return sendInvalidSearch4002(
13994
- res,
13995
- `?participant must be a typed reference like "Practitioner/<id>"; got "${participantRef}".`
13996
- );
13997
- }
13998
- const ctx = req.openhiContext;
13999
- try {
14000
- const result = await searchEncountersByParticipantOperation({
14001
- context: ctx,
14002
- participantReference: participantRef,
14003
- periodConstraints
14004
- });
14005
- const bundle = buildSearchsetBundle(BASE_PATH.ENCOUNTER, result.entries);
14006
- return res.json(bundle);
14007
- } catch (err) {
14008
- return sendOperationOutcome500(
14009
- res,
14010
- err,
14011
- "GET /Encounter?participant= search error:"
14012
- );
14013
- }
14014
- }
14015
- if (patientId !== void 0) {
14016
- const ctx = req.openhiContext;
14017
- try {
14018
- const result = await searchEncountersByPatientOperation({
14019
- context: ctx,
14020
- patientId,
14021
- periodConstraints
14022
- });
14023
- const bundle = buildSearchsetBundle(BASE_PATH.ENCOUNTER, result.entries);
14024
- return res.json(bundle);
14025
- } catch (err) {
14026
- return sendOperationOutcome500(
14027
- res,
14028
- err,
14029
- "GET /Encounter?patient= search error:"
14030
- );
14031
- }
13868
+ const malformedRef = findMalformedReference2(
13869
+ req.query,
13870
+ searchParamKeys
13871
+ );
13872
+ if (malformedRef !== void 0) {
13873
+ return sendInvalidSearch4002(
13874
+ res,
13875
+ `?${malformedRef.rawKey} must be a typed reference like "Practitioner/<id>"; got "${malformedRef.value}".`
13876
+ );
14032
13877
  }
14033
- if (periodConstraints.length > 0) {
14034
- const ctx = req.openhiContext;
14035
- try {
14036
- const result = await searchEncountersByDateOperation({
14037
- context: ctx,
14038
- periodConstraints
14039
- });
14040
- const bundle = buildSearchsetBundle(BASE_PATH.ENCOUNTER, result.entries);
14041
- return res.json(bundle);
14042
- } catch (err) {
14043
- return sendOperationOutcome500(
14044
- res,
14045
- err,
14046
- "GET /Encounter?date= search error:"
14047
- );
14048
- }
13878
+ const ctx = req.openhiContext;
13879
+ try {
13880
+ const result = await genericSearchOperation({
13881
+ resourceType: ENCOUNTER_RESOURCE_TYPE,
13882
+ tenantId: ctx.tenantId,
13883
+ workspaceId: ctx.workspaceId,
13884
+ query: req.query,
13885
+ resolver: defaultSearchParameterResolver
13886
+ });
13887
+ const bundle = buildSearchsetBundle(BASE_PATH.ENCOUNTER, result.entries);
13888
+ return res.json(bundle);
13889
+ } catch (err) {
13890
+ return sendOperationOutcome500(res, err, "GET /Encounter search error:");
14049
13891
  }
14050
- return handleListRoute({
14051
- req,
14052
- res,
14053
- basePath: BASE_PATH.ENCOUNTER,
14054
- listOperation: listEncountersOperation,
14055
- errorLogContext: "GET /Encounter list error:"
14056
- });
14057
13892
  }
14058
13893
 
14059
13894
  // src/data/operations/data/encounter/encounter-update-operation.ts
@@ -24733,15 +24568,108 @@ async function listObservationsOperation(params) {
24733
24568
  }
24734
24569
 
24735
24570
  // src/data/rest-api/routes/data/observation/observation-list-route.ts
24736
- async function listObservationsRoute(req, res) {
24737
- return handleListRoute({
24738
- req,
24739
- res,
24740
- basePath: BASE_PATH.OBSERVATION,
24741
- listOperation: listObservationsOperation,
24742
- errorLogContext: "GET /Observation list error:"
24571
+ var OBSERVATION_RESOURCE_TYPE = "Observation";
24572
+ function stripModifier3(key) {
24573
+ const idx = key.indexOf(":");
24574
+ return idx === -1 ? key : key.slice(0, idx);
24575
+ }
24576
+ function isResultParameter3(key) {
24577
+ return key.startsWith("_");
24578
+ }
24579
+ function sendInvalidSearch4003(res, diagnostics) {
24580
+ return res.status(400).json({
24581
+ resourceType: "OperationOutcome",
24582
+ issue: [{ severity: "error", code: "invalid", diagnostics }]
24743
24583
  });
24744
24584
  }
24585
+ function extractSearchParamKeys3(query) {
24586
+ const out = [];
24587
+ for (const rawKey of Object.keys(query)) {
24588
+ if (isResultParameter3(rawKey)) {
24589
+ continue;
24590
+ }
24591
+ out.push({ rawKey, code: stripModifier3(rawKey) });
24592
+ }
24593
+ return out;
24594
+ }
24595
+ function buildUnknownParamDiagnostics3(unknownCodes) {
24596
+ const validCodes = getRegisteredSearchParameters(OBSERVATION_RESOURCE_TYPE).map((p) => p.code).sort().join(", ");
24597
+ const codes = unknownCodes.join(", ");
24598
+ const isPlural = unknownCodes.length !== 1;
24599
+ return [
24600
+ `Unknown search ${isPlural ? "parameters" : "parameter"} for Observation: ${codes}.`,
24601
+ `Valid codes: ${validCodes}.`
24602
+ ].join(" ");
24603
+ }
24604
+ function findMalformedReference3(query, searchParamKeys) {
24605
+ const referenceCodes = new Set(
24606
+ getRegisteredSearchParameters(OBSERVATION_RESOURCE_TYPE).filter((p) => p.type === "reference").map((p) => p.code)
24607
+ );
24608
+ for (const { rawKey, code } of searchParamKeys) {
24609
+ if (!referenceCodes.has(code)) {
24610
+ continue;
24611
+ }
24612
+ const raw = query[rawKey];
24613
+ const values = typeof raw === "string" ? raw.split(",") : Array.isArray(raw) ? raw.flatMap((v) => v.split(",")) : [];
24614
+ for (const v of values) {
24615
+ const trimmed = v.trim();
24616
+ if (trimmed.length === 0) {
24617
+ continue;
24618
+ }
24619
+ if (parseTypedReference(trimmed) === void 0) {
24620
+ return { rawKey, value: trimmed };
24621
+ }
24622
+ }
24623
+ }
24624
+ return void 0;
24625
+ }
24626
+ async function listObservationsRoute(req, res) {
24627
+ const searchParamKeys = extractSearchParamKeys3(
24628
+ req.query
24629
+ );
24630
+ if (searchParamKeys.length === 0) {
24631
+ return handleListRoute({
24632
+ req,
24633
+ res,
24634
+ basePath: BASE_PATH.OBSERVATION,
24635
+ listOperation: listObservationsOperation,
24636
+ errorLogContext: "GET /Observation list error:"
24637
+ });
24638
+ }
24639
+ const registered = getRegisteredSearchParameters(OBSERVATION_RESOURCE_TYPE);
24640
+ const validCodes = new Set(registered.map((p) => p.code));
24641
+ const unknownCodes = searchParamKeys.map((k) => k.code).filter((code) => !validCodes.has(code));
24642
+ if (unknownCodes.length > 0) {
24643
+ return sendInvalidSearch4003(
24644
+ res,
24645
+ buildUnknownParamDiagnostics3([...new Set(unknownCodes)])
24646
+ );
24647
+ }
24648
+ const malformedRef = findMalformedReference3(
24649
+ req.query,
24650
+ searchParamKeys
24651
+ );
24652
+ if (malformedRef !== void 0) {
24653
+ return sendInvalidSearch4003(
24654
+ res,
24655
+ `?${malformedRef.rawKey} must be a typed reference like "Practitioner/<id>"; got "${malformedRef.value}".`
24656
+ );
24657
+ }
24658
+ const ctx = req.openhiContext;
24659
+ try {
24660
+ const result = await genericSearchOperation({
24661
+ resourceType: OBSERVATION_RESOURCE_TYPE,
24662
+ tenantId: ctx.tenantId,
24663
+ workspaceId: ctx.workspaceId,
24664
+ query: req.query,
24665
+ resolver: defaultSearchParameterResolver
24666
+ });
24667
+ const bundle = buildSearchsetBundle(BASE_PATH.OBSERVATION, result.entries);
24668
+ return res.json(bundle);
24669
+ } catch (err) {
24670
+ return sendOperationOutcome500(res, err, "GET /Observation search error:");
24671
+ }
24672
+ }
24745
24673
 
24746
24674
  // src/data/operations/data/observation/observation-update-operation.ts
24747
24675
  async function updateObservationOperation(params) {
@@ -25717,30 +25645,30 @@ async function listPatientsOperation(params) {
25717
25645
 
25718
25646
  // src/data/rest-api/routes/data/patient/patient-list-route.ts
25719
25647
  var PATIENT_RESOURCE_TYPE = "Patient";
25720
- function stripModifier2(key) {
25648
+ function stripModifier4(key) {
25721
25649
  const idx = key.indexOf(":");
25722
25650
  return idx === -1 ? key : key.slice(0, idx);
25723
25651
  }
25724
- function isResultParameter2(key) {
25652
+ function isResultParameter4(key) {
25725
25653
  return key.startsWith("_");
25726
25654
  }
25727
- function sendInvalidSearch4003(res, diagnostics) {
25655
+ function sendInvalidSearch4004(res, diagnostics) {
25728
25656
  return res.status(400).json({
25729
25657
  resourceType: "OperationOutcome",
25730
25658
  issue: [{ severity: "error", code: "invalid", diagnostics }]
25731
25659
  });
25732
25660
  }
25733
- function extractSearchParamKeys2(query) {
25661
+ function extractSearchParamKeys4(query) {
25734
25662
  const out = [];
25735
25663
  for (const rawKey of Object.keys(query)) {
25736
- if (isResultParameter2(rawKey)) {
25664
+ if (isResultParameter4(rawKey)) {
25737
25665
  continue;
25738
25666
  }
25739
- out.push({ rawKey, code: stripModifier2(rawKey) });
25667
+ out.push({ rawKey, code: stripModifier4(rawKey) });
25740
25668
  }
25741
25669
  return out;
25742
25670
  }
25743
- function buildUnknownParamDiagnostics2(unknownCodes) {
25671
+ function buildUnknownParamDiagnostics4(unknownCodes) {
25744
25672
  const validCodes = getRegisteredSearchParameters(PATIENT_RESOURCE_TYPE).map((p) => p.code).sort().join(", ");
25745
25673
  const codes = unknownCodes.join(", ");
25746
25674
  const isPlural = unknownCodes.length !== 1;
@@ -25749,7 +25677,7 @@ function buildUnknownParamDiagnostics2(unknownCodes) {
25749
25677
  `Valid codes: ${validCodes}.`
25750
25678
  ].join(" ");
25751
25679
  }
25752
- function findMalformedReference2(query, searchParamKeys) {
25680
+ function findMalformedReference4(query, searchParamKeys) {
25753
25681
  const referenceCodes = new Set(
25754
25682
  getRegisteredSearchParameters(PATIENT_RESOURCE_TYPE).filter((p) => p.type === "reference").map((p) => p.code)
25755
25683
  );
@@ -25772,7 +25700,7 @@ function findMalformedReference2(query, searchParamKeys) {
25772
25700
  return void 0;
25773
25701
  }
25774
25702
  async function listPatientsRoute(req, res) {
25775
- const searchParamKeys = extractSearchParamKeys2(
25703
+ const searchParamKeys = extractSearchParamKeys4(
25776
25704
  req.query
25777
25705
  );
25778
25706
  if (searchParamKeys.length === 0) {
@@ -25788,17 +25716,17 @@ async function listPatientsRoute(req, res) {
25788
25716
  const validCodes = new Set(registered.map((p) => p.code));
25789
25717
  const unknownCodes = searchParamKeys.map((k) => k.code).filter((code) => !validCodes.has(code));
25790
25718
  if (unknownCodes.length > 0) {
25791
- return sendInvalidSearch4003(
25719
+ return sendInvalidSearch4004(
25792
25720
  res,
25793
- buildUnknownParamDiagnostics2([...new Set(unknownCodes)])
25721
+ buildUnknownParamDiagnostics4([...new Set(unknownCodes)])
25794
25722
  );
25795
25723
  }
25796
- const malformedRef = findMalformedReference2(
25724
+ const malformedRef = findMalformedReference4(
25797
25725
  req.query,
25798
25726
  searchParamKeys
25799
25727
  );
25800
25728
  if (malformedRef !== void 0) {
25801
- return sendInvalidSearch4003(
25729
+ return sendInvalidSearch4004(
25802
25730
  res,
25803
25731
  `?${malformedRef.rawKey} must be a typed reference like "Practitioner/<id>"; got "${malformedRef.value}".`
25804
25732
  );
@@ -27137,15 +27065,108 @@ async function listProceduresOperation(params) {
27137
27065
  }
27138
27066
 
27139
27067
  // src/data/rest-api/routes/data/procedure/procedure-list-route.ts
27140
- async function listProceduresRoute(req, res) {
27141
- return handleListRoute({
27142
- req,
27143
- res,
27144
- basePath: BASE_PATH.PROCEDURE,
27145
- listOperation: listProceduresOperation,
27146
- errorLogContext: "GET /Procedure list error:"
27068
+ var PROCEDURE_RESOURCE_TYPE = "Procedure";
27069
+ function stripModifier5(key) {
27070
+ const idx = key.indexOf(":");
27071
+ return idx === -1 ? key : key.slice(0, idx);
27072
+ }
27073
+ function isResultParameter5(key) {
27074
+ return key.startsWith("_");
27075
+ }
27076
+ function sendInvalidSearch4005(res, diagnostics) {
27077
+ return res.status(400).json({
27078
+ resourceType: "OperationOutcome",
27079
+ issue: [{ severity: "error", code: "invalid", diagnostics }]
27147
27080
  });
27148
27081
  }
27082
+ function extractSearchParamKeys5(query) {
27083
+ const out = [];
27084
+ for (const rawKey of Object.keys(query)) {
27085
+ if (isResultParameter5(rawKey)) {
27086
+ continue;
27087
+ }
27088
+ out.push({ rawKey, code: stripModifier5(rawKey) });
27089
+ }
27090
+ return out;
27091
+ }
27092
+ function buildUnknownParamDiagnostics5(unknownCodes) {
27093
+ const validCodes = getRegisteredSearchParameters(PROCEDURE_RESOURCE_TYPE).map((p) => p.code).sort().join(", ");
27094
+ const codes = unknownCodes.join(", ");
27095
+ const isPlural = unknownCodes.length !== 1;
27096
+ return [
27097
+ `Unknown search ${isPlural ? "parameters" : "parameter"} for Procedure: ${codes}.`,
27098
+ `Valid codes: ${validCodes}.`
27099
+ ].join(" ");
27100
+ }
27101
+ function findMalformedReference5(query, searchParamKeys) {
27102
+ const referenceCodes = new Set(
27103
+ getRegisteredSearchParameters(PROCEDURE_RESOURCE_TYPE).filter((p) => p.type === "reference").map((p) => p.code)
27104
+ );
27105
+ for (const { rawKey, code } of searchParamKeys) {
27106
+ if (!referenceCodes.has(code)) {
27107
+ continue;
27108
+ }
27109
+ const raw = query[rawKey];
27110
+ const values = typeof raw === "string" ? raw.split(",") : Array.isArray(raw) ? raw.flatMap((v) => v.split(",")) : [];
27111
+ for (const v of values) {
27112
+ const trimmed = v.trim();
27113
+ if (trimmed.length === 0) {
27114
+ continue;
27115
+ }
27116
+ if (parseTypedReference(trimmed) === void 0) {
27117
+ return { rawKey, value: trimmed };
27118
+ }
27119
+ }
27120
+ }
27121
+ return void 0;
27122
+ }
27123
+ async function listProceduresRoute(req, res) {
27124
+ const searchParamKeys = extractSearchParamKeys5(
27125
+ req.query
27126
+ );
27127
+ if (searchParamKeys.length === 0) {
27128
+ return handleListRoute({
27129
+ req,
27130
+ res,
27131
+ basePath: BASE_PATH.PROCEDURE,
27132
+ listOperation: listProceduresOperation,
27133
+ errorLogContext: "GET /Procedure list error:"
27134
+ });
27135
+ }
27136
+ const registered = getRegisteredSearchParameters(PROCEDURE_RESOURCE_TYPE);
27137
+ const validCodes = new Set(registered.map((p) => p.code));
27138
+ const unknownCodes = searchParamKeys.map((k) => k.code).filter((code) => !validCodes.has(code));
27139
+ if (unknownCodes.length > 0) {
27140
+ return sendInvalidSearch4005(
27141
+ res,
27142
+ buildUnknownParamDiagnostics5([...new Set(unknownCodes)])
27143
+ );
27144
+ }
27145
+ const malformedRef = findMalformedReference5(
27146
+ req.query,
27147
+ searchParamKeys
27148
+ );
27149
+ if (malformedRef !== void 0) {
27150
+ return sendInvalidSearch4005(
27151
+ res,
27152
+ `?${malformedRef.rawKey} must be a typed reference like "Practitioner/<id>"; got "${malformedRef.value}".`
27153
+ );
27154
+ }
27155
+ const ctx = req.openhiContext;
27156
+ try {
27157
+ const result = await genericSearchOperation({
27158
+ resourceType: PROCEDURE_RESOURCE_TYPE,
27159
+ tenantId: ctx.tenantId,
27160
+ workspaceId: ctx.workspaceId,
27161
+ query: req.query,
27162
+ resolver: defaultSearchParameterResolver
27163
+ });
27164
+ const bundle = buildSearchsetBundle(BASE_PATH.PROCEDURE, result.entries);
27165
+ return res.json(bundle);
27166
+ } catch (err) {
27167
+ return sendOperationOutcome500(res, err, "GET /Procedure search error:");
27168
+ }
27169
+ }
27149
27170
 
27150
27171
  // src/data/operations/data/procedure/procedure-update-operation.ts
27151
27172
  async function updateProcedureOperation(params) {
@@ -29601,7 +29622,7 @@ async function listSchedulesOperation(params) {
29601
29622
  }
29602
29623
 
29603
29624
  // src/data/operations/data/schedule/schedule-search-by-actor-operation.ts
29604
- var DEFAULT_LIMIT5 = 100;
29625
+ var DEFAULT_LIMIT2 = 100;
29605
29626
  function buildSearchSchedulesByActorSql() {
29606
29627
  return [
29607
29628
  "SELECT resource_id AS id, resource",
@@ -29619,7 +29640,7 @@ async function searchSchedulesByActorOperation(params) {
29619
29640
  const { context, actorReference } = params;
29620
29641
  const { tenantId, workspaceId } = context;
29621
29642
  const runner = params.runner ?? getDefaultPostgresQueryRunner();
29622
- const limit = params.limit ?? DEFAULT_LIMIT5;
29643
+ const limit = params.limit ?? DEFAULT_LIMIT2;
29623
29644
  const { containmentRelative, containmentUrn } = buildReferenceContainmentPayload({
29624
29645
  shape: { kind: "array-of-references", field: "actor" },
29625
29646
  reference: actorReference,
@@ -29645,7 +29666,7 @@ async function searchSchedulesByActorOperation(params) {
29645
29666
  }
29646
29667
 
29647
29668
  // src/data/rest-api/routes/data/schedule/schedule-list-route.ts
29648
- function singleStringQueryParam2(req, name) {
29669
+ function singleStringQueryParam(req, name) {
29649
29670
  const v = req.query[name];
29650
29671
  if (typeof v !== "string") {
29651
29672
  return void 0;
@@ -29653,17 +29674,17 @@ function singleStringQueryParam2(req, name) {
29653
29674
  const trimmed = v.trim();
29654
29675
  return trimmed === "" ? void 0 : trimmed;
29655
29676
  }
29656
- function sendInvalidSearch4004(res, diagnostics) {
29677
+ function sendInvalidSearch4006(res, diagnostics) {
29657
29678
  return res.status(400).json({
29658
29679
  resourceType: "OperationOutcome",
29659
29680
  issue: [{ severity: "error", code: "invalid", diagnostics }]
29660
29681
  });
29661
29682
  }
29662
29683
  async function listSchedulesRoute(req, res) {
29663
- const actorRef = singleStringQueryParam2(req, "actor");
29684
+ const actorRef = singleStringQueryParam(req, "actor");
29664
29685
  if (actorRef !== void 0) {
29665
29686
  if (parseTypedReference(actorRef) === void 0) {
29666
- return sendInvalidSearch4004(
29687
+ return sendInvalidSearch4006(
29667
29688
  res,
29668
29689
  `?actor must be a typed reference like "Practitioner/<id>"; got "${actorRef}".`
29669
29690
  );
@@ -33365,7 +33386,7 @@ async function listTasksOperation(params) {
33365
33386
  }
33366
33387
 
33367
33388
  // src/data/operations/data/task/task-search-by-owner-operation.ts
33368
- var DEFAULT_LIMIT6 = 100;
33389
+ var DEFAULT_LIMIT3 = 100;
33369
33390
  function buildSearchTasksByOwnerSql() {
33370
33391
  return [
33371
33392
  "SELECT resource_id AS id, resource",
@@ -33383,7 +33404,7 @@ async function searchTasksByOwnerOperation(params) {
33383
33404
  const { context, ownerReference } = params;
33384
33405
  const { tenantId, workspaceId } = context;
33385
33406
  const runner = params.runner ?? getDefaultPostgresQueryRunner();
33386
- const limit = params.limit ?? DEFAULT_LIMIT6;
33407
+ const limit = params.limit ?? DEFAULT_LIMIT3;
33387
33408
  const { containmentRelative, containmentUrn } = buildReferenceContainmentPayload({
33388
33409
  shape: { kind: "scalar", field: "owner" },
33389
33410
  reference: ownerReference,
@@ -33409,7 +33430,7 @@ async function searchTasksByOwnerOperation(params) {
33409
33430
  }
33410
33431
 
33411
33432
  // src/data/operations/data/task/task-search-by-requester-operation.ts
33412
- var DEFAULT_LIMIT7 = 100;
33433
+ var DEFAULT_LIMIT4 = 100;
33413
33434
  function buildSearchTasksByRequesterSql() {
33414
33435
  return [
33415
33436
  "SELECT resource_id AS id, resource",
@@ -33427,7 +33448,7 @@ async function searchTasksByRequesterOperation(params) {
33427
33448
  const { context, requesterReference } = params;
33428
33449
  const { tenantId, workspaceId } = context;
33429
33450
  const runner = params.runner ?? getDefaultPostgresQueryRunner();
33430
- const limit = params.limit ?? DEFAULT_LIMIT7;
33451
+ const limit = params.limit ?? DEFAULT_LIMIT4;
33431
33452
  const { containmentRelative, containmentUrn } = buildReferenceContainmentPayload({
33432
33453
  shape: { kind: "scalar", field: "requester" },
33433
33454
  reference: requesterReference,
@@ -33453,7 +33474,7 @@ async function searchTasksByRequesterOperation(params) {
33453
33474
  }
33454
33475
 
33455
33476
  // src/data/rest-api/routes/data/task/task-list-route.ts
33456
- function singleStringQueryParam3(req, name) {
33477
+ function singleStringQueryParam2(req, name) {
33457
33478
  const v = req.query[name];
33458
33479
  if (typeof v !== "string") {
33459
33480
  return void 0;
@@ -33461,24 +33482,24 @@ function singleStringQueryParam3(req, name) {
33461
33482
  const trimmed = v.trim();
33462
33483
  return trimmed === "" ? void 0 : trimmed;
33463
33484
  }
33464
- function sendInvalidSearch4005(res, diagnostics) {
33485
+ function sendInvalidSearch4007(res, diagnostics) {
33465
33486
  return res.status(400).json({
33466
33487
  resourceType: "OperationOutcome",
33467
33488
  issue: [{ severity: "error", code: "invalid", diagnostics }]
33468
33489
  });
33469
33490
  }
33470
33491
  async function listTasksRoute(req, res) {
33471
- const ownerRef = singleStringQueryParam3(req, "owner");
33472
- const requesterRef = singleStringQueryParam3(req, "requester");
33492
+ const ownerRef = singleStringQueryParam2(req, "owner");
33493
+ const requesterRef = singleStringQueryParam2(req, "requester");
33473
33494
  if (ownerRef !== void 0 && requesterRef !== void 0) {
33474
- return sendInvalidSearch4005(
33495
+ return sendInvalidSearch4007(
33475
33496
  res,
33476
33497
  "?owner= and ?requester= cannot be combined on the same request."
33477
33498
  );
33478
33499
  }
33479
33500
  if (ownerRef !== void 0) {
33480
33501
  if (parseTypedReference(ownerRef) === void 0) {
33481
- return sendInvalidSearch4005(
33502
+ return sendInvalidSearch4007(
33482
33503
  res,
33483
33504
  `?owner must be a typed reference like "Practitioner/<id>"; got "${ownerRef}".`
33484
33505
  );
@@ -33501,7 +33522,7 @@ async function listTasksRoute(req, res) {
33501
33522
  }
33502
33523
  if (requesterRef !== void 0) {
33503
33524
  if (parseTypedReference(requesterRef) === void 0) {
33504
- return sendInvalidSearch4005(
33525
+ return sendInvalidSearch4007(
33505
33526
  res,
33506
33527
  `?requester must be a typed reference like "Practitioner/<id>"; got "${requesterRef}".`
33507
33528
  );