@openhi/constructs 0.0.32 → 0.0.33

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.
@@ -7,7 +7,7 @@ import serverlessExpress from "@codegenie/serverless-express";
7
7
 
8
8
  // src/data/rest-api/rest-api.ts
9
9
  import path from "path";
10
- import express3 from "express";
10
+ import express5 from "express";
11
11
 
12
12
  // src/data/middleware/normalize-json-body.ts
13
13
  function normalizeJsonBodyMiddleware(req, _res, next) {
@@ -72,13 +72,9 @@ function openHiContextMiddleware(req, res, next) {
72
72
  next();
73
73
  }
74
74
 
75
- // src/data/rest-api/routes/configuration/configuration.ts
75
+ // src/data/rest-api/routes/control/configuration/configuration.ts
76
76
  import express from "express";
77
77
 
78
- // src/data/rest-api/routes/configuration/configuration-common.ts
79
- var BASE_PATH = "/Configuration";
80
- var SK = "CURRENT";
81
-
82
78
  // src/lib/compression.ts
83
79
  import { gzipSync, gunzipSync } from "zlib";
84
80
  var ENVELOPE_VERSION = 1;
@@ -135,16 +131,26 @@ function decompressResource(compressedOrRaw) {
135
131
  return compressedOrRaw;
136
132
  }
137
133
 
138
- // src/data/dynamo/dynamo-service.ts
139
- import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
134
+ // src/data/dynamo/dynamo-control-service.ts
140
135
  import { Service } from "electrodb";
141
136
 
142
- // src/data/dynamo/entities/configuration.ts
137
+ // src/data/dynamo/dynamo-client.ts
138
+ import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
139
+ var defaultTableName = process.env.DYNAMO_TABLE_NAME ?? "jesttesttable";
140
+ var dynamoClient = new DynamoDBClient({
141
+ ...process.env.MOCK_DYNAMODB_ENDPOINT && {
142
+ endpoint: process.env.MOCK_DYNAMODB_ENDPOINT,
143
+ sslEnabled: false,
144
+ region: "local"
145
+ }
146
+ });
147
+
148
+ // src/data/dynamo/entities/control/configuration-entity.ts
143
149
  import { Entity } from "electrodb";
144
- var Configuration = new Entity({
150
+ var ConfigurationEntity = new Entity({
145
151
  model: {
146
152
  entity: "configuration",
147
- service: "openhi",
153
+ service: "control",
148
154
  version: "01"
149
155
  },
150
156
  attributes: {
@@ -221,7 +227,7 @@ var Configuration = new Entity({
221
227
  pk: {
222
228
  field: "PK",
223
229
  composite: ["tenantId", "workspaceId", "userId", "roleId"],
224
- template: "OHI#CONFIG#TID#${tenantId}#WID#${workspaceId}#UID#${userId}#RID#${roleId}"
230
+ template: "CONFIG#TID#${tenantId}#WID#${workspaceId}#UID#${userId}#RID#${roleId}"
225
231
  },
226
232
  sk: {
227
233
  field: "SK",
@@ -247,213 +253,134 @@ var Configuration = new Entity({
247
253
  }
248
254
  });
249
255
 
250
- // src/data/dynamo/entities/patient.ts
251
- import { Entity as Entity2 } from "electrodb";
252
- var Patient = new Entity2({
253
- model: {
254
- entity: "patient",
255
- service: "openhi",
256
- version: "01"
257
- },
258
- attributes: {
259
- /** Sort key. "CURRENT" for current version; version history in S3. */
260
- sk: {
261
- type: "string",
262
- required: true,
263
- default: "CURRENT"
264
- },
265
- tenantId: {
266
- type: "string",
267
- required: true
268
- },
269
- workspaceId: {
270
- type: "string",
271
- required: true
272
- },
273
- /** FHIR Resource.id; logical id in URL and PK. */
274
- id: {
275
- type: "string",
276
- required: true
277
- },
278
- /** FHIR resource as JSON string. JSON.stringify(resource) on write; JSON.parse(item.resource) on read. */
279
- resource: {
280
- type: "string",
281
- required: true
282
- },
283
- /** Version id (e.g. ULID). Tracks current version; S3 history key. */
284
- vid: {
285
- type: "string",
286
- required: true
287
- },
288
- lastUpdated: {
289
- type: "string",
290
- required: true
291
- },
292
- deleted: {
293
- type: "boolean",
294
- required: false
295
- },
296
- bundleId: {
297
- type: "string",
298
- required: false
299
- },
300
- msgId: {
301
- type: "string",
302
- required: false
303
- },
304
- // Audit is in FHIR resource meta (meta.extension), not item attributes. See data-store-entities.md.
305
- // --- GSI2 (Identifier Lookup): optional; set when indexing this patient by identifier (e.g. MRN)
306
- /** Identifier system (e.g. MRN system URI). When set with identifierValue, item is written to GSI2. */
307
- identifierSystem: {
308
- type: "string",
309
- required: false
310
- },
311
- /** Identifier value (e.g. MRN). When set with identifierSystem, item is written to GSI2. */
312
- identifierValue: {
313
- type: "string",
314
- required: false
315
- },
316
- /** For GSI2/GSI3 projection: base table PK/SK so GetItem can be used after query. */
317
- resourcePk: {
318
- type: "string",
319
- required: false
320
- },
321
- resourceSk: {
322
- type: "string",
323
- required: false
324
- },
325
- /** For GSI2 projection: display name for roster/lookup. */
326
- display: {
327
- type: "string",
328
- required: false
329
- },
330
- /** For GSI2 projection: resource status if applicable. */
331
- status: {
332
- type: "string",
333
- required: false
334
- },
335
- // --- GSI3 (Facility Ops): optional; set when indexing this patient on a facility roster
336
- /** Facility id. When set with normalizedName, item is written to GSI3. */
337
- facilityId: {
338
- type: "string",
339
- required: false
340
- },
341
- /** Normalized display name for roster sort. When set with facilityId, item is written to GSI3. */
342
- normalizedName: {
343
- type: "string",
344
- required: false
345
- }
346
- },
347
- indexes: {
348
- /** Base table: PK, SK (data store key names). PK is built from tenantId, workspaceId, id; do not supply PK from outside. */
349
- record: {
350
- pk: {
351
- field: "PK",
352
- composite: ["tenantId", "workspaceId", "id"],
353
- template: "TID#${tenantId}#WID#${workspaceId}#RT#Patient#ID#${id}"
354
- },
355
- sk: {
356
- field: "SK",
357
- composite: ["sk"]
358
- }
359
- },
360
- /**
361
- * GSI1 — Reverse Reference: query "what references this patient?".
362
- * Patient items are never written to GSI1 (condition: false); reference index items
363
- * are written by other resources. This index enables querying GSI1 by REFTO#RT#Patient#ID#<id>.
364
- */
365
- gsi1: {
366
- index: "GSI1",
367
- condition: () => false,
368
- pk: {
369
- field: "GSI1PK",
370
- composite: ["tenantId", "workspaceId", "id"],
371
- template: "TID#${tenantId}#WID#${workspaceId}#REFTO#RT#Patient#ID#${id}"
372
- },
373
- sk: {
374
- field: "GSI1SK",
375
- composite: []
376
- }
377
- },
378
- /** GSI2 — Identifier Lookup: MRN, NPI, member ID, etc. Keys built from identifier components. */
379
- gsi2: {
380
- index: "GSI2",
381
- condition: (attr) => attr.identifierSystem != null && attr.identifierValue != null,
382
- pk: {
383
- field: "GSI2PK",
384
- composite: [
385
- "tenantId",
386
- "workspaceId",
387
- "identifierSystem",
388
- "identifierValue"
389
- ],
390
- template: "TID#${tenantId}#WID#${workspaceId}#IDENT#${identifierSystem}#${identifierValue}"
391
- },
392
- sk: {
393
- field: "GSI2SK",
394
- composite: ["id"],
395
- template: "RT#Patient#ID#${id}"
396
- }
397
- },
398
- /** GSI3 — Facility Ops: facility roster, worklists. Keys built from facility + name. */
399
- gsi3: {
400
- index: "GSI3",
401
- condition: (attr) => attr.facilityId != null && attr.normalizedName != null,
402
- pk: {
403
- field: "GSI3PK",
404
- composite: ["tenantId", "workspaceId", "facilityId"],
405
- template: "TID#${tenantId}#WID#${workspaceId}#FAC#${facilityId}"
406
- },
407
- sk: {
408
- field: "GSI3SK",
409
- composite: ["id", "normalizedName"],
410
- template: "TYPE#PATIENT#PAT#${id}#NAME#${normalizedName}"
411
- }
412
- },
413
- /** GSI4 — Resource Type Index: list all Patients in workspace (no scan). */
414
- gsi4: {
415
- index: "GSI4",
416
- condition: () => true,
417
- pk: {
418
- field: "GSI4PK",
419
- composite: ["tenantId", "workspaceId"],
420
- template: "TID#${tenantId}#WID#${workspaceId}#RT#Patient"
421
- },
422
- sk: {
423
- field: "GSI4SK",
424
- composite: ["id"],
425
- template: "ID#${id}"
426
- }
427
- }
428
- }
429
- });
430
-
431
- // src/data/dynamo/dynamo-service.ts
432
- var table = process.env.DYNAMO_TABLE_NAME ?? "jesttesttable";
433
- var client = new DynamoDBClient({
434
- ...process.env.MOCK_DYNAMODB_ENDPOINT && {
435
- endpoint: process.env.MOCK_DYNAMODB_ENDPOINT,
436
- sslEnabled: false,
437
- region: "local"
438
- }
256
+ // src/data/dynamo/dynamo-control-service.ts
257
+ var controlPlaneEntities = {
258
+ configuration: ConfigurationEntity
259
+ };
260
+ var controlPlaneService = new Service(controlPlaneEntities, {
261
+ table: defaultTableName,
262
+ client: dynamoClient
439
263
  });
440
- var entities = { patient: Patient, configuration: Configuration };
441
- var DynamoDataService = new Service(entities, { table, client });
442
- function getDynamoDataService(tableName) {
443
- const resolved = tableName ?? table;
444
- return new Service(entities, { table: resolved, client });
264
+ var DynamoControlService = {
265
+ entities: controlPlaneService.entities
266
+ };
267
+ function getDynamoControlService(tableName) {
268
+ const resolved = tableName ?? defaultTableName;
269
+ const service = new Service(controlPlaneEntities, {
270
+ table: resolved,
271
+ client: dynamoClient
272
+ });
273
+ return {
274
+ entities: service.entities
275
+ };
445
276
  }
446
277
 
447
- // src/data/rest-api/routes/configuration/configuration-create.ts
448
- async function createConfiguration(req, res) {
449
- const ctx = req.openhiContext;
278
+ // src/data/operations/control/configuration/configuration-create-operation.ts
279
+ var SK = "CURRENT";
280
+ async function createConfigurationOperation(params) {
281
+ const { context, body, tableName } = params;
450
282
  const {
451
283
  tenantId: ctxTenantId,
452
284
  workspaceId: ctxWorkspaceId,
453
285
  actorId: ctxActorId,
454
286
  date
455
- } = ctx;
456
- const body = req.body;
287
+ } = context;
288
+ const key = body.key;
289
+ const id = body.id ?? `config-${key}-${Date.now()}`;
290
+ const resourcePayload = body.resource;
291
+ const resourceStr = typeof resourcePayload === "string" ? resourcePayload : JSON.stringify(resourcePayload ?? {});
292
+ const tenantId = body.tenantId ?? ctxTenantId;
293
+ const workspaceId = body.workspaceId ?? ctxWorkspaceId;
294
+ const userId = body.userId ?? ctxActorId ?? "-";
295
+ const roleId = body.roleId ?? context.roleId ?? "-";
296
+ const lastUpdated = body.lastUpdated ?? date;
297
+ const vid = body.vid ?? (date.replace(/[-:T.Z]/g, "").slice(0, 12) || Date.now().toString(36));
298
+ const service = getDynamoControlService(tableName);
299
+ await service.entities.configuration.put({
300
+ tenantId,
301
+ workspaceId,
302
+ userId,
303
+ roleId,
304
+ key,
305
+ id,
306
+ resource: compressResource(resourceStr),
307
+ vid,
308
+ lastUpdated,
309
+ sk: SK
310
+ }).go();
311
+ const resource = typeof resourcePayload === "object" ? resourcePayload : JSON.parse(resourceStr);
312
+ return {
313
+ id,
314
+ key,
315
+ resource: {
316
+ resourceType: "Configuration",
317
+ id,
318
+ key,
319
+ resource
320
+ },
321
+ meta: { lastUpdated, versionId: vid }
322
+ };
323
+ }
324
+
325
+ // src/data/rest-api/routes/common.ts
326
+ var BASE_PATH = {
327
+ CONFIGURATION: "/Configuration",
328
+ ENCOUNTER: "/Encounter",
329
+ PATIENT: "/Patient",
330
+ PRACTITIONER: "/Practitioner"
331
+ };
332
+ function requireJsonBody(req, res) {
333
+ const raw = req.body;
334
+ if (raw == null || typeof raw !== "object" || Array.isArray(raw)) {
335
+ return {
336
+ errorResponse: res.status(400).json({
337
+ resourceType: "OperationOutcome",
338
+ issue: [
339
+ {
340
+ severity: "error",
341
+ code: "invalid",
342
+ diagnostics: "Request body must be a JSON object."
343
+ }
344
+ ]
345
+ })
346
+ };
347
+ }
348
+ return { body: raw };
349
+ }
350
+ function buildSearchsetBundle(basePath, entries) {
351
+ return {
352
+ resourceType: "Bundle",
353
+ type: "searchset",
354
+ total: entries.length,
355
+ link: [{ relation: "self", url: basePath }],
356
+ entry: entries.map((item) => ({
357
+ fullUrl: `${basePath}/${item.id}`,
358
+ resource: item.resource
359
+ }))
360
+ };
361
+ }
362
+ function sendOperationOutcome404(res, diagnostics) {
363
+ return res.status(404).json({
364
+ resourceType: "OperationOutcome",
365
+ issue: [{ severity: "error", code: "not-found", diagnostics }]
366
+ });
367
+ }
368
+ function sendOperationOutcome500(res, err, logContext) {
369
+ if (logContext) {
370
+ console.error(logContext, err);
371
+ }
372
+ return res.status(500).json({
373
+ resourceType: "OperationOutcome",
374
+ issue: [{ severity: "error", code: "exception", diagnostics: String(err) }]
375
+ });
376
+ }
377
+
378
+ // src/data/rest-api/routes/control/configuration/configuration-create-route.ts
379
+ async function createConfigurationRoute(req, res) {
380
+ const bodyResult = requireJsonBody(req, res);
381
+ if ("errorResponse" in bodyResult) return bodyResult.errorResponse;
382
+ const ctx = req.openhiContext;
383
+ const body = bodyResult.body;
457
384
  const key = body?.key;
458
385
  if (!key || typeof key !== "string") {
459
386
  return res.status(400).json({
@@ -467,37 +394,26 @@ async function createConfiguration(req, res) {
467
394
  ]
468
395
  });
469
396
  }
470
- const id = body?.id ?? `config-${key}-${Date.now()}`;
471
- const resourcePayload = body?.resource;
472
- const resourceStr = typeof resourcePayload === "string" ? resourcePayload : JSON.stringify(resourcePayload ?? {});
473
- const tenantId = body?.tenantId ?? ctxTenantId;
474
- const workspaceId = body?.workspaceId ?? ctxWorkspaceId;
475
- const userId = body?.userId ?? ctxActorId ?? "-";
476
- const roleId = body?.roleId ?? "-";
477
- const vid = body?.vid ?? (date.replace(/[-:T.Z]/g, "").slice(0, 12) || Date.now().toString(36));
478
- const lastUpdated = body?.lastUpdated ?? date;
479
- const service = getDynamoDataService();
480
397
  try {
481
- await service.entities.configuration.put({
482
- tenantId,
483
- workspaceId,
484
- userId,
485
- roleId,
486
- key,
487
- id,
488
- resource: compressResource(resourceStr),
489
- vid,
490
- lastUpdated,
491
- sk: SK
492
- }).go();
398
+ const result = await createConfigurationOperation({
399
+ context: ctx,
400
+ body: {
401
+ key,
402
+ id: body?.id,
403
+ resource: body?.resource,
404
+ tenantId: body?.tenantId,
405
+ workspaceId: body?.workspaceId,
406
+ userId: body?.userId,
407
+ roleId: body?.roleId,
408
+ vid: body?.vid,
409
+ lastUpdated: body?.lastUpdated
410
+ }
411
+ });
493
412
  const config = {
494
- resourceType: "Configuration",
495
- id,
496
- key,
497
- resource: typeof resourcePayload === "object" ? resourcePayload : JSON.parse(resourceStr),
498
- meta: { lastUpdated, versionId: vid }
413
+ ...result.resource,
414
+ meta: result.meta
499
415
  };
500
- return res.status(201).location(`${BASE_PATH}/${key}`).json(config);
416
+ return res.status(201).location(`${BASE_PATH.CONFIGURATION}/${result.key}`).json(config);
501
417
  } catch (err) {
502
418
  console.error("POST Configuration error:", err);
503
419
  return res.status(500).json({
@@ -509,15 +425,29 @@ async function createConfiguration(req, res) {
509
425
  }
510
426
  }
511
427
 
512
- // src/data/rest-api/routes/configuration/configuration-delete.ts
513
- async function deleteConfiguration(req, res) {
514
- const key = String(req.params.key);
515
- const ctx = req.openhiContext;
516
- const { tenantId, workspaceId, actorId, roleId: ctxRoleId } = ctx;
428
+ // src/data/operations/control/configuration/configuration-delete-operation.ts
429
+ var SK2 = "CURRENT";
430
+ async function deleteConfigurationOperation(params) {
431
+ const { context, id, tableName } = params;
432
+ const { tenantId, workspaceId, actorId, roleId: ctxRoleId } = context;
517
433
  const roleId = ctxRoleId ?? "-";
518
- const service = getDynamoDataService();
434
+ const service = getDynamoControlService(tableName);
435
+ await service.entities.configuration.delete({
436
+ tenantId,
437
+ workspaceId,
438
+ userId: actorId,
439
+ roleId,
440
+ key: id,
441
+ sk: SK2
442
+ }).go();
443
+ }
444
+
445
+ // src/data/rest-api/routes/control/configuration/configuration-delete-route.ts
446
+ async function deleteConfigurationRoute(req, res) {
447
+ const id = String(req.params.id);
448
+ const ctx = req.openhiContext;
519
449
  try {
520
- await service.entities.configuration.delete({ tenantId, workspaceId, userId: actorId, roleId, key, sk: SK }).go();
450
+ await deleteConfigurationOperation({ context: ctx, id });
521
451
  return res.status(204).send();
522
452
  } catch (err) {
523
453
  console.error("DELETE Configuration error:", err);
@@ -530,37 +460,87 @@ async function deleteConfiguration(req, res) {
530
460
  }
531
461
  }
532
462
 
533
- // src/data/rest-api/routes/configuration/configuration-get-by-key.ts
534
- async function getConfigurationByKey(req, res) {
535
- const key = String(req.params.key);
536
- const ctx = req.openhiContext;
537
- const { tenantId, workspaceId, actorId, roleId: ctxRoleId } = ctx;
463
+ // src/data/errors/domain-errors.ts
464
+ var DomainError = class extends Error {
465
+ constructor(message, code, options) {
466
+ super(message, options);
467
+ this.name = this.constructor.name;
468
+ this.code = code;
469
+ this.details = options?.details;
470
+ Object.setPrototypeOf(this, new.target.prototype);
471
+ }
472
+ };
473
+ var NotFoundError = class extends DomainError {
474
+ constructor(message, options) {
475
+ super(message, "NOT_FOUND", options);
476
+ }
477
+ };
478
+ var ValidationError = class extends DomainError {
479
+ constructor(message, options) {
480
+ super(message, "VALIDATION", options);
481
+ }
482
+ };
483
+ var ConflictError = class extends DomainError {
484
+ constructor(message, options) {
485
+ super(message, "CONFLICT", options);
486
+ }
487
+ };
488
+ function domainErrorToHttpStatus(err) {
489
+ if (err instanceof NotFoundError) return 404;
490
+ if (err instanceof ValidationError) return 400;
491
+ if (err instanceof ConflictError) return 409;
492
+ return null;
493
+ }
494
+
495
+ // src/data/operations/control/configuration/configuration-get-by-id-operation.ts
496
+ var SK3 = "CURRENT";
497
+ async function getConfigurationByIdOperation(params) {
498
+ const { context, id, tableName } = params;
499
+ const { tenantId, workspaceId, actorId, roleId: ctxRoleId } = context;
538
500
  const roleId = ctxRoleId ?? "-";
539
- const service = getDynamoDataService();
501
+ const service = getDynamoControlService(tableName);
502
+ const result = await service.entities.configuration.get({ tenantId, workspaceId, userId: actorId, roleId, key: id, sk: SK3 }).go();
503
+ if (!result.data) {
504
+ throw new NotFoundError(`Configuration ${id} not found`, {
505
+ details: { id }
506
+ });
507
+ }
508
+ const resource = JSON.parse(
509
+ decompressResource(result.data.resource)
510
+ );
511
+ return {
512
+ id: result.data.id,
513
+ key: result.data.key,
514
+ resource: {
515
+ ...resource,
516
+ resourceType: "Configuration",
517
+ id: result.data.id,
518
+ key: result.data.key
519
+ }
520
+ };
521
+ }
522
+
523
+ // src/data/rest-api/routes/control/configuration/configuration-get-by-id-route.ts
524
+ async function getConfigurationByIdRoute(req, res) {
525
+ const id = String(req.params.id);
526
+ const ctx = req.openhiContext;
540
527
  try {
541
- const result = await service.entities.configuration.get({ tenantId, workspaceId, userId: actorId, roleId, key, sk: SK }).go();
542
- if (!result.data) {
528
+ const result = await getConfigurationByIdOperation({ context: ctx, id });
529
+ return res.json(result.resource);
530
+ } catch (err) {
531
+ const status = domainErrorToHttpStatus(err);
532
+ if (status === 404) {
543
533
  return res.status(404).json({
544
534
  resourceType: "OperationOutcome",
545
535
  issue: [
546
536
  {
547
537
  severity: "error",
548
538
  code: "not-found",
549
- diagnostics: `Configuration ${key} not found`
539
+ diagnostics: err instanceof NotFoundError ? err.message : `Configuration ${id} not found`
550
540
  }
551
541
  ]
552
542
  });
553
543
  }
554
- const resource = JSON.parse(
555
- decompressResource(result.data.resource)
556
- );
557
- return res.json({
558
- ...resource,
559
- resourceType: "Configuration",
560
- id: result.data.id,
561
- key: result.data.key
562
- });
563
- } catch (err) {
564
544
  console.error("GET Configuration error:", err);
565
545
  return res.status(500).json({
566
546
  resourceType: "OperationOutcome",
@@ -575,6 +555,30 @@ async function getConfigurationByKey(req, res) {
575
555
  }
576
556
  }
577
557
 
558
+ // src/data/operations/control/configuration/configuration-list-operation.ts
559
+ async function listConfigurationsOperation(params) {
560
+ const { context, tableName } = params;
561
+ const { tenantId, workspaceId } = context;
562
+ const service = getDynamoControlService(tableName);
563
+ const result = await service.entities.configuration.query.gsi4({ tenantId, workspaceId }).go();
564
+ const entries = (result.data ?? []).map(
565
+ (item) => {
566
+ const resource = JSON.parse(decompressResource(item.resource));
567
+ return {
568
+ id: item.id,
569
+ key: item.key,
570
+ resource: {
571
+ resourceType: "Configuration",
572
+ id: item.id,
573
+ key: item.key,
574
+ resource
575
+ }
576
+ };
577
+ }
578
+ );
579
+ return { entries };
580
+ }
581
+
578
582
  // src/data/rest-api/dynamic-configuration.ts
579
583
  import {
580
584
  DescribeParametersCommand,
@@ -598,9 +602,9 @@ async function getDynamicConfigurationEntries(context) {
598
602
  return getStaticDummyEntry(context);
599
603
  }
600
604
  const region = process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION;
601
- const client2 = new SSMClient({ region });
605
+ const client = new SSMClient({ region });
602
606
  try {
603
- const describeResult = await client2.send(
607
+ const describeResult = await client.send(
604
608
  new DescribeParametersCommand({
605
609
  ParameterFilters: [
606
610
  {
@@ -624,7 +628,7 @@ async function getDynamicConfigurationEntries(context) {
624
628
  const parameters = [];
625
629
  for (let i = 0; i < names.length; i += 10) {
626
630
  const batch = names.slice(i, i + 10);
627
- const getResult = await client2.send(
631
+ const getResult = await client.send(
628
632
  new GetParametersCommand({
629
633
  Names: batch,
630
634
  WithDecryption: true
@@ -688,29 +692,28 @@ function getStaticDummyEntry(_context) {
688
692
  return [dummy];
689
693
  }
690
694
 
691
- // src/data/rest-api/routes/configuration/configuration-list.ts
692
- async function listConfigurations(req, res) {
693
- const { tenantId, workspaceId } = req.openhiContext;
694
- const service = getDynamoDataService();
695
+ // src/data/rest-api/routes/control/configuration/configuration-list-route.ts
696
+ async function listConfigurationsRoute(req, res) {
697
+ const ctx = req.openhiContext;
698
+ const { tenantId, workspaceId } = ctx;
695
699
  try {
696
- const result = await service.entities.configuration.query.gsi4({ tenantId, workspaceId }).go();
697
- const dynamoEntries = (result.data ?? []).map((item) => {
698
- const resource = JSON.parse(decompressResource(item.resource));
699
- return {
700
- fullUrl: `${BASE_PATH}/${item.key}`,
701
- resource: { ...resource, id: item.id, key: item.key }
702
- };
700
+ const { entries: dynamoEntries } = await listConfigurationsOperation({
701
+ context: ctx
703
702
  });
703
+ const dynamoBundleEntries = dynamoEntries.map((e) => ({
704
+ fullUrl: `${BASE_PATH.CONFIGURATION}/${e.key}`,
705
+ resource: e.resource
706
+ }));
704
707
  const dynamicEntries = await getDynamicConfigurationEntries({
705
708
  tenantId,
706
709
  workspaceId
707
710
  });
708
- const entries = [...dynamoEntries, ...dynamicEntries];
711
+ const entries = [...dynamoBundleEntries, ...dynamicEntries];
709
712
  const bundle = {
710
713
  resourceType: "Bundle",
711
714
  type: "searchset",
712
715
  total: entries.length,
713
- link: [{ relation: "self", url: BASE_PATH }],
716
+ link: [{ relation: "self", url: BASE_PATH.CONFIGURATION }],
714
717
  entry: entries
715
718
  };
716
719
  return res.json(bundle);
@@ -729,114 +732,1180 @@ async function listConfigurations(req, res) {
729
732
  }
730
733
  }
731
734
 
732
- // src/data/rest-api/routes/configuration/configuration-update.ts
733
- async function updateConfiguration(req, res) {
734
- const key = String(req.params.key);
735
- const ctx = req.openhiContext;
736
- const { tenantId, workspaceId, actorId, date, roleId: ctxRoleId } = ctx;
735
+ // src/data/operations/control/configuration/configuration-update-operation.ts
736
+ var SK4 = "CURRENT";
737
+ async function updateConfigurationOperation(params) {
738
+ const { context, id, body, tableName } = params;
739
+ const { tenantId, workspaceId, actorId, date, roleId: ctxRoleId } = context;
737
740
  const roleId = ctxRoleId ?? "-";
738
- const body = req.body;
739
- const resourcePayload = body?.resource;
741
+ const service = getDynamoControlService(tableName);
742
+ const existing = await service.entities.configuration.get({ tenantId, workspaceId, userId: actorId, roleId, key: id, sk: SK4 }).go();
743
+ if (!existing.data) {
744
+ throw new NotFoundError(`Configuration ${id} not found`, {
745
+ details: { id }
746
+ });
747
+ }
748
+ const resourcePayload = body.resource;
740
749
  const resourceStr = typeof resourcePayload === "string" ? resourcePayload : JSON.stringify(resourcePayload ?? {});
741
- const lastUpdated = body?.lastUpdated ?? date;
742
- const service = getDynamoDataService();
743
- try {
744
- const existing = await service.entities.configuration.get({ tenantId, workspaceId, userId: actorId, roleId, key, sk: SK }).go();
745
- if (!existing.data) {
746
- return res.status(404).json({
747
- resourceType: "OperationOutcome",
748
- issue: [
749
- {
750
- severity: "error",
750
+ const lastUpdated = body.lastUpdated ?? date;
751
+ const nextVid = existing.data.vid != null ? String(Number(existing.data.vid) + 1) : date.replace(/[-:T.Z]/g, "").slice(0, 12) || "2";
752
+ await service.entities.configuration.patch({ tenantId, workspaceId, userId: actorId, roleId, key: id, sk: SK4 }).set({
753
+ resource: compressResource(resourceStr),
754
+ lastUpdated,
755
+ vid: nextVid
756
+ }).go();
757
+ const parsedResource = typeof resourcePayload === "object" ? resourcePayload : JSON.parse(resourceStr);
758
+ return {
759
+ id: existing.data.id,
760
+ key: existing.data.key,
761
+ resource: {
762
+ resourceType: "Configuration",
763
+ id: existing.data.id,
764
+ key: existing.data.key,
765
+ resource: parsedResource
766
+ },
767
+ meta: { lastUpdated, versionId: nextVid }
768
+ };
769
+ }
770
+
771
+ // src/data/rest-api/routes/control/configuration/configuration-update-route.ts
772
+ async function updateConfigurationRoute(req, res) {
773
+ const bodyResult = requireJsonBody(req, res);
774
+ if ("errorResponse" in bodyResult) return bodyResult.errorResponse;
775
+ const id = String(req.params.id);
776
+ const ctx = req.openhiContext;
777
+ const body = bodyResult.body;
778
+ try {
779
+ const result = await updateConfigurationOperation({
780
+ context: ctx,
781
+ id,
782
+ body: {
783
+ resource: body?.resource,
784
+ lastUpdated: body?.lastUpdated
785
+ }
786
+ });
787
+ return res.json({
788
+ ...result.resource,
789
+ meta: result.meta
790
+ });
791
+ } catch (err) {
792
+ const status = domainErrorToHttpStatus(err);
793
+ if (status === 404) {
794
+ return res.status(404).json({
795
+ resourceType: "OperationOutcome",
796
+ issue: [
797
+ {
798
+ severity: "error",
751
799
  code: "not-found",
752
- diagnostics: `Configuration ${key} not found`
800
+ diagnostics: err instanceof NotFoundError ? err.message : `Configuration ${id} not found`
753
801
  }
754
802
  ]
755
803
  });
756
804
  }
757
- const nextVid = existing.data.vid != null ? String(Number(existing.data.vid) + 1) : date.replace(/[-:T.Z]/g, "").slice(0, 12) || "2";
758
- await service.entities.configuration.patch({ tenantId, workspaceId, userId: actorId, roleId, key, sk: SK }).set({
759
- resource: compressResource(resourceStr),
760
- lastUpdated,
761
- vid: nextVid
762
- }).go();
763
- const config = {
764
- resourceType: "Configuration",
765
- id: existing.data.id,
766
- key: existing.data.key,
767
- resource: typeof resourcePayload === "object" ? resourcePayload : JSON.parse(resourceStr),
768
- meta: { lastUpdated, versionId: nextVid }
769
- };
770
- return res.json(config);
771
- } catch (err) {
772
805
  console.error("PUT Configuration error:", err);
773
806
  return res.status(500).json({
774
807
  resourceType: "OperationOutcome",
775
808
  issue: [
776
- { severity: "error", code: "exception", diagnostics: String(err) }
809
+ {
810
+ severity: "error",
811
+ code: "exception",
812
+ diagnostics: String(err)
813
+ }
777
814
  ]
778
815
  });
779
816
  }
780
817
  }
781
818
 
782
- // src/data/rest-api/routes/configuration/configuration.ts
819
+ // src/data/rest-api/routes/control/configuration/configuration.ts
783
820
  var router = express.Router();
784
- router.get("/", listConfigurations);
785
- router.get("/:key", getConfigurationByKey);
786
- router.post("/", createConfiguration);
787
- router.put("/:key", updateConfiguration);
788
- router.delete("/:key", deleteConfiguration);
821
+ router.get("/", listConfigurationsRoute);
822
+ router.get("/:id", getConfigurationByIdRoute);
823
+ router.post("/", createConfigurationRoute);
824
+ router.put("/:id", updateConfigurationRoute);
825
+ router.delete("/:id", deleteConfigurationRoute);
789
826
 
790
- // src/data/rest-api/routes/patient/patient.ts
827
+ // src/data/rest-api/routes/data/encounter/encounter.ts
791
828
  import express2 from "express";
792
829
 
793
- // src/data/rest-api/routes/patient/patient-common.ts
794
- var BASE_PATH3 = "/Patient";
795
- var SK2 = "CURRENT";
796
- function requireJsonBody(req, res) {
797
- const raw = req.body;
798
- if (raw == null || typeof raw !== "object" || Array.isArray(raw)) {
830
+ // src/data/operations/data/encounter/encounter-create-operation.ts
831
+ import { ulid } from "ulid";
832
+
833
+ // src/data/audit-meta.ts
834
+ var OPENHI_EXT = "http://openhi.org/fhir/StructureDefinition";
835
+ function mergeAuditIntoMeta(meta, audit) {
836
+ const existing = meta ?? {};
837
+ const ext = [
838
+ ...Array.isArray(existing.extension) ? existing.extension : []
839
+ ];
840
+ const byUrl = new Map(ext.map((e) => [e.url, e]));
841
+ function set(url, value, type) {
842
+ if (value == null) return;
843
+ byUrl.set(url, { url, [type]: value });
844
+ }
845
+ set(`${OPENHI_EXT}/created-date`, audit.createdDate, "valueDateTime");
846
+ set(`${OPENHI_EXT}/created-by-id`, audit.createdById, "valueString");
847
+ set(`${OPENHI_EXT}/created-by-name`, audit.createdByName, "valueString");
848
+ set(`${OPENHI_EXT}/modified-date`, audit.modifiedDate, "valueDateTime");
849
+ set(`${OPENHI_EXT}/modified-by-id`, audit.modifiedById, "valueString");
850
+ set(`${OPENHI_EXT}/modified-by-name`, audit.modifiedByName, "valueString");
851
+ set(`${OPENHI_EXT}/deleted-date`, audit.deletedDate, "valueDateTime");
852
+ set(`${OPENHI_EXT}/deleted-by-id`, audit.deletedById, "valueString");
853
+ set(`${OPENHI_EXT}/deleted-by-name`, audit.deletedByName, "valueString");
854
+ return { ...existing, extension: Array.from(byUrl.values()) };
855
+ }
856
+
857
+ // src/data/dynamo/dynamo-data-service.ts
858
+ import { Service as Service2 } from "electrodb";
859
+
860
+ // src/data/dynamo/entities/data-entity-common.ts
861
+ import { Entity as Entity2 } from "electrodb";
862
+ var dataEntityAttributes = {
863
+ /** Sort key. "CURRENT" for current version; version history in S3. */
864
+ sk: {
865
+ type: "string",
866
+ required: true,
867
+ default: "CURRENT"
868
+ },
869
+ tenantId: {
870
+ type: "string",
871
+ required: true
872
+ },
873
+ workspaceId: {
874
+ type: "string",
875
+ required: true
876
+ },
877
+ /** FHIR Resource.id; logical id in URL and PK. */
878
+ id: {
879
+ type: "string",
880
+ required: true
881
+ },
882
+ /** FHIR resource as JSON string. JSON.stringify(resource) on write; JSON.parse(item.resource) on read. */
883
+ resource: {
884
+ type: "string",
885
+ required: true
886
+ },
887
+ /** Version id (e.g. ULID). Tracks current version; S3 history key. */
888
+ vid: {
889
+ type: "string",
890
+ required: true
891
+ },
892
+ lastUpdated: {
893
+ type: "string",
894
+ required: true
895
+ },
896
+ deleted: {
897
+ type: "boolean",
898
+ required: false
899
+ },
900
+ bundleId: {
901
+ type: "string",
902
+ required: false
903
+ },
904
+ msgId: {
905
+ type: "string",
906
+ required: false
907
+ }
908
+ };
909
+ function createDataEntity(entity, resourceTypeLabel) {
910
+ return new Entity2({
911
+ model: {
912
+ entity,
913
+ service: "data",
914
+ version: "01"
915
+ },
916
+ attributes: dataEntityAttributes,
917
+ indexes: {
918
+ /** Base table: PK, SK (data store key names). PK is built from tenantId, workspaceId, id. */
919
+ record: {
920
+ pk: {
921
+ field: "PK",
922
+ composite: ["tenantId", "workspaceId", "id"],
923
+ template: `TID#\${tenantId}#WID#\${workspaceId}#RT#${resourceTypeLabel}#ID#\${id}`
924
+ },
925
+ sk: {
926
+ field: "SK",
927
+ composite: ["sk"]
928
+ }
929
+ },
930
+ /** GSI4 — Resource Type Index: list all resources of this type in a workspace (no scan). Used by list operations (e.g. GET /Patient). */
931
+ gsi4: {
932
+ index: "GSI4",
933
+ pk: {
934
+ field: "GSI4PK",
935
+ composite: ["tenantId", "workspaceId"],
936
+ template: `TID#\${tenantId}#WID#\${workspaceId}#RT#${resourceTypeLabel}`
937
+ },
938
+ sk: {
939
+ field: "GSI4SK",
940
+ composite: ["id"],
941
+ template: `ID#\${id}`
942
+ }
943
+ }
944
+ }
945
+ });
946
+ }
947
+
948
+ // src/data/dynamo/entities/data/account-entity.ts
949
+ var AccountEntity = createDataEntity("account", "Account");
950
+
951
+ // src/data/dynamo/entities/data/activity-definition-entity.ts
952
+ var ActivityDefinitionEntity = createDataEntity(
953
+ "activitydefinition",
954
+ "ActivityDefinition"
955
+ );
956
+
957
+ // src/data/dynamo/entities/data/adverse-event-entity.ts
958
+ var AdverseEventEntity = createDataEntity(
959
+ "adverseevent",
960
+ "AdverseEvent"
961
+ );
962
+
963
+ // src/data/dynamo/entities/data/allergy-intolerance-entity.ts
964
+ var AllergyIntoleranceEntity = createDataEntity(
965
+ "allergyintolerance",
966
+ "AllergyIntolerance"
967
+ );
968
+
969
+ // src/data/dynamo/entities/data/appointment-entity.ts
970
+ var AppointmentEntity = createDataEntity("appointment", "Appointment");
971
+
972
+ // src/data/dynamo/entities/data/appointment-response-entity.ts
973
+ var AppointmentResponseEntity = createDataEntity(
974
+ "appointmentresponse",
975
+ "AppointmentResponse"
976
+ );
977
+
978
+ // src/data/dynamo/entities/data/audit-event-entity.ts
979
+ var AuditEventEntity = createDataEntity("auditevent", "AuditEvent");
980
+
981
+ // src/data/dynamo/entities/data/basic-entity.ts
982
+ var BasicEntity = createDataEntity("basic", "Basic");
983
+
984
+ // src/data/dynamo/entities/data/biologically-derived-product-entity.ts
985
+ var BiologicallyDerivedProductEntity = createDataEntity(
986
+ "biologicallyderivedproduct",
987
+ "BiologicallyDerivedProduct"
988
+ );
989
+
990
+ // src/data/dynamo/entities/data/body-structure-entity.ts
991
+ var BodyStructureEntity = createDataEntity(
992
+ "bodystructure",
993
+ "BodyStructure"
994
+ );
995
+
996
+ // src/data/dynamo/entities/data/care-plan-entity.ts
997
+ var CarePlanEntity = createDataEntity("careplan", "CarePlan");
998
+
999
+ // src/data/dynamo/entities/data/care-team-entity.ts
1000
+ var CareTeamEntity = createDataEntity("careteam", "CareTeam");
1001
+
1002
+ // src/data/dynamo/entities/data/catalog-entry-entity.ts
1003
+ var CatalogEntryEntity = createDataEntity(
1004
+ "catalogentry",
1005
+ "CatalogEntry"
1006
+ );
1007
+
1008
+ // src/data/dynamo/entities/data/charge-item-definition-entity.ts
1009
+ var ChargeItemDefinitionEntity = createDataEntity(
1010
+ "chargeitemdefinition",
1011
+ "ChargeItemDefinition"
1012
+ );
1013
+
1014
+ // src/data/dynamo/entities/data/charge-item-entity.ts
1015
+ var ChargeItemEntity = createDataEntity("chargeitem", "ChargeItem");
1016
+
1017
+ // src/data/dynamo/entities/data/claim-entity.ts
1018
+ var ClaimEntity = createDataEntity("claim", "Claim");
1019
+
1020
+ // src/data/dynamo/entities/data/claim-response-entity.ts
1021
+ var ClaimResponseEntity = createDataEntity(
1022
+ "claimresponse",
1023
+ "ClaimResponse"
1024
+ );
1025
+
1026
+ // src/data/dynamo/entities/data/clinical-impression-entity.ts
1027
+ var ClinicalImpressionEntity = createDataEntity(
1028
+ "clinicalimpression",
1029
+ "ClinicalImpression"
1030
+ );
1031
+
1032
+ // src/data/dynamo/entities/data/communication-entity.ts
1033
+ var CommunicationEntity = createDataEntity(
1034
+ "communication",
1035
+ "Communication"
1036
+ );
1037
+
1038
+ // src/data/dynamo/entities/data/communication-request-entity.ts
1039
+ var CommunicationRequestEntity = createDataEntity(
1040
+ "communicationrequest",
1041
+ "CommunicationRequest"
1042
+ );
1043
+
1044
+ // src/data/dynamo/entities/data/composition-entity.ts
1045
+ var CompositionEntity = createDataEntity("composition", "Composition");
1046
+
1047
+ // src/data/dynamo/entities/data/condition-entity.ts
1048
+ var ConditionEntity = createDataEntity("condition", "Condition");
1049
+
1050
+ // src/data/dynamo/entities/data/consent-entity.ts
1051
+ var ConsentEntity = createDataEntity("consent", "Consent");
1052
+
1053
+ // src/data/dynamo/entities/data/contract-entity.ts
1054
+ var ContractEntity = createDataEntity("contract", "Contract");
1055
+
1056
+ // src/data/dynamo/entities/data/coverage-eligibility-request-entity.ts
1057
+ var CoverageEligibilityRequestEntity = createDataEntity(
1058
+ "coverageeligibilityrequest",
1059
+ "CoverageEligibilityRequest"
1060
+ );
1061
+
1062
+ // src/data/dynamo/entities/data/coverage-eligibility-response-entity.ts
1063
+ var CoverageEligibilityResponseEntity = createDataEntity(
1064
+ "coverageeligibilityresponse",
1065
+ "CoverageEligibilityResponse"
1066
+ );
1067
+
1068
+ // src/data/dynamo/entities/data/coverage-entity.ts
1069
+ var CoverageEntity = createDataEntity("coverage", "Coverage");
1070
+
1071
+ // src/data/dynamo/entities/data/detected-issue-entity.ts
1072
+ var DetectedIssueEntity = createDataEntity(
1073
+ "detectedissue",
1074
+ "DetectedIssue"
1075
+ );
1076
+
1077
+ // src/data/dynamo/entities/data/device-definition-entity.ts
1078
+ var DeviceDefinitionEntity = createDataEntity(
1079
+ "devicedefinition",
1080
+ "DeviceDefinition"
1081
+ );
1082
+
1083
+ // src/data/dynamo/entities/data/device-entity.ts
1084
+ var DeviceEntity = createDataEntity("device", "Device");
1085
+
1086
+ // src/data/dynamo/entities/data/device-metric-entity.ts
1087
+ var DeviceMetricEntity = createDataEntity(
1088
+ "devicemetric",
1089
+ "DeviceMetric"
1090
+ );
1091
+
1092
+ // src/data/dynamo/entities/data/device-request-entity.ts
1093
+ var DeviceRequestEntity = createDataEntity(
1094
+ "devicerequest",
1095
+ "DeviceRequest"
1096
+ );
1097
+
1098
+ // src/data/dynamo/entities/data/device-use-statement-entity.ts
1099
+ var DeviceUseStatementEntity = createDataEntity(
1100
+ "deviceusestatement",
1101
+ "DeviceUseStatement"
1102
+ );
1103
+
1104
+ // src/data/dynamo/entities/data/diagnostic-report-entity.ts
1105
+ var DiagnosticReportEntity = createDataEntity(
1106
+ "diagnosticreport",
1107
+ "DiagnosticReport"
1108
+ );
1109
+
1110
+ // src/data/dynamo/entities/data/document-manifest-entity.ts
1111
+ var DocumentManifestEntity = createDataEntity(
1112
+ "documentmanifest",
1113
+ "DocumentManifest"
1114
+ );
1115
+
1116
+ // src/data/dynamo/entities/data/document-reference-entity.ts
1117
+ var DocumentReferenceEntity = createDataEntity(
1118
+ "documentreference",
1119
+ "DocumentReference"
1120
+ );
1121
+
1122
+ // src/data/dynamo/entities/data/effect-evidence-synthesis-entity.ts
1123
+ var EffectEvidenceSynthesisEntity = createDataEntity(
1124
+ "effectevidencesynthesis",
1125
+ "EffectEvidenceSynthesis"
1126
+ );
1127
+
1128
+ // src/data/dynamo/entities/data/encounter-entity.ts
1129
+ var EncounterEntity = createDataEntity("encounter", "Encounter");
1130
+
1131
+ // src/data/dynamo/entities/data/endpoint-entity.ts
1132
+ var EndpointEntity = createDataEntity("endpoint", "Endpoint");
1133
+
1134
+ // src/data/dynamo/entities/data/enrollment-request-entity.ts
1135
+ var EnrollmentRequestEntity = createDataEntity(
1136
+ "enrollmentrequest",
1137
+ "EnrollmentRequest"
1138
+ );
1139
+
1140
+ // src/data/dynamo/entities/data/enrollment-response-entity.ts
1141
+ var EnrollmentResponseEntity = createDataEntity(
1142
+ "enrollmentresponse",
1143
+ "EnrollmentResponse"
1144
+ );
1145
+
1146
+ // src/data/dynamo/entities/data/episode-of-care-entity.ts
1147
+ var EpisodeOfCareEntity = createDataEntity(
1148
+ "episodeofcare",
1149
+ "EpisodeOfCare"
1150
+ );
1151
+
1152
+ // src/data/dynamo/entities/data/event-definition-entity.ts
1153
+ var EventDefinitionEntity = createDataEntity(
1154
+ "eventdefinition",
1155
+ "EventDefinition"
1156
+ );
1157
+
1158
+ // src/data/dynamo/entities/data/evidence-entity.ts
1159
+ var EvidenceEntity = createDataEntity("evidence", "Evidence");
1160
+
1161
+ // src/data/dynamo/entities/data/evidence-variable-entity.ts
1162
+ var EvidenceVariableEntity = createDataEntity(
1163
+ "evidencevariable",
1164
+ "EvidenceVariable"
1165
+ );
1166
+
1167
+ // src/data/dynamo/entities/data/explanation-of-benefit-entity.ts
1168
+ var ExplanationOfBenefitEntity = createDataEntity(
1169
+ "explanationofbenefit",
1170
+ "ExplanationOfBenefit"
1171
+ );
1172
+
1173
+ // src/data/dynamo/entities/data/family-member-history-entity.ts
1174
+ var FamilyMemberHistoryEntity = createDataEntity(
1175
+ "familymemberhistory",
1176
+ "FamilyMemberHistory"
1177
+ );
1178
+
1179
+ // src/data/dynamo/entities/data/flag-entity.ts
1180
+ var FlagEntity = createDataEntity("flag", "Flag");
1181
+
1182
+ // src/data/dynamo/entities/data/goal-entity.ts
1183
+ var GoalEntity = createDataEntity("goal", "Goal");
1184
+
1185
+ // src/data/dynamo/entities/data/group-entity.ts
1186
+ var GroupEntity = createDataEntity("group", "Group");
1187
+
1188
+ // src/data/dynamo/entities/data/guidance-response-entity.ts
1189
+ var GuidanceResponseEntity = createDataEntity(
1190
+ "guidanceresponse",
1191
+ "GuidanceResponse"
1192
+ );
1193
+
1194
+ // src/data/dynamo/entities/data/healthcare-service-entity.ts
1195
+ var HealthcareServiceEntity = createDataEntity(
1196
+ "healthcareservice",
1197
+ "HealthcareService"
1198
+ );
1199
+
1200
+ // src/data/dynamo/entities/data/imaging-study-entity.ts
1201
+ var ImagingStudyEntity = createDataEntity(
1202
+ "imagingstudy",
1203
+ "ImagingStudy"
1204
+ );
1205
+
1206
+ // src/data/dynamo/entities/data/immunization-entity.ts
1207
+ var ImmunizationEntity = createDataEntity(
1208
+ "immunization",
1209
+ "Immunization"
1210
+ );
1211
+
1212
+ // src/data/dynamo/entities/data/immunization-evaluation-entity.ts
1213
+ var ImmunizationEvaluationEntity = createDataEntity(
1214
+ "immunizationevaluation",
1215
+ "ImmunizationEvaluation"
1216
+ );
1217
+
1218
+ // src/data/dynamo/entities/data/immunization-recommendation-entity.ts
1219
+ var ImmunizationRecommendationEntity = createDataEntity(
1220
+ "immunizationrecommendation",
1221
+ "ImmunizationRecommendation"
1222
+ );
1223
+
1224
+ // src/data/dynamo/entities/data/insurance-plan-entity.ts
1225
+ var InsurancePlanEntity = createDataEntity(
1226
+ "insuranceplan",
1227
+ "InsurancePlan"
1228
+ );
1229
+
1230
+ // src/data/dynamo/entities/data/invoice-entity.ts
1231
+ var InvoiceEntity = createDataEntity("invoice", "Invoice");
1232
+
1233
+ // src/data/dynamo/entities/data/library-entity.ts
1234
+ var LibraryEntity = createDataEntity("library", "Library");
1235
+
1236
+ // src/data/dynamo/entities/data/linkage-entity.ts
1237
+ var LinkageEntity = createDataEntity("linkage", "Linkage");
1238
+
1239
+ // src/data/dynamo/entities/data/list-entity.ts
1240
+ var ListEntity = createDataEntity("list", "List");
1241
+
1242
+ // src/data/dynamo/entities/data/location-entity.ts
1243
+ var LocationEntity = createDataEntity("location", "Location");
1244
+
1245
+ // src/data/dynamo/entities/data/measure-entity.ts
1246
+ var MeasureEntity = createDataEntity("measure", "Measure");
1247
+
1248
+ // src/data/dynamo/entities/data/measure-report-entity.ts
1249
+ var MeasureReportEntity = createDataEntity(
1250
+ "measurereport",
1251
+ "MeasureReport"
1252
+ );
1253
+
1254
+ // src/data/dynamo/entities/data/media-entity.ts
1255
+ var MediaEntity = createDataEntity("media", "Media");
1256
+
1257
+ // src/data/dynamo/entities/data/medication-administration-entity.ts
1258
+ var MedicationAdministrationEntity = createDataEntity(
1259
+ "medicationadministration",
1260
+ "MedicationAdministration"
1261
+ );
1262
+
1263
+ // src/data/dynamo/entities/data/medication-dispense-entity.ts
1264
+ var MedicationDispenseEntity = createDataEntity(
1265
+ "medicationdispense",
1266
+ "MedicationDispense"
1267
+ );
1268
+
1269
+ // src/data/dynamo/entities/data/medication-entity.ts
1270
+ var MedicationEntity = createDataEntity("medication", "Medication");
1271
+
1272
+ // src/data/dynamo/entities/data/medication-knowledge-entity.ts
1273
+ var MedicationKnowledgeEntity = createDataEntity(
1274
+ "medicationknowledge",
1275
+ "MedicationKnowledge"
1276
+ );
1277
+
1278
+ // src/data/dynamo/entities/data/medication-request-entity.ts
1279
+ var MedicationRequestEntity = createDataEntity(
1280
+ "medicationrequest",
1281
+ "MedicationRequest"
1282
+ );
1283
+
1284
+ // src/data/dynamo/entities/data/medication-statement-entity.ts
1285
+ var MedicationStatementEntity = createDataEntity(
1286
+ "medicationstatement",
1287
+ "MedicationStatement"
1288
+ );
1289
+
1290
+ // src/data/dynamo/entities/data/message-header-entity.ts
1291
+ var MessageHeaderEntity = createDataEntity(
1292
+ "messageheader",
1293
+ "MessageHeader"
1294
+ );
1295
+
1296
+ // src/data/dynamo/entities/data/molecular-sequence-entity.ts
1297
+ var MolecularSequenceEntity = createDataEntity(
1298
+ "molecularsequence",
1299
+ "MolecularSequence"
1300
+ );
1301
+
1302
+ // src/data/dynamo/entities/data/nutrition-order-entity.ts
1303
+ var NutritionOrderEntity = createDataEntity(
1304
+ "nutritionorder",
1305
+ "NutritionOrder"
1306
+ );
1307
+
1308
+ // src/data/dynamo/entities/data/observation-entity.ts
1309
+ var ObservationEntity = createDataEntity("observation", "Observation");
1310
+
1311
+ // src/data/dynamo/entities/data/organization-affiliation-entity.ts
1312
+ var OrganizationAffiliationEntity = createDataEntity(
1313
+ "organizationaffiliation",
1314
+ "OrganizationAffiliation"
1315
+ );
1316
+
1317
+ // src/data/dynamo/entities/data/organization-entity.ts
1318
+ var OrganizationEntity = createDataEntity(
1319
+ "organization",
1320
+ "Organization"
1321
+ );
1322
+
1323
+ // src/data/dynamo/entities/data/patient-entity.ts
1324
+ var PatientEntity = createDataEntity("patient", "Patient");
1325
+
1326
+ // src/data/dynamo/entities/data/payment-notice-entity.ts
1327
+ var PaymentNoticeEntity = createDataEntity(
1328
+ "paymentnotice",
1329
+ "PaymentNotice"
1330
+ );
1331
+
1332
+ // src/data/dynamo/entities/data/payment-reconciliation-entity.ts
1333
+ var PaymentReconciliationEntity = createDataEntity(
1334
+ "paymentreconciliation",
1335
+ "PaymentReconciliation"
1336
+ );
1337
+
1338
+ // src/data/dynamo/entities/data/person-entity.ts
1339
+ var PersonEntity = createDataEntity("person", "Person");
1340
+
1341
+ // src/data/dynamo/entities/data/plan-definition-entity.ts
1342
+ var PlanDefinitionEntity = createDataEntity(
1343
+ "plandefinition",
1344
+ "PlanDefinition"
1345
+ );
1346
+
1347
+ // src/data/dynamo/entities/data/practitioner-entity.ts
1348
+ var PractitionerEntity = createDataEntity(
1349
+ "practitioner",
1350
+ "Practitioner"
1351
+ );
1352
+
1353
+ // src/data/dynamo/entities/data/practitioner-role-entity.ts
1354
+ var PractitionerRoleEntity = createDataEntity(
1355
+ "practitionerrole",
1356
+ "PractitionerRole"
1357
+ );
1358
+
1359
+ // src/data/dynamo/entities/data/procedure-entity.ts
1360
+ var ProcedureEntity = createDataEntity("procedure", "Procedure");
1361
+
1362
+ // src/data/dynamo/entities/data/provenance-entity.ts
1363
+ var ProvenanceEntity = createDataEntity("provenance", "Provenance");
1364
+
1365
+ // src/data/dynamo/entities/data/questionnaire-entity.ts
1366
+ var QuestionnaireEntity = createDataEntity(
1367
+ "questionnaire",
1368
+ "Questionnaire"
1369
+ );
1370
+
1371
+ // src/data/dynamo/entities/data/questionnaire-response-entity.ts
1372
+ var QuestionnaireResponseEntity = createDataEntity(
1373
+ "questionnaireresponse",
1374
+ "QuestionnaireResponse"
1375
+ );
1376
+
1377
+ // src/data/dynamo/entities/data/related-person-entity.ts
1378
+ var RelatedPersonEntity = createDataEntity(
1379
+ "relatedperson",
1380
+ "RelatedPerson"
1381
+ );
1382
+
1383
+ // src/data/dynamo/entities/data/request-group-entity.ts
1384
+ var RequestGroupEntity = createDataEntity(
1385
+ "requestgroup",
1386
+ "RequestGroup"
1387
+ );
1388
+
1389
+ // src/data/dynamo/entities/data/research-study-entity.ts
1390
+ var ResearchStudyEntity = createDataEntity(
1391
+ "researchstudy",
1392
+ "ResearchStudy"
1393
+ );
1394
+
1395
+ // src/data/dynamo/entities/data/research-subject-entity.ts
1396
+ var ResearchSubjectEntity = createDataEntity(
1397
+ "researchsubject",
1398
+ "ResearchSubject"
1399
+ );
1400
+
1401
+ // src/data/dynamo/entities/data/risk-assessment-entity.ts
1402
+ var RiskAssessmentEntity = createDataEntity(
1403
+ "riskassessment",
1404
+ "RiskAssessment"
1405
+ );
1406
+
1407
+ // src/data/dynamo/entities/data/risk-evidence-synthesis-entity.ts
1408
+ var RiskEvidenceSynthesisEntity = createDataEntity(
1409
+ "riskevidencesynthesis",
1410
+ "RiskEvidenceSynthesis"
1411
+ );
1412
+
1413
+ // src/data/dynamo/entities/data/schedule-entity.ts
1414
+ var ScheduleEntity = createDataEntity("schedule", "Schedule");
1415
+
1416
+ // src/data/dynamo/entities/data/service-request-entity.ts
1417
+ var ServiceRequestEntity = createDataEntity(
1418
+ "servicerequest",
1419
+ "ServiceRequest"
1420
+ );
1421
+
1422
+ // src/data/dynamo/entities/data/slot-entity.ts
1423
+ var SlotEntity = createDataEntity("slot", "Slot");
1424
+
1425
+ // src/data/dynamo/entities/data/specimen-entity.ts
1426
+ var SpecimenEntity = createDataEntity("specimen", "Specimen");
1427
+
1428
+ // src/data/dynamo/entities/data/subscription-entity.ts
1429
+ var SubscriptionEntity = createDataEntity(
1430
+ "subscription",
1431
+ "Subscription"
1432
+ );
1433
+
1434
+ // src/data/dynamo/entities/data/substance-entity.ts
1435
+ var SubstanceEntity = createDataEntity("substance", "Substance");
1436
+
1437
+ // src/data/dynamo/entities/data/supply-delivery-entity.ts
1438
+ var SupplyDeliveryEntity = createDataEntity(
1439
+ "supplydelivery",
1440
+ "SupplyDelivery"
1441
+ );
1442
+
1443
+ // src/data/dynamo/entities/data/supply-request-entity.ts
1444
+ var SupplyRequestEntity = createDataEntity(
1445
+ "supplyrequest",
1446
+ "SupplyRequest"
1447
+ );
1448
+
1449
+ // src/data/dynamo/entities/data/task-entity.ts
1450
+ var TaskEntity = createDataEntity("task", "Task");
1451
+
1452
+ // src/data/dynamo/entities/data/verification-result-entity.ts
1453
+ var VerificationResultEntity = createDataEntity(
1454
+ "verificationresult",
1455
+ "VerificationResult"
1456
+ );
1457
+
1458
+ // src/data/dynamo/entities/data/vision-prescription-entity.ts
1459
+ var VisionPrescriptionEntity = createDataEntity(
1460
+ "visionprescription",
1461
+ "VisionPrescription"
1462
+ );
1463
+
1464
+ // src/data/dynamo/dynamo-data-service.ts
1465
+ var dataPlaneEntities = {
1466
+ account: AccountEntity,
1467
+ activitydefinition: ActivityDefinitionEntity,
1468
+ adverseevent: AdverseEventEntity,
1469
+ allergyintolerance: AllergyIntoleranceEntity,
1470
+ auditevent: AuditEventEntity,
1471
+ basic: BasicEntity,
1472
+ biologicallyderivedproduct: BiologicallyDerivedProductEntity,
1473
+ bodystructure: BodyStructureEntity,
1474
+ appointment: AppointmentEntity,
1475
+ appointmentresponse: AppointmentResponseEntity,
1476
+ careplan: CarePlanEntity,
1477
+ careteam: CareTeamEntity,
1478
+ catalogentry: CatalogEntryEntity,
1479
+ chargeitem: ChargeItemEntity,
1480
+ chargeitemdefinition: ChargeItemDefinitionEntity,
1481
+ claim: ClaimEntity,
1482
+ claimresponse: ClaimResponseEntity,
1483
+ clinicalimpression: ClinicalImpressionEntity,
1484
+ communication: CommunicationEntity,
1485
+ communicationrequest: CommunicationRequestEntity,
1486
+ composition: CompositionEntity,
1487
+ condition: ConditionEntity,
1488
+ consent: ConsentEntity,
1489
+ contract: ContractEntity,
1490
+ coverage: CoverageEntity,
1491
+ coverageeligibilityrequest: CoverageEligibilityRequestEntity,
1492
+ coverageeligibilityresponse: CoverageEligibilityResponseEntity,
1493
+ detectedissue: DetectedIssueEntity,
1494
+ device: DeviceEntity,
1495
+ devicedefinition: DeviceDefinitionEntity,
1496
+ devicemetric: DeviceMetricEntity,
1497
+ devicerequest: DeviceRequestEntity,
1498
+ deviceusestatement: DeviceUseStatementEntity,
1499
+ diagnosticreport: DiagnosticReportEntity,
1500
+ documentmanifest: DocumentManifestEntity,
1501
+ documentreference: DocumentReferenceEntity,
1502
+ effectevidencesynthesis: EffectEvidenceSynthesisEntity,
1503
+ encounter: EncounterEntity,
1504
+ endpoint: EndpointEntity,
1505
+ enrollmentrequest: EnrollmentRequestEntity,
1506
+ enrollmentresponse: EnrollmentResponseEntity,
1507
+ episodeofcare: EpisodeOfCareEntity,
1508
+ eventdefinition: EventDefinitionEntity,
1509
+ evidence: EvidenceEntity,
1510
+ evidencevariable: EvidenceVariableEntity,
1511
+ explanationofbenefit: ExplanationOfBenefitEntity,
1512
+ familymemberhistory: FamilyMemberHistoryEntity,
1513
+ flag: FlagEntity,
1514
+ goal: GoalEntity,
1515
+ group: GroupEntity,
1516
+ guidanceresponse: GuidanceResponseEntity,
1517
+ healthcareservice: HealthcareServiceEntity,
1518
+ immunization: ImmunizationEntity,
1519
+ immunizationevaluation: ImmunizationEvaluationEntity,
1520
+ immunizationrecommendation: ImmunizationRecommendationEntity,
1521
+ imagingstudy: ImagingStudyEntity,
1522
+ insuranceplan: InsurancePlanEntity,
1523
+ invoice: InvoiceEntity,
1524
+ library: LibraryEntity,
1525
+ linkage: LinkageEntity,
1526
+ list: ListEntity,
1527
+ location: LocationEntity,
1528
+ medication: MedicationEntity,
1529
+ medicationadministration: MedicationAdministrationEntity,
1530
+ medicationdispense: MedicationDispenseEntity,
1531
+ medicationknowledge: MedicationKnowledgeEntity,
1532
+ medicationrequest: MedicationRequestEntity,
1533
+ medicationstatement: MedicationStatementEntity,
1534
+ media: MediaEntity,
1535
+ measure: MeasureEntity,
1536
+ measurereport: MeasureReportEntity,
1537
+ messageheader: MessageHeaderEntity,
1538
+ molecularsequence: MolecularSequenceEntity,
1539
+ nutritionorder: NutritionOrderEntity,
1540
+ observation: ObservationEntity,
1541
+ organization: OrganizationEntity,
1542
+ organizationaffiliation: OrganizationAffiliationEntity,
1543
+ patient: PatientEntity,
1544
+ paymentnotice: PaymentNoticeEntity,
1545
+ paymentreconciliation: PaymentReconciliationEntity,
1546
+ person: PersonEntity,
1547
+ plandefinition: PlanDefinitionEntity,
1548
+ practitioner: PractitionerEntity,
1549
+ practitionerrole: PractitionerRoleEntity,
1550
+ procedure: ProcedureEntity,
1551
+ provenance: ProvenanceEntity,
1552
+ questionnaire: QuestionnaireEntity,
1553
+ questionnaireresponse: QuestionnaireResponseEntity,
1554
+ requestgroup: RequestGroupEntity,
1555
+ relatedperson: RelatedPersonEntity,
1556
+ researchstudy: ResearchStudyEntity,
1557
+ researchsubject: ResearchSubjectEntity,
1558
+ riskassessment: RiskAssessmentEntity,
1559
+ riskevidencesynthesis: RiskEvidenceSynthesisEntity,
1560
+ schedule: ScheduleEntity,
1561
+ servicerequest: ServiceRequestEntity,
1562
+ specimen: SpecimenEntity,
1563
+ substance: SubstanceEntity,
1564
+ subscription: SubscriptionEntity,
1565
+ supplydelivery: SupplyDeliveryEntity,
1566
+ supplyrequest: SupplyRequestEntity,
1567
+ slot: SlotEntity,
1568
+ task: TaskEntity,
1569
+ visionprescription: VisionPrescriptionEntity,
1570
+ verificationresult: VerificationResultEntity
1571
+ };
1572
+ var dataPlaneService = new Service2(dataPlaneEntities, {
1573
+ table: defaultTableName,
1574
+ client: dynamoClient
1575
+ });
1576
+ var DynamoDataService = {
1577
+ entities: dataPlaneService.entities
1578
+ };
1579
+ function getDynamoDataService(tableName) {
1580
+ const resolved = tableName ?? defaultTableName;
1581
+ const service = new Service2(dataPlaneEntities, {
1582
+ table: resolved,
1583
+ client: dynamoClient
1584
+ });
1585
+ return {
1586
+ entities: service.entities
1587
+ };
1588
+ }
1589
+
1590
+ // src/data/operations/data-operations-common.ts
1591
+ var DATA_ENTITY_SK = "CURRENT";
1592
+ async function getDataEntityById(entity, tenantId, workspaceId, id, resourceLabel) {
1593
+ const result = await entity.get({
1594
+ tenantId,
1595
+ workspaceId,
1596
+ id,
1597
+ sk: DATA_ENTITY_SK
1598
+ }).go();
1599
+ if (!result.data) {
1600
+ throw new NotFoundError(`${resourceLabel} ${id} not found`, {
1601
+ details: { id }
1602
+ });
1603
+ }
1604
+ const parsed = JSON.parse(decompressResource(result.data.resource));
1605
+ return {
1606
+ id: result.data.id,
1607
+ resource: { ...parsed, id: result.data.id }
1608
+ };
1609
+ }
1610
+ async function deleteDataEntityById(entity, tenantId, workspaceId, id) {
1611
+ await entity.delete({
1612
+ tenantId,
1613
+ workspaceId,
1614
+ id,
1615
+ sk: DATA_ENTITY_SK
1616
+ }).go();
1617
+ }
1618
+ async function listDataEntitiesByWorkspace(entity, tenantId, workspaceId) {
1619
+ const result = await entity.query.gsi4({ tenantId, workspaceId }).go();
1620
+ const items = result.data ?? [];
1621
+ const entries = items.map((item) => {
1622
+ const parsed = JSON.parse(decompressResource(item.resource));
799
1623
  return {
800
- errorResponse: res.status(400).json({
801
- resourceType: "OperationOutcome",
802
- issue: [
803
- {
804
- severity: "error",
805
- code: "invalid",
806
- diagnostics: "Request body must be a JSON object."
807
- }
808
- ]
809
- })
1624
+ id: item.id,
1625
+ resource: { ...parsed, id: item.id }
810
1626
  };
1627
+ });
1628
+ return { entries };
1629
+ }
1630
+ async function createDataEntityRecord(entity, tenantId, workspaceId, id, resourceWithAudit, fallbackDate) {
1631
+ const lastUpdated = resourceWithAudit.meta?.lastUpdated ?? fallbackDate ?? (/* @__PURE__ */ new Date()).toISOString();
1632
+ const vid = lastUpdated.replace(/[-:T.Z]/g, "").slice(0, 12) || Date.now().toString(36);
1633
+ await entity.put({
1634
+ sk: DATA_ENTITY_SK,
1635
+ tenantId,
1636
+ workspaceId,
1637
+ id,
1638
+ resource: compressResource(JSON.stringify(resourceWithAudit)),
1639
+ vid,
1640
+ lastUpdated
1641
+ }).go();
1642
+ return {
1643
+ id,
1644
+ resource: resourceWithAudit
1645
+ };
1646
+ }
1647
+ async function updateDataEntityById(entity, tenantId, workspaceId, id, resourceLabel, context, buildPatched) {
1648
+ const existing = await entity.get({
1649
+ tenantId,
1650
+ workspaceId,
1651
+ id,
1652
+ sk: DATA_ENTITY_SK
1653
+ }).go();
1654
+ if (!existing.data) {
1655
+ throw new NotFoundError(`${resourceLabel} ${id} not found`, {
1656
+ details: { id }
1657
+ });
1658
+ }
1659
+ const existingStr = decompressResource(existing.data.resource);
1660
+ const { resource, lastUpdated } = buildPatched(existingStr);
1661
+ await entity.patch({
1662
+ tenantId,
1663
+ workspaceId,
1664
+ id,
1665
+ sk: DATA_ENTITY_SK
1666
+ }).set({
1667
+ resource: compressResource(JSON.stringify(resource)),
1668
+ lastUpdated
1669
+ }).go();
1670
+ return {
1671
+ id,
1672
+ resource
1673
+ };
1674
+ }
1675
+
1676
+ // src/data/operations/data/encounter/encounter-create-operation.ts
1677
+ async function createEncounterOperation(params) {
1678
+ const { context, body, id: optionalId, tableName } = params;
1679
+ const { tenantId, workspaceId, date, actorId, actorName } = context;
1680
+ const id = body.id ?? optionalId ?? ulid();
1681
+ const meta = {
1682
+ ...body.meta ?? {},
1683
+ lastUpdated: date,
1684
+ versionId: "1"
1685
+ };
1686
+ const encounterWithAudit = {
1687
+ ...body,
1688
+ resourceType: "Encounter",
1689
+ id,
1690
+ meta: mergeAuditIntoMeta(meta, {
1691
+ createdDate: date,
1692
+ createdById: actorId,
1693
+ createdByName: actorName,
1694
+ modifiedDate: date,
1695
+ modifiedById: actorId,
1696
+ modifiedByName: actorName
1697
+ })
1698
+ };
1699
+ const service = getDynamoDataService(tableName);
1700
+ return createDataEntityRecord(
1701
+ service.entities.encounter,
1702
+ tenantId,
1703
+ workspaceId,
1704
+ id,
1705
+ encounterWithAudit,
1706
+ date
1707
+ );
1708
+ }
1709
+
1710
+ // src/data/rest-api/routes/data/encounter/encounter-create-route.ts
1711
+ async function createEncounterRoute(req, res) {
1712
+ const bodyResult = requireJsonBody(req, res);
1713
+ if ("errorResponse" in bodyResult) return bodyResult.errorResponse;
1714
+ const ctx = req.openhiContext;
1715
+ const body = bodyResult.body;
1716
+ const encounter = {
1717
+ ...body,
1718
+ resourceType: "Encounter"
1719
+ };
1720
+ try {
1721
+ const result = await createEncounterOperation({
1722
+ context: ctx,
1723
+ body: encounter
1724
+ });
1725
+ return res.status(201).location(`${BASE_PATH.ENCOUNTER}/${result.id}`).json(result.resource);
1726
+ } catch (err) {
1727
+ console.error("POST Encounter error:", err);
1728
+ return res.status(500).json({
1729
+ resourceType: "OperationOutcome",
1730
+ issue: [
1731
+ { severity: "error", code: "exception", diagnostics: String(err) }
1732
+ ]
1733
+ });
1734
+ }
1735
+ }
1736
+
1737
+ // src/data/operations/data/encounter/encounter-delete-operation.ts
1738
+ async function deleteEncounterOperation(params) {
1739
+ const { context, id, tableName } = params;
1740
+ const { tenantId, workspaceId } = context;
1741
+ const service = getDynamoDataService(tableName);
1742
+ await deleteDataEntityById(
1743
+ service.entities.encounter,
1744
+ tenantId,
1745
+ workspaceId,
1746
+ id
1747
+ );
1748
+ }
1749
+
1750
+ // src/data/rest-api/routes/data/encounter/encounter-delete-route.ts
1751
+ async function deleteEncounterRoute(req, res) {
1752
+ const id = String(req.params.id);
1753
+ const ctx = req.openhiContext;
1754
+ try {
1755
+ await deleteEncounterOperation({ context: ctx, id });
1756
+ return res.status(204).send();
1757
+ } catch (err) {
1758
+ return sendOperationOutcome500(res, err, "DELETE Encounter error:");
1759
+ }
1760
+ }
1761
+
1762
+ // src/data/operations/data/encounter/encounter-get-by-id-operation.ts
1763
+ async function getEncounterByIdOperation(params) {
1764
+ const { context, id, tableName } = params;
1765
+ const { tenantId, workspaceId } = context;
1766
+ const service = getDynamoDataService(tableName);
1767
+ return getDataEntityById(
1768
+ service.entities.encounter,
1769
+ tenantId,
1770
+ workspaceId,
1771
+ id,
1772
+ "Encounter"
1773
+ );
1774
+ }
1775
+
1776
+ // src/data/rest-api/routes/data/encounter/encounter-get-by-id-route.ts
1777
+ async function getEncounterByIdRoute(req, res) {
1778
+ const id = String(req.params.id);
1779
+ const ctx = req.openhiContext;
1780
+ try {
1781
+ const result = await getEncounterByIdOperation({ context: ctx, id });
1782
+ return res.json(result.resource);
1783
+ } catch (err) {
1784
+ const status = domainErrorToHttpStatus(err);
1785
+ if (status === 404) {
1786
+ const diagnostics = err instanceof NotFoundError ? err.message : `Encounter ${id} not found`;
1787
+ return sendOperationOutcome404(res, diagnostics);
1788
+ }
1789
+ return sendOperationOutcome500(res, err, "GET Encounter error:");
1790
+ }
1791
+ }
1792
+
1793
+ // src/data/operations/data/encounter/encounter-list-operation.ts
1794
+ async function listEncountersOperation(params) {
1795
+ const { context, tableName } = params;
1796
+ const { tenantId, workspaceId } = context;
1797
+ const service = getDynamoDataService(tableName);
1798
+ return listDataEntitiesByWorkspace(
1799
+ service.entities.encounter,
1800
+ tenantId,
1801
+ workspaceId
1802
+ );
1803
+ }
1804
+
1805
+ // src/data/rest-api/routes/data/encounter/encounter-list-route.ts
1806
+ async function listEncountersRoute(req, res) {
1807
+ const ctx = req.openhiContext;
1808
+ try {
1809
+ const result = await listEncountersOperation({ context: ctx });
1810
+ const bundle = buildSearchsetBundle(BASE_PATH.ENCOUNTER, result.entries);
1811
+ return res.json(bundle);
1812
+ } catch (err) {
1813
+ return sendOperationOutcome500(res, err, "GET /Encounter list error:");
1814
+ }
1815
+ }
1816
+
1817
+ // src/data/operations/data/encounter/encounter-update-operation.ts
1818
+ async function updateEncounterOperation(params) {
1819
+ const { context, id, body, tableName } = params;
1820
+ const { tenantId, workspaceId, date, actorId, actorName } = context;
1821
+ const service = getDynamoDataService(tableName);
1822
+ return updateDataEntityById(
1823
+ service.entities.encounter,
1824
+ tenantId,
1825
+ workspaceId,
1826
+ id,
1827
+ "Encounter",
1828
+ context,
1829
+ (existingResourceStr) => {
1830
+ const bodyWithResource = body;
1831
+ const existingMeta = JSON.parse(existingResourceStr).meta;
1832
+ const encounter = {
1833
+ ...body,
1834
+ resourceType: "Encounter",
1835
+ id,
1836
+ meta: {
1837
+ ...bodyWithResource.meta ?? {},
1838
+ lastUpdated: date,
1839
+ versionId: "2"
1840
+ }
1841
+ };
1842
+ const encounterWithMeta = {
1843
+ ...encounter,
1844
+ meta: mergeAuditIntoMeta(encounter.meta ?? existingMeta, {
1845
+ modifiedDate: date,
1846
+ modifiedById: actorId,
1847
+ modifiedByName: actorName
1848
+ })
1849
+ };
1850
+ return {
1851
+ resource: encounterWithMeta,
1852
+ lastUpdated: date
1853
+ };
1854
+ }
1855
+ );
1856
+ }
1857
+
1858
+ // src/data/rest-api/routes/data/encounter/encounter-update-route.ts
1859
+ async function updateEncounterRoute(req, res) {
1860
+ const bodyResult = requireJsonBody(req, res);
1861
+ if ("errorResponse" in bodyResult) return bodyResult.errorResponse;
1862
+ const id = String(req.params.id);
1863
+ const ctx = req.openhiContext;
1864
+ const body = bodyResult.body;
1865
+ const encounter = {
1866
+ ...body,
1867
+ resourceType: "Encounter",
1868
+ id,
1869
+ meta: {
1870
+ ...body?.meta ?? {},
1871
+ lastUpdated: ctx.date,
1872
+ versionId: "2"
1873
+ }
1874
+ };
1875
+ try {
1876
+ const result = await updateEncounterOperation({
1877
+ context: ctx,
1878
+ id,
1879
+ body: encounter
1880
+ });
1881
+ return res.json(result.resource);
1882
+ } catch (err) {
1883
+ const status = domainErrorToHttpStatus(err);
1884
+ if (status === 404) {
1885
+ const diagnostics = err instanceof NotFoundError ? err.message : `Encounter ${id} not found`;
1886
+ return sendOperationOutcome404(res, diagnostics);
1887
+ }
1888
+ return sendOperationOutcome500(res, err, "PUT Encounter error:");
811
1889
  }
812
- return { body: raw };
813
1890
  }
814
1891
 
1892
+ // src/data/rest-api/routes/data/encounter/encounter.ts
1893
+ var router2 = express2.Router();
1894
+ router2.get("/", listEncountersRoute);
1895
+ router2.get("/:id", getEncounterByIdRoute);
1896
+ router2.post("/", createEncounterRoute);
1897
+ router2.put("/:id", updateEncounterRoute);
1898
+ router2.delete("/:id", deleteEncounterRoute);
1899
+
1900
+ // src/data/rest-api/routes/data/patient/patient.ts
1901
+ import express3 from "express";
1902
+
1903
+ // src/data/operations/data/patient/patient-create-operation.ts
1904
+ import { ulid as ulid2 } from "ulid";
1905
+
815
1906
  // src/data/import-patient.ts
816
1907
  import { readFileSync } from "fs";
817
1908
  import { resolve } from "path";
818
- var OPENHI_EXT = "http://openhi.org/fhir/StructureDefinition";
819
- function mergeAuditIntoMeta(meta, audit) {
820
- const existing = meta ?? {};
821
- const ext = [
822
- ...Array.isArray(existing.extension) ? existing.extension : []
823
- ];
824
- const byUrl = new Map(ext.map((e) => [e.url, e]));
825
- function set(url, value, type) {
826
- if (value == null) return;
827
- byUrl.set(url, { url, [type]: value });
828
- }
829
- set(`${OPENHI_EXT}/created-date`, audit.createdDate, "valueDateTime");
830
- set(`${OPENHI_EXT}/created-by-id`, audit.createdById, "valueString");
831
- set(`${OPENHI_EXT}/created-by-name`, audit.createdByName, "valueString");
832
- set(`${OPENHI_EXT}/modified-date`, audit.modifiedDate, "valueDateTime");
833
- set(`${OPENHI_EXT}/modified-by-id`, audit.modifiedById, "valueString");
834
- set(`${OPENHI_EXT}/modified-by-name`, audit.modifiedByName, "valueString");
835
- set(`${OPENHI_EXT}/deleted-date`, audit.deletedDate, "valueDateTime");
836
- set(`${OPENHI_EXT}/deleted-by-id`, audit.deletedById, "valueString");
837
- set(`${OPENHI_EXT}/deleted-by-name`, audit.deletedByName, "valueString");
838
- return { ...existing, extension: Array.from(byUrl.values()) };
839
- }
840
1909
  function extractPatient(parsed) {
841
1910
  if (parsed && typeof parsed === "object" && "resourceType" in parsed) {
842
1911
  const root = parsed;
@@ -859,7 +1928,7 @@ function extractPatient(parsed) {
859
1928
  "File must be a FHIR Patient resource or a Bundle containing at least one Patient entry"
860
1929
  );
861
1930
  }
862
- var SK3 = "CURRENT";
1931
+ var SK5 = "CURRENT";
863
1932
  var defaultAudit = {
864
1933
  createdDate: (/* @__PURE__ */ new Date()).toISOString(),
865
1934
  createdById: "import",
@@ -897,7 +1966,7 @@ function patientToPutAttrs(patient, options) {
897
1966
  patientWithMeta.meta.lastUpdated = lastUpdated;
898
1967
  }
899
1968
  return {
900
- sk: SK3,
1969
+ sk: SK5,
901
1970
  tenantId,
902
1971
  workspaceId,
903
1972
  id: patient.id,
@@ -953,23 +2022,21 @@ if (__require.main === module) {
953
2022
  void main();
954
2023
  }
955
2024
 
956
- // src/data/rest-api/routes/patient/patient-create.ts
957
- async function createPatient(req, res) {
958
- const bodyResult = requireJsonBody(req, res);
959
- if ("errorResponse" in bodyResult) return bodyResult.errorResponse;
960
- const ctx = req.openhiContext;
961
- const { tenantId, workspaceId, date, actorId, actorName } = ctx;
962
- const body = bodyResult.body;
963
- const id = body?.id ?? `patient-${Date.now()}`;
2025
+ // src/data/operations/data/patient/patient-create-operation.ts
2026
+ async function createPatientOperation(params) {
2027
+ const { context, body, id: optionalId } = params;
2028
+ const { tenantId, workspaceId, date, actorId, actorName } = context;
2029
+ const id = body.id ?? optionalId ?? ulid2();
2030
+ const meta = {
2031
+ ...body.meta ?? {},
2032
+ lastUpdated: date,
2033
+ versionId: "1"
2034
+ };
964
2035
  const patient = {
965
2036
  ...body,
966
2037
  resourceType: "Patient",
967
2038
  id,
968
- meta: {
969
- ...body?.meta ?? {},
970
- lastUpdated: date,
971
- versionId: "1"
972
- }
2039
+ meta
973
2040
  };
974
2041
  const options = {
975
2042
  tenantId,
@@ -981,13 +2048,31 @@ async function createPatient(req, res) {
981
2048
  modifiedById: actorId,
982
2049
  modifiedByName: actorName
983
2050
  };
984
- const service = getDynamoDataService();
2051
+ const service = getDynamoDataService(params.tableName);
2052
+ const attrs = patientToPutAttrs(patient, options);
2053
+ await service.entities.patient.put(attrs).go();
2054
+ return {
2055
+ id,
2056
+ resource: patient
2057
+ };
2058
+ }
2059
+
2060
+ // src/data/rest-api/routes/data/patient/patient-create-route.ts
2061
+ async function createPatientRoute(req, res) {
2062
+ const bodyResult = requireJsonBody(req, res);
2063
+ if ("errorResponse" in bodyResult) return bodyResult.errorResponse;
2064
+ const ctx = req.openhiContext;
2065
+ const body = bodyResult.body;
2066
+ const patient = {
2067
+ ...body,
2068
+ resourceType: "Patient"
2069
+ };
985
2070
  try {
986
- const attrs = patientToPutAttrs(patient, options);
987
- await service.entities.patient.put(
988
- attrs
989
- ).go();
990
- return res.status(201).location(`${BASE_PATH3}/${id}`).json(patient);
2071
+ const result = await createPatientOperation({
2072
+ context: ctx,
2073
+ body: patient
2074
+ });
2075
+ return res.status(201).location(`${BASE_PATH.PATIENT}/${result.id}`).json(result.resource);
991
2076
  } catch (err) {
992
2077
  console.error("POST Patient error:", err);
993
2078
  return res.status(500).json({
@@ -999,180 +2084,409 @@ async function createPatient(req, res) {
999
2084
  }
1000
2085
  }
1001
2086
 
1002
- // src/data/rest-api/routes/patient/patient-delete.ts
1003
- async function deletePatient(req, res) {
2087
+ // src/data/operations/data/patient/patient-delete-operation.ts
2088
+ async function deletePatientOperation(params) {
2089
+ const { context, id, tableName } = params;
2090
+ const { tenantId, workspaceId } = context;
2091
+ const service = getDynamoDataService(tableName);
2092
+ await deleteDataEntityById(
2093
+ service.entities.patient,
2094
+ tenantId,
2095
+ workspaceId,
2096
+ id
2097
+ );
2098
+ }
2099
+
2100
+ // src/data/rest-api/routes/data/patient/patient-delete-route.ts
2101
+ async function deletePatientRoute(req, res) {
1004
2102
  const id = String(req.params.id);
1005
- const { tenantId, workspaceId } = req.openhiContext;
1006
- const service = getDynamoDataService();
2103
+ const ctx = req.openhiContext;
1007
2104
  try {
1008
- await service.entities.patient.delete({ tenantId, workspaceId, id, sk: SK2 }).go();
2105
+ await deletePatientOperation({ context: ctx, id });
1009
2106
  return res.status(204).send();
1010
2107
  } catch (err) {
1011
- console.error("DELETE Patient error:", err);
1012
- return res.status(500).json({
1013
- resourceType: "OperationOutcome",
1014
- issue: [
1015
- { severity: "error", code: "exception", diagnostics: String(err) }
1016
- ]
1017
- });
2108
+ return sendOperationOutcome500(res, err, "DELETE Patient error:");
1018
2109
  }
1019
2110
  }
1020
2111
 
1021
- // src/data/rest-api/routes/patient/patient-get-by-id.ts
1022
- async function getPatientById(req, res) {
2112
+ // src/data/operations/data/patient/patient-get-by-id-operation.ts
2113
+ async function getPatientByIdOperation(params) {
2114
+ const { context, id, tableName } = params;
2115
+ const { tenantId, workspaceId } = context;
2116
+ const service = getDynamoDataService(tableName);
2117
+ return getDataEntityById(
2118
+ service.entities.patient,
2119
+ tenantId,
2120
+ workspaceId,
2121
+ id,
2122
+ "Patient"
2123
+ );
2124
+ }
2125
+
2126
+ // src/data/rest-api/routes/data/patient/patient-get-by-id-route.ts
2127
+ async function getPatientByIdRoute(req, res) {
1023
2128
  const id = String(req.params.id);
1024
- const { tenantId, workspaceId } = req.openhiContext;
1025
- const service = getDynamoDataService();
2129
+ const ctx = req.openhiContext;
1026
2130
  try {
1027
- const result = await service.entities.patient.get({ tenantId, workspaceId, id, sk: SK2 }).go();
1028
- if (!result.data) {
1029
- return res.status(404).json({
1030
- resourceType: "OperationOutcome",
1031
- issue: [
1032
- {
1033
- severity: "error",
1034
- code: "not-found",
1035
- diagnostics: `Patient ${id} not found`
1036
- }
1037
- ]
1038
- });
2131
+ const result = await getPatientByIdOperation({ context: ctx, id });
2132
+ return res.json(result.resource);
2133
+ } catch (err) {
2134
+ const status = domainErrorToHttpStatus(err);
2135
+ if (status === 404) {
2136
+ const diagnostics = err instanceof NotFoundError ? err.message : `Patient ${id} not found`;
2137
+ return sendOperationOutcome404(res, diagnostics);
1039
2138
  }
1040
- const resource = JSON.parse(
1041
- decompressResource(result.data.resource)
1042
- );
1043
- return res.json({ ...resource, id: result.data.id });
2139
+ return sendOperationOutcome500(res, err, "GET Patient error:");
2140
+ }
2141
+ }
2142
+
2143
+ // src/data/operations/data/patient/patient-list-operation.ts
2144
+ async function listPatientsOperation(params) {
2145
+ const { context, tableName } = params;
2146
+ const { tenantId, workspaceId } = context;
2147
+ const service = getDynamoDataService(tableName);
2148
+ return listDataEntitiesByWorkspace(
2149
+ service.entities.patient,
2150
+ tenantId,
2151
+ workspaceId
2152
+ );
2153
+ }
2154
+
2155
+ // src/data/rest-api/routes/data/patient/patient-list-route.ts
2156
+ async function listPatientsRoute(req, res) {
2157
+ const ctx = req.openhiContext;
2158
+ try {
2159
+ const result = await listPatientsOperation({ context: ctx });
2160
+ const bundle = buildSearchsetBundle(BASE_PATH.PATIENT, result.entries);
2161
+ return res.json(bundle);
1044
2162
  } catch (err) {
1045
- console.error("GET Patient error:", err);
1046
- return res.status(500).json({
1047
- resourceType: "OperationOutcome",
1048
- issue: [
1049
- {
1050
- severity: "error",
1051
- code: "exception",
1052
- diagnostics: String(err)
2163
+ return sendOperationOutcome500(res, err, "GET /Patient list error:");
2164
+ }
2165
+ }
2166
+
2167
+ // src/data/operations/data/patient/patient-update-operation.ts
2168
+ async function updatePatientOperation(params) {
2169
+ const { context, id, body, tableName } = params;
2170
+ const { tenantId, workspaceId, date, actorId, actorName } = context;
2171
+ const service = getDynamoDataService(tableName);
2172
+ return updateDataEntityById(
2173
+ service.entities.patient,
2174
+ tenantId,
2175
+ workspaceId,
2176
+ id,
2177
+ "Patient",
2178
+ context,
2179
+ (existingResourceStr) => {
2180
+ const bodyWithResource = body;
2181
+ const existingMeta = JSON.parse(existingResourceStr).meta;
2182
+ const patient = {
2183
+ ...body,
2184
+ resourceType: "Patient",
2185
+ id,
2186
+ meta: {
2187
+ ...bodyWithResource.meta ?? {},
2188
+ lastUpdated: date,
2189
+ versionId: "2"
1053
2190
  }
1054
- ]
2191
+ };
2192
+ const patientWithMeta = {
2193
+ ...patient,
2194
+ meta: mergeAuditIntoMeta(patient.meta ?? existingMeta, {
2195
+ modifiedDate: date,
2196
+ modifiedById: actorId,
2197
+ modifiedByName: actorName
2198
+ })
2199
+ };
2200
+ return {
2201
+ resource: patientWithMeta,
2202
+ lastUpdated: date
2203
+ };
2204
+ }
2205
+ );
2206
+ }
2207
+
2208
+ // src/data/rest-api/routes/data/patient/patient-update-route.ts
2209
+ async function updatePatientRoute(req, res) {
2210
+ const bodyResult = requireJsonBody(req, res);
2211
+ if ("errorResponse" in bodyResult) return bodyResult.errorResponse;
2212
+ const id = String(req.params.id);
2213
+ const ctx = req.openhiContext;
2214
+ const body = bodyResult.body;
2215
+ const patient = {
2216
+ ...body,
2217
+ resourceType: "Patient",
2218
+ id
2219
+ };
2220
+ try {
2221
+ const result = await updatePatientOperation({
2222
+ context: ctx,
2223
+ id,
2224
+ body: patient
1055
2225
  });
2226
+ return res.json(result.resource);
2227
+ } catch (err) {
2228
+ const status = domainErrorToHttpStatus(err);
2229
+ if (status === 404) {
2230
+ const diagnostics = err instanceof NotFoundError ? err.message : `Patient ${id} not found`;
2231
+ return sendOperationOutcome404(res, diagnostics);
2232
+ }
2233
+ return sendOperationOutcome500(res, err, "PUT Patient error:");
1056
2234
  }
1057
2235
  }
1058
2236
 
1059
- // src/data/rest-api/routes/patient/patient-list.ts
1060
- async function listPatients(req, res) {
1061
- const { tenantId, workspaceId } = req.openhiContext;
1062
- const service = getDynamoDataService();
2237
+ // src/data/rest-api/routes/data/patient/patient.ts
2238
+ var router3 = express3.Router();
2239
+ router3.get("/", listPatientsRoute);
2240
+ router3.get("/:id", getPatientByIdRoute);
2241
+ router3.post("/", createPatientRoute);
2242
+ router3.put("/:id", updatePatientRoute);
2243
+ router3.delete("/:id", deletePatientRoute);
2244
+
2245
+ // src/data/rest-api/routes/data/practitioner/practitioner.ts
2246
+ import express4 from "express";
2247
+
2248
+ // src/data/operations/data/practitioner/practitioner-create-operation.ts
2249
+ import { ulid as ulid3 } from "ulid";
2250
+ async function createPractitionerOperation(params) {
2251
+ const { context, body, id: optionalId, tableName } = params;
2252
+ const { tenantId, workspaceId, date, actorId, actorName } = context;
2253
+ const id = body.id ?? optionalId ?? ulid3();
2254
+ const meta = {
2255
+ ...body.meta ?? {},
2256
+ lastUpdated: date,
2257
+ versionId: "1"
2258
+ };
2259
+ const practitionerWithAudit = {
2260
+ ...body,
2261
+ resourceType: "Practitioner",
2262
+ id,
2263
+ meta: mergeAuditIntoMeta(meta, {
2264
+ createdDate: date,
2265
+ createdById: actorId,
2266
+ createdByName: actorName,
2267
+ modifiedDate: date,
2268
+ modifiedById: actorId,
2269
+ modifiedByName: actorName
2270
+ })
2271
+ };
2272
+ const service = getDynamoDataService(tableName);
2273
+ return createDataEntityRecord(
2274
+ service.entities.practitioner,
2275
+ tenantId,
2276
+ workspaceId,
2277
+ id,
2278
+ practitionerWithAudit,
2279
+ date
2280
+ );
2281
+ }
2282
+
2283
+ // src/data/rest-api/routes/data/practitioner/practitioner-create-route.ts
2284
+ async function createPractitionerRoute(req, res) {
2285
+ const bodyResult = requireJsonBody(req, res);
2286
+ if ("errorResponse" in bodyResult) return bodyResult.errorResponse;
2287
+ const ctx = req.openhiContext;
2288
+ const body = bodyResult.body;
2289
+ const practitioner = {
2290
+ ...body,
2291
+ resourceType: "Practitioner"
2292
+ };
1063
2293
  try {
1064
- const result = await service.entities.patient.query.gsi4({ tenantId, workspaceId }).go();
1065
- const entries = (result.data ?? []).map((item) => {
1066
- const resource = JSON.parse(decompressResource(item.resource));
1067
- return {
1068
- fullUrl: `${BASE_PATH3}/${item.id}`,
1069
- resource: { ...resource, id: item.id }
1070
- };
2294
+ const result = await createPractitionerOperation({
2295
+ context: ctx,
2296
+ body: practitioner
1071
2297
  });
1072
- const bundle = {
1073
- resourceType: "Bundle",
1074
- type: "searchset",
1075
- total: entries.length,
1076
- link: [{ relation: "self", url: BASE_PATH3 }],
1077
- entry: entries
1078
- };
1079
- return res.json(bundle);
2298
+ return res.status(201).location(`${BASE_PATH.PRACTITIONER}/${result.id}`).json(result.resource);
1080
2299
  } catch (err) {
1081
- console.error("GET /Patient list error:", err);
2300
+ console.error("POST Practitioner error:", err);
1082
2301
  return res.status(500).json({
1083
2302
  resourceType: "OperationOutcome",
1084
2303
  issue: [
1085
- {
1086
- severity: "error",
1087
- code: "exception",
1088
- diagnostics: String(err)
1089
- }
2304
+ { severity: "error", code: "exception", diagnostics: String(err) }
1090
2305
  ]
1091
2306
  });
1092
2307
  }
1093
2308
  }
1094
2309
 
1095
- // src/data/rest-api/routes/patient/patient-update.ts
1096
- async function updatePatient(req, res) {
2310
+ // src/data/operations/data/practitioner/practitioner-delete-operation.ts
2311
+ async function deletePractitionerOperation(params) {
2312
+ const { context, id, tableName } = params;
2313
+ const { tenantId, workspaceId } = context;
2314
+ const service = getDynamoDataService(tableName);
2315
+ await deleteDataEntityById(
2316
+ service.entities.practitioner,
2317
+ tenantId,
2318
+ workspaceId,
2319
+ id
2320
+ );
2321
+ }
2322
+
2323
+ // src/data/rest-api/routes/data/practitioner/practitioner-delete-route.ts
2324
+ async function deletePractitionerRoute(req, res) {
2325
+ const id = String(req.params.id);
2326
+ const ctx = req.openhiContext;
2327
+ try {
2328
+ await deletePractitionerOperation({ context: ctx, id });
2329
+ return res.status(204).send();
2330
+ } catch (err) {
2331
+ return sendOperationOutcome500(res, err, "DELETE Practitioner error:");
2332
+ }
2333
+ }
2334
+
2335
+ // src/data/operations/data/practitioner/practitioner-get-by-id-operation.ts
2336
+ async function getPractitionerByIdOperation(params) {
2337
+ const { context, id, tableName } = params;
2338
+ const { tenantId, workspaceId } = context;
2339
+ const service = getDynamoDataService(tableName);
2340
+ return getDataEntityById(
2341
+ service.entities.practitioner,
2342
+ tenantId,
2343
+ workspaceId,
2344
+ id,
2345
+ "Practitioner"
2346
+ );
2347
+ }
2348
+
2349
+ // src/data/rest-api/routes/data/practitioner/practitioner-get-by-id-route.ts
2350
+ async function getPractitionerByIdRoute(req, res) {
2351
+ const id = String(req.params.id);
2352
+ const ctx = req.openhiContext;
2353
+ try {
2354
+ const result = await getPractitionerByIdOperation({ context: ctx, id });
2355
+ return res.json(result.resource);
2356
+ } catch (err) {
2357
+ const status = domainErrorToHttpStatus(err);
2358
+ if (status === 404) {
2359
+ const diagnostics = err instanceof NotFoundError ? err.message : `Practitioner ${id} not found`;
2360
+ return sendOperationOutcome404(res, diagnostics);
2361
+ }
2362
+ return sendOperationOutcome500(res, err, "GET Practitioner error:");
2363
+ }
2364
+ }
2365
+
2366
+ // src/data/operations/data/practitioner/practitioner-list-operation.ts
2367
+ async function listPractitionersOperation(params) {
2368
+ const { context, tableName } = params;
2369
+ const { tenantId, workspaceId } = context;
2370
+ const service = getDynamoDataService(tableName);
2371
+ return listDataEntitiesByWorkspace(
2372
+ service.entities.practitioner,
2373
+ tenantId,
2374
+ workspaceId
2375
+ );
2376
+ }
2377
+
2378
+ // src/data/rest-api/routes/data/practitioner/practitioner-list-route.ts
2379
+ async function listPractitionersRoute(req, res) {
2380
+ const ctx = req.openhiContext;
2381
+ try {
2382
+ const result = await listPractitionersOperation({ context: ctx });
2383
+ const bundle = buildSearchsetBundle(BASE_PATH.PRACTITIONER, result.entries);
2384
+ return res.json(bundle);
2385
+ } catch (err) {
2386
+ return sendOperationOutcome500(res, err, "GET /Practitioner list error:");
2387
+ }
2388
+ }
2389
+
2390
+ // src/data/operations/data/practitioner/practitioner-update-operation.ts
2391
+ async function updatePractitionerOperation(params) {
2392
+ const { context, id, body, tableName } = params;
2393
+ const { tenantId, workspaceId, date, actorId, actorName } = context;
2394
+ const service = getDynamoDataService(tableName);
2395
+ return updateDataEntityById(
2396
+ service.entities.practitioner,
2397
+ tenantId,
2398
+ workspaceId,
2399
+ id,
2400
+ "Practitioner",
2401
+ context,
2402
+ (existingResourceStr) => {
2403
+ const bodyWithResource = body;
2404
+ const existingMeta = JSON.parse(existingResourceStr).meta;
2405
+ const practitioner = {
2406
+ ...body,
2407
+ resourceType: "Practitioner",
2408
+ id,
2409
+ meta: {
2410
+ ...bodyWithResource.meta ?? {},
2411
+ lastUpdated: date,
2412
+ versionId: "2"
2413
+ }
2414
+ };
2415
+ const practitionerWithMeta = {
2416
+ ...practitioner,
2417
+ meta: mergeAuditIntoMeta(practitioner.meta ?? existingMeta, {
2418
+ modifiedDate: date,
2419
+ modifiedById: actorId,
2420
+ modifiedByName: actorName
2421
+ })
2422
+ };
2423
+ return {
2424
+ resource: practitionerWithMeta,
2425
+ lastUpdated: date
2426
+ };
2427
+ }
2428
+ );
2429
+ }
2430
+
2431
+ // src/data/rest-api/routes/data/practitioner/practitioner-update-route.ts
2432
+ async function updatePractitionerRoute(req, res) {
1097
2433
  const bodyResult = requireJsonBody(req, res);
1098
2434
  if ("errorResponse" in bodyResult) return bodyResult.errorResponse;
1099
2435
  const id = String(req.params.id);
1100
2436
  const ctx = req.openhiContext;
1101
- const { tenantId, workspaceId, date, actorId, actorName } = ctx;
1102
2437
  const body = bodyResult.body;
1103
- const patient = {
2438
+ const practitioner = {
1104
2439
  ...body,
1105
- resourceType: "Patient",
2440
+ resourceType: "Practitioner",
1106
2441
  id,
1107
2442
  meta: {
1108
2443
  ...body?.meta ?? {},
1109
- lastUpdated: date,
2444
+ lastUpdated: ctx.date,
1110
2445
  versionId: "2"
1111
2446
  }
1112
2447
  };
1113
- const service = getDynamoDataService();
1114
2448
  try {
1115
- const existing = await service.entities.patient.get({ tenantId, workspaceId, id, sk: SK2 }).go();
1116
- if (!existing.data) {
1117
- return res.status(404).json({
1118
- resourceType: "OperationOutcome",
1119
- issue: [
1120
- {
1121
- severity: "error",
1122
- code: "not-found",
1123
- diagnostics: `Patient ${id} not found`
1124
- }
1125
- ]
1126
- });
1127
- }
1128
- const existingMeta = existing.data.resource != null ? JSON.parse(decompressResource(existing.data.resource)).meta : void 0;
1129
- const patientWithMeta = {
1130
- ...patient,
1131
- meta: mergeAuditIntoMeta(
1132
- patient.meta ?? existingMeta,
1133
- {
1134
- modifiedDate: date,
1135
- modifiedById: actorId,
1136
- modifiedByName: actorName
1137
- }
1138
- )
1139
- };
1140
- await service.entities.patient.patch({ tenantId, workspaceId, id, sk: SK2 }).set({
1141
- resource: compressResource(JSON.stringify(patientWithMeta)),
1142
- lastUpdated: date
1143
- }).go();
1144
- return res.json(patientWithMeta);
1145
- } catch (err) {
1146
- console.error("PUT Patient error:", err);
1147
- return res.status(500).json({
1148
- resourceType: "OperationOutcome",
1149
- issue: [
1150
- { severity: "error", code: "exception", diagnostics: String(err) }
1151
- ]
2449
+ const result = await updatePractitionerOperation({
2450
+ context: ctx,
2451
+ id,
2452
+ body: practitioner
1152
2453
  });
2454
+ return res.json(result.resource);
2455
+ } catch (err) {
2456
+ const status = domainErrorToHttpStatus(err);
2457
+ if (status === 404) {
2458
+ const diagnostics = err instanceof NotFoundError ? err.message : `Practitioner ${id} not found`;
2459
+ return sendOperationOutcome404(res, diagnostics);
2460
+ }
2461
+ return sendOperationOutcome500(res, err, "PUT Practitioner error:");
1153
2462
  }
1154
2463
  }
1155
2464
 
1156
- // src/data/rest-api/routes/patient/patient.ts
1157
- var router2 = express2.Router();
1158
- router2.get("/", listPatients);
1159
- router2.get("/:id", getPatientById);
1160
- router2.post("/", createPatient);
1161
- router2.put("/:id", updatePatient);
1162
- router2.delete("/:id", deletePatient);
2465
+ // src/data/rest-api/routes/data/practitioner/practitioner.ts
2466
+ var router4 = express4.Router();
2467
+ router4.get("/", listPractitionersRoute);
2468
+ router4.get("/:id", getPractitionerByIdRoute);
2469
+ router4.post("/", createPractitionerRoute);
2470
+ router4.put("/:id", updatePractitionerRoute);
2471
+ router4.delete("/:id", deletePractitionerRoute);
1163
2472
 
1164
2473
  // src/data/rest-api/rest-api.ts
1165
- var app = express3();
2474
+ var app = express5();
1166
2475
  app.set("view engine", "ejs");
1167
2476
  app.set("views", path.join(__dirname, "views"));
1168
- app.use(express3.json());
1169
- app.use(express3.urlencoded({ extended: true }));
2477
+ app.use(express5.json());
2478
+ app.use(express5.urlencoded({ extended: true }));
1170
2479
  app.use(normalizeJsonBodyMiddleware);
1171
2480
  app.get("/", (_req, res) => {
1172
2481
  return res.status(200).json({ message: "POC App is running" });
1173
2482
  });
1174
- app.use(["/Patient", "/Configuration"], openHiContextMiddleware);
1175
- app.use("/Patient", router2);
2483
+ app.use(
2484
+ ["/Patient", "/Encounter", "/Practitioner", "/Configuration"],
2485
+ openHiContextMiddleware
2486
+ );
2487
+ app.use("/Patient", router3);
2488
+ app.use("/Encounter", router2);
2489
+ app.use("/Practitioner", router4);
1176
2490
  app.use("/Configuration", router);
1177
2491
 
1178
2492
  // src/data/lambda/rest-api-lambda.handler.ts