@openhi/constructs 0.0.140 → 0.0.142

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.
@@ -9868,13 +9868,17 @@ function parseDateSearchValue(raw) {
9868
9868
  return { prefix: "eq", value: raw };
9869
9869
  }
9870
9870
  function flatJsonbExtract(jsonbPath) {
9871
- const match = /^\$\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(jsonbPath);
9872
- if (!match) {
9873
- throw new Error(
9874
- `Generic date predicate requires a flat top-level JSONPath like "$.fieldName"; received "${jsonbPath}".`
9875
- );
9871
+ const topLevel = /^\$\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(jsonbPath);
9872
+ if (topLevel) {
9873
+ return `resource->>'${topLevel[1]}'`;
9874
+ }
9875
+ const nested = /^\$\.([A-Za-z_][A-Za-z0-9_]*)\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(jsonbPath);
9876
+ if (nested) {
9877
+ return `resource->'${nested[1]}'->>'${nested[2]}'`;
9876
9878
  }
9877
- return `resource->>'${match[1]}'`;
9879
+ throw new Error(
9880
+ `Generic date predicate requires a scalar JSONPath like "$.fieldName" or "$.field.subfield"; received "${jsonbPath}".`
9881
+ );
9878
9882
  }
9879
9883
  function emitDatePredicate(opts) {
9880
9884
  const { jsonbPath, rawValue, paramName } = opts;
@@ -10282,6 +10286,53 @@ var APPOINTMENT_SEARCH_PARAMETERS = [
10282
10286
  { code: "slot", type: "reference", jsonbPath: "$.slot[*]" }
10283
10287
  ];
10284
10288
 
10289
+ // src/data/search/registry/encounter-search-parameters.ts
10290
+ var ENCOUNTER_SEARCH_PARAMETERS = [
10291
+ { code: "status", type: "token", jsonbPath: "$.status" },
10292
+ { code: "class", type: "token", jsonbPath: "$.class" },
10293
+ { code: "type", type: "token", jsonbPath: "$.type[*]" },
10294
+ { code: "subject", type: "reference", jsonbPath: "$.subject" },
10295
+ { code: "patient", type: "reference", jsonbPath: "$.subject" },
10296
+ {
10297
+ code: "participant",
10298
+ type: "reference",
10299
+ jsonbPath: "$.participant[*].individual"
10300
+ },
10301
+ { code: "date", type: "date", jsonbPath: "$.period.start" },
10302
+ {
10303
+ code: "service-provider",
10304
+ type: "reference",
10305
+ jsonbPath: "$.serviceProvider"
10306
+ },
10307
+ { code: "appointment", type: "reference", jsonbPath: "$.appointment[*]" },
10308
+ {
10309
+ code: "episode-of-care",
10310
+ type: "reference",
10311
+ jsonbPath: "$.episodeOfCare[*]"
10312
+ }
10313
+ ];
10314
+
10315
+ // src/data/search/registry/observation-search-parameters.ts
10316
+ var OBSERVATION_SEARCH_PARAMETERS = [
10317
+ { code: "status", type: "token", jsonbPath: "$.status" },
10318
+ { code: "category", type: "token", jsonbPath: "$.category[*]" },
10319
+ { code: "code", type: "token", jsonbPath: "$.code" },
10320
+ { code: "subject", type: "reference", jsonbPath: "$.subject" },
10321
+ { code: "patient", type: "reference", jsonbPath: "$.subject" },
10322
+ { code: "encounter", type: "reference", jsonbPath: "$.encounter" },
10323
+ { code: "performer", type: "reference", jsonbPath: "$.performer[*]" },
10324
+ { code: "date", type: "date", jsonbPath: "$.effectiveDateTime" },
10325
+ {
10326
+ code: "value-string",
10327
+ type: "string",
10328
+ jsonbPath: "$.valueString",
10329
+ modifiers: ["exact", "contains", "missing", "not"]
10330
+ },
10331
+ { code: "identifier", type: "token", jsonbPath: "$.identifier[*]" },
10332
+ { code: "based-on", type: "reference", jsonbPath: "$.basedOn[*]" },
10333
+ { code: "part-of", type: "reference", jsonbPath: "$.partOf[*]" }
10334
+ ];
10335
+
10285
10336
  // src/data/search/registry/patient-search-parameters.ts
10286
10337
  var PATIENT_SEARCH_PARAMETERS = [
10287
10338
  { code: "gender", type: "token", jsonbPath: "$.gender" },
@@ -10319,10 +10370,39 @@ var PATIENT_SEARCH_PARAMETERS = [
10319
10370
  }
10320
10371
  ];
10321
10372
 
10373
+ // src/data/search/registry/procedure-search-parameters.ts
10374
+ var PROCEDURE_SEARCH_PARAMETERS = [
10375
+ { code: "status", type: "token", jsonbPath: "$.status" },
10376
+ { code: "category", type: "token", jsonbPath: "$.category" },
10377
+ { code: "code", type: "token", jsonbPath: "$.code" },
10378
+ { code: "subject", type: "reference", jsonbPath: "$.subject" },
10379
+ { code: "patient", type: "reference", jsonbPath: "$.subject" },
10380
+ { code: "encounter", type: "reference", jsonbPath: "$.encounter" },
10381
+ {
10382
+ code: "performer",
10383
+ type: "reference",
10384
+ jsonbPath: "$.performer[*].actor"
10385
+ },
10386
+ { code: "date", type: "date", jsonbPath: "$.performedDateTime" },
10387
+ { code: "location", type: "reference", jsonbPath: "$.location" },
10388
+ { code: "identifier", type: "token", jsonbPath: "$.identifier[*]" },
10389
+ { code: "based-on", type: "reference", jsonbPath: "$.basedOn[*]" },
10390
+ { code: "part-of", type: "reference", jsonbPath: "$.partOf[*]" },
10391
+ { code: "reason-code", type: "token", jsonbPath: "$.reasonCode[*]" },
10392
+ {
10393
+ code: "reason-reference",
10394
+ type: "reference",
10395
+ jsonbPath: "$.reasonReference[*]"
10396
+ }
10397
+ ];
10398
+
10322
10399
  // src/data/search/registry/resolver.ts
10323
10400
  var STATIC_SEARCH_PARAMETER_MAP = {
10324
10401
  Appointment: APPOINTMENT_SEARCH_PARAMETERS,
10325
- Patient: PATIENT_SEARCH_PARAMETERS
10402
+ Encounter: ENCOUNTER_SEARCH_PARAMETERS,
10403
+ Observation: OBSERVATION_SEARCH_PARAMETERS,
10404
+ Patient: PATIENT_SEARCH_PARAMETERS,
10405
+ Procedure: PROCEDURE_SEARCH_PARAMETERS
10326
10406
  };
10327
10407
  var defaultSearchParameterResolver = (resourceType, _tenantId) => STATIC_SEARCH_PARAMETER_MAP[resourceType] ?? [];
10328
10408
  function getRegisteredSearchParameters(resourceType) {
@@ -17973,353 +18053,108 @@ async function listEncountersOperation(params) {
17973
18053
  );
17974
18054
  }
17975
18055
 
17976
- // src/data/operations/data/encounter/encounter-period-search-predicate.ts
17977
- var PERIOD_SEARCH_PREFIXES = [
17978
- "gt",
17979
- "lt",
17980
- "ge",
17981
- "le",
17982
- "sa",
17983
- "eb"
17984
- ];
17985
- function isPeriodSearchPrefix(s) {
17986
- return PERIOD_SEARCH_PREFIXES.includes(s);
17987
- }
17988
- var PERIOD_START = "resource->'period'->>'start'";
17989
- var PERIOD_END = "resource->'period'->>'end'";
17990
- var HAS_ANY_BOUND_GUARD = `(${PERIOD_START} IS NOT NULL OR ${PERIOD_END} IS NOT NULL)`;
17991
- function buildSinglePredicateSql(prefix, paramName) {
17992
- switch (prefix) {
17993
- case "gt":
17994
- return `(${PERIOD_END} IS NULL OR ${PERIOD_END} > :${paramName})`;
17995
- case "lt":
17996
- return `(${PERIOD_START} IS NULL OR ${PERIOD_START} < :${paramName})`;
17997
- case "ge":
17998
- return `(${PERIOD_END} IS NULL OR ${PERIOD_END} >= :${paramName})`;
17999
- case "le":
18000
- return `(${PERIOD_START} IS NULL OR ${PERIOD_START} <= :${paramName})`;
18001
- case "sa":
18002
- return `(${PERIOD_START} IS NOT NULL AND ${PERIOD_START} > :${paramName})`;
18003
- case "eb":
18004
- return `(${PERIOD_END} IS NOT NULL AND ${PERIOD_END} < :${paramName})`;
18005
- }
18006
- }
18007
- function periodConstraintParamName(index) {
18008
- return `periodConstraint${index}`;
18009
- }
18010
- function buildPeriodSearchPredicateSql(constraints) {
18011
- if (constraints.length === 0) {
18012
- return [];
18013
- }
18014
- const fragments = constraints.map(
18015
- (c, i) => buildSinglePredicateSql(c.prefix, periodConstraintParamName(i))
18016
- );
18017
- fragments.push(HAS_ANY_BOUND_GUARD);
18018
- return fragments;
18019
- }
18020
- function buildPeriodSearchPredicateParams(constraints) {
18021
- return constraints.map((c, i) => ({
18022
- name: periodConstraintParamName(i),
18023
- value: c.value
18024
- }));
18025
- }
18026
-
18027
- // src/data/operations/data/encounter/encounter-search-by-date-operation.ts
18028
- var DEFAULT_LIMIT2 = 100;
18029
- function buildSearchEncountersByDateSql(opts) {
18030
- const periodPredicates = buildPeriodSearchPredicateSql(
18031
- opts.periodConstraints
18032
- );
18033
- const lines = [
18034
- "SELECT resource_id AS id, resource",
18035
- "FROM resources",
18036
- "WHERE tenant_id = :tenantId",
18037
- " AND workspace_id = :workspaceId",
18038
- " AND resource_type = 'Encounter'",
18039
- " AND deleted_at IS NULL"
18040
- ];
18041
- for (const fragment of periodPredicates) {
18042
- lines.push(` AND ${fragment}`);
18043
- }
18044
- lines.push("ORDER BY last_updated DESC");
18045
- lines.push("LIMIT :limit;");
18046
- return lines.join("\n");
18047
- }
18048
- async function searchEncountersByDateOperation(params) {
18049
- const { context, periodConstraints } = params;
18050
- if (periodConstraints.length === 0) {
18051
- throw new Error(
18052
- "searchEncountersByDateOperation requires at least one periodConstraint"
18053
- );
18054
- }
18055
- const { tenantId, workspaceId } = context;
18056
- const runner = params.runner ?? getDefaultPostgresQueryRunner();
18057
- const limit = params.limit ?? DEFAULT_LIMIT2;
18058
- const sql = buildSearchEncountersByDateSql({ periodConstraints });
18059
- const queryParams = [
18060
- { name: "tenantId", value: tenantId },
18061
- { name: "workspaceId", value: workspaceId },
18062
- { name: "limit", value: limit },
18063
- ...buildPeriodSearchPredicateParams(periodConstraints)
18064
- ];
18065
- const rows = await runner.query(sql, queryParams);
18066
- const entries = rows.map((row) => ({
18067
- id: row.id,
18068
- resource: {
18069
- ...row.resource,
18070
- id: row.id
18071
- }
18072
- }));
18073
- return { entries, total: entries.length };
18056
+ // src/data/rest-api/routes/data/encounter/encounter-list-route.ts
18057
+ var ENCOUNTER_RESOURCE_TYPE = "Encounter";
18058
+ function stripModifier2(key) {
18059
+ const idx = key.indexOf(":");
18060
+ return idx === -1 ? key : key.slice(0, idx);
18074
18061
  }
18075
-
18076
- // src/data/operations/data/encounter/encounter-search-by-participant-operation.ts
18077
- var DEFAULT_LIMIT3 = 100;
18078
- function buildSearchEncountersByParticipantSql(opts) {
18079
- const periodPredicates = buildPeriodSearchPredicateSql(
18080
- opts?.periodConstraints ?? []
18081
- );
18082
- const lines = [
18083
- "SELECT resource_id AS id, resource",
18084
- "FROM resources",
18085
- "WHERE tenant_id = :tenantId",
18086
- " AND workspace_id = :workspaceId",
18087
- " AND resource_type = 'Encounter'",
18088
- " AND deleted_at IS NULL",
18089
- ` AND ${REFERENCE_CONTAINMENT_SQL_FRAGMENT}`
18090
- ];
18091
- for (const fragment of periodPredicates) {
18092
- lines.push(` AND ${fragment}`);
18093
- }
18094
- lines.push("ORDER BY last_updated DESC");
18095
- lines.push("LIMIT :limit;");
18096
- return lines.join("\n");
18062
+ function isResultParameter2(key) {
18063
+ return key.startsWith("_");
18097
18064
  }
18098
- async function searchEncountersByParticipantOperation(params) {
18099
- const { context, participantReference } = params;
18100
- const periodConstraints = params.periodConstraints ?? [];
18101
- const { tenantId, workspaceId } = context;
18102
- const runner = params.runner ?? getDefaultPostgresQueryRunner();
18103
- const limit = params.limit ?? DEFAULT_LIMIT3;
18104
- const { containmentRelative, containmentUrn } = buildReferenceContainmentPayload({
18105
- shape: {
18106
- kind: "array-of-objects",
18107
- field: "participant",
18108
- subfield: "individual"
18109
- },
18110
- reference: participantReference,
18111
- tenantId,
18112
- workspaceId
18065
+ function sendInvalidSearch4002(res, diagnostics) {
18066
+ return res.status(400).json({
18067
+ resourceType: "OperationOutcome",
18068
+ issue: [{ severity: "error", code: "invalid", diagnostics }]
18113
18069
  });
18114
- const sql = buildSearchEncountersByParticipantSql({ periodConstraints });
18115
- const queryParams = [
18116
- { name: "tenantId", value: tenantId },
18117
- { name: "workspaceId", value: workspaceId },
18118
- { name: "containmentRelative", value: containmentRelative },
18119
- { name: "containmentUrn", value: containmentUrn },
18120
- { name: "limit", value: limit },
18121
- ...buildPeriodSearchPredicateParams(periodConstraints)
18122
- ];
18123
- const rows = await runner.query(sql, queryParams);
18124
- const entries = rows.map((row) => ({
18125
- id: row.id,
18126
- resource: {
18127
- ...row.resource,
18128
- id: row.id
18129
- }
18130
- }));
18131
- return { entries, total: entries.length };
18132
18070
  }
18133
-
18134
- // src/data/operations/data/encounter/encounter-search-by-patient-operation.ts
18135
- var DEFAULT_LIMIT4 = 100;
18136
- function buildSearchEncountersByPatientSql(opts) {
18137
- const periodPredicates = buildPeriodSearchPredicateSql(
18138
- opts?.periodConstraints ?? []
18139
- );
18140
- const lines = [
18141
- "SELECT resource_id AS id, resource",
18142
- "FROM resources",
18143
- "WHERE tenant_id = :tenantId",
18144
- " AND workspace_id = :workspaceId",
18145
- " AND resource_type = 'Encounter'",
18146
- " AND deleted_at IS NULL",
18147
- " AND (resource @> :containmentRelative::jsonb",
18148
- " OR resource @> :containmentUrn::jsonb)"
18149
- ];
18150
- for (const fragment of periodPredicates) {
18151
- lines.push(` AND ${fragment}`);
18152
- }
18153
- lines.push("ORDER BY last_updated DESC");
18154
- lines.push("LIMIT :limit;");
18155
- return lines.join("\n");
18156
- }
18157
- async function searchEncountersByPatientOperation(params) {
18158
- const { context, patientId } = params;
18159
- const periodConstraints = params.periodConstraints ?? [];
18160
- const { tenantId, workspaceId } = context;
18161
- const runner = params.runner ?? getDefaultPostgresQueryRunner();
18162
- const limit = params.limit ?? DEFAULT_LIMIT4;
18163
- const containmentRelative = JSON.stringify({
18164
- subject: { reference: `Patient/${patientId}` }
18165
- });
18166
- const containmentUrn = JSON.stringify({
18167
- subject: {
18168
- reference: buildOpenHiResourceUrn({
18169
- tenantId,
18170
- workspaceId,
18171
- resourceType: "Patient",
18172
- resourceId: patientId
18173
- })
18174
- }
18175
- });
18176
- const sql = buildSearchEncountersByPatientSql({ periodConstraints });
18177
- const queryParams = [
18178
- { name: "tenantId", value: tenantId },
18179
- { name: "workspaceId", value: workspaceId },
18180
- { name: "containmentRelative", value: containmentRelative },
18181
- { name: "containmentUrn", value: containmentUrn },
18182
- { name: "limit", value: limit },
18183
- ...buildPeriodSearchPredicateParams(periodConstraints)
18184
- ];
18185
- const rows = await runner.query(sql, queryParams);
18186
- const entries = rows.map((row) => ({
18187
- id: row.id,
18188
- resource: {
18189
- ...row.resource,
18190
- id: row.id
18071
+ function extractSearchParamKeys2(query) {
18072
+ const out = [];
18073
+ for (const rawKey of Object.keys(query)) {
18074
+ if (isResultParameter2(rawKey)) {
18075
+ continue;
18191
18076
  }
18192
- }));
18193
- return { entries, total: entries.length };
18194
- }
18195
-
18196
- // src/data/rest-api/routes/data/encounter/encounter-list-route.ts
18197
- function singleStringQueryParam(req, name) {
18198
- const v = req.query[name];
18199
- if (typeof v !== "string") {
18200
- return void 0;
18077
+ out.push({ rawKey, code: stripModifier2(rawKey) });
18201
18078
  }
18202
- const trimmed = v.trim();
18203
- return trimmed === "" ? void 0 : trimmed;
18079
+ return out;
18204
18080
  }
18205
- function isError(v) {
18206
- return v.error !== void 0;
18081
+ function buildUnknownParamDiagnostics2(unknownCodes) {
18082
+ const validCodes = getRegisteredSearchParameters(ENCOUNTER_RESOURCE_TYPE).map((p) => p.code).sort().join(", ");
18083
+ const codes = unknownCodes.join(", ");
18084
+ const isPlural = unknownCodes.length !== 1;
18085
+ return [
18086
+ `Unknown search ${isPlural ? "parameters" : "parameter"} for Encounter: ${codes}.`,
18087
+ `Valid codes: ${validCodes}.`
18088
+ ].join(" ");
18207
18089
  }
18208
- function parseEncounterDateConstraints(req) {
18209
- const raw = req.query.date;
18210
- if (raw === void 0) {
18211
- return [];
18212
- }
18213
- const values = Array.isArray(raw) ? raw : [raw];
18214
- const out = [];
18215
- for (const v of values) {
18216
- if (typeof v !== "string") {
18217
- return { error: "Each ?date= value must be a string." };
18218
- }
18219
- const trimmed = v.trim();
18220
- if (trimmed === "") {
18221
- return { error: "?date= value must not be empty." };
18222
- }
18223
- const prefix = trimmed.slice(0, 2);
18224
- const datetime = trimmed.slice(2);
18225
- if (!isPeriodSearchPrefix(prefix)) {
18226
- return {
18227
- error: `Unsupported ?date= prefix in "${trimmed}". Supported prefixes: ${PERIOD_SEARCH_PREFIXES.join(", ")}.`
18228
- };
18090
+ function findMalformedReference2(query, searchParamKeys) {
18091
+ const referenceCodes = new Set(
18092
+ getRegisteredSearchParameters(ENCOUNTER_RESOURCE_TYPE).filter((p) => p.type === "reference").map((p) => p.code)
18093
+ );
18094
+ for (const { rawKey, code } of searchParamKeys) {
18095
+ if (!referenceCodes.has(code)) {
18096
+ continue;
18229
18097
  }
18230
- if (datetime === "" || Number.isNaN(Date.parse(datetime))) {
18231
- return { error: `Invalid datetime in ?date=${trimmed}.` };
18098
+ const raw = query[rawKey];
18099
+ const values = typeof raw === "string" ? raw.split(",") : Array.isArray(raw) ? raw.flatMap((v) => v.split(",")) : [];
18100
+ for (const v of values) {
18101
+ const trimmed = v.trim();
18102
+ if (trimmed.length === 0) {
18103
+ continue;
18104
+ }
18105
+ if (parseTypedReference(trimmed) === void 0) {
18106
+ return { rawKey, value: trimmed };
18107
+ }
18232
18108
  }
18233
- out.push({ prefix, value: datetime });
18234
18109
  }
18235
- return out;
18236
- }
18237
- function sendInvalidSearch4002(res, diagnostics) {
18238
- return res.status(400).json({
18239
- resourceType: "OperationOutcome",
18240
- issue: [{ severity: "error", code: "invalid", diagnostics }]
18241
- });
18110
+ return void 0;
18242
18111
  }
18243
18112
  async function listEncountersRoute(req, res) {
18244
- const patientId = singleStringQueryParam(req, "patient");
18245
- const participantRef = singleStringQueryParam(req, "participant");
18246
- const parsed = parseEncounterDateConstraints(req);
18247
- if (isError(parsed)) {
18248
- return sendInvalidSearch4002(res, parsed.error);
18249
- }
18250
- const periodConstraints = parsed;
18251
- if (patientId !== void 0 && participantRef !== void 0) {
18113
+ const searchParamKeys = extractSearchParamKeys2(
18114
+ req.query
18115
+ );
18116
+ if (searchParamKeys.length === 0) {
18117
+ return handleListRoute({
18118
+ req,
18119
+ res,
18120
+ basePath: BASE_PATH.ENCOUNTER,
18121
+ listOperation: listEncountersOperation,
18122
+ errorLogContext: "GET /Encounter list error:"
18123
+ });
18124
+ }
18125
+ const registered = getRegisteredSearchParameters(ENCOUNTER_RESOURCE_TYPE);
18126
+ const validCodes = new Set(registered.map((p) => p.code));
18127
+ const unknownCodes = searchParamKeys.map((k) => k.code).filter((code) => !validCodes.has(code));
18128
+ if (unknownCodes.length > 0) {
18252
18129
  return sendInvalidSearch4002(
18253
18130
  res,
18254
- "?patient= and ?participant= cannot be combined on the same request."
18131
+ buildUnknownParamDiagnostics2([...new Set(unknownCodes)])
18255
18132
  );
18256
18133
  }
18257
- if (participantRef !== void 0) {
18258
- if (parseTypedReference(participantRef) === void 0) {
18259
- return sendInvalidSearch4002(
18260
- res,
18261
- `?participant must be a typed reference like "Practitioner/<id>"; got "${participantRef}".`
18262
- );
18263
- }
18264
- const ctx = req.openhiContext;
18265
- try {
18266
- const result = await searchEncountersByParticipantOperation({
18267
- context: ctx,
18268
- participantReference: participantRef,
18269
- periodConstraints
18270
- });
18271
- const bundle = buildSearchsetBundle(BASE_PATH.ENCOUNTER, result.entries);
18272
- return res.json(bundle);
18273
- } catch (err) {
18274
- return sendOperationOutcome500(
18275
- res,
18276
- err,
18277
- "GET /Encounter?participant= search error:"
18278
- );
18279
- }
18280
- }
18281
- if (patientId !== void 0) {
18282
- const ctx = req.openhiContext;
18283
- try {
18284
- const result = await searchEncountersByPatientOperation({
18285
- context: ctx,
18286
- patientId,
18287
- periodConstraints
18288
- });
18289
- const bundle = buildSearchsetBundle(BASE_PATH.ENCOUNTER, result.entries);
18290
- return res.json(bundle);
18291
- } catch (err) {
18292
- return sendOperationOutcome500(
18293
- res,
18294
- err,
18295
- "GET /Encounter?patient= search error:"
18296
- );
18297
- }
18134
+ const malformedRef = findMalformedReference2(
18135
+ req.query,
18136
+ searchParamKeys
18137
+ );
18138
+ if (malformedRef !== void 0) {
18139
+ return sendInvalidSearch4002(
18140
+ res,
18141
+ `?${malformedRef.rawKey} must be a typed reference like "Practitioner/<id>"; got "${malformedRef.value}".`
18142
+ );
18298
18143
  }
18299
- if (periodConstraints.length > 0) {
18300
- const ctx = req.openhiContext;
18301
- try {
18302
- const result = await searchEncountersByDateOperation({
18303
- context: ctx,
18304
- periodConstraints
18305
- });
18306
- const bundle = buildSearchsetBundle(BASE_PATH.ENCOUNTER, result.entries);
18307
- return res.json(bundle);
18308
- } catch (err) {
18309
- return sendOperationOutcome500(
18310
- res,
18311
- err,
18312
- "GET /Encounter?date= search error:"
18313
- );
18314
- }
18144
+ const ctx = req.openhiContext;
18145
+ try {
18146
+ const result = await genericSearchOperation({
18147
+ resourceType: ENCOUNTER_RESOURCE_TYPE,
18148
+ tenantId: ctx.tenantId,
18149
+ workspaceId: ctx.workspaceId,
18150
+ query: req.query,
18151
+ resolver: defaultSearchParameterResolver
18152
+ });
18153
+ const bundle = buildSearchsetBundle(BASE_PATH.ENCOUNTER, result.entries);
18154
+ return res.json(bundle);
18155
+ } catch (err) {
18156
+ return sendOperationOutcome500(res, err, "GET /Encounter search error:");
18315
18157
  }
18316
- return handleListRoute({
18317
- req,
18318
- res,
18319
- basePath: BASE_PATH.ENCOUNTER,
18320
- listOperation: listEncountersOperation,
18321
- errorLogContext: "GET /Encounter list error:"
18322
- });
18323
18158
  }
18324
18159
 
18325
18160
  // src/data/operations/data/encounter/encounter-update-operation.ts
@@ -29034,15 +28869,108 @@ async function listObservationsOperation(params) {
29034
28869
  }
29035
28870
 
29036
28871
  // src/data/rest-api/routes/data/observation/observation-list-route.ts
29037
- async function listObservationsRoute(req, res) {
29038
- return handleListRoute({
29039
- req,
29040
- res,
29041
- basePath: BASE_PATH.OBSERVATION,
29042
- listOperation: listObservationsOperation,
29043
- errorLogContext: "GET /Observation list error:"
28872
+ var OBSERVATION_RESOURCE_TYPE = "Observation";
28873
+ function stripModifier3(key) {
28874
+ const idx = key.indexOf(":");
28875
+ return idx === -1 ? key : key.slice(0, idx);
28876
+ }
28877
+ function isResultParameter3(key) {
28878
+ return key.startsWith("_");
28879
+ }
28880
+ function sendInvalidSearch4003(res, diagnostics) {
28881
+ return res.status(400).json({
28882
+ resourceType: "OperationOutcome",
28883
+ issue: [{ severity: "error", code: "invalid", diagnostics }]
29044
28884
  });
29045
28885
  }
28886
+ function extractSearchParamKeys3(query) {
28887
+ const out = [];
28888
+ for (const rawKey of Object.keys(query)) {
28889
+ if (isResultParameter3(rawKey)) {
28890
+ continue;
28891
+ }
28892
+ out.push({ rawKey, code: stripModifier3(rawKey) });
28893
+ }
28894
+ return out;
28895
+ }
28896
+ function buildUnknownParamDiagnostics3(unknownCodes) {
28897
+ const validCodes = getRegisteredSearchParameters(OBSERVATION_RESOURCE_TYPE).map((p) => p.code).sort().join(", ");
28898
+ const codes = unknownCodes.join(", ");
28899
+ const isPlural = unknownCodes.length !== 1;
28900
+ return [
28901
+ `Unknown search ${isPlural ? "parameters" : "parameter"} for Observation: ${codes}.`,
28902
+ `Valid codes: ${validCodes}.`
28903
+ ].join(" ");
28904
+ }
28905
+ function findMalformedReference3(query, searchParamKeys) {
28906
+ const referenceCodes = new Set(
28907
+ getRegisteredSearchParameters(OBSERVATION_RESOURCE_TYPE).filter((p) => p.type === "reference").map((p) => p.code)
28908
+ );
28909
+ for (const { rawKey, code } of searchParamKeys) {
28910
+ if (!referenceCodes.has(code)) {
28911
+ continue;
28912
+ }
28913
+ const raw = query[rawKey];
28914
+ const values = typeof raw === "string" ? raw.split(",") : Array.isArray(raw) ? raw.flatMap((v) => v.split(",")) : [];
28915
+ for (const v of values) {
28916
+ const trimmed = v.trim();
28917
+ if (trimmed.length === 0) {
28918
+ continue;
28919
+ }
28920
+ if (parseTypedReference(trimmed) === void 0) {
28921
+ return { rawKey, value: trimmed };
28922
+ }
28923
+ }
28924
+ }
28925
+ return void 0;
28926
+ }
28927
+ async function listObservationsRoute(req, res) {
28928
+ const searchParamKeys = extractSearchParamKeys3(
28929
+ req.query
28930
+ );
28931
+ if (searchParamKeys.length === 0) {
28932
+ return handleListRoute({
28933
+ req,
28934
+ res,
28935
+ basePath: BASE_PATH.OBSERVATION,
28936
+ listOperation: listObservationsOperation,
28937
+ errorLogContext: "GET /Observation list error:"
28938
+ });
28939
+ }
28940
+ const registered = getRegisteredSearchParameters(OBSERVATION_RESOURCE_TYPE);
28941
+ const validCodes = new Set(registered.map((p) => p.code));
28942
+ const unknownCodes = searchParamKeys.map((k) => k.code).filter((code) => !validCodes.has(code));
28943
+ if (unknownCodes.length > 0) {
28944
+ return sendInvalidSearch4003(
28945
+ res,
28946
+ buildUnknownParamDiagnostics3([...new Set(unknownCodes)])
28947
+ );
28948
+ }
28949
+ const malformedRef = findMalformedReference3(
28950
+ req.query,
28951
+ searchParamKeys
28952
+ );
28953
+ if (malformedRef !== void 0) {
28954
+ return sendInvalidSearch4003(
28955
+ res,
28956
+ `?${malformedRef.rawKey} must be a typed reference like "Practitioner/<id>"; got "${malformedRef.value}".`
28957
+ );
28958
+ }
28959
+ const ctx = req.openhiContext;
28960
+ try {
28961
+ const result = await genericSearchOperation({
28962
+ resourceType: OBSERVATION_RESOURCE_TYPE,
28963
+ tenantId: ctx.tenantId,
28964
+ workspaceId: ctx.workspaceId,
28965
+ query: req.query,
28966
+ resolver: defaultSearchParameterResolver
28967
+ });
28968
+ const bundle = buildSearchsetBundle(BASE_PATH.OBSERVATION, result.entries);
28969
+ return res.json(bundle);
28970
+ } catch (err) {
28971
+ return sendOperationOutcome500(res, err, "GET /Observation search error:");
28972
+ }
28973
+ }
29046
28974
 
29047
28975
  // src/data/operations/data/observation/observation-update-operation.ts
29048
28976
  async function updateObservationOperation(params) {
@@ -30053,30 +29981,30 @@ async function listPatientsOperation(params) {
30053
29981
 
30054
29982
  // src/data/rest-api/routes/data/patient/patient-list-route.ts
30055
29983
  var PATIENT_RESOURCE_TYPE = "Patient";
30056
- function stripModifier2(key) {
29984
+ function stripModifier4(key) {
30057
29985
  const idx = key.indexOf(":");
30058
29986
  return idx === -1 ? key : key.slice(0, idx);
30059
29987
  }
30060
- function isResultParameter2(key) {
29988
+ function isResultParameter4(key) {
30061
29989
  return key.startsWith("_");
30062
29990
  }
30063
- function sendInvalidSearch4003(res, diagnostics) {
29991
+ function sendInvalidSearch4004(res, diagnostics) {
30064
29992
  return res.status(400).json({
30065
29993
  resourceType: "OperationOutcome",
30066
29994
  issue: [{ severity: "error", code: "invalid", diagnostics }]
30067
29995
  });
30068
29996
  }
30069
- function extractSearchParamKeys2(query) {
29997
+ function extractSearchParamKeys4(query) {
30070
29998
  const out = [];
30071
29999
  for (const rawKey of Object.keys(query)) {
30072
- if (isResultParameter2(rawKey)) {
30000
+ if (isResultParameter4(rawKey)) {
30073
30001
  continue;
30074
30002
  }
30075
- out.push({ rawKey, code: stripModifier2(rawKey) });
30003
+ out.push({ rawKey, code: stripModifier4(rawKey) });
30076
30004
  }
30077
30005
  return out;
30078
30006
  }
30079
- function buildUnknownParamDiagnostics2(unknownCodes) {
30007
+ function buildUnknownParamDiagnostics4(unknownCodes) {
30080
30008
  const validCodes = getRegisteredSearchParameters(PATIENT_RESOURCE_TYPE).map((p) => p.code).sort().join(", ");
30081
30009
  const codes = unknownCodes.join(", ");
30082
30010
  const isPlural = unknownCodes.length !== 1;
@@ -30085,7 +30013,7 @@ function buildUnknownParamDiagnostics2(unknownCodes) {
30085
30013
  `Valid codes: ${validCodes}.`
30086
30014
  ].join(" ");
30087
30015
  }
30088
- function findMalformedReference2(query, searchParamKeys) {
30016
+ function findMalformedReference4(query, searchParamKeys) {
30089
30017
  const referenceCodes = new Set(
30090
30018
  getRegisteredSearchParameters(PATIENT_RESOURCE_TYPE).filter((p) => p.type === "reference").map((p) => p.code)
30091
30019
  );
@@ -30108,7 +30036,7 @@ function findMalformedReference2(query, searchParamKeys) {
30108
30036
  return void 0;
30109
30037
  }
30110
30038
  async function listPatientsRoute(req, res) {
30111
- const searchParamKeys = extractSearchParamKeys2(
30039
+ const searchParamKeys = extractSearchParamKeys4(
30112
30040
  req.query
30113
30041
  );
30114
30042
  if (searchParamKeys.length === 0) {
@@ -30124,17 +30052,17 @@ async function listPatientsRoute(req, res) {
30124
30052
  const validCodes = new Set(registered.map((p) => p.code));
30125
30053
  const unknownCodes = searchParamKeys.map((k) => k.code).filter((code) => !validCodes.has(code));
30126
30054
  if (unknownCodes.length > 0) {
30127
- return sendInvalidSearch4003(
30055
+ return sendInvalidSearch4004(
30128
30056
  res,
30129
- buildUnknownParamDiagnostics2([...new Set(unknownCodes)])
30057
+ buildUnknownParamDiagnostics4([...new Set(unknownCodes)])
30130
30058
  );
30131
30059
  }
30132
- const malformedRef = findMalformedReference2(
30060
+ const malformedRef = findMalformedReference4(
30133
30061
  req.query,
30134
30062
  searchParamKeys
30135
30063
  );
30136
30064
  if (malformedRef !== void 0) {
30137
- return sendInvalidSearch4003(
30065
+ return sendInvalidSearch4004(
30138
30066
  res,
30139
30067
  `?${malformedRef.rawKey} must be a typed reference like "Practitioner/<id>"; got "${malformedRef.value}".`
30140
30068
  );
@@ -31508,15 +31436,108 @@ async function listProceduresOperation(params) {
31508
31436
  }
31509
31437
 
31510
31438
  // src/data/rest-api/routes/data/procedure/procedure-list-route.ts
31511
- async function listProceduresRoute(req, res) {
31512
- return handleListRoute({
31513
- req,
31514
- res,
31515
- basePath: BASE_PATH.PROCEDURE,
31516
- listOperation: listProceduresOperation,
31517
- errorLogContext: "GET /Procedure list error:"
31439
+ var PROCEDURE_RESOURCE_TYPE = "Procedure";
31440
+ function stripModifier5(key) {
31441
+ const idx = key.indexOf(":");
31442
+ return idx === -1 ? key : key.slice(0, idx);
31443
+ }
31444
+ function isResultParameter5(key) {
31445
+ return key.startsWith("_");
31446
+ }
31447
+ function sendInvalidSearch4005(res, diagnostics) {
31448
+ return res.status(400).json({
31449
+ resourceType: "OperationOutcome",
31450
+ issue: [{ severity: "error", code: "invalid", diagnostics }]
31518
31451
  });
31519
31452
  }
31453
+ function extractSearchParamKeys5(query) {
31454
+ const out = [];
31455
+ for (const rawKey of Object.keys(query)) {
31456
+ if (isResultParameter5(rawKey)) {
31457
+ continue;
31458
+ }
31459
+ out.push({ rawKey, code: stripModifier5(rawKey) });
31460
+ }
31461
+ return out;
31462
+ }
31463
+ function buildUnknownParamDiagnostics5(unknownCodes) {
31464
+ const validCodes = getRegisteredSearchParameters(PROCEDURE_RESOURCE_TYPE).map((p) => p.code).sort().join(", ");
31465
+ const codes = unknownCodes.join(", ");
31466
+ const isPlural = unknownCodes.length !== 1;
31467
+ return [
31468
+ `Unknown search ${isPlural ? "parameters" : "parameter"} for Procedure: ${codes}.`,
31469
+ `Valid codes: ${validCodes}.`
31470
+ ].join(" ");
31471
+ }
31472
+ function findMalformedReference5(query, searchParamKeys) {
31473
+ const referenceCodes = new Set(
31474
+ getRegisteredSearchParameters(PROCEDURE_RESOURCE_TYPE).filter((p) => p.type === "reference").map((p) => p.code)
31475
+ );
31476
+ for (const { rawKey, code } of searchParamKeys) {
31477
+ if (!referenceCodes.has(code)) {
31478
+ continue;
31479
+ }
31480
+ const raw = query[rawKey];
31481
+ const values = typeof raw === "string" ? raw.split(",") : Array.isArray(raw) ? raw.flatMap((v) => v.split(",")) : [];
31482
+ for (const v of values) {
31483
+ const trimmed = v.trim();
31484
+ if (trimmed.length === 0) {
31485
+ continue;
31486
+ }
31487
+ if (parseTypedReference(trimmed) === void 0) {
31488
+ return { rawKey, value: trimmed };
31489
+ }
31490
+ }
31491
+ }
31492
+ return void 0;
31493
+ }
31494
+ async function listProceduresRoute(req, res) {
31495
+ const searchParamKeys = extractSearchParamKeys5(
31496
+ req.query
31497
+ );
31498
+ if (searchParamKeys.length === 0) {
31499
+ return handleListRoute({
31500
+ req,
31501
+ res,
31502
+ basePath: BASE_PATH.PROCEDURE,
31503
+ listOperation: listProceduresOperation,
31504
+ errorLogContext: "GET /Procedure list error:"
31505
+ });
31506
+ }
31507
+ const registered = getRegisteredSearchParameters(PROCEDURE_RESOURCE_TYPE);
31508
+ const validCodes = new Set(registered.map((p) => p.code));
31509
+ const unknownCodes = searchParamKeys.map((k) => k.code).filter((code) => !validCodes.has(code));
31510
+ if (unknownCodes.length > 0) {
31511
+ return sendInvalidSearch4005(
31512
+ res,
31513
+ buildUnknownParamDiagnostics5([...new Set(unknownCodes)])
31514
+ );
31515
+ }
31516
+ const malformedRef = findMalformedReference5(
31517
+ req.query,
31518
+ searchParamKeys
31519
+ );
31520
+ if (malformedRef !== void 0) {
31521
+ return sendInvalidSearch4005(
31522
+ res,
31523
+ `?${malformedRef.rawKey} must be a typed reference like "Practitioner/<id>"; got "${malformedRef.value}".`
31524
+ );
31525
+ }
31526
+ const ctx = req.openhiContext;
31527
+ try {
31528
+ const result = await genericSearchOperation({
31529
+ resourceType: PROCEDURE_RESOURCE_TYPE,
31530
+ tenantId: ctx.tenantId,
31531
+ workspaceId: ctx.workspaceId,
31532
+ query: req.query,
31533
+ resolver: defaultSearchParameterResolver
31534
+ });
31535
+ const bundle = buildSearchsetBundle(BASE_PATH.PROCEDURE, result.entries);
31536
+ return res.json(bundle);
31537
+ } catch (err) {
31538
+ return sendOperationOutcome500(res, err, "GET /Procedure search error:");
31539
+ }
31540
+ }
31520
31541
 
31521
31542
  // src/data/operations/data/procedure/procedure-update-operation.ts
31522
31543
  async function updateProcedureOperation(params) {
@@ -33972,7 +33993,7 @@ async function listSchedulesOperation(params) {
33972
33993
  }
33973
33994
 
33974
33995
  // src/data/operations/data/schedule/schedule-search-by-actor-operation.ts
33975
- var DEFAULT_LIMIT5 = 100;
33996
+ var DEFAULT_LIMIT2 = 100;
33976
33997
  function buildSearchSchedulesByActorSql() {
33977
33998
  return [
33978
33999
  "SELECT resource_id AS id, resource",
@@ -33990,7 +34011,7 @@ async function searchSchedulesByActorOperation(params) {
33990
34011
  const { context, actorReference } = params;
33991
34012
  const { tenantId, workspaceId } = context;
33992
34013
  const runner = params.runner ?? getDefaultPostgresQueryRunner();
33993
- const limit = params.limit ?? DEFAULT_LIMIT5;
34014
+ const limit = params.limit ?? DEFAULT_LIMIT2;
33994
34015
  const { containmentRelative, containmentUrn } = buildReferenceContainmentPayload({
33995
34016
  shape: { kind: "array-of-references", field: "actor" },
33996
34017
  reference: actorReference,
@@ -34016,7 +34037,7 @@ async function searchSchedulesByActorOperation(params) {
34016
34037
  }
34017
34038
 
34018
34039
  // src/data/rest-api/routes/data/schedule/schedule-list-route.ts
34019
- function singleStringQueryParam2(req, name) {
34040
+ function singleStringQueryParam(req, name) {
34020
34041
  const v = req.query[name];
34021
34042
  if (typeof v !== "string") {
34022
34043
  return void 0;
@@ -34024,17 +34045,17 @@ function singleStringQueryParam2(req, name) {
34024
34045
  const trimmed = v.trim();
34025
34046
  return trimmed === "" ? void 0 : trimmed;
34026
34047
  }
34027
- function sendInvalidSearch4004(res, diagnostics) {
34048
+ function sendInvalidSearch4006(res, diagnostics) {
34028
34049
  return res.status(400).json({
34029
34050
  resourceType: "OperationOutcome",
34030
34051
  issue: [{ severity: "error", code: "invalid", diagnostics }]
34031
34052
  });
34032
34053
  }
34033
34054
  async function listSchedulesRoute(req, res) {
34034
- const actorRef = singleStringQueryParam2(req, "actor");
34055
+ const actorRef = singleStringQueryParam(req, "actor");
34035
34056
  if (actorRef !== void 0) {
34036
34057
  if (parseTypedReference(actorRef) === void 0) {
34037
- return sendInvalidSearch4004(
34058
+ return sendInvalidSearch4006(
34038
34059
  res,
34039
34060
  `?actor must be a typed reference like "Practitioner/<id>"; got "${actorRef}".`
34040
34061
  );
@@ -37736,7 +37757,7 @@ async function listTasksOperation(params) {
37736
37757
  }
37737
37758
 
37738
37759
  // src/data/operations/data/task/task-search-by-owner-operation.ts
37739
- var DEFAULT_LIMIT6 = 100;
37760
+ var DEFAULT_LIMIT3 = 100;
37740
37761
  function buildSearchTasksByOwnerSql() {
37741
37762
  return [
37742
37763
  "SELECT resource_id AS id, resource",
@@ -37754,7 +37775,7 @@ async function searchTasksByOwnerOperation(params) {
37754
37775
  const { context, ownerReference } = params;
37755
37776
  const { tenantId, workspaceId } = context;
37756
37777
  const runner = params.runner ?? getDefaultPostgresQueryRunner();
37757
- const limit = params.limit ?? DEFAULT_LIMIT6;
37778
+ const limit = params.limit ?? DEFAULT_LIMIT3;
37758
37779
  const { containmentRelative, containmentUrn } = buildReferenceContainmentPayload({
37759
37780
  shape: { kind: "scalar", field: "owner" },
37760
37781
  reference: ownerReference,
@@ -37780,7 +37801,7 @@ async function searchTasksByOwnerOperation(params) {
37780
37801
  }
37781
37802
 
37782
37803
  // src/data/operations/data/task/task-search-by-requester-operation.ts
37783
- var DEFAULT_LIMIT7 = 100;
37804
+ var DEFAULT_LIMIT4 = 100;
37784
37805
  function buildSearchTasksByRequesterSql() {
37785
37806
  return [
37786
37807
  "SELECT resource_id AS id, resource",
@@ -37798,7 +37819,7 @@ async function searchTasksByRequesterOperation(params) {
37798
37819
  const { context, requesterReference } = params;
37799
37820
  const { tenantId, workspaceId } = context;
37800
37821
  const runner = params.runner ?? getDefaultPostgresQueryRunner();
37801
- const limit = params.limit ?? DEFAULT_LIMIT7;
37822
+ const limit = params.limit ?? DEFAULT_LIMIT4;
37802
37823
  const { containmentRelative, containmentUrn } = buildReferenceContainmentPayload({
37803
37824
  shape: { kind: "scalar", field: "requester" },
37804
37825
  reference: requesterReference,
@@ -37824,7 +37845,7 @@ async function searchTasksByRequesterOperation(params) {
37824
37845
  }
37825
37846
 
37826
37847
  // src/data/rest-api/routes/data/task/task-list-route.ts
37827
- function singleStringQueryParam3(req, name) {
37848
+ function singleStringQueryParam2(req, name) {
37828
37849
  const v = req.query[name];
37829
37850
  if (typeof v !== "string") {
37830
37851
  return void 0;
@@ -37832,24 +37853,24 @@ function singleStringQueryParam3(req, name) {
37832
37853
  const trimmed = v.trim();
37833
37854
  return trimmed === "" ? void 0 : trimmed;
37834
37855
  }
37835
- function sendInvalidSearch4005(res, diagnostics) {
37856
+ function sendInvalidSearch4007(res, diagnostics) {
37836
37857
  return res.status(400).json({
37837
37858
  resourceType: "OperationOutcome",
37838
37859
  issue: [{ severity: "error", code: "invalid", diagnostics }]
37839
37860
  });
37840
37861
  }
37841
37862
  async function listTasksRoute(req, res) {
37842
- const ownerRef = singleStringQueryParam3(req, "owner");
37843
- const requesterRef = singleStringQueryParam3(req, "requester");
37863
+ const ownerRef = singleStringQueryParam2(req, "owner");
37864
+ const requesterRef = singleStringQueryParam2(req, "requester");
37844
37865
  if (ownerRef !== void 0 && requesterRef !== void 0) {
37845
- return sendInvalidSearch4005(
37866
+ return sendInvalidSearch4007(
37846
37867
  res,
37847
37868
  "?owner= and ?requester= cannot be combined on the same request."
37848
37869
  );
37849
37870
  }
37850
37871
  if (ownerRef !== void 0) {
37851
37872
  if (parseTypedReference(ownerRef) === void 0) {
37852
- return sendInvalidSearch4005(
37873
+ return sendInvalidSearch4007(
37853
37874
  res,
37854
37875
  `?owner must be a typed reference like "Practitioner/<id>"; got "${ownerRef}".`
37855
37876
  );
@@ -37872,7 +37893,7 @@ async function listTasksRoute(req, res) {
37872
37893
  }
37873
37894
  if (requesterRef !== void 0) {
37874
37895
  if (parseTypedReference(requesterRef) === void 0) {
37875
- return sendInvalidSearch4005(
37896
+ return sendInvalidSearch4007(
37876
37897
  res,
37877
37898
  `?requester must be a typed reference like "Practitioner/<id>"; got "${requesterRef}".`
37878
37899
  );