@openhi/constructs 0.0.32 → 0.0.34
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.
- package/lib/index.d.mts +12 -29
- package/lib/index.d.ts +12 -29
- package/lib/index.js +6 -22
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +6 -23
- package/lib/index.mjs.map +1 -1
- package/lib/rest-api-lambda.handler.js +1818 -504
- package/lib/rest-api-lambda.handler.js.map +1 -1
- package/lib/rest-api-lambda.handler.mjs +1818 -504
- package/lib/rest-api-lambda.handler.mjs.map +1 -1
- package/package.json +3 -3
|
@@ -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
|
|
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/
|
|
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
|
|
150
|
+
var ConfigurationEntity = new Entity({
|
|
145
151
|
model: {
|
|
146
152
|
entity: "configuration",
|
|
147
|
-
service: "
|
|
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: "
|
|
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/
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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/
|
|
448
|
-
|
|
449
|
-
|
|
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
|
-
} =
|
|
456
|
-
const
|
|
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
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
-
|
|
495
|
-
|
|
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/
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
const
|
|
516
|
-
const { tenantId, workspaceId, actorId, roleId: ctxRoleId } =
|
|
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 =
|
|
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
|
|
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/
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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 =
|
|
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
|
|
542
|
-
|
|
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 ${
|
|
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
|
|
605
|
+
const client = new SSMClient({ region });
|
|
602
606
|
try {
|
|
603
|
-
const describeResult = await
|
|
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
|
|
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
|
|
693
|
-
const
|
|
694
|
-
const
|
|
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
|
|
697
|
-
|
|
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 = [...
|
|
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/
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
const
|
|
736
|
-
const { tenantId, workspaceId, actorId, date, roleId: ctxRoleId } =
|
|
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
|
|
739
|
-
const
|
|
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
|
|
742
|
-
const
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
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 ${
|
|
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
|
-
{
|
|
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("/",
|
|
785
|
-
router.get("/:
|
|
786
|
-
router.post("/",
|
|
787
|
-
router.put("/:
|
|
788
|
-
router.delete("/:
|
|
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/
|
|
827
|
+
// src/data/rest-api/routes/data/encounter/encounter.ts
|
|
791
828
|
import express2 from "express";
|
|
792
829
|
|
|
793
|
-
// src/data/
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
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
|
-
|
|
801
|
-
|
|
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
|
|
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:
|
|
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/
|
|
957
|
-
async function
|
|
958
|
-
const
|
|
959
|
-
|
|
960
|
-
const
|
|
961
|
-
const
|
|
962
|
-
|
|
963
|
-
|
|
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
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
)
|
|
990
|
-
return res.status(201).location(`${
|
|
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/
|
|
1003
|
-
async function
|
|
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
|
|
1006
|
-
const service = getDynamoDataService();
|
|
2103
|
+
const ctx = req.openhiContext;
|
|
1007
2104
|
try {
|
|
1008
|
-
await
|
|
2105
|
+
await deletePatientOperation({ context: ctx, id });
|
|
1009
2106
|
return res.status(204).send();
|
|
1010
2107
|
} catch (err) {
|
|
1011
|
-
|
|
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/
|
|
1022
|
-
async function
|
|
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
|
|
1025
|
-
const service = getDynamoDataService();
|
|
2129
|
+
const ctx = req.openhiContext;
|
|
1026
2130
|
try {
|
|
1027
|
-
const result = await
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
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
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
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
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
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
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
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
|
|
1065
|
-
|
|
1066
|
-
|
|
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
|
-
|
|
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("
|
|
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/
|
|
1096
|
-
async function
|
|
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
|
|
2438
|
+
const practitioner = {
|
|
1104
2439
|
...body,
|
|
1105
|
-
resourceType: "
|
|
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
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
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/
|
|
1157
|
-
var
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
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 =
|
|
2474
|
+
var app = express5();
|
|
1166
2475
|
app.set("view engine", "ejs");
|
|
1167
2476
|
app.set("views", path.join(__dirname, "views"));
|
|
1168
|
-
app.use(
|
|
1169
|
-
app.use(
|
|
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(
|
|
1175
|
-
|
|
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
|