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