@openhi/constructs 0.0.139 → 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.
@@ -9599,6 +9599,89 @@ async function listAppointmentsOperation(params) {
9599
9599
  );
9600
9600
  }
9601
9601
 
9602
+ // src/data/search/engine/reference-predicate.ts
9603
+ function buildOpenHiResourceUrn(opts) {
9604
+ return `urn:ohi:${opts.tenantId}:${opts.workspaceId}:${opts.resourceType}:${opts.resourceId}`;
9605
+ }
9606
+ var REFERENCE_CONTAINMENT_SQL_FRAGMENT = "(resource @> :containmentRelative::jsonb OR resource @> :containmentUrn::jsonb)";
9607
+ function parseTypedReference(s) {
9608
+ const match = /^([A-Za-z][A-Za-z0-9_]*)\/([^\s/]+)$/.exec(s);
9609
+ if (!match) {
9610
+ return void 0;
9611
+ }
9612
+ return { resourceType: match[1], resourceId: match[2] };
9613
+ }
9614
+ function wrapReferenceInShape(shape, reference) {
9615
+ switch (shape.kind) {
9616
+ case "scalar":
9617
+ return { [shape.field]: { reference } };
9618
+ case "array-of-references":
9619
+ return { [shape.field]: [{ reference }] };
9620
+ case "array-of-objects":
9621
+ return { [shape.field]: [{ [shape.subfield]: { reference } }] };
9622
+ }
9623
+ }
9624
+ function buildReferenceContainmentPayload(params) {
9625
+ const parsed = parseTypedReference(params.reference);
9626
+ if (!parsed) {
9627
+ throw new Error(
9628
+ `Reference "${params.reference}" is not a valid typed reference (<ResourceType>/<id>).`
9629
+ );
9630
+ }
9631
+ const urn = buildOpenHiResourceUrn({
9632
+ tenantId: params.tenantId,
9633
+ workspaceId: params.workspaceId,
9634
+ resourceType: parsed.resourceType,
9635
+ resourceId: parsed.resourceId
9636
+ });
9637
+ return {
9638
+ containmentRelative: JSON.stringify(
9639
+ wrapReferenceInShape(params.shape, params.reference)
9640
+ ),
9641
+ containmentUrn: JSON.stringify(wrapReferenceInShape(params.shape, urn))
9642
+ };
9643
+ }
9644
+ function jsonbPathToReferenceShape(jsonbPath) {
9645
+ const scalar = /^\$\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(jsonbPath);
9646
+ if (scalar) {
9647
+ return { kind: "scalar", field: scalar[1] };
9648
+ }
9649
+ const arrayOfRefs = /^\$\.([A-Za-z_][A-Za-z0-9_]*)\[\*\]$/.exec(jsonbPath);
9650
+ if (arrayOfRefs) {
9651
+ return { kind: "array-of-references", field: arrayOfRefs[1] };
9652
+ }
9653
+ const arrayOfObjs = /^\$\.([A-Za-z_][A-Za-z0-9_]*)\[\*\]\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(
9654
+ jsonbPath
9655
+ );
9656
+ if (arrayOfObjs) {
9657
+ return {
9658
+ kind: "array-of-objects",
9659
+ field: arrayOfObjs[1],
9660
+ subfield: arrayOfObjs[2]
9661
+ };
9662
+ }
9663
+ throw new Error(
9664
+ `Reference predicate cannot translate JSONPath "${jsonbPath}". Supported shapes: "$.field", "$.field[*]", "$.field[*].subfield".`
9665
+ );
9666
+ }
9667
+ function emitReferencePredicate(opts) {
9668
+ const shape = jsonbPathToReferenceShape(opts.jsonbPath);
9669
+ const { containmentRelative, containmentUrn } = buildReferenceContainmentPayload({
9670
+ shape,
9671
+ reference: opts.rawValue,
9672
+ tenantId: opts.context.tenantId,
9673
+ workspaceId: opts.context.workspaceId
9674
+ });
9675
+ const relName = `${opts.paramName}R`;
9676
+ const urnName = `${opts.paramName}U`;
9677
+ const sql = `(resource @> :${relName}::jsonb OR resource @> :${urnName}::jsonb)`;
9678
+ const params = [
9679
+ { name: relName, value: containmentRelative },
9680
+ { name: urnName, value: containmentUrn }
9681
+ ];
9682
+ return { sql, params };
9683
+ }
9684
+
9602
9685
  // src/data/postgres/data-api-postgres-query-runner.ts
9603
9686
  var import_client_rds_data = require("@aws-sdk/client-rds-data");
9604
9687
 
@@ -9827,129 +9910,95 @@ function emitFieldMissingPredicate(opts) {
9827
9910
  const sql = opts.missing ? `${extract} IS NULL` : `${extract} IS NOT NULL`;
9828
9911
  return { sql, params: [] };
9829
9912
  }
9830
- var DEFAULT_INTERVAL_PARAM_PREFIX = "intervalDateConstraint";
9831
- function intervalConstraintParamName(prefix, index) {
9832
- return `${prefix}${index}`;
9913
+
9914
+ // src/data/search/engine/string-predicate.ts
9915
+ var STRING_MODIFIERS = ["exact", "contains"];
9916
+ function isStringModifier(s) {
9917
+ return STRING_MODIFIERS.includes(s);
9833
9918
  }
9834
- function buildIntervalSingleSql(prefix, startExtract, endExtract, paramName) {
9835
- switch (prefix) {
9836
- case "eq":
9837
- return `(${startExtract} IS NOT NULL AND ${startExtract} = :${paramName})`;
9838
- case "gt":
9839
- return `(${endExtract} IS NULL OR ${endExtract} > :${paramName})`;
9840
- case "lt":
9841
- return `(${startExtract} IS NULL OR ${startExtract} < :${paramName})`;
9842
- case "ge":
9843
- return `(${endExtract} IS NULL OR ${endExtract} >= :${paramName})`;
9844
- case "le":
9845
- return `(${startExtract} IS NULL OR ${startExtract} <= :${paramName})`;
9846
- case "sa":
9847
- return `(${startExtract} IS NOT NULL AND ${startExtract} > :${paramName})`;
9848
- case "eb":
9849
- return `(${endExtract} IS NOT NULL AND ${endExtract} < :${paramName})`;
9919
+ function jsonbPathToStringShape(jsonbPath) {
9920
+ const scalar = /^\$\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(jsonbPath);
9921
+ if (scalar) {
9922
+ return { kind: "scalar", field: scalar[1] };
9850
9923
  }
9851
- }
9852
- function buildIntervalDateSearchPredicateSql(constraints, opts) {
9853
- if (constraints.length === 0) {
9854
- return [];
9924
+ const arrayOfScalars = /^\$\.([A-Za-z_][A-Za-z0-9_]*)\[\*\]$/.exec(jsonbPath);
9925
+ if (arrayOfScalars) {
9926
+ return { kind: "array-of-scalars", field: arrayOfScalars[1] };
9855
9927
  }
9856
- const paramPrefix = opts.paramNamePrefix ?? DEFAULT_INTERVAL_PARAM_PREFIX;
9857
- const startExtract = flatJsonbExtract(opts.startPath);
9858
- const endExtract = flatJsonbExtract(opts.endPath);
9859
- const fragments = constraints.map(
9860
- (c, i) => buildIntervalSingleSql(
9861
- c.prefix,
9862
- startExtract,
9863
- endExtract,
9864
- intervalConstraintParamName(paramPrefix, i)
9865
- )
9928
+ const arrayOfObjs = /^\$\.([A-Za-z_][A-Za-z0-9_]*)\[\*\]\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(
9929
+ jsonbPath
9866
9930
  );
9867
- fragments.push(`(${startExtract} IS NOT NULL OR ${endExtract} IS NOT NULL)`);
9868
- return fragments;
9869
- }
9870
- function buildIntervalDateSearchPredicateParams(constraints, opts) {
9871
- const paramPrefix = opts?.paramNamePrefix ?? DEFAULT_INTERVAL_PARAM_PREFIX;
9872
- return constraints.map((c, i) => ({
9873
- name: intervalConstraintParamName(paramPrefix, i),
9874
- value: c.value
9875
- }));
9876
- }
9877
- var APPOINTMENT_DATE_SEARCH_PREFIXES = [
9878
- "gt",
9879
- "lt",
9880
- "ge",
9881
- "le",
9882
- "sa",
9883
- "eb"
9884
- ];
9885
- function isAppointmentDateSearchPrefix(s) {
9886
- return APPOINTMENT_DATE_SEARCH_PREFIXES.includes(
9887
- s
9931
+ if (arrayOfObjs) {
9932
+ return {
9933
+ kind: "array-of-objects",
9934
+ field: arrayOfObjs[1],
9935
+ subfield: arrayOfObjs[2]
9936
+ };
9937
+ }
9938
+ throw new Error(
9939
+ `String predicate cannot translate JSONPath "${jsonbPath}". Supported shapes: "$.field", "$.field[*]", "$.field[*].subfield".`
9888
9940
  );
9889
9941
  }
9890
- function buildAppointmentDateSearchPredicateSql(constraints) {
9891
- return buildIntervalDateSearchPredicateSql(constraints, {
9892
- startPath: "$.start",
9893
- endPath: "$.end",
9894
- paramNamePrefix: "apptDateConstraint"
9895
- });
9896
- }
9897
- function buildAppointmentDateSearchPredicateParams(constraints) {
9898
- return buildIntervalDateSearchPredicateParams(constraints, {
9899
- paramNamePrefix: "apptDateConstraint"
9900
- });
9901
- }
9902
-
9903
- // src/data/search/engine/reference-predicate.ts
9904
- function buildOpenHiResourceUrn(opts) {
9905
- return `urn:ohi:${opts.tenantId}:${opts.workspaceId}:${opts.resourceType}:${opts.resourceId}`;
9942
+ function escapeLikePattern(value) {
9943
+ return value.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
9906
9944
  }
9907
- var REFERENCE_CONTAINMENT_SQL_FRAGMENT = "(resource @> :containmentRelative::jsonb OR resource @> :containmentUrn::jsonb)";
9908
- function parseTypedReference(s) {
9909
- const match = /^([A-Za-z][A-Za-z0-9_]*)\/([^\s/]+)$/.exec(s);
9910
- if (!match) {
9911
- return void 0;
9945
+ function buildLikePattern(value, modifier) {
9946
+ const escaped = escapeLikePattern(value);
9947
+ switch (modifier) {
9948
+ case "exact":
9949
+ return escaped;
9950
+ case "contains":
9951
+ return `%${escaped}%`;
9952
+ case void 0:
9953
+ return `${escaped}%`;
9912
9954
  }
9913
- return { resourceType: match[1], resourceId: match[2] };
9914
9955
  }
9915
- function wrapReferenceInShape(shape, reference) {
9956
+ function buildIlikeExtractSql(shape, paramName) {
9916
9957
  switch (shape.kind) {
9917
9958
  case "scalar":
9918
- return { [shape.field]: { reference } };
9919
- case "array-of-references":
9920
- return { [shape.field]: [{ reference }] };
9959
+ return `resource->>'${shape.field}' ILIKE :${paramName}`;
9960
+ case "array-of-scalars":
9961
+ return [
9962
+ "EXISTS (SELECT 1 FROM",
9963
+ `jsonb_array_elements_text(resource->'${shape.field}') AS s_elem(text_val)`,
9964
+ `WHERE s_elem.text_val ILIKE :${paramName})`
9965
+ ].join(" ");
9921
9966
  case "array-of-objects":
9922
- return { [shape.field]: [{ [shape.subfield]: { reference } }] };
9967
+ return [
9968
+ "EXISTS (SELECT 1 FROM",
9969
+ `jsonb_array_elements(resource->'${shape.field}') AS s_obj(obj)`,
9970
+ `WHERE s_obj.obj->>'${shape.subfield}' ILIKE :${paramName})`
9971
+ ].join(" ");
9923
9972
  }
9924
9973
  }
9925
- function buildReferenceContainmentPayload(params) {
9926
- const parsed = parseTypedReference(params.reference);
9927
- if (!parsed) {
9974
+ function emitStringPredicate(opts) {
9975
+ const shape = jsonbPathToStringShape(opts.jsonbPath);
9976
+ const modifier = opts.modifier === void 0 ? void 0 : checkModifier(opts.modifier);
9977
+ const sql = buildIlikeExtractSql(shape, opts.paramName);
9978
+ const pattern = buildLikePattern(opts.rawValue, modifier);
9979
+ const params = [
9980
+ { name: opts.paramName, value: pattern }
9981
+ ];
9982
+ return { sql, params };
9983
+ }
9984
+ function checkModifier(modifier) {
9985
+ if (!isStringModifier(modifier)) {
9928
9986
  throw new Error(
9929
- `Reference "${params.reference}" is not a valid typed reference (<ResourceType>/<id>).`
9987
+ `String predicate does not support modifier ":${modifier}". Supported: ${STRING_MODIFIERS.map((m) => `:${m}`).join(", ")}.`
9930
9988
  );
9931
9989
  }
9932
- const urn = buildOpenHiResourceUrn({
9933
- tenantId: params.tenantId,
9934
- workspaceId: params.workspaceId,
9935
- resourceType: parsed.resourceType,
9936
- resourceId: parsed.resourceId
9937
- });
9938
- return {
9939
- containmentRelative: JSON.stringify(
9940
- wrapReferenceInShape(params.shape, params.reference)
9941
- ),
9942
- containmentUrn: JSON.stringify(wrapReferenceInShape(params.shape, urn))
9943
- };
9990
+ return modifier;
9944
9991
  }
9945
- function jsonbPathToReferenceShape(jsonbPath) {
9992
+
9993
+ // src/data/search/engine/token-predicate.ts
9994
+ function jsonbPathToTokenShape(jsonbPath) {
9946
9995
  const scalar = /^\$\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(jsonbPath);
9947
9996
  if (scalar) {
9948
9997
  return { kind: "scalar", field: scalar[1] };
9949
9998
  }
9950
- const arrayOfRefs = /^\$\.([A-Za-z_][A-Za-z0-9_]*)\[\*\]$/.exec(jsonbPath);
9951
- if (arrayOfRefs) {
9952
- return { kind: "array-of-references", field: arrayOfRefs[1] };
9999
+ const arrayOfScalars = /^\$\.([A-Za-z_][A-Za-z0-9_]*)\[\*\]$/.exec(jsonbPath);
10000
+ if (arrayOfScalars) {
10001
+ return { kind: "array-of-scalars", field: arrayOfScalars[1] };
9953
10002
  }
9954
10003
  const arrayOfObjs = /^\$\.([A-Za-z_][A-Za-z0-9_]*)\[\*\]\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(
9955
10004
  jsonbPath
@@ -9962,240 +10011,332 @@ function jsonbPathToReferenceShape(jsonbPath) {
9962
10011
  };
9963
10012
  }
9964
10013
  throw new Error(
9965
- `Reference predicate cannot translate JSONPath "${jsonbPath}". Supported shapes: "$.field", "$.field[*]", "$.field[*].subfield".`
10014
+ `Token predicate cannot translate JSONPath "${jsonbPath}". Supported shapes: "$.field", "$.field[*]", "$.field[*].subfield".`
9966
10015
  );
9967
10016
  }
9968
- function emitReferencePredicate(opts) {
9969
- const shape = jsonbPathToReferenceShape(opts.jsonbPath);
9970
- const { containmentRelative, containmentUrn } = buildReferenceContainmentPayload({
9971
- shape,
9972
- reference: opts.rawValue,
9973
- tenantId: opts.context.tenantId,
9974
- workspaceId: opts.context.workspaceId
9975
- });
9976
- const relName = `${opts.paramName}R`;
9977
- const urnName = `${opts.paramName}U`;
9978
- const sql = `(resource @> :${relName}::jsonb OR resource @> :${urnName}::jsonb)`;
10017
+ function parseTokenValue(raw) {
10018
+ const idx = raw.indexOf("|");
10019
+ if (idx === -1) {
10020
+ return { code: raw };
10021
+ }
10022
+ const system = raw.slice(0, idx);
10023
+ const code = raw.slice(idx + 1);
10024
+ return system.length > 0 ? { system, code } : { code };
10025
+ }
10026
+ function wrapTokenInShape(shape, value) {
10027
+ switch (shape.kind) {
10028
+ case "scalar":
10029
+ return { [shape.field]: value };
10030
+ case "array-of-scalars":
10031
+ return { [shape.field]: [value] };
10032
+ case "array-of-objects":
10033
+ return { [shape.field]: [{ [shape.subfield]: value }] };
10034
+ }
10035
+ }
10036
+ function emitTokenPredicate(opts) {
10037
+ const shape = jsonbPathToTokenShape(opts.jsonbPath);
10038
+ const { code } = parseTokenValue(opts.rawValue);
10039
+ const payload = wrapTokenInShape(shape, code);
10040
+ const sql = `resource @> :${opts.paramName}::jsonb`;
9979
10041
  const params = [
9980
- { name: relName, value: containmentRelative },
9981
- { name: urnName, value: containmentUrn }
10042
+ { name: opts.paramName, value: JSON.stringify(payload) }
9982
10043
  ];
9983
10044
  return { sql, params };
9984
10045
  }
9985
10046
 
9986
- // src/data/operations/data/appointment/appointment-search-by-actor-operation.ts
9987
- var DEFAULT_LIMIT = 100;
9988
- function buildSearchAppointmentsByActorSql(opts) {
9989
- const datePredicates = buildAppointmentDateSearchPredicateSql(
9990
- opts?.dateConstraints ?? []
9991
- );
9992
- const lines = [
9993
- "SELECT resource_id AS id, resource",
9994
- "FROM resources",
9995
- "WHERE tenant_id = :tenantId",
9996
- " AND workspace_id = :workspaceId",
9997
- " AND resource_type = 'Appointment'",
9998
- " AND deleted_at IS NULL",
9999
- ` AND ${REFERENCE_CONTAINMENT_SQL_FRAGMENT}`
10000
- ];
10001
- for (const fragment of datePredicates) {
10002
- lines.push(` AND ${fragment}`);
10047
+ // src/data/search/engine/combinator.ts
10048
+ function parseQueryKey(key) {
10049
+ const idx = key.indexOf(":");
10050
+ if (idx === -1) {
10051
+ return { code: key, modifier: void 0 };
10003
10052
  }
10004
- lines.push("ORDER BY last_updated DESC");
10005
- lines.push("LIMIT :limit;");
10006
- return lines.join("\n");
10053
+ return { code: key.slice(0, idx), modifier: key.slice(idx + 1) };
10007
10054
  }
10008
- async function searchAppointmentsByActorOperation(params) {
10009
- const { context, actorReference } = params;
10010
- const dateConstraints = params.dateConstraints ?? [];
10011
- const { tenantId, workspaceId } = context;
10012
- const runner = params.runner ?? getDefaultPostgresQueryRunner();
10013
- const limit = params.limit ?? DEFAULT_LIMIT;
10014
- const { containmentRelative, containmentUrn } = buildReferenceContainmentPayload({
10015
- shape: {
10016
- kind: "array-of-objects",
10017
- field: "participant",
10018
- subfield: "actor"
10019
- },
10020
- reference: actorReference,
10021
- tenantId,
10022
- workspaceId
10055
+ function flattenQueryValues(raw) {
10056
+ const list = Array.isArray(raw) ? raw : [raw];
10057
+ const out = [];
10058
+ for (const v of list) {
10059
+ for (const piece of v.split(",")) {
10060
+ out.push(piece);
10061
+ }
10062
+ }
10063
+ return out;
10064
+ }
10065
+ function parseQueryEntries(query) {
10066
+ const entries = [];
10067
+ for (const [rawKey, rawValue] of Object.entries(query)) {
10068
+ if (rawValue === void 0) continue;
10069
+ const { code, modifier } = parseQueryKey(rawKey);
10070
+ entries.push({
10071
+ code,
10072
+ modifier,
10073
+ values: flattenQueryValues(rawValue)
10074
+ });
10075
+ }
10076
+ return entries;
10077
+ }
10078
+ function findParam(params, code) {
10079
+ return params.find((p) => p.code === code);
10080
+ }
10081
+ function checkModifierAllowed(modifier, param) {
10082
+ const universal = modifier === "missing" || modifier === "not";
10083
+ const stringNative = param.type === "string" && (modifier === "exact" || modifier === "contains");
10084
+ if (universal || stringNative) {
10085
+ if (param.modifiers && !param.modifiers.includes(modifier)) {
10086
+ throw new Error(
10087
+ `Modifier ":${modifier}" is not in the allow-list for param "${param.code}".`
10088
+ );
10089
+ }
10090
+ return;
10091
+ }
10092
+ throw new Error(
10093
+ `Modifier ":${modifier}" is not recognized for param "${param.code}" (type "${param.type}").`
10094
+ );
10095
+ }
10096
+ function emitOne(opts) {
10097
+ const { param, modifier, rawValue, paramName, context } = opts;
10098
+ if (modifier === "missing") {
10099
+ const missing = parseMissingValue(rawValue);
10100
+ return emitFieldMissingPredicate({
10101
+ jsonbPath: param.jsonbPath,
10102
+ missing
10103
+ });
10104
+ }
10105
+ const negate = modifier === "not";
10106
+ const effectiveModifier = negate ? void 0 : modifier;
10107
+ const inner = emitForType({
10108
+ paramType: param.type,
10109
+ jsonbPath: param.jsonbPath,
10110
+ rawValue,
10111
+ paramName,
10112
+ modifier: effectiveModifier,
10113
+ context
10023
10114
  });
10024
- const sql = buildSearchAppointmentsByActorSql({ dateConstraints });
10025
- const queryParams = [
10026
- { name: "tenantId", value: tenantId },
10027
- { name: "workspaceId", value: workspaceId },
10028
- { name: "containmentRelative", value: containmentRelative },
10029
- { name: "containmentUrn", value: containmentUrn },
10030
- { name: "limit", value: limit },
10031
- ...buildAppointmentDateSearchPredicateParams(dateConstraints)
10032
- ];
10033
- const rows = await runner.query(sql, queryParams);
10034
- const entries = rows.map((row) => ({
10035
- id: row.id,
10036
- resource: {
10037
- ...row.resource,
10038
- id: row.id
10115
+ if (!negate) {
10116
+ return inner;
10117
+ }
10118
+ return { sql: `NOT (${inner.sql})`, params: inner.params };
10119
+ }
10120
+ function parseMissingValue(raw) {
10121
+ if (raw === "true") return true;
10122
+ if (raw === "false") return false;
10123
+ throw new Error(
10124
+ `:missing requires a value of "true" or "false"; received "${raw}".`
10125
+ );
10126
+ }
10127
+ function emitForType(opts) {
10128
+ switch (opts.paramType) {
10129
+ case "token":
10130
+ return emitTokenPredicate({
10131
+ jsonbPath: opts.jsonbPath,
10132
+ rawValue: opts.rawValue,
10133
+ paramName: opts.paramName,
10134
+ context: opts.context
10135
+ });
10136
+ case "date":
10137
+ return emitDatePredicate({
10138
+ jsonbPath: opts.jsonbPath,
10139
+ rawValue: opts.rawValue,
10140
+ paramName: opts.paramName,
10141
+ context: opts.context
10142
+ });
10143
+ case "reference":
10144
+ return emitReferencePredicate({
10145
+ jsonbPath: opts.jsonbPath,
10146
+ rawValue: opts.rawValue,
10147
+ paramName: opts.paramName,
10148
+ context: opts.context
10149
+ });
10150
+ case "string":
10151
+ return emitStringPredicate({
10152
+ jsonbPath: opts.jsonbPath,
10153
+ rawValue: opts.rawValue,
10154
+ paramName: opts.paramName,
10155
+ modifier: opts.modifier,
10156
+ context: opts.context
10157
+ });
10158
+ }
10159
+ }
10160
+ function combineSearchPredicates(opts) {
10161
+ const entries = parseQueryEntries(opts.query);
10162
+ const groupSqls = [];
10163
+ const allParams = [];
10164
+ let paramIdx = 0;
10165
+ for (const entry of entries) {
10166
+ const registered = findParam(opts.registeredParams, entry.code);
10167
+ if (!registered) {
10168
+ continue;
10039
10169
  }
10040
- }));
10041
- return { entries, total: entries.length };
10170
+ if (entry.modifier !== void 0) {
10171
+ checkModifierAllowed(entry.modifier, registered);
10172
+ }
10173
+ if (entry.values.length === 0) {
10174
+ continue;
10175
+ }
10176
+ const fragmentSqls = [];
10177
+ let valueIdx = 0;
10178
+ for (const rawValue of entry.values) {
10179
+ const paramName = `p${paramIdx}v${valueIdx}`;
10180
+ const frag = emitOne({
10181
+ param: registered,
10182
+ modifier: entry.modifier,
10183
+ rawValue,
10184
+ paramName,
10185
+ context: opts.context
10186
+ });
10187
+ fragmentSqls.push(frag.sql);
10188
+ for (const p of frag.params) {
10189
+ allParams.push(p);
10190
+ }
10191
+ valueIdx++;
10192
+ }
10193
+ paramIdx++;
10194
+ if (fragmentSqls.length === 1) {
10195
+ groupSqls.push(fragmentSqls[0]);
10196
+ } else {
10197
+ groupSqls.push(`(${fragmentSqls.join(" OR ")})`);
10198
+ }
10199
+ }
10200
+ if (groupSqls.length === 0) {
10201
+ return { sql: "", params: [] };
10202
+ }
10203
+ return { sql: groupSqls.join(" AND "), params: allParams };
10042
10204
  }
10043
10205
 
10044
- // src/data/operations/data/appointment/appointment-search-by-date-operation.ts
10045
- var DEFAULT_LIMIT2 = 100;
10046
- function buildSearchAppointmentsByDateSql(opts) {
10047
- const datePredicates = buildAppointmentDateSearchPredicateSql(
10048
- opts.dateConstraints
10049
- );
10206
+ // src/data/search/operations/generic-search-operation.ts
10207
+ var DEFAULT_LIMIT = 100;
10208
+ function buildGenericSearchSql(opts) {
10050
10209
  const lines = [
10051
10210
  "SELECT resource_id AS id, resource",
10052
10211
  "FROM resources",
10053
10212
  "WHERE tenant_id = :tenantId",
10054
10213
  " AND workspace_id = :workspaceId",
10055
- " AND resource_type = 'Appointment'",
10214
+ " AND resource_type = :resourceType",
10056
10215
  " AND deleted_at IS NULL"
10057
10216
  ];
10058
- for (const fragment of datePredicates) {
10059
- lines.push(` AND ${fragment}`);
10217
+ if (opts.combinedSql.length > 0) {
10218
+ lines.push(` AND (${opts.combinedSql})`);
10060
10219
  }
10061
10220
  lines.push("ORDER BY last_updated DESC");
10062
10221
  lines.push("LIMIT :limit;");
10063
10222
  return lines.join("\n");
10064
10223
  }
10065
- async function searchAppointmentsByDateOperation(params) {
10066
- const { context, dateConstraints } = params;
10067
- if (dateConstraints.length === 0) {
10068
- throw new Error(
10069
- "searchAppointmentsByDateOperation requires at least one dateConstraint"
10070
- );
10071
- }
10072
- const { tenantId, workspaceId } = context;
10224
+ async function genericSearchOperation(params) {
10225
+ const { resourceType, tenantId, workspaceId, query, resolver } = params;
10073
10226
  const runner = params.runner ?? getDefaultPostgresQueryRunner();
10074
- const limit = params.limit ?? DEFAULT_LIMIT2;
10075
- const sql = buildSearchAppointmentsByDateSql({ dateConstraints });
10227
+ const limit = params.limit ?? DEFAULT_LIMIT;
10228
+ const registeredParams = resolver(resourceType, tenantId);
10229
+ const context = { tenantId, workspaceId, resourceType };
10230
+ const combined = combineSearchPredicates({
10231
+ query,
10232
+ registeredParams,
10233
+ context
10234
+ });
10235
+ const sql = buildGenericSearchSql({ combinedSql: combined.sql });
10076
10236
  const queryParams = [
10077
10237
  { name: "tenantId", value: tenantId },
10078
10238
  { name: "workspaceId", value: workspaceId },
10239
+ { name: "resourceType", value: resourceType },
10079
10240
  { name: "limit", value: limit },
10080
- ...buildAppointmentDateSearchPredicateParams(dateConstraints)
10241
+ ...combined.params
10081
10242
  ];
10082
10243
  const rows = await runner.query(sql, queryParams);
10083
10244
  const entries = rows.map((row) => ({
10084
10245
  id: row.id,
10085
- resource: {
10086
- ...row.resource,
10087
- id: row.id
10088
- }
10246
+ resource: { ...row.resource, id: row.id }
10089
10247
  }));
10090
10248
  return { entries, total: entries.length };
10091
10249
  }
10092
10250
 
10093
- // src/data/operations/data/appointment/appointment-search-by-patient-operation.ts
10094
- var DEFAULT_LIMIT3 = 100;
10095
- function buildSearchAppointmentsByPatientSql(opts) {
10096
- const datePredicates = buildAppointmentDateSearchPredicateSql(
10097
- opts?.dateConstraints ?? []
10098
- );
10099
- const lines = [
10100
- "SELECT resource_id AS id, resource",
10101
- "FROM resources",
10102
- "WHERE tenant_id = :tenantId",
10103
- " AND workspace_id = :workspaceId",
10104
- " AND resource_type = 'Appointment'",
10105
- " AND deleted_at IS NULL",
10106
- " AND (resource @> :containmentRelative::jsonb",
10107
- " OR resource @> :containmentUrn::jsonb)"
10108
- ];
10109
- for (const fragment of datePredicates) {
10110
- lines.push(` AND ${fragment}`);
10111
- }
10112
- lines.push("ORDER BY last_updated DESC");
10113
- lines.push("LIMIT :limit;");
10114
- return lines.join("\n");
10115
- }
10116
- async function searchAppointmentsByPatientOperation(params) {
10117
- const { context, patientId } = params;
10118
- const dateConstraints = params.dateConstraints ?? [];
10119
- const { tenantId, workspaceId } = context;
10120
- const runner = params.runner ?? getDefaultPostgresQueryRunner();
10121
- const limit = params.limit ?? DEFAULT_LIMIT3;
10122
- const containmentRelative = JSON.stringify({
10123
- participant: [{ actor: { reference: `Patient/${patientId}` } }]
10124
- });
10125
- const containmentUrn = JSON.stringify({
10126
- participant: [
10127
- {
10128
- actor: {
10129
- reference: buildOpenHiResourceUrn({
10130
- tenantId,
10131
- workspaceId,
10132
- resourceType: "Patient",
10133
- resourceId: patientId
10134
- })
10135
- }
10136
- }
10137
- ]
10138
- });
10139
- const sql = buildSearchAppointmentsByPatientSql({ dateConstraints });
10140
- const queryParams = [
10141
- { name: "tenantId", value: tenantId },
10142
- { name: "workspaceId", value: workspaceId },
10143
- { name: "containmentRelative", value: containmentRelative },
10144
- { name: "containmentUrn", value: containmentUrn },
10145
- { name: "limit", value: limit },
10146
- ...buildAppointmentDateSearchPredicateParams(dateConstraints)
10147
- ];
10148
- const rows = await runner.query(sql, queryParams);
10149
- const entries = rows.map((row) => ({
10150
- id: row.id,
10151
- resource: {
10152
- ...row.resource,
10153
- id: row.id
10154
- }
10155
- }));
10156
- return { entries, total: entries.length };
10157
- }
10251
+ // src/data/search/registry/appointment-search-parameters.ts
10252
+ var APPOINTMENT_SEARCH_PARAMETERS = [
10253
+ { code: "status", type: "token", jsonbPath: "$.status" },
10254
+ { code: "date", type: "date", jsonbPath: "$.start" },
10255
+ {
10256
+ code: "patient",
10257
+ type: "reference",
10258
+ jsonbPath: "$.participant[*].actor"
10259
+ },
10260
+ {
10261
+ code: "actor",
10262
+ type: "reference",
10263
+ jsonbPath: "$.participant[*].actor"
10264
+ },
10265
+ {
10266
+ code: "practitioner",
10267
+ type: "reference",
10268
+ jsonbPath: "$.participant[*].actor"
10269
+ },
10270
+ { code: "service-type", type: "token", jsonbPath: "$.serviceType[*]" },
10271
+ {
10272
+ code: "service-category",
10273
+ type: "token",
10274
+ jsonbPath: "$.serviceCategory[*]"
10275
+ },
10276
+ { code: "specialty", type: "token", jsonbPath: "$.specialty[*]" },
10277
+ {
10278
+ code: "appointment-type",
10279
+ type: "token",
10280
+ jsonbPath: "$.appointmentType"
10281
+ },
10282
+ { code: "slot", type: "reference", jsonbPath: "$.slot[*]" }
10283
+ ];
10158
10284
 
10159
- // src/data/rest-api/routes/data/appointment/appointment-list-route.ts
10160
- function singleStringQueryParam(req, name) {
10161
- const v = req.query[name];
10162
- if (typeof v !== "string") {
10163
- return void 0;
10285
+ // src/data/search/registry/patient-search-parameters.ts
10286
+ var PATIENT_SEARCH_PARAMETERS = [
10287
+ { code: "gender", type: "token", jsonbPath: "$.gender" },
10288
+ { code: "identifier", type: "token", jsonbPath: "$.identifier[*].value" },
10289
+ { code: "birthdate", type: "date", jsonbPath: "$.birthDate" },
10290
+ {
10291
+ code: "name",
10292
+ type: "string",
10293
+ jsonbPath: "$.name[*].text",
10294
+ modifiers: ["exact", "contains", "missing", "not"]
10295
+ },
10296
+ {
10297
+ code: "family",
10298
+ type: "string",
10299
+ jsonbPath: "$.name[*].family",
10300
+ modifiers: ["exact", "contains", "missing", "not"]
10301
+ },
10302
+ {
10303
+ code: "given",
10304
+ type: "string",
10305
+ jsonbPath: "$.name[*].given",
10306
+ modifiers: ["exact", "contains", "missing", "not"]
10307
+ },
10308
+ { code: "active", type: "token", jsonbPath: "$.active" },
10309
+ { code: "deceased", type: "token", jsonbPath: "$.deceasedBoolean" },
10310
+ {
10311
+ code: "general-practitioner",
10312
+ type: "reference",
10313
+ jsonbPath: "$.generalPractitioner[*]"
10314
+ },
10315
+ {
10316
+ code: "organization",
10317
+ type: "reference",
10318
+ jsonbPath: "$.managingOrganization"
10164
10319
  }
10165
- const trimmed = v.trim();
10166
- return trimmed === "" ? void 0 : trimmed;
10320
+ ];
10321
+
10322
+ // src/data/search/registry/resolver.ts
10323
+ var STATIC_SEARCH_PARAMETER_MAP = {
10324
+ Appointment: APPOINTMENT_SEARCH_PARAMETERS,
10325
+ Patient: PATIENT_SEARCH_PARAMETERS
10326
+ };
10327
+ var defaultSearchParameterResolver = (resourceType, _tenantId) => STATIC_SEARCH_PARAMETER_MAP[resourceType] ?? [];
10328
+ function getRegisteredSearchParameters(resourceType) {
10329
+ return STATIC_SEARCH_PARAMETER_MAP[resourceType] ?? [];
10167
10330
  }
10168
- function isError(v) {
10169
- return v.error !== void 0;
10331
+
10332
+ // src/data/rest-api/routes/data/appointment/appointment-list-route.ts
10333
+ var APPOINTMENT_RESOURCE_TYPE = "Appointment";
10334
+ function stripModifier(key) {
10335
+ const idx = key.indexOf(":");
10336
+ return idx === -1 ? key : key.slice(0, idx);
10170
10337
  }
10171
- function parseAppointmentDateConstraints(req) {
10172
- const raw = req.query.date;
10173
- if (raw === void 0) {
10174
- return [];
10175
- }
10176
- const values = Array.isArray(raw) ? raw : [raw];
10177
- const out = [];
10178
- for (const v of values) {
10179
- if (typeof v !== "string") {
10180
- return { error: "Each ?date= value must be a string." };
10181
- }
10182
- const trimmed = v.trim();
10183
- if (trimmed === "") {
10184
- return { error: "?date= value must not be empty." };
10185
- }
10186
- const prefix = trimmed.slice(0, 2);
10187
- const datetime = trimmed.slice(2);
10188
- if (!isAppointmentDateSearchPrefix(prefix)) {
10189
- return {
10190
- error: `Unsupported ?date= prefix in "${trimmed}". Supported prefixes: ${APPOINTMENT_DATE_SEARCH_PREFIXES.join(", ")}.`
10191
- };
10192
- }
10193
- if (datetime === "" || Number.isNaN(Date.parse(datetime))) {
10194
- return { error: `Invalid datetime in ?date=${trimmed}.` };
10195
- }
10196
- out.push({ prefix, value: datetime });
10197
- }
10198
- return out;
10338
+ function isResultParameter(key) {
10339
+ return key.startsWith("_");
10199
10340
  }
10200
10341
  function sendInvalidSearch400(res, diagnostics) {
10201
10342
  return res.status(400).json({
@@ -10203,95 +10344,93 @@ function sendInvalidSearch400(res, diagnostics) {
10203
10344
  issue: [{ severity: "error", code: "invalid", diagnostics }]
10204
10345
  });
10205
10346
  }
10347
+ function extractSearchParamKeys(query) {
10348
+ const out = [];
10349
+ for (const rawKey of Object.keys(query)) {
10350
+ if (isResultParameter(rawKey)) {
10351
+ continue;
10352
+ }
10353
+ out.push({ rawKey, code: stripModifier(rawKey) });
10354
+ }
10355
+ return out;
10356
+ }
10357
+ function buildUnknownParamDiagnostics(unknownCodes) {
10358
+ const validCodes = getRegisteredSearchParameters(APPOINTMENT_RESOURCE_TYPE).map((p) => p.code).sort().join(", ");
10359
+ const codes = unknownCodes.join(", ");
10360
+ const isPlural = unknownCodes.length !== 1;
10361
+ return [
10362
+ `Unknown search ${isPlural ? "parameters" : "parameter"} for Appointment: ${codes}.`,
10363
+ `Valid codes: ${validCodes}.`
10364
+ ].join(" ");
10365
+ }
10366
+ function findMalformedReference(query, searchParamKeys) {
10367
+ const referenceCodes = new Set(
10368
+ getRegisteredSearchParameters(APPOINTMENT_RESOURCE_TYPE).filter((p) => p.type === "reference").map((p) => p.code)
10369
+ );
10370
+ for (const { rawKey, code } of searchParamKeys) {
10371
+ if (!referenceCodes.has(code)) {
10372
+ continue;
10373
+ }
10374
+ const raw = query[rawKey];
10375
+ const values = typeof raw === "string" ? raw.split(",") : Array.isArray(raw) ? raw.flatMap((v) => v.split(",")) : [];
10376
+ for (const v of values) {
10377
+ const trimmed = v.trim();
10378
+ if (trimmed.length === 0) {
10379
+ continue;
10380
+ }
10381
+ if (parseTypedReference(trimmed) === void 0) {
10382
+ return { rawKey, value: trimmed };
10383
+ }
10384
+ }
10385
+ }
10386
+ return void 0;
10387
+ }
10206
10388
  async function listAppointmentsRoute(req, res) {
10207
- const patientId = singleStringQueryParam(req, "patient");
10208
- const actorRef = singleStringQueryParam(req, "actor");
10209
- const parsed = parseAppointmentDateConstraints(req);
10210
- if (isError(parsed)) {
10211
- return sendInvalidSearch400(res, parsed.error);
10389
+ const searchParamKeys = extractSearchParamKeys(
10390
+ req.query
10391
+ );
10392
+ if (searchParamKeys.length === 0) {
10393
+ return handleListRoute({
10394
+ req,
10395
+ res,
10396
+ basePath: BASE_PATH.APPOINTMENT,
10397
+ listOperation: listAppointmentsOperation,
10398
+ errorLogContext: "GET /Appointment list error:"
10399
+ });
10212
10400
  }
10213
- const dateConstraints = parsed;
10214
- if (patientId !== void 0 && actorRef !== void 0) {
10401
+ const registered = getRegisteredSearchParameters(APPOINTMENT_RESOURCE_TYPE);
10402
+ const validCodes = new Set(registered.map((p) => p.code));
10403
+ const unknownCodes = searchParamKeys.map((k) => k.code).filter((code) => !validCodes.has(code));
10404
+ if (unknownCodes.length > 0) {
10215
10405
  return sendInvalidSearch400(
10216
10406
  res,
10217
- "?patient= and ?actor= cannot be combined on the same request."
10407
+ buildUnknownParamDiagnostics([...new Set(unknownCodes)])
10218
10408
  );
10219
10409
  }
10220
- if (actorRef !== void 0) {
10221
- if (parseTypedReference(actorRef) === void 0) {
10222
- return sendInvalidSearch400(
10223
- res,
10224
- `?actor must be a typed reference like "Practitioner/<id>"; got "${actorRef}".`
10225
- );
10226
- }
10227
- const ctx = req.openhiContext;
10228
- try {
10229
- const result = await searchAppointmentsByActorOperation({
10230
- context: ctx,
10231
- actorReference: actorRef,
10232
- dateConstraints
10233
- });
10234
- const bundle = buildSearchsetBundle(
10235
- BASE_PATH.APPOINTMENT,
10236
- result.entries
10237
- );
10238
- return res.json(bundle);
10239
- } catch (err) {
10240
- return sendOperationOutcome500(
10241
- res,
10242
- err,
10243
- "GET /Appointment?actor= search error:"
10244
- );
10245
- }
10246
- }
10247
- if (patientId !== void 0) {
10248
- const ctx = req.openhiContext;
10249
- try {
10250
- const result = await searchAppointmentsByPatientOperation({
10251
- context: ctx,
10252
- patientId,
10253
- dateConstraints
10254
- });
10255
- const bundle = buildSearchsetBundle(
10256
- BASE_PATH.APPOINTMENT,
10257
- result.entries
10258
- );
10259
- return res.json(bundle);
10260
- } catch (err) {
10261
- return sendOperationOutcome500(
10262
- res,
10263
- err,
10264
- "GET /Appointment?patient= search error:"
10265
- );
10266
- }
10410
+ const malformedRef = findMalformedReference(
10411
+ req.query,
10412
+ searchParamKeys
10413
+ );
10414
+ if (malformedRef !== void 0) {
10415
+ return sendInvalidSearch400(
10416
+ res,
10417
+ `?${malformedRef.rawKey} must be a typed reference like "Practitioner/<id>"; got "${malformedRef.value}".`
10418
+ );
10267
10419
  }
10268
- if (dateConstraints.length > 0) {
10269
- const ctx = req.openhiContext;
10270
- try {
10271
- const result = await searchAppointmentsByDateOperation({
10272
- context: ctx,
10273
- dateConstraints
10274
- });
10275
- const bundle = buildSearchsetBundle(
10276
- BASE_PATH.APPOINTMENT,
10277
- result.entries
10278
- );
10279
- return res.json(bundle);
10280
- } catch (err) {
10281
- return sendOperationOutcome500(
10282
- res,
10283
- err,
10284
- "GET /Appointment?date= search error:"
10285
- );
10286
- }
10420
+ const ctx = req.openhiContext;
10421
+ try {
10422
+ const result = await genericSearchOperation({
10423
+ resourceType: APPOINTMENT_RESOURCE_TYPE,
10424
+ tenantId: ctx.tenantId,
10425
+ workspaceId: ctx.workspaceId,
10426
+ query: req.query,
10427
+ resolver: defaultSearchParameterResolver
10428
+ });
10429
+ const bundle = buildSearchsetBundle(BASE_PATH.APPOINTMENT, result.entries);
10430
+ return res.json(bundle);
10431
+ } catch (err) {
10432
+ return sendOperationOutcome500(res, err, "GET /Appointment search error:");
10287
10433
  }
10288
- return handleListRoute({
10289
- req,
10290
- res,
10291
- basePath: BASE_PATH.APPOINTMENT,
10292
- listOperation: listAppointmentsOperation,
10293
- errorLogContext: "GET /Appointment list error:"
10294
- });
10295
10434
  }
10296
10435
 
10297
10436
  // src/data/operations/data/appointment/appointment-update-operation.ts
@@ -17886,7 +18025,7 @@ function buildPeriodSearchPredicateParams(constraints) {
17886
18025
  }
17887
18026
 
17888
18027
  // src/data/operations/data/encounter/encounter-search-by-date-operation.ts
17889
- var DEFAULT_LIMIT4 = 100;
18028
+ var DEFAULT_LIMIT2 = 100;
17890
18029
  function buildSearchEncountersByDateSql(opts) {
17891
18030
  const periodPredicates = buildPeriodSearchPredicateSql(
17892
18031
  opts.periodConstraints
@@ -17915,7 +18054,7 @@ async function searchEncountersByDateOperation(params) {
17915
18054
  }
17916
18055
  const { tenantId, workspaceId } = context;
17917
18056
  const runner = params.runner ?? getDefaultPostgresQueryRunner();
17918
- const limit = params.limit ?? DEFAULT_LIMIT4;
18057
+ const limit = params.limit ?? DEFAULT_LIMIT2;
17919
18058
  const sql = buildSearchEncountersByDateSql({ periodConstraints });
17920
18059
  const queryParams = [
17921
18060
  { name: "tenantId", value: tenantId },
@@ -17935,7 +18074,7 @@ async function searchEncountersByDateOperation(params) {
17935
18074
  }
17936
18075
 
17937
18076
  // src/data/operations/data/encounter/encounter-search-by-participant-operation.ts
17938
- var DEFAULT_LIMIT5 = 100;
18077
+ var DEFAULT_LIMIT3 = 100;
17939
18078
  function buildSearchEncountersByParticipantSql(opts) {
17940
18079
  const periodPredicates = buildPeriodSearchPredicateSql(
17941
18080
  opts?.periodConstraints ?? []
@@ -17961,7 +18100,7 @@ async function searchEncountersByParticipantOperation(params) {
17961
18100
  const periodConstraints = params.periodConstraints ?? [];
17962
18101
  const { tenantId, workspaceId } = context;
17963
18102
  const runner = params.runner ?? getDefaultPostgresQueryRunner();
17964
- const limit = params.limit ?? DEFAULT_LIMIT5;
18103
+ const limit = params.limit ?? DEFAULT_LIMIT3;
17965
18104
  const { containmentRelative, containmentUrn } = buildReferenceContainmentPayload({
17966
18105
  shape: {
17967
18106
  kind: "array-of-objects",
@@ -17993,7 +18132,7 @@ async function searchEncountersByParticipantOperation(params) {
17993
18132
  }
17994
18133
 
17995
18134
  // src/data/operations/data/encounter/encounter-search-by-patient-operation.ts
17996
- var DEFAULT_LIMIT6 = 100;
18135
+ var DEFAULT_LIMIT4 = 100;
17997
18136
  function buildSearchEncountersByPatientSql(opts) {
17998
18137
  const periodPredicates = buildPeriodSearchPredicateSql(
17999
18138
  opts?.periodConstraints ?? []
@@ -18020,7 +18159,7 @@ async function searchEncountersByPatientOperation(params) {
18020
18159
  const periodConstraints = params.periodConstraints ?? [];
18021
18160
  const { tenantId, workspaceId } = context;
18022
18161
  const runner = params.runner ?? getDefaultPostgresQueryRunner();
18023
- const limit = params.limit ?? DEFAULT_LIMIT6;
18162
+ const limit = params.limit ?? DEFAULT_LIMIT4;
18024
18163
  const containmentRelative = JSON.stringify({
18025
18164
  subject: { reference: `Patient/${patientId}` }
18026
18165
  });
@@ -18055,7 +18194,7 @@ async function searchEncountersByPatientOperation(params) {
18055
18194
  }
18056
18195
 
18057
18196
  // src/data/rest-api/routes/data/encounter/encounter-list-route.ts
18058
- function singleStringQueryParam2(req, name) {
18197
+ function singleStringQueryParam(req, name) {
18059
18198
  const v = req.query[name];
18060
18199
  if (typeof v !== "string") {
18061
18200
  return void 0;
@@ -18063,7 +18202,7 @@ function singleStringQueryParam2(req, name) {
18063
18202
  const trimmed = v.trim();
18064
18203
  return trimmed === "" ? void 0 : trimmed;
18065
18204
  }
18066
- function isError2(v) {
18205
+ function isError(v) {
18067
18206
  return v.error !== void 0;
18068
18207
  }
18069
18208
  function parseEncounterDateConstraints(req) {
@@ -18102,10 +18241,10 @@ function sendInvalidSearch4002(res, diagnostics) {
18102
18241
  });
18103
18242
  }
18104
18243
  async function listEncountersRoute(req, res) {
18105
- const patientId = singleStringQueryParam2(req, "patient");
18106
- const participantRef = singleStringQueryParam2(req, "participant");
18244
+ const patientId = singleStringQueryParam(req, "patient");
18245
+ const participantRef = singleStringQueryParam(req, "participant");
18107
18246
  const parsed = parseEncounterDateConstraints(req);
18108
- if (isError2(parsed)) {
18247
+ if (isError(parsed)) {
18109
18248
  return sendInvalidSearch4002(res, parsed.error);
18110
18249
  }
18111
18250
  const periodConstraints = parsed;
@@ -29821,487 +29960,104 @@ async function createPatientOperation(params) {
29821
29960
  date
29822
29961
  );
29823
29962
  }
29824
-
29825
- // src/data/rest-api/routes/data/patient/patient-create-route.ts
29826
- async function createPatientRoute(req, res) {
29827
- const bodyResult = requireJsonBodyAs(req, res);
29828
- if ("errorResponse" in bodyResult) return bodyResult.errorResponse;
29829
- const ctx = req.openhiContext;
29830
- const body = bodyResult.body;
29831
- const patient = {
29832
- ...body,
29833
- resourceType: "Patient"
29834
- };
29835
- try {
29836
- const result = await createPatientOperation({
29837
- context: ctx,
29838
- body: patient
29839
- });
29840
- return res.status(201).location(`${BASE_PATH.PATIENT}/${result.id}`).json(result.resource);
29841
- } catch (err) {
29842
- return sendOperationOutcome500(res, err, "POST Patient error:");
29843
- }
29844
- }
29845
-
29846
- // src/data/operations/data/patient/patient-delete-operation.ts
29847
- async function deletePatientOperation(params) {
29848
- const { context, id, tableName } = params;
29849
- const { tenantId, workspaceId } = context;
29850
- const service = getDynamoDataService(tableName);
29851
- await deleteDataEntityById(
29852
- service.entities.patient,
29853
- tenantId,
29854
- workspaceId,
29855
- id
29856
- );
29857
- }
29858
-
29859
- // src/data/rest-api/routes/data/patient/patient-delete-route.ts
29860
- async function deletePatientRoute(req, res) {
29861
- const id = String(req.params.id);
29862
- const ctx = req.openhiContext;
29863
- try {
29864
- await deletePatientOperation({ context: ctx, id });
29865
- return res.status(204).send();
29866
- } catch (err) {
29867
- return sendOperationOutcome500(res, err, "DELETE Patient error:");
29868
- }
29869
- }
29870
-
29871
- // src/data/operations/data/patient/patient-get-by-id-operation.ts
29872
- async function getPatientByIdOperation(params) {
29873
- const { context, id, tableName } = params;
29874
- const { tenantId, workspaceId } = context;
29875
- const service = getDynamoDataService(tableName);
29876
- return getDataEntityById(
29877
- service.entities.patient,
29878
- tenantId,
29879
- workspaceId,
29880
- id,
29881
- "Patient"
29882
- );
29883
- }
29884
-
29885
- // src/data/rest-api/routes/data/patient/patient-get-by-id-route.ts
29886
- async function getPatientByIdRoute(req, res) {
29887
- const id = String(req.params.id);
29888
- const ctx = req.openhiContext;
29889
- try {
29890
- const result = await getPatientByIdOperation({ context: ctx, id });
29891
- return res.json(result.resource);
29892
- } catch (err) {
29893
- const status = domainErrorToHttpStatus(err);
29894
- if (status === 404) {
29895
- const diagnostics = err instanceof NotFoundError ? err.message : `Patient ${id} not found`;
29896
- return sendOperationOutcome404(res, diagnostics);
29897
- }
29898
- return sendOperationOutcome500(res, err, "GET Patient error:");
29899
- }
29900
- }
29901
-
29902
- // src/data/operations/data/patient/patient-list-operation.ts
29903
- async function listPatientsOperation(params) {
29904
- const { context, tableName, mode } = params;
29905
- const { tenantId, workspaceId } = context;
29906
- const service = getDynamoDataService(tableName);
29907
- return listDataEntitiesByWorkspace(
29908
- service.entities.patient,
29909
- tenantId,
29910
- workspaceId,
29911
- mode
29912
- );
29913
- }
29914
-
29915
- // src/data/search/engine/string-predicate.ts
29916
- var STRING_MODIFIERS = ["exact", "contains"];
29917
- function isStringModifier(s) {
29918
- return STRING_MODIFIERS.includes(s);
29919
- }
29920
- function jsonbPathToStringShape(jsonbPath) {
29921
- const scalar = /^\$\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(jsonbPath);
29922
- if (scalar) {
29923
- return { kind: "scalar", field: scalar[1] };
29924
- }
29925
- const arrayOfScalars = /^\$\.([A-Za-z_][A-Za-z0-9_]*)\[\*\]$/.exec(jsonbPath);
29926
- if (arrayOfScalars) {
29927
- return { kind: "array-of-scalars", field: arrayOfScalars[1] };
29928
- }
29929
- const arrayOfObjs = /^\$\.([A-Za-z_][A-Za-z0-9_]*)\[\*\]\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(
29930
- jsonbPath
29931
- );
29932
- if (arrayOfObjs) {
29933
- return {
29934
- kind: "array-of-objects",
29935
- field: arrayOfObjs[1],
29936
- subfield: arrayOfObjs[2]
29937
- };
29938
- }
29939
- throw new Error(
29940
- `String predicate cannot translate JSONPath "${jsonbPath}". Supported shapes: "$.field", "$.field[*]", "$.field[*].subfield".`
29941
- );
29942
- }
29943
- function escapeLikePattern(value) {
29944
- return value.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
29945
- }
29946
- function buildLikePattern(value, modifier) {
29947
- const escaped = escapeLikePattern(value);
29948
- switch (modifier) {
29949
- case "exact":
29950
- return escaped;
29951
- case "contains":
29952
- return `%${escaped}%`;
29953
- case void 0:
29954
- return `${escaped}%`;
29955
- }
29956
- }
29957
- function buildIlikeExtractSql(shape, paramName) {
29958
- switch (shape.kind) {
29959
- case "scalar":
29960
- return `resource->>'${shape.field}' ILIKE :${paramName}`;
29961
- case "array-of-scalars":
29962
- return [
29963
- "EXISTS (SELECT 1 FROM",
29964
- `jsonb_array_elements_text(resource->'${shape.field}') AS s_elem(text_val)`,
29965
- `WHERE s_elem.text_val ILIKE :${paramName})`
29966
- ].join(" ");
29967
- case "array-of-objects":
29968
- return [
29969
- "EXISTS (SELECT 1 FROM",
29970
- `jsonb_array_elements(resource->'${shape.field}') AS s_obj(obj)`,
29971
- `WHERE s_obj.obj->>'${shape.subfield}' ILIKE :${paramName})`
29972
- ].join(" ");
29973
- }
29974
- }
29975
- function emitStringPredicate(opts) {
29976
- const shape = jsonbPathToStringShape(opts.jsonbPath);
29977
- const modifier = opts.modifier === void 0 ? void 0 : checkModifier(opts.modifier);
29978
- const sql = buildIlikeExtractSql(shape, opts.paramName);
29979
- const pattern = buildLikePattern(opts.rawValue, modifier);
29980
- const params = [
29981
- { name: opts.paramName, value: pattern }
29982
- ];
29983
- return { sql, params };
29984
- }
29985
- function checkModifier(modifier) {
29986
- if (!isStringModifier(modifier)) {
29987
- throw new Error(
29988
- `String predicate does not support modifier ":${modifier}". Supported: ${STRING_MODIFIERS.map((m) => `:${m}`).join(", ")}.`
29989
- );
29990
- }
29991
- return modifier;
29992
- }
29993
-
29994
- // src/data/search/engine/token-predicate.ts
29995
- function jsonbPathToTokenShape(jsonbPath) {
29996
- const scalar = /^\$\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(jsonbPath);
29997
- if (scalar) {
29998
- return { kind: "scalar", field: scalar[1] };
29999
- }
30000
- const arrayOfScalars = /^\$\.([A-Za-z_][A-Za-z0-9_]*)\[\*\]$/.exec(jsonbPath);
30001
- if (arrayOfScalars) {
30002
- return { kind: "array-of-scalars", field: arrayOfScalars[1] };
30003
- }
30004
- const arrayOfObjs = /^\$\.([A-Za-z_][A-Za-z0-9_]*)\[\*\]\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(
30005
- jsonbPath
30006
- );
30007
- if (arrayOfObjs) {
30008
- return {
30009
- kind: "array-of-objects",
30010
- field: arrayOfObjs[1],
30011
- subfield: arrayOfObjs[2]
30012
- };
30013
- }
30014
- throw new Error(
30015
- `Token predicate cannot translate JSONPath "${jsonbPath}". Supported shapes: "$.field", "$.field[*]", "$.field[*].subfield".`
30016
- );
30017
- }
30018
- function parseTokenValue(raw) {
30019
- const idx = raw.indexOf("|");
30020
- if (idx === -1) {
30021
- return { code: raw };
30022
- }
30023
- const system = raw.slice(0, idx);
30024
- const code = raw.slice(idx + 1);
30025
- return system.length > 0 ? { system, code } : { code };
30026
- }
30027
- function wrapTokenInShape(shape, value) {
30028
- switch (shape.kind) {
30029
- case "scalar":
30030
- return { [shape.field]: value };
30031
- case "array-of-scalars":
30032
- return { [shape.field]: [value] };
30033
- case "array-of-objects":
30034
- return { [shape.field]: [{ [shape.subfield]: value }] };
30035
- }
30036
- }
30037
- function emitTokenPredicate(opts) {
30038
- const shape = jsonbPathToTokenShape(opts.jsonbPath);
30039
- const { code } = parseTokenValue(opts.rawValue);
30040
- const payload = wrapTokenInShape(shape, code);
30041
- const sql = `resource @> :${opts.paramName}::jsonb`;
30042
- const params = [
30043
- { name: opts.paramName, value: JSON.stringify(payload) }
30044
- ];
30045
- return { sql, params };
30046
- }
30047
-
30048
- // src/data/search/engine/combinator.ts
30049
- function parseQueryKey(key) {
30050
- const idx = key.indexOf(":");
30051
- if (idx === -1) {
30052
- return { code: key, modifier: void 0 };
30053
- }
30054
- return { code: key.slice(0, idx), modifier: key.slice(idx + 1) };
30055
- }
30056
- function flattenQueryValues(raw) {
30057
- const list = Array.isArray(raw) ? raw : [raw];
30058
- const out = [];
30059
- for (const v of list) {
30060
- for (const piece of v.split(",")) {
30061
- out.push(piece);
30062
- }
30063
- }
30064
- return out;
30065
- }
30066
- function parseQueryEntries(query) {
30067
- const entries = [];
30068
- for (const [rawKey, rawValue] of Object.entries(query)) {
30069
- if (rawValue === void 0) continue;
30070
- const { code, modifier } = parseQueryKey(rawKey);
30071
- entries.push({
30072
- code,
30073
- modifier,
30074
- values: flattenQueryValues(rawValue)
30075
- });
30076
- }
30077
- return entries;
30078
- }
30079
- function findParam(params, code) {
30080
- return params.find((p) => p.code === code);
30081
- }
30082
- function checkModifierAllowed(modifier, param) {
30083
- const universal = modifier === "missing" || modifier === "not";
30084
- const stringNative = param.type === "string" && (modifier === "exact" || modifier === "contains");
30085
- if (universal || stringNative) {
30086
- if (param.modifiers && !param.modifiers.includes(modifier)) {
30087
- throw new Error(
30088
- `Modifier ":${modifier}" is not in the allow-list for param "${param.code}".`
30089
- );
30090
- }
30091
- return;
30092
- }
30093
- throw new Error(
30094
- `Modifier ":${modifier}" is not recognized for param "${param.code}" (type "${param.type}").`
30095
- );
30096
- }
30097
- function emitOne(opts) {
30098
- const { param, modifier, rawValue, paramName, context } = opts;
30099
- if (modifier === "missing") {
30100
- const missing = parseMissingValue(rawValue);
30101
- return emitFieldMissingPredicate({
30102
- jsonbPath: param.jsonbPath,
30103
- missing
30104
- });
30105
- }
30106
- const negate = modifier === "not";
30107
- const effectiveModifier = negate ? void 0 : modifier;
30108
- const inner = emitForType({
30109
- paramType: param.type,
30110
- jsonbPath: param.jsonbPath,
30111
- rawValue,
30112
- paramName,
30113
- modifier: effectiveModifier,
30114
- context
30115
- });
30116
- if (!negate) {
30117
- return inner;
30118
- }
30119
- return { sql: `NOT (${inner.sql})`, params: inner.params };
30120
- }
30121
- function parseMissingValue(raw) {
30122
- if (raw === "true") return true;
30123
- if (raw === "false") return false;
30124
- throw new Error(
30125
- `:missing requires a value of "true" or "false"; received "${raw}".`
30126
- );
30127
- }
30128
- function emitForType(opts) {
30129
- switch (opts.paramType) {
30130
- case "token":
30131
- return emitTokenPredicate({
30132
- jsonbPath: opts.jsonbPath,
30133
- rawValue: opts.rawValue,
30134
- paramName: opts.paramName,
30135
- context: opts.context
30136
- });
30137
- case "date":
30138
- return emitDatePredicate({
30139
- jsonbPath: opts.jsonbPath,
30140
- rawValue: opts.rawValue,
30141
- paramName: opts.paramName,
30142
- context: opts.context
30143
- });
30144
- case "reference":
30145
- return emitReferencePredicate({
30146
- jsonbPath: opts.jsonbPath,
30147
- rawValue: opts.rawValue,
30148
- paramName: opts.paramName,
30149
- context: opts.context
30150
- });
30151
- case "string":
30152
- return emitStringPredicate({
30153
- jsonbPath: opts.jsonbPath,
30154
- rawValue: opts.rawValue,
30155
- paramName: opts.paramName,
30156
- modifier: opts.modifier,
30157
- context: opts.context
30158
- });
29963
+
29964
+ // src/data/rest-api/routes/data/patient/patient-create-route.ts
29965
+ async function createPatientRoute(req, res) {
29966
+ const bodyResult = requireJsonBodyAs(req, res);
29967
+ if ("errorResponse" in bodyResult) return bodyResult.errorResponse;
29968
+ const ctx = req.openhiContext;
29969
+ const body = bodyResult.body;
29970
+ const patient = {
29971
+ ...body,
29972
+ resourceType: "Patient"
29973
+ };
29974
+ try {
29975
+ const result = await createPatientOperation({
29976
+ context: ctx,
29977
+ body: patient
29978
+ });
29979
+ return res.status(201).location(`${BASE_PATH.PATIENT}/${result.id}`).json(result.resource);
29980
+ } catch (err) {
29981
+ return sendOperationOutcome500(res, err, "POST Patient error:");
30159
29982
  }
30160
29983
  }
30161
- function combineSearchPredicates(opts) {
30162
- const entries = parseQueryEntries(opts.query);
30163
- const groupSqls = [];
30164
- const allParams = [];
30165
- let paramIdx = 0;
30166
- for (const entry of entries) {
30167
- const registered = findParam(opts.registeredParams, entry.code);
30168
- if (!registered) {
30169
- continue;
30170
- }
30171
- if (entry.modifier !== void 0) {
30172
- checkModifierAllowed(entry.modifier, registered);
30173
- }
30174
- if (entry.values.length === 0) {
30175
- continue;
30176
- }
30177
- const fragmentSqls = [];
30178
- let valueIdx = 0;
30179
- for (const rawValue of entry.values) {
30180
- const paramName = `p${paramIdx}v${valueIdx}`;
30181
- const frag = emitOne({
30182
- param: registered,
30183
- modifier: entry.modifier,
30184
- rawValue,
30185
- paramName,
30186
- context: opts.context
30187
- });
30188
- fragmentSqls.push(frag.sql);
30189
- for (const p of frag.params) {
30190
- allParams.push(p);
30191
- }
30192
- valueIdx++;
30193
- }
30194
- paramIdx++;
30195
- if (fragmentSqls.length === 1) {
30196
- groupSqls.push(fragmentSqls[0]);
30197
- } else {
30198
- groupSqls.push(`(${fragmentSqls.join(" OR ")})`);
30199
- }
30200
- }
30201
- if (groupSqls.length === 0) {
30202
- return { sql: "", params: [] };
30203
- }
30204
- return { sql: groupSqls.join(" AND "), params: allParams };
29984
+
29985
+ // src/data/operations/data/patient/patient-delete-operation.ts
29986
+ async function deletePatientOperation(params) {
29987
+ const { context, id, tableName } = params;
29988
+ const { tenantId, workspaceId } = context;
29989
+ const service = getDynamoDataService(tableName);
29990
+ await deleteDataEntityById(
29991
+ service.entities.patient,
29992
+ tenantId,
29993
+ workspaceId,
29994
+ id
29995
+ );
30205
29996
  }
30206
29997
 
30207
- // src/data/search/operations/generic-search-operation.ts
30208
- var DEFAULT_LIMIT7 = 100;
30209
- function buildGenericSearchSql(opts) {
30210
- const lines = [
30211
- "SELECT resource_id AS id, resource",
30212
- "FROM resources",
30213
- "WHERE tenant_id = :tenantId",
30214
- " AND workspace_id = :workspaceId",
30215
- " AND resource_type = :resourceType",
30216
- " AND deleted_at IS NULL"
30217
- ];
30218
- if (opts.combinedSql.length > 0) {
30219
- lines.push(` AND (${opts.combinedSql})`);
29998
+ // src/data/rest-api/routes/data/patient/patient-delete-route.ts
29999
+ async function deletePatientRoute(req, res) {
30000
+ const id = String(req.params.id);
30001
+ const ctx = req.openhiContext;
30002
+ try {
30003
+ await deletePatientOperation({ context: ctx, id });
30004
+ return res.status(204).send();
30005
+ } catch (err) {
30006
+ return sendOperationOutcome500(res, err, "DELETE Patient error:");
30220
30007
  }
30221
- lines.push("ORDER BY last_updated DESC");
30222
- lines.push("LIMIT :limit;");
30223
- return lines.join("\n");
30224
30008
  }
30225
- async function genericSearchOperation(params) {
30226
- const { resourceType, tenantId, workspaceId, query, resolver } = params;
30227
- const runner = params.runner ?? getDefaultPostgresQueryRunner();
30228
- const limit = params.limit ?? DEFAULT_LIMIT7;
30229
- const registeredParams = resolver(resourceType, tenantId);
30230
- const context = { tenantId, workspaceId, resourceType };
30231
- const combined = combineSearchPredicates({
30232
- query,
30233
- registeredParams,
30234
- context
30235
- });
30236
- const sql = buildGenericSearchSql({ combinedSql: combined.sql });
30237
- const queryParams = [
30238
- { name: "tenantId", value: tenantId },
30239
- { name: "workspaceId", value: workspaceId },
30240
- { name: "resourceType", value: resourceType },
30241
- { name: "limit", value: limit },
30242
- ...combined.params
30243
- ];
30244
- const rows = await runner.query(sql, queryParams);
30245
- const entries = rows.map((row) => ({
30246
- id: row.id,
30247
- resource: { ...row.resource, id: row.id }
30248
- }));
30249
- return { entries, total: entries.length };
30009
+
30010
+ // src/data/operations/data/patient/patient-get-by-id-operation.ts
30011
+ async function getPatientByIdOperation(params) {
30012
+ const { context, id, tableName } = params;
30013
+ const { tenantId, workspaceId } = context;
30014
+ const service = getDynamoDataService(tableName);
30015
+ return getDataEntityById(
30016
+ service.entities.patient,
30017
+ tenantId,
30018
+ workspaceId,
30019
+ id,
30020
+ "Patient"
30021
+ );
30250
30022
  }
30251
30023
 
30252
- // src/data/search/registry/patient-search-parameters.ts
30253
- var PATIENT_SEARCH_PARAMETERS = [
30254
- { code: "gender", type: "token", jsonbPath: "$.gender" },
30255
- { code: "identifier", type: "token", jsonbPath: "$.identifier[*].value" },
30256
- { code: "birthdate", type: "date", jsonbPath: "$.birthDate" },
30257
- {
30258
- code: "name",
30259
- type: "string",
30260
- jsonbPath: "$.name[*].text",
30261
- modifiers: ["exact", "contains", "missing", "not"]
30262
- },
30263
- {
30264
- code: "family",
30265
- type: "string",
30266
- jsonbPath: "$.name[*].family",
30267
- modifiers: ["exact", "contains", "missing", "not"]
30268
- },
30269
- {
30270
- code: "given",
30271
- type: "string",
30272
- jsonbPath: "$.name[*].given",
30273
- modifiers: ["exact", "contains", "missing", "not"]
30274
- },
30275
- { code: "active", type: "token", jsonbPath: "$.active" },
30276
- { code: "deceased", type: "token", jsonbPath: "$.deceasedBoolean" },
30277
- {
30278
- code: "general-practitioner",
30279
- type: "reference",
30280
- jsonbPath: "$.generalPractitioner[*]"
30281
- },
30282
- {
30283
- code: "organization",
30284
- type: "reference",
30285
- jsonbPath: "$.managingOrganization"
30024
+ // src/data/rest-api/routes/data/patient/patient-get-by-id-route.ts
30025
+ async function getPatientByIdRoute(req, res) {
30026
+ const id = String(req.params.id);
30027
+ const ctx = req.openhiContext;
30028
+ try {
30029
+ const result = await getPatientByIdOperation({ context: ctx, id });
30030
+ return res.json(result.resource);
30031
+ } catch (err) {
30032
+ const status = domainErrorToHttpStatus(err);
30033
+ if (status === 404) {
30034
+ const diagnostics = err instanceof NotFoundError ? err.message : `Patient ${id} not found`;
30035
+ return sendOperationOutcome404(res, diagnostics);
30036
+ }
30037
+ return sendOperationOutcome500(res, err, "GET Patient error:");
30286
30038
  }
30287
- ];
30039
+ }
30288
30040
 
30289
- // src/data/search/registry/resolver.ts
30290
- var STATIC_SEARCH_PARAMETER_MAP = {
30291
- Patient: PATIENT_SEARCH_PARAMETERS
30292
- };
30293
- var defaultSearchParameterResolver = (resourceType, _tenantId) => STATIC_SEARCH_PARAMETER_MAP[resourceType] ?? [];
30294
- function getRegisteredSearchParameters(resourceType) {
30295
- return STATIC_SEARCH_PARAMETER_MAP[resourceType] ?? [];
30041
+ // src/data/operations/data/patient/patient-list-operation.ts
30042
+ async function listPatientsOperation(params) {
30043
+ const { context, tableName, mode } = params;
30044
+ const { tenantId, workspaceId } = context;
30045
+ const service = getDynamoDataService(tableName);
30046
+ return listDataEntitiesByWorkspace(
30047
+ service.entities.patient,
30048
+ tenantId,
30049
+ workspaceId,
30050
+ mode
30051
+ );
30296
30052
  }
30297
30053
 
30298
30054
  // src/data/rest-api/routes/data/patient/patient-list-route.ts
30299
30055
  var PATIENT_RESOURCE_TYPE = "Patient";
30300
- function stripModifier(key) {
30056
+ function stripModifier2(key) {
30301
30057
  const idx = key.indexOf(":");
30302
30058
  return idx === -1 ? key : key.slice(0, idx);
30303
30059
  }
30304
- function isResultParameter(key) {
30060
+ function isResultParameter2(key) {
30305
30061
  return key.startsWith("_");
30306
30062
  }
30307
30063
  function sendInvalidSearch4003(res, diagnostics) {
@@ -30310,17 +30066,17 @@ function sendInvalidSearch4003(res, diagnostics) {
30310
30066
  issue: [{ severity: "error", code: "invalid", diagnostics }]
30311
30067
  });
30312
30068
  }
30313
- function extractSearchParamKeys(query) {
30069
+ function extractSearchParamKeys2(query) {
30314
30070
  const out = [];
30315
30071
  for (const rawKey of Object.keys(query)) {
30316
- if (isResultParameter(rawKey)) {
30072
+ if (isResultParameter2(rawKey)) {
30317
30073
  continue;
30318
30074
  }
30319
- out.push({ rawKey, code: stripModifier(rawKey) });
30075
+ out.push({ rawKey, code: stripModifier2(rawKey) });
30320
30076
  }
30321
30077
  return out;
30322
30078
  }
30323
- function buildUnknownParamDiagnostics(unknownCodes) {
30079
+ function buildUnknownParamDiagnostics2(unknownCodes) {
30324
30080
  const validCodes = getRegisteredSearchParameters(PATIENT_RESOURCE_TYPE).map((p) => p.code).sort().join(", ");
30325
30081
  const codes = unknownCodes.join(", ");
30326
30082
  const isPlural = unknownCodes.length !== 1;
@@ -30329,7 +30085,7 @@ function buildUnknownParamDiagnostics(unknownCodes) {
30329
30085
  `Valid codes: ${validCodes}.`
30330
30086
  ].join(" ");
30331
30087
  }
30332
- function findMalformedReference(query, searchParamKeys) {
30088
+ function findMalformedReference2(query, searchParamKeys) {
30333
30089
  const referenceCodes = new Set(
30334
30090
  getRegisteredSearchParameters(PATIENT_RESOURCE_TYPE).filter((p) => p.type === "reference").map((p) => p.code)
30335
30091
  );
@@ -30352,7 +30108,7 @@ function findMalformedReference(query, searchParamKeys) {
30352
30108
  return void 0;
30353
30109
  }
30354
30110
  async function listPatientsRoute(req, res) {
30355
- const searchParamKeys = extractSearchParamKeys(
30111
+ const searchParamKeys = extractSearchParamKeys2(
30356
30112
  req.query
30357
30113
  );
30358
30114
  if (searchParamKeys.length === 0) {
@@ -30370,10 +30126,10 @@ async function listPatientsRoute(req, res) {
30370
30126
  if (unknownCodes.length > 0) {
30371
30127
  return sendInvalidSearch4003(
30372
30128
  res,
30373
- buildUnknownParamDiagnostics([...new Set(unknownCodes)])
30129
+ buildUnknownParamDiagnostics2([...new Set(unknownCodes)])
30374
30130
  );
30375
30131
  }
30376
- const malformedRef = findMalformedReference(
30132
+ const malformedRef = findMalformedReference2(
30377
30133
  req.query,
30378
30134
  searchParamKeys
30379
30135
  );
@@ -34216,7 +33972,7 @@ async function listSchedulesOperation(params) {
34216
33972
  }
34217
33973
 
34218
33974
  // src/data/operations/data/schedule/schedule-search-by-actor-operation.ts
34219
- var DEFAULT_LIMIT8 = 100;
33975
+ var DEFAULT_LIMIT5 = 100;
34220
33976
  function buildSearchSchedulesByActorSql() {
34221
33977
  return [
34222
33978
  "SELECT resource_id AS id, resource",
@@ -34234,7 +33990,7 @@ async function searchSchedulesByActorOperation(params) {
34234
33990
  const { context, actorReference } = params;
34235
33991
  const { tenantId, workspaceId } = context;
34236
33992
  const runner = params.runner ?? getDefaultPostgresQueryRunner();
34237
- const limit = params.limit ?? DEFAULT_LIMIT8;
33993
+ const limit = params.limit ?? DEFAULT_LIMIT5;
34238
33994
  const { containmentRelative, containmentUrn } = buildReferenceContainmentPayload({
34239
33995
  shape: { kind: "array-of-references", field: "actor" },
34240
33996
  reference: actorReference,
@@ -34260,7 +34016,7 @@ async function searchSchedulesByActorOperation(params) {
34260
34016
  }
34261
34017
 
34262
34018
  // src/data/rest-api/routes/data/schedule/schedule-list-route.ts
34263
- function singleStringQueryParam3(req, name) {
34019
+ function singleStringQueryParam2(req, name) {
34264
34020
  const v = req.query[name];
34265
34021
  if (typeof v !== "string") {
34266
34022
  return void 0;
@@ -34275,7 +34031,7 @@ function sendInvalidSearch4004(res, diagnostics) {
34275
34031
  });
34276
34032
  }
34277
34033
  async function listSchedulesRoute(req, res) {
34278
- const actorRef = singleStringQueryParam3(req, "actor");
34034
+ const actorRef = singleStringQueryParam2(req, "actor");
34279
34035
  if (actorRef !== void 0) {
34280
34036
  if (parseTypedReference(actorRef) === void 0) {
34281
34037
  return sendInvalidSearch4004(
@@ -37980,7 +37736,7 @@ async function listTasksOperation(params) {
37980
37736
  }
37981
37737
 
37982
37738
  // src/data/operations/data/task/task-search-by-owner-operation.ts
37983
- var DEFAULT_LIMIT9 = 100;
37739
+ var DEFAULT_LIMIT6 = 100;
37984
37740
  function buildSearchTasksByOwnerSql() {
37985
37741
  return [
37986
37742
  "SELECT resource_id AS id, resource",
@@ -37998,7 +37754,7 @@ async function searchTasksByOwnerOperation(params) {
37998
37754
  const { context, ownerReference } = params;
37999
37755
  const { tenantId, workspaceId } = context;
38000
37756
  const runner = params.runner ?? getDefaultPostgresQueryRunner();
38001
- const limit = params.limit ?? DEFAULT_LIMIT9;
37757
+ const limit = params.limit ?? DEFAULT_LIMIT6;
38002
37758
  const { containmentRelative, containmentUrn } = buildReferenceContainmentPayload({
38003
37759
  shape: { kind: "scalar", field: "owner" },
38004
37760
  reference: ownerReference,
@@ -38024,7 +37780,7 @@ async function searchTasksByOwnerOperation(params) {
38024
37780
  }
38025
37781
 
38026
37782
  // src/data/operations/data/task/task-search-by-requester-operation.ts
38027
- var DEFAULT_LIMIT10 = 100;
37783
+ var DEFAULT_LIMIT7 = 100;
38028
37784
  function buildSearchTasksByRequesterSql() {
38029
37785
  return [
38030
37786
  "SELECT resource_id AS id, resource",
@@ -38042,7 +37798,7 @@ async function searchTasksByRequesterOperation(params) {
38042
37798
  const { context, requesterReference } = params;
38043
37799
  const { tenantId, workspaceId } = context;
38044
37800
  const runner = params.runner ?? getDefaultPostgresQueryRunner();
38045
- const limit = params.limit ?? DEFAULT_LIMIT10;
37801
+ const limit = params.limit ?? DEFAULT_LIMIT7;
38046
37802
  const { containmentRelative, containmentUrn } = buildReferenceContainmentPayload({
38047
37803
  shape: { kind: "scalar", field: "requester" },
38048
37804
  reference: requesterReference,
@@ -38068,7 +37824,7 @@ async function searchTasksByRequesterOperation(params) {
38068
37824
  }
38069
37825
 
38070
37826
  // src/data/rest-api/routes/data/task/task-list-route.ts
38071
- function singleStringQueryParam4(req, name) {
37827
+ function singleStringQueryParam3(req, name) {
38072
37828
  const v = req.query[name];
38073
37829
  if (typeof v !== "string") {
38074
37830
  return void 0;
@@ -38083,8 +37839,8 @@ function sendInvalidSearch4005(res, diagnostics) {
38083
37839
  });
38084
37840
  }
38085
37841
  async function listTasksRoute(req, res) {
38086
- const ownerRef = singleStringQueryParam4(req, "owner");
38087
- const requesterRef = singleStringQueryParam4(req, "requester");
37842
+ const ownerRef = singleStringQueryParam3(req, "owner");
37843
+ const requesterRef = singleStringQueryParam3(req, "requester");
38088
37844
  if (ownerRef !== void 0 && requesterRef !== void 0) {
38089
37845
  return sendInvalidSearch4005(
38090
37846
  res,