@openhi/constructs 0.0.0 → 0.0.1
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/chunk-LZOMFHX3.mjs +38 -0
- package/lib/chunk-LZOMFHX3.mjs.map +1 -0
- package/lib/index.d.mts +664 -0
- package/lib/index.d.ts +745 -3
- package/lib/index.js +1133 -19
- package/lib/index.js.map +1 -0
- package/lib/index.mjs +1120 -0
- package/lib/index.mjs.map +1 -0
- package/lib/rest-api-lambda.handler.d.mts +6 -0
- package/lib/rest-api-lambda.handler.d.ts +6 -0
- package/lib/rest-api-lambda.handler.js +677 -0
- package/lib/rest-api-lambda.handler.js.map +1 -0
- package/lib/rest-api-lambda.handler.mjs +646 -0
- package/lib/rest-api-lambda.handler.mjs.map +1 -0
- package/package.json +37 -28
- package/lib/app/index.d.ts +0 -4
- package/lib/app/index.js +0 -21
- package/lib/app/open-hi-app.d.ts +0 -85
- package/lib/app/open-hi-app.js +0 -127
- package/lib/app/open-hi-environment.d.ts +0 -59
- package/lib/app/open-hi-environment.js +0 -72
- package/lib/app/open-hi-service.d.ts +0 -169
- package/lib/app/open-hi-service.js +0 -195
- package/lib/app/open-hi-stage.d.ts +0 -71
- package/lib/app/open-hi-stage.js +0 -70
- package/lib/components/acm/root-wildcard-certificate.d.ts +0 -15
- package/lib/components/acm/root-wildcard-certificate.js +0 -35
- package/lib/components/api-gateway/core-http-api.d.ts +0 -10
- package/lib/components/api-gateway/core-http-api.js +0 -44
- package/lib/components/api-gateway/http-lambda-integration-no-permissions.d.ts +0 -18
- package/lib/components/api-gateway/http-lambda-integration-no-permissions.js +0 -26
- package/lib/components/app-sync/core-graphql-api.d.ts +0 -12
- package/lib/components/app-sync/core-graphql-api.js +0 -54
- package/lib/components/auth.d.ts +0 -75
- package/lib/components/auth.js +0 -100
- package/lib/components/cognito/core-user-pool-client.d.ts +0 -10
- package/lib/components/cognito/core-user-pool-client.js +0 -47
- package/lib/components/cognito/core-user-pool-domain.d.ts +0 -10
- package/lib/components/cognito/core-user-pool-domain.js +0 -41
- package/lib/components/cognito/core-user-pool-kms-key.d.ts +0 -10
- package/lib/components/cognito/core-user-pool-kms-key.js +0 -37
- package/lib/components/cognito/core-user-pool.d.ts +0 -10
- package/lib/components/cognito/core-user-pool.js +0 -54
- package/lib/components/core.d.ts +0 -102
- package/lib/components/core.js +0 -79
- package/lib/components/dynamodb/dynamo-db-data-store.d.ts +0 -33
- package/lib/components/dynamodb/dynamo-db-data-store.js +0 -107
- package/lib/components/event-bridge/data-event-bus.d.ts +0 -19
- package/lib/components/event-bridge/data-event-bus.js +0 -34
- package/lib/components/event-bridge/ops-event-bus.d.ts +0 -19
- package/lib/components/event-bridge/ops-event-bus.js +0 -34
- package/lib/components/global.d.ts +0 -36
- package/lib/components/global.js +0 -63
- package/lib/components/index.d.ts +0 -1
- package/lib/components/index.js +0 -18
- package/lib/components/route-53/child-hosted-zone.d.ts +0 -20
- package/lib/components/route-53/child-hosted-zone.js +0 -48
- package/lib/components/route-53/root-hosted-zone.d.ts +0 -10
- package/lib/components/route-53/root-hosted-zone.js +0 -20
- package/lib/components/ssm/discoverable-string-parameter.d.ts +0 -59
- package/lib/components/ssm/discoverable-string-parameter.js +0 -50
- package/lib/components/ssm/index.d.ts +0 -1
- package/lib/components/ssm/index.js +0 -18
- package/lib/data/dynamo/ehr/r4/Patient.d.ts +0 -180
- package/lib/data/dynamo/ehr/r4/Patient.js +0 -192
- package/lib/data/dynamo/ehr/r4/ehr-r4-data-service.d.ts +0 -162
- package/lib/data/dynamo/ehr/r4/ehr-r4-data-service.js +0 -37
- package/lib/data/hello-world.d.ts +0 -39
- package/lib/data/hello-world.js +0 -59
- package/lib/data/import-patient-with-dynalite.d.ts +0 -1
- package/lib/data/import-patient-with-dynalite.js +0 -87
- package/lib/data/import-patient.d.ts +0 -47
- package/lib/data/import-patient.js +0 -158
- package/lib/data/lambda/rest-api-lambda.d.ts +0 -13
- package/lib/data/lambda/rest-api-lambda.handler.d.ts +0 -1
- package/lib/data/lambda/rest-api-lambda.handler.js +0 -10
- package/lib/data/lambda/rest-api-lambda.js +0 -22
- package/lib/data/middleware/open-hi-context.d.ts +0 -13
- package/lib/data/middleware/open-hi-context.js +0 -31
- package/lib/data/rest-api/ehr/r4/Patient.d.ts +0 -16
- package/lib/data/rest-api/ehr/r4/Patient.js +0 -234
- package/lib/data/rest-api/rest-api-local.d.ts +0 -1
- package/lib/data/rest-api/rest-api-local.js +0 -8
- package/lib/data/rest-api/rest-api-mockdata.d.ts +0 -7
- package/lib/data/rest-api/rest-api-mockdata.js +0 -585
- package/lib/data/rest-api/rest-api.d.ts +0 -3
- package/lib/data/rest-api/rest-api.js +0 -26
- package/lib/lib/compression.d.ts +0 -27
- package/lib/lib/compression.js +0 -87
- package/lib/services/index.d.ts +0 -5
- package/lib/services/index.js +0 -22
- package/lib/services/open-hi-auth-service.d.ts +0 -31
- package/lib/services/open-hi-auth-service.js +0 -31
- package/lib/services/open-hi-core-service.d.ts +0 -15
- package/lib/services/open-hi-core-service.js +0 -38
- package/lib/services/open-hi-data-service.d.ts +0 -18
- package/lib/services/open-hi-data-service.js +0 -18
- package/lib/services/open-hi-global-service.d.ts +0 -15
- package/lib/services/open-hi-global-service.js +0 -44
- package/lib/services/open-hi-rest-api-service.d.ts +0 -17
- package/lib/services/open-hi-rest-api-service.js +0 -107
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__require
|
|
3
|
+
} from "./chunk-LZOMFHX3.mjs";
|
|
4
|
+
|
|
5
|
+
// src/data/lambda/rest-api-lambda.handler.ts
|
|
6
|
+
import serverlessExpress from "@codegenie/serverless-express";
|
|
7
|
+
|
|
8
|
+
// src/data/rest-api/rest-api.ts
|
|
9
|
+
import path from "path";
|
|
10
|
+
import cors from "cors";
|
|
11
|
+
import express2 from "express";
|
|
12
|
+
|
|
13
|
+
// src/data/middleware/open-hi-context.ts
|
|
14
|
+
var STATIC_TENANT_ID = "tenant-1";
|
|
15
|
+
var STATIC_WORKSPACE_ID = "ws-1";
|
|
16
|
+
var STATIC_USER_ID = "rest-api";
|
|
17
|
+
var STATIC_USER_NAME = "REST API";
|
|
18
|
+
function openHiContextMiddleware(req, _res, next) {
|
|
19
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
20
|
+
req.openhiContext = {
|
|
21
|
+
tenantId: STATIC_TENANT_ID,
|
|
22
|
+
workspaceId: STATIC_WORKSPACE_ID,
|
|
23
|
+
date: now,
|
|
24
|
+
userId: STATIC_USER_ID,
|
|
25
|
+
username: STATIC_USER_NAME
|
|
26
|
+
};
|
|
27
|
+
next();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/data/rest-api/ehr/r4/Patient.ts
|
|
31
|
+
import express from "express";
|
|
32
|
+
|
|
33
|
+
// src/lib/compression.ts
|
|
34
|
+
import { gzipSync, gunzipSync } from "zlib";
|
|
35
|
+
var ENVELOPE_VERSION = 1;
|
|
36
|
+
var COMPRESSION_ALGOS = {
|
|
37
|
+
NONE: "none",
|
|
38
|
+
GZIP: "gzip",
|
|
39
|
+
BROTLI: "brotli",
|
|
40
|
+
DEFLATE: "deflate"
|
|
41
|
+
};
|
|
42
|
+
function isEnvelope(obj) {
|
|
43
|
+
return typeof obj === "object" && obj !== null && "v" in obj && "algo" in obj && "payload" in obj && typeof obj.payload === "string";
|
|
44
|
+
}
|
|
45
|
+
function compressResource(jsonString, options) {
|
|
46
|
+
const algo = options?.algo ?? COMPRESSION_ALGOS.GZIP;
|
|
47
|
+
if (algo === COMPRESSION_ALGOS.NONE) {
|
|
48
|
+
const envelope2 = {
|
|
49
|
+
v: ENVELOPE_VERSION,
|
|
50
|
+
algo: COMPRESSION_ALGOS.NONE,
|
|
51
|
+
payload: jsonString
|
|
52
|
+
};
|
|
53
|
+
return JSON.stringify(envelope2);
|
|
54
|
+
}
|
|
55
|
+
const buf = Buffer.from(jsonString, "utf-8");
|
|
56
|
+
const payload = gzipSync(buf).toString("base64");
|
|
57
|
+
const envelope = {
|
|
58
|
+
v: ENVELOPE_VERSION,
|
|
59
|
+
algo: COMPRESSION_ALGOS.GZIP,
|
|
60
|
+
payload
|
|
61
|
+
};
|
|
62
|
+
return JSON.stringify(envelope);
|
|
63
|
+
}
|
|
64
|
+
function decompressResource(compressedOrRaw) {
|
|
65
|
+
try {
|
|
66
|
+
const parsed = JSON.parse(compressedOrRaw);
|
|
67
|
+
if (isEnvelope(parsed)) {
|
|
68
|
+
if (parsed.algo === COMPRESSION_ALGOS.GZIP) {
|
|
69
|
+
const buf = Buffer.from(parsed.payload, "base64");
|
|
70
|
+
return gunzipSync(buf).toString("utf-8");
|
|
71
|
+
}
|
|
72
|
+
if (parsed.algo === COMPRESSION_ALGOS.NONE) {
|
|
73
|
+
return parsed.payload;
|
|
74
|
+
}
|
|
75
|
+
return parsed.payload;
|
|
76
|
+
}
|
|
77
|
+
} catch {
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const buf = Buffer.from(compressedOrRaw, "base64");
|
|
81
|
+
if (buf.length >= 2 && buf[0] === 31 && buf[1] === 139) {
|
|
82
|
+
return gunzipSync(buf).toString("utf-8");
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
}
|
|
86
|
+
return compressedOrRaw;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/data/dynamo/ehr/r4/ehr-r4-data-service.ts
|
|
90
|
+
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
91
|
+
import { Service } from "electrodb";
|
|
92
|
+
|
|
93
|
+
// src/data/dynamo/ehr/r4/Patient.ts
|
|
94
|
+
import { Entity } from "electrodb";
|
|
95
|
+
var Patient = new Entity({
|
|
96
|
+
model: {
|
|
97
|
+
entity: "patient",
|
|
98
|
+
service: "fhir",
|
|
99
|
+
version: "01"
|
|
100
|
+
},
|
|
101
|
+
attributes: {
|
|
102
|
+
/** Sort key. "CURRENT" for current version; version history in S3. */
|
|
103
|
+
sk: {
|
|
104
|
+
type: "string",
|
|
105
|
+
required: true,
|
|
106
|
+
default: "CURRENT"
|
|
107
|
+
},
|
|
108
|
+
tenantId: {
|
|
109
|
+
type: "string",
|
|
110
|
+
required: true
|
|
111
|
+
},
|
|
112
|
+
workspaceId: {
|
|
113
|
+
type: "string",
|
|
114
|
+
required: true
|
|
115
|
+
},
|
|
116
|
+
/** FHIR Resource.id; logical id in URL and PK. */
|
|
117
|
+
id: {
|
|
118
|
+
type: "string",
|
|
119
|
+
required: true
|
|
120
|
+
},
|
|
121
|
+
/** FHIR resource as JSON string. JSON.stringify(resource) on write; JSON.parse(item.resource) on read. */
|
|
122
|
+
resource: {
|
|
123
|
+
type: "string",
|
|
124
|
+
required: true
|
|
125
|
+
},
|
|
126
|
+
/** Version id (e.g. ULID). Tracks current version; S3 history key. */
|
|
127
|
+
vid: {
|
|
128
|
+
type: "string",
|
|
129
|
+
required: true
|
|
130
|
+
},
|
|
131
|
+
lastUpdated: {
|
|
132
|
+
type: "string",
|
|
133
|
+
required: true
|
|
134
|
+
},
|
|
135
|
+
deleted: {
|
|
136
|
+
type: "boolean",
|
|
137
|
+
required: false
|
|
138
|
+
},
|
|
139
|
+
bundleId: {
|
|
140
|
+
type: "string",
|
|
141
|
+
required: false
|
|
142
|
+
},
|
|
143
|
+
msgId: {
|
|
144
|
+
type: "string",
|
|
145
|
+
required: false
|
|
146
|
+
},
|
|
147
|
+
// Audit is in FHIR resource meta (meta.extension), not item attributes. See data-store-entities.md.
|
|
148
|
+
// --- GSI2 (Identifier Lookup): optional; set when indexing this patient by identifier (e.g. MRN)
|
|
149
|
+
/** Identifier system (e.g. MRN system URI). When set with identifierValue, item is written to GSI2. */
|
|
150
|
+
identifierSystem: {
|
|
151
|
+
type: "string",
|
|
152
|
+
required: false
|
|
153
|
+
},
|
|
154
|
+
/** Identifier value (e.g. MRN). When set with identifierSystem, item is written to GSI2. */
|
|
155
|
+
identifierValue: {
|
|
156
|
+
type: "string",
|
|
157
|
+
required: false
|
|
158
|
+
},
|
|
159
|
+
/** For GSI2/GSI3 projection: base table PK/SK so GetItem can be used after query. */
|
|
160
|
+
resourcePk: {
|
|
161
|
+
type: "string",
|
|
162
|
+
required: false
|
|
163
|
+
},
|
|
164
|
+
resourceSk: {
|
|
165
|
+
type: "string",
|
|
166
|
+
required: false
|
|
167
|
+
},
|
|
168
|
+
/** For GSI2 projection: display name for roster/lookup. */
|
|
169
|
+
display: {
|
|
170
|
+
type: "string",
|
|
171
|
+
required: false
|
|
172
|
+
},
|
|
173
|
+
/** For GSI2 projection: resource status if applicable. */
|
|
174
|
+
status: {
|
|
175
|
+
type: "string",
|
|
176
|
+
required: false
|
|
177
|
+
},
|
|
178
|
+
// --- GSI3 (Facility Ops): optional; set when indexing this patient on a facility roster
|
|
179
|
+
/** Facility id. When set with normalizedName, item is written to GSI3. */
|
|
180
|
+
facilityId: {
|
|
181
|
+
type: "string",
|
|
182
|
+
required: false
|
|
183
|
+
},
|
|
184
|
+
/** Normalized display name for roster sort. When set with facilityId, item is written to GSI3. */
|
|
185
|
+
normalizedName: {
|
|
186
|
+
type: "string",
|
|
187
|
+
required: false
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
indexes: {
|
|
191
|
+
/** Base table: PK, SK (data store key names). PK is built from tenantId, workspaceId, id; do not supply PK from outside. */
|
|
192
|
+
record: {
|
|
193
|
+
pk: {
|
|
194
|
+
field: "PK",
|
|
195
|
+
composite: ["tenantId", "workspaceId", "id"],
|
|
196
|
+
template: "TID#${tenantId}#WID#${workspaceId}#RT#Patient#ID#${id}"
|
|
197
|
+
},
|
|
198
|
+
sk: {
|
|
199
|
+
field: "SK",
|
|
200
|
+
composite: ["sk"]
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
/**
|
|
204
|
+
* GSI1 — Reverse Reference: query "what references this patient?".
|
|
205
|
+
* Patient items are never written to GSI1 (condition: false); reference index items
|
|
206
|
+
* are written by other resources. This index enables querying GSI1 by REFTO#RT#Patient#ID#<id>.
|
|
207
|
+
*/
|
|
208
|
+
gsi1: {
|
|
209
|
+
index: "GSI1",
|
|
210
|
+
condition: () => false,
|
|
211
|
+
pk: {
|
|
212
|
+
field: "GSI1PK",
|
|
213
|
+
composite: ["tenantId", "workspaceId", "id"],
|
|
214
|
+
template: "TID#${tenantId}#WID#${workspaceId}#REFTO#RT#Patient#ID#${id}"
|
|
215
|
+
},
|
|
216
|
+
sk: {
|
|
217
|
+
field: "GSI1SK",
|
|
218
|
+
composite: []
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
/** GSI2 — Identifier Lookup: MRN, NPI, member ID, etc. Keys built from identifier components. */
|
|
222
|
+
gsi2: {
|
|
223
|
+
index: "GSI2",
|
|
224
|
+
condition: (attr) => attr.identifierSystem != null && attr.identifierValue != null,
|
|
225
|
+
pk: {
|
|
226
|
+
field: "GSI2PK",
|
|
227
|
+
composite: [
|
|
228
|
+
"tenantId",
|
|
229
|
+
"workspaceId",
|
|
230
|
+
"identifierSystem",
|
|
231
|
+
"identifierValue"
|
|
232
|
+
],
|
|
233
|
+
template: "TID#${tenantId}#WID#${workspaceId}#IDENT#${identifierSystem}#${identifierValue}"
|
|
234
|
+
},
|
|
235
|
+
sk: {
|
|
236
|
+
field: "GSI2SK",
|
|
237
|
+
composite: ["id"],
|
|
238
|
+
template: "RT#Patient#ID#${id}"
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
/** GSI3 — Facility Ops: facility roster, worklists. Keys built from facility + name. */
|
|
242
|
+
gsi3: {
|
|
243
|
+
index: "GSI3",
|
|
244
|
+
condition: (attr) => attr.facilityId != null && attr.normalizedName != null,
|
|
245
|
+
pk: {
|
|
246
|
+
field: "GSI3PK",
|
|
247
|
+
composite: ["tenantId", "workspaceId", "facilityId"],
|
|
248
|
+
template: "TID#${tenantId}#WID#${workspaceId}#FAC#${facilityId}"
|
|
249
|
+
},
|
|
250
|
+
sk: {
|
|
251
|
+
field: "GSI3SK",
|
|
252
|
+
composite: ["id", "normalizedName"],
|
|
253
|
+
template: "TYPE#PATIENT#PAT#${id}#NAME#${normalizedName}"
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
/** GSI4 — Resource Type Index: list all Patients in workspace (no scan). */
|
|
257
|
+
gsi4: {
|
|
258
|
+
index: "GSI4",
|
|
259
|
+
condition: () => true,
|
|
260
|
+
pk: {
|
|
261
|
+
field: "GSI4PK",
|
|
262
|
+
composite: ["tenantId", "workspaceId"],
|
|
263
|
+
template: "TID#${tenantId}#WID#${workspaceId}#RT#Patient"
|
|
264
|
+
},
|
|
265
|
+
sk: {
|
|
266
|
+
field: "GSI4SK",
|
|
267
|
+
composite: ["id"],
|
|
268
|
+
template: "ID#${id}"
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// src/data/dynamo/ehr/r4/ehr-r4-data-service.ts
|
|
275
|
+
var table = process.env.DYNAMO_TABLE_NAME ?? "jesttesttable";
|
|
276
|
+
var client = new DynamoDBClient({
|
|
277
|
+
...process.env.MOCK_DYNAMODB_ENDPOINT && {
|
|
278
|
+
endpoint: process.env.MOCK_DYNAMODB_ENDPOINT,
|
|
279
|
+
sslEnabled: false,
|
|
280
|
+
region: "local"
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
var entities = { patient: Patient };
|
|
284
|
+
var EhrR4DataService = new Service(entities, { table, client });
|
|
285
|
+
function getEhrR4DataService(tableName) {
|
|
286
|
+
return new Service(entities, { table: tableName, client });
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// src/data/import-patient.ts
|
|
290
|
+
import { readFileSync } from "fs";
|
|
291
|
+
import { resolve } from "path";
|
|
292
|
+
var OPENHI_EXT = "http://openhi.org/fhir/StructureDefinition";
|
|
293
|
+
function mergeAuditIntoMeta(meta, audit) {
|
|
294
|
+
const existing = meta ?? {};
|
|
295
|
+
const ext = [
|
|
296
|
+
...Array.isArray(existing.extension) ? existing.extension : []
|
|
297
|
+
];
|
|
298
|
+
const byUrl = new Map(ext.map((e) => [e.url, e]));
|
|
299
|
+
function set(url, value, type) {
|
|
300
|
+
if (value == null) return;
|
|
301
|
+
byUrl.set(url, { url, [type]: value });
|
|
302
|
+
}
|
|
303
|
+
set(`${OPENHI_EXT}/created-date`, audit.createdDate, "valueDateTime");
|
|
304
|
+
set(`${OPENHI_EXT}/created-by-id`, audit.createdById, "valueString");
|
|
305
|
+
set(`${OPENHI_EXT}/created-by-name`, audit.createdByName, "valueString");
|
|
306
|
+
set(`${OPENHI_EXT}/modified-date`, audit.modifiedDate, "valueDateTime");
|
|
307
|
+
set(`${OPENHI_EXT}/modified-by-id`, audit.modifiedById, "valueString");
|
|
308
|
+
set(`${OPENHI_EXT}/modified-by-name`, audit.modifiedByName, "valueString");
|
|
309
|
+
set(`${OPENHI_EXT}/deleted-date`, audit.deletedDate, "valueDateTime");
|
|
310
|
+
set(`${OPENHI_EXT}/deleted-by-id`, audit.deletedById, "valueString");
|
|
311
|
+
set(`${OPENHI_EXT}/deleted-by-name`, audit.deletedByName, "valueString");
|
|
312
|
+
return { ...existing, extension: Array.from(byUrl.values()) };
|
|
313
|
+
}
|
|
314
|
+
function extractPatient(parsed) {
|
|
315
|
+
if (parsed && typeof parsed === "object" && "resourceType" in parsed) {
|
|
316
|
+
const root = parsed;
|
|
317
|
+
if (root.resourceType === "Patient" && root.id) {
|
|
318
|
+
return root;
|
|
319
|
+
}
|
|
320
|
+
if (root.resourceType === "Bundle" && "entry" in parsed) {
|
|
321
|
+
const entries = parsed.entry;
|
|
322
|
+
if (Array.isArray(entries)) {
|
|
323
|
+
const patientEntry = entries.find(
|
|
324
|
+
(e) => e?.resource?.resourceType === "Patient" && e.resource.id
|
|
325
|
+
);
|
|
326
|
+
if (patientEntry?.resource) {
|
|
327
|
+
return patientEntry.resource;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
throw new Error(
|
|
333
|
+
"File must be a FHIR Patient resource or a Bundle containing at least one Patient entry"
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
var SK = "CURRENT";
|
|
337
|
+
var defaultAudit = {
|
|
338
|
+
createdDate: (/* @__PURE__ */ new Date()).toISOString(),
|
|
339
|
+
createdById: "import",
|
|
340
|
+
createdByName: "Bulk import",
|
|
341
|
+
modifiedDate: (/* @__PURE__ */ new Date()).toISOString(),
|
|
342
|
+
modifiedById: "import",
|
|
343
|
+
modifiedByName: "Bulk import"
|
|
344
|
+
};
|
|
345
|
+
function patientToPutAttrs(patient, options) {
|
|
346
|
+
const {
|
|
347
|
+
tenantId,
|
|
348
|
+
workspaceId,
|
|
349
|
+
createdDate,
|
|
350
|
+
createdById,
|
|
351
|
+
createdByName,
|
|
352
|
+
modifiedDate,
|
|
353
|
+
modifiedById,
|
|
354
|
+
modifiedByName
|
|
355
|
+
} = options;
|
|
356
|
+
const lastUpdated = patient.meta?.lastUpdated ?? modifiedDate ?? defaultAudit.modifiedDate ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
357
|
+
const auditMerged = {
|
|
358
|
+
...defaultAudit,
|
|
359
|
+
...createdDate != null && { createdDate },
|
|
360
|
+
...createdById != null && { createdById },
|
|
361
|
+
...createdByName != null && { createdByName },
|
|
362
|
+
...modifiedDate != null && { modifiedDate },
|
|
363
|
+
...modifiedById != null && { modifiedById },
|
|
364
|
+
...modifiedByName != null && { modifiedByName }
|
|
365
|
+
};
|
|
366
|
+
const patientWithMeta = {
|
|
367
|
+
...patient,
|
|
368
|
+
meta: mergeAuditIntoMeta(patient.meta, auditMerged)
|
|
369
|
+
};
|
|
370
|
+
if (lastUpdated && !patientWithMeta.meta.lastUpdated) {
|
|
371
|
+
patientWithMeta.meta.lastUpdated = lastUpdated;
|
|
372
|
+
}
|
|
373
|
+
return {
|
|
374
|
+
sk: SK,
|
|
375
|
+
tenantId,
|
|
376
|
+
workspaceId,
|
|
377
|
+
id: patient.id,
|
|
378
|
+
resource: compressResource(JSON.stringify(patientWithMeta)),
|
|
379
|
+
vid: lastUpdated.replace(/[-:T.Z]/g, "").slice(0, 12) || Date.now().toString(36),
|
|
380
|
+
lastUpdated,
|
|
381
|
+
identifierSystem: "",
|
|
382
|
+
identifierValue: "",
|
|
383
|
+
facilityId: "",
|
|
384
|
+
normalizedName: ""
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
async function importPatientFromFile(filePath, options) {
|
|
388
|
+
const resolved = resolve(filePath);
|
|
389
|
+
const raw = readFileSync(resolved, "utf-8");
|
|
390
|
+
const parsed = JSON.parse(raw);
|
|
391
|
+
const patient = extractPatient(parsed);
|
|
392
|
+
const tableName = options.tableName ?? process.env.DYNAMO_TABLE_NAME ?? "jesttesttable";
|
|
393
|
+
const service = getEhrR4DataService(tableName);
|
|
394
|
+
const attrs = patientToPutAttrs(patient, options);
|
|
395
|
+
const result = await service.entities.patient.put(attrs).go();
|
|
396
|
+
const data = result.data;
|
|
397
|
+
if (!data) {
|
|
398
|
+
throw new Error(`Put failed for Patient ${patient.id}`);
|
|
399
|
+
}
|
|
400
|
+
return {
|
|
401
|
+
id: data.id,
|
|
402
|
+
tenantId: data.tenantId,
|
|
403
|
+
workspaceId: data.workspaceId
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
async function main() {
|
|
407
|
+
const [, , fileArg, tenantId = "tenant-1", workspaceId = "ws-1"] = process.argv;
|
|
408
|
+
if (!fileArg) {
|
|
409
|
+
console.error(
|
|
410
|
+
"Usage: import-patient.ts <path-to-patient.json> [tenantId] [workspaceId]"
|
|
411
|
+
);
|
|
412
|
+
process.exit(1);
|
|
413
|
+
}
|
|
414
|
+
try {
|
|
415
|
+
const result = await importPatientFromFile(fileArg, {
|
|
416
|
+
tenantId,
|
|
417
|
+
workspaceId
|
|
418
|
+
});
|
|
419
|
+
console.log(
|
|
420
|
+
`Imported Patient ${result.id} (tenant=${result.tenantId}, workspace=${result.workspaceId})`
|
|
421
|
+
);
|
|
422
|
+
} catch (err) {
|
|
423
|
+
console.error(err);
|
|
424
|
+
process.exit(1);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (__require.main === module) {
|
|
428
|
+
void main();
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// src/data/rest-api/ehr/r4/Patient.ts
|
|
432
|
+
var BASE_PATH = "/ehr/r4/Patient";
|
|
433
|
+
var router = express.Router();
|
|
434
|
+
var SK2 = "CURRENT";
|
|
435
|
+
var TABLE_NAME = process.env.DYNAMO_TABLE_NAME ?? "jesttesttable";
|
|
436
|
+
async function listPatients(req, res) {
|
|
437
|
+
const { tenantId, workspaceId } = req.openhiContext;
|
|
438
|
+
const service = getEhrR4DataService(TABLE_NAME);
|
|
439
|
+
try {
|
|
440
|
+
const result = await service.entities.patient.query.gsi4({ tenantId, workspaceId }).go();
|
|
441
|
+
const entries = (result.data ?? []).map((item) => {
|
|
442
|
+
const resource = JSON.parse(decompressResource(item.resource));
|
|
443
|
+
return {
|
|
444
|
+
fullUrl: `${BASE_PATH}/${item.id}`,
|
|
445
|
+
resource: { ...resource, id: item.id }
|
|
446
|
+
};
|
|
447
|
+
});
|
|
448
|
+
const bundle = {
|
|
449
|
+
resourceType: "Bundle",
|
|
450
|
+
type: "searchset",
|
|
451
|
+
total: entries.length,
|
|
452
|
+
link: [{ relation: "self", url: BASE_PATH }],
|
|
453
|
+
entry: entries
|
|
454
|
+
};
|
|
455
|
+
return res.json(bundle);
|
|
456
|
+
} catch (err) {
|
|
457
|
+
console.error("GET /Patient list error:", err);
|
|
458
|
+
return res.status(500).json({
|
|
459
|
+
resourceType: "OperationOutcome",
|
|
460
|
+
issue: [
|
|
461
|
+
{
|
|
462
|
+
severity: "error",
|
|
463
|
+
code: "exception",
|
|
464
|
+
diagnostics: String(err)
|
|
465
|
+
}
|
|
466
|
+
]
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
router.get("/", listPatients);
|
|
471
|
+
async function getPatientById(req, res) {
|
|
472
|
+
const id = String(req.params.id);
|
|
473
|
+
const { tenantId, workspaceId } = req.openhiContext;
|
|
474
|
+
const service = getEhrR4DataService(TABLE_NAME);
|
|
475
|
+
try {
|
|
476
|
+
const result = await service.entities.patient.get({ tenantId, workspaceId, id, sk: "CURRENT" }).go();
|
|
477
|
+
if (!result.data) {
|
|
478
|
+
return res.status(404).json({
|
|
479
|
+
resourceType: "OperationOutcome",
|
|
480
|
+
issue: [
|
|
481
|
+
{
|
|
482
|
+
severity: "error",
|
|
483
|
+
code: "not-found",
|
|
484
|
+
diagnostics: `Patient ${id} not found`
|
|
485
|
+
}
|
|
486
|
+
]
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
const resource = JSON.parse(
|
|
490
|
+
decompressResource(result.data.resource)
|
|
491
|
+
);
|
|
492
|
+
return res.json({ ...resource, id: result.data.id });
|
|
493
|
+
} catch (err) {
|
|
494
|
+
console.error("GET Patient error:", err);
|
|
495
|
+
return res.status(500).json({
|
|
496
|
+
resourceType: "OperationOutcome",
|
|
497
|
+
issue: [
|
|
498
|
+
{
|
|
499
|
+
severity: "error",
|
|
500
|
+
code: "exception",
|
|
501
|
+
diagnostics: String(err)
|
|
502
|
+
}
|
|
503
|
+
]
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
router.get("/:id", getPatientById);
|
|
508
|
+
async function createPatient(req, res) {
|
|
509
|
+
const ctx = req.openhiContext;
|
|
510
|
+
const { tenantId, workspaceId, date, userId, username } = ctx;
|
|
511
|
+
const body = req.body;
|
|
512
|
+
const id = body?.id ?? `patient-${Date.now()}`;
|
|
513
|
+
const patient = {
|
|
514
|
+
...body,
|
|
515
|
+
resourceType: "Patient",
|
|
516
|
+
id,
|
|
517
|
+
meta: {
|
|
518
|
+
...body?.meta ?? {},
|
|
519
|
+
lastUpdated: date,
|
|
520
|
+
versionId: "1"
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
const options = {
|
|
524
|
+
tenantId,
|
|
525
|
+
workspaceId,
|
|
526
|
+
createdDate: date,
|
|
527
|
+
createdById: userId,
|
|
528
|
+
createdByName: username,
|
|
529
|
+
modifiedDate: date,
|
|
530
|
+
modifiedById: userId,
|
|
531
|
+
modifiedByName: username
|
|
532
|
+
};
|
|
533
|
+
const service = getEhrR4DataService(TABLE_NAME);
|
|
534
|
+
try {
|
|
535
|
+
const attrs = patientToPutAttrs(patient, options);
|
|
536
|
+
await service.entities.patient.put(
|
|
537
|
+
attrs
|
|
538
|
+
).go();
|
|
539
|
+
return res.status(201).location(`${BASE_PATH}/${id}`).json(patient);
|
|
540
|
+
} catch (err) {
|
|
541
|
+
console.error("POST Patient error:", err);
|
|
542
|
+
return res.status(500).json({
|
|
543
|
+
resourceType: "OperationOutcome",
|
|
544
|
+
issue: [
|
|
545
|
+
{ severity: "error", code: "exception", diagnostics: String(err) }
|
|
546
|
+
]
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
router.post("/", createPatient);
|
|
551
|
+
async function updatePatient(req, res) {
|
|
552
|
+
const id = String(req.params.id);
|
|
553
|
+
const ctx = req.openhiContext;
|
|
554
|
+
const { tenantId, workspaceId, date, userId, username } = ctx;
|
|
555
|
+
const body = req.body;
|
|
556
|
+
const patient = {
|
|
557
|
+
...body,
|
|
558
|
+
resourceType: "Patient",
|
|
559
|
+
id,
|
|
560
|
+
meta: {
|
|
561
|
+
...body?.meta ?? {},
|
|
562
|
+
lastUpdated: date,
|
|
563
|
+
versionId: "2"
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
const service = getEhrR4DataService(TABLE_NAME);
|
|
567
|
+
try {
|
|
568
|
+
const existing = await service.entities.patient.get({ tenantId, workspaceId, id, sk: SK2 }).go();
|
|
569
|
+
if (!existing.data) {
|
|
570
|
+
return res.status(404).json({
|
|
571
|
+
resourceType: "OperationOutcome",
|
|
572
|
+
issue: [
|
|
573
|
+
{
|
|
574
|
+
severity: "error",
|
|
575
|
+
code: "not-found",
|
|
576
|
+
diagnostics: `Patient ${id} not found`
|
|
577
|
+
}
|
|
578
|
+
]
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
const existingMeta = existing.data.resource != null ? JSON.parse(decompressResource(existing.data.resource)).meta : void 0;
|
|
582
|
+
const patientWithMeta = {
|
|
583
|
+
...patient,
|
|
584
|
+
meta: mergeAuditIntoMeta(
|
|
585
|
+
patient.meta ?? existingMeta,
|
|
586
|
+
{
|
|
587
|
+
modifiedDate: date,
|
|
588
|
+
modifiedById: userId,
|
|
589
|
+
modifiedByName: username
|
|
590
|
+
}
|
|
591
|
+
)
|
|
592
|
+
};
|
|
593
|
+
await service.entities.patient.patch({ tenantId, workspaceId, id, sk: SK2 }).set({
|
|
594
|
+
resource: compressResource(JSON.stringify(patientWithMeta)),
|
|
595
|
+
lastUpdated: date
|
|
596
|
+
}).go();
|
|
597
|
+
return res.json(patientWithMeta);
|
|
598
|
+
} catch (err) {
|
|
599
|
+
console.error("PUT Patient error:", err);
|
|
600
|
+
return res.status(500).json({
|
|
601
|
+
resourceType: "OperationOutcome",
|
|
602
|
+
issue: [
|
|
603
|
+
{ severity: "error", code: "exception", diagnostics: String(err) }
|
|
604
|
+
]
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
router.put("/:id", updatePatient);
|
|
609
|
+
async function deletePatient(req, res) {
|
|
610
|
+
const id = String(req.params.id);
|
|
611
|
+
const { tenantId, workspaceId } = req.openhiContext;
|
|
612
|
+
const service = getEhrR4DataService(TABLE_NAME);
|
|
613
|
+
try {
|
|
614
|
+
await service.entities.patient.delete({ tenantId, workspaceId, id, sk: SK2 }).go();
|
|
615
|
+
return res.status(204).send();
|
|
616
|
+
} catch (err) {
|
|
617
|
+
console.error("DELETE Patient error:", err);
|
|
618
|
+
return res.status(500).json({
|
|
619
|
+
resourceType: "OperationOutcome",
|
|
620
|
+
issue: [
|
|
621
|
+
{ severity: "error", code: "exception", diagnostics: String(err) }
|
|
622
|
+
]
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
router.delete("/:id", deletePatient);
|
|
627
|
+
|
|
628
|
+
// src/data/rest-api/rest-api.ts
|
|
629
|
+
var app = express2();
|
|
630
|
+
app.set("view engine", "ejs");
|
|
631
|
+
app.set("views", path.join(__dirname, "views"));
|
|
632
|
+
app.use(cors());
|
|
633
|
+
app.use(express2.json());
|
|
634
|
+
app.use(express2.urlencoded({ extended: true }));
|
|
635
|
+
app.get("/", (_req, res) => {
|
|
636
|
+
return res.status(200).json({ message: "POC App is running" });
|
|
637
|
+
});
|
|
638
|
+
app.use("/ehr", openHiContextMiddleware);
|
|
639
|
+
app.use("/ehr/r4/Patient", router);
|
|
640
|
+
|
|
641
|
+
// src/data/lambda/rest-api-lambda.handler.ts
|
|
642
|
+
var handler = serverlessExpress({ app });
|
|
643
|
+
export {
|
|
644
|
+
handler
|
|
645
|
+
};
|
|
646
|
+
//# sourceMappingURL=rest-api-lambda.handler.mjs.map
|