@openhi/constructs 0.0.5 → 0.0.7

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/data/lambda/rest-api-lambda.handler.ts","../src/data/rest-api/rest-api.ts","../src/data/middleware/open-hi-context.ts","../src/data/rest-api/ehr/r4/Patient.ts","../src/lib/compression.ts","../src/data/dynamo/ehr/r4/ehr-r4-data-service.ts","../src/data/dynamo/ehr/r4/Patient.ts","../src/data/import-patient.ts"],"sourcesContent":["import serverlessExpress from \"@codegenie/serverless-express\";\nimport { app } from \"../rest-api/rest-api\";\n\nexport const handler = serverlessExpress({ app });\n","import path from \"path\";\nimport cors from \"cors\";\nimport express, { Express, Request, Response } from \"express\";\nimport { openHiContextMiddleware } from \"../middleware/open-hi-context\";\nimport { patientRouter } from \"./ehr/r4/Patient\";\n\nconst app: Express = express();\n\napp.set(\"view engine\", \"ejs\");\napp.set(\"views\", path.join(__dirname, \"views\"));\n\napp.use(cors());\napp.use(express.json());\napp.use(express.urlencoded({ extended: true }));\n\napp.get(\"/\", (_req: Request, res: Response) => {\n return res.status(200).json({ message: \"POC App is running\" });\n});\n\n// Tenant/workspace resolved from headers/env (or JWT when implemented); attached to req for /ehr routes\napp.use(\"/ehr\", openHiContextMiddleware);\n\n// FHIR R4 EHR endpoints: path structure mirrors folder structure (ehr/r4/Patient.ts → /ehr/r4/Patient)\napp.use(\"/ehr/r4/Patient\", patientRouter);\n\nexport { app };\n","import type { Request, Response, NextFunction } from \"express\";\n\n/** Static context used until JWT claims are implemented. */\nconst STATIC_TENANT_ID = \"tenant-1\";\nconst STATIC_WORKSPACE_ID = \"ws-1\";\nconst STATIC_USER_ID = \"rest-api\";\nconst STATIC_USER_NAME = \"REST API\";\n\n/**\n * Express middleware that sets req.openhiContext for /ehr (and other domain API)\n * routes. Context includes tenantId, workspaceId, and the three standard audit\n * fields (date, userId, username) for audit records and for merging into FHIR\n * data on mutations.\n *\n * **TODO: A future task will populate context from JWT claims** (e.g. after\n * verifying the token). For now, static values are used.\n *\n * @see sites/www-docs/content/packages/@openhi/constructs/rest-api.md — Middleware\n */\nexport function openHiContextMiddleware(\n req: Request,\n _res: Response,\n next: NextFunction,\n): void {\n const now = new Date().toISOString();\n req.openhiContext = {\n tenantId: STATIC_TENANT_ID,\n workspaceId: STATIC_WORKSPACE_ID,\n date: now,\n userId: STATIC_USER_ID,\n username: STATIC_USER_NAME,\n };\n next();\n}\n","import express, { Request, Response } from \"express\";\nimport {\n compressResource,\n decompressResource,\n} from \"../../../../lib/compression\";\nimport { getEhrR4DataService } from \"../../../dynamo/ehr/r4/ehr-r4-data-service\";\nimport {\n mergeAuditIntoMeta,\n patientToPutAttrs,\n type ImportPatientOptions,\n} from \"../../../import-patient\";\n\nconst BASE_PATH = \"/ehr/r4/Patient\";\nconst router: express.Router = express.Router();\nconst SK = \"CURRENT\";\n\nconst TABLE_NAME = process.env.DYNAMO_TABLE_NAME ?? \"jesttesttable\";\n\n/**\n * GET /ehr/r4/Patient — search/list: returns a FHIR Bundle (searchset) from the data store.\n * Uses GSI4 (Resource Type Index) to list all Patients in the workspace without a table scan.\n * @see {@link https://github.com/codedrifters/openhi/blob/main/sites/www-docs/content/architecture/dynamodb-single-table-design.md | DynamoDB Single-Table Design} — GSI4, Query and Access Rules (no scans).\n */\nexport async function listPatients(\n req: Request,\n res: Response,\n): Promise<Response> {\n const { tenantId, workspaceId } = req.openhiContext!;\n const service = getEhrR4DataService(TABLE_NAME);\n\n try {\n const result = await service.entities.patient.query\n .gsi4({ tenantId, workspaceId })\n .go();\n\n const entries = (result.data ?? []).map((item) => {\n const resource = JSON.parse(decompressResource(item.resource)) as Record<\n string,\n unknown\n >;\n return {\n fullUrl: `${BASE_PATH}/${item.id}`,\n resource: { ...resource, id: item.id },\n };\n });\n const bundle = {\n resourceType: \"Bundle\",\n type: \"searchset\",\n total: entries.length,\n link: [{ relation: \"self\", url: BASE_PATH }],\n entry: entries,\n };\n return res.json(bundle);\n } catch (err: unknown) {\n console.error(\"GET /Patient list error:\", err);\n return res.status(500).json({\n resourceType: \"OperationOutcome\",\n issue: [\n {\n severity: \"error\",\n code: \"exception\",\n diagnostics: String(err),\n },\n ],\n });\n }\n}\n\nrouter.get(\"/\", listPatients);\n\nexport async function getPatientById(\n req: Request,\n res: Response,\n): Promise<Response> {\n const id = String(req.params.id);\n const { tenantId, workspaceId } = req.openhiContext!;\n const service = getEhrR4DataService(TABLE_NAME);\n\n try {\n const result = await service.entities.patient\n .get({ tenantId, workspaceId, id, sk: \"CURRENT\" })\n .go();\n\n if (!result.data) {\n return res.status(404).json({\n resourceType: \"OperationOutcome\",\n issue: [\n {\n severity: \"error\",\n code: \"not-found\",\n diagnostics: `Patient ${id} not found`,\n },\n ],\n });\n }\n\n const resource = JSON.parse(\n decompressResource(result.data.resource),\n ) as Record<string, unknown>;\n return res.json({ ...resource, id: result.data.id });\n } catch (err: unknown) {\n console.error(\"GET Patient error:\", err);\n return res.status(500).json({\n resourceType: \"OperationOutcome\",\n issue: [\n {\n severity: \"error\",\n code: \"exception\",\n diagnostics: String(err),\n },\n ],\n });\n }\n}\n\n/** GET /ehr/r4/Patient/:id — read: returns a single Patient resource from the data store or 404. */\nrouter.get(\"/:id\", getPatientById);\n\n/** POST /ehr/r4/Patient — create: accepts Patient in body, persists via data store, returns 201. */\nexport async function createPatient(\n req: Request,\n res: Response,\n): Promise<Response> {\n const ctx = req.openhiContext!;\n const { tenantId, workspaceId, date, userId, username } = ctx;\n const body = req.body as Record<string, unknown>;\n const id = (body?.id as string) ?? `patient-${Date.now()}`;\n const patient = {\n ...body,\n resourceType: \"Patient\",\n id,\n meta: {\n ...((body?.meta as object) ?? {}),\n lastUpdated: date,\n versionId: \"1\",\n },\n } as { resourceType: string; id: string; meta?: { lastUpdated?: string } };\n const options: ImportPatientOptions = {\n tenantId,\n workspaceId,\n createdDate: date,\n createdById: userId,\n createdByName: username,\n modifiedDate: date,\n modifiedById: userId,\n modifiedByName: username,\n };\n const service = getEhrR4DataService(TABLE_NAME);\n\n try {\n const attrs = patientToPutAttrs(patient, options);\n await service.entities.patient\n .put(\n attrs as unknown as Parameters<typeof service.entities.patient.put>[0],\n )\n .go();\n return res.status(201).location(`${BASE_PATH}/${id}`).json(patient);\n } catch (err: unknown) {\n console.error(\"POST Patient error:\", err);\n return res.status(500).json({\n resourceType: \"OperationOutcome\",\n issue: [\n { severity: \"error\", code: \"exception\", diagnostics: String(err) },\n ],\n });\n }\n}\n\nrouter.post(\"/\", createPatient);\n\n/** PUT /ehr/r4/Patient/:id — update: accepts Patient in body, persists via data store, returns 200. */\nexport async function updatePatient(\n req: Request,\n res: Response,\n): Promise<Response> {\n const id = String(req.params.id);\n const ctx = req.openhiContext!;\n const { tenantId, workspaceId, date, userId, username } = ctx;\n const body = req.body as Record<string, unknown>;\n const patient = {\n ...body,\n resourceType: \"Patient\",\n id,\n meta: {\n ...((body?.meta as object) ?? {}),\n lastUpdated: date,\n versionId: \"2\",\n },\n };\n const service = getEhrR4DataService(TABLE_NAME);\n\n try {\n const existing = await service.entities.patient\n .get({ tenantId, workspaceId, id, sk: SK })\n .go();\n if (!existing.data) {\n return res.status(404).json({\n resourceType: \"OperationOutcome\",\n issue: [\n {\n severity: \"error\",\n code: \"not-found\",\n diagnostics: `Patient ${id} not found`,\n },\n ],\n });\n }\n const existingMeta =\n existing.data.resource != null\n ? (\n JSON.parse(decompressResource(existing.data.resource)) as {\n meta?: Record<string, unknown>;\n }\n ).meta\n : undefined;\n const patientWithMeta = {\n ...patient,\n meta: mergeAuditIntoMeta(\n (patient.meta as Record<string, unknown> | undefined) ?? existingMeta,\n {\n modifiedDate: date,\n modifiedById: userId,\n modifiedByName: username,\n },\n ),\n };\n await service.entities.patient\n .patch({ tenantId, workspaceId, id, sk: SK })\n .set({\n resource: compressResource(JSON.stringify(patientWithMeta)),\n lastUpdated: date,\n })\n .go();\n return res.json(patientWithMeta);\n } catch (err: unknown) {\n console.error(\"PUT Patient error:\", err);\n return res.status(500).json({\n resourceType: \"OperationOutcome\",\n issue: [\n { severity: \"error\", code: \"exception\", diagnostics: String(err) },\n ],\n });\n }\n}\n\nrouter.put(\"/:id\", updatePatient);\n\n/** DELETE /ehr/r4/Patient/:id — delete: removes from data store, returns 204. */\nexport async function deletePatient(\n req: Request,\n res: Response,\n): Promise<Response> {\n const id = String(req.params.id);\n const { tenantId, workspaceId } = req.openhiContext!;\n const service = getEhrR4DataService(TABLE_NAME);\n\n try {\n await service.entities.patient\n .delete({ tenantId, workspaceId, id, sk: SK })\n .go();\n return res.status(204).send();\n } catch (err: unknown) {\n console.error(\"DELETE Patient error:\", err);\n return res.status(500).json({\n resourceType: \"OperationOutcome\",\n issue: [\n { severity: \"error\", code: \"exception\", diagnostics: String(err) },\n ],\n });\n }\n}\n\nrouter.delete(\"/:id\", deletePatient);\n\nexport { router as patientRouter };\n","import { gzipSync, gunzipSync } from \"node:zlib\";\n\n/** Envelope format version. See ADR 2026-02-15-02 (data layer compression). */\nconst ENVELOPE_VERSION = 1;\n\n/**\n * Compression algorithm identifiers supported by the envelope (string values).\n * Only algos that Node.js supports out of the box (zlib): gzip, brotli, deflate.\n * \"none\" = uncompressed payload. zstd was considered in the ADR but requires native addon/WASM.\n */\nexport const COMPRESSION_ALGOS = {\n NONE: \"none\",\n GZIP: \"gzip\",\n BROTLI: \"brotli\",\n DEFLATE: \"deflate\",\n} as const;\n\n/** Algorithm value for envelope `algo`; only gzip and none are implemented today. */\nexport type CompressionAlgo =\n (typeof COMPRESSION_ALGOS)[keyof typeof COMPRESSION_ALGOS];\n\n/** Stored value is a JSON string of this envelope. */\ninterface CompressionEnvelope {\n v: number;\n algo: string;\n payload: string;\n}\n\nfunction isEnvelope(obj: unknown): obj is CompressionEnvelope {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n \"v\" in obj &&\n \"algo\" in obj &&\n \"payload\" in obj &&\n typeof (obj as CompressionEnvelope).payload === \"string\"\n );\n}\n\n/**\n * Compresses a JSON string (e.g. serialized FHIR resource) for storage in DynamoDB.\n * Uses a versioned envelope: { v, algo, payload } with gzip+base64 in payload.\n * Used by the data layer on write; see REST API docs (compression in data layer).\n * Optional compression: pass `{ algo: COMPRESSION_ALGOS.NONE }` to store in envelope without compressing.\n */\nexport function compressResource(\n jsonString: string,\n options?: { algo?: CompressionAlgo },\n): string {\n const algo = options?.algo ?? COMPRESSION_ALGOS.GZIP;\n if (algo === COMPRESSION_ALGOS.NONE) {\n const envelope: CompressionEnvelope = {\n v: ENVELOPE_VERSION,\n algo: COMPRESSION_ALGOS.NONE,\n payload: jsonString,\n };\n return JSON.stringify(envelope);\n }\n const buf = Buffer.from(jsonString, \"utf-8\");\n const payload = gzipSync(buf).toString(\"base64\");\n const envelope: CompressionEnvelope = {\n v: ENVELOPE_VERSION,\n algo: COMPRESSION_ALGOS.GZIP,\n payload,\n };\n return JSON.stringify(envelope);\n}\n\n/**\n * Decompresses a stored value: versioned envelope (v, algo, payload) or legacy gzip+base64 / raw.\n * If the value is not valid envelope JSON, falls back to legacy: try gzip magic on base64, else return as-is.\n */\nexport function decompressResource(compressedOrRaw: string): string {\n try {\n const parsed = JSON.parse(compressedOrRaw) as unknown;\n if (isEnvelope(parsed)) {\n if (parsed.algo === COMPRESSION_ALGOS.GZIP) {\n const buf = Buffer.from(parsed.payload, \"base64\");\n return gunzipSync(buf).toString(\"utf-8\");\n }\n if (parsed.algo === COMPRESSION_ALGOS.NONE) {\n return parsed.payload;\n }\n // Unknown algo: return payload as-is (safe fallback per ADR)\n return parsed.payload;\n }\n } catch {\n // Not valid envelope JSON — legacy path\n }\n\n // Legacy: pre-envelope gzip+base64 or raw\n try {\n const buf = Buffer.from(compressedOrRaw, \"base64\");\n if (buf.length >= 2 && buf[0] === 0x1f && buf[1] === 0x8b) {\n return gunzipSync(buf).toString(\"utf-8\");\n }\n } catch {\n // not base64 or gunzip failed\n }\n return compressedOrRaw;\n}\n","import { DynamoDBClient } from \"@aws-sdk/client-dynamodb\";\nimport { Service } from \"electrodb\";\nimport { Patient } from \"./Patient\";\n\n/**\n * DynamoDB table name for the data store. Set via DYNAMO_TABLE_NAME at runtime\n * (e.g. from Lambda env); defaults for local/test.\n */\nconst table = process.env.DYNAMO_TABLE_NAME ?? \"jesttesttable\";\n\n/**\n * DynamoDB client. When MOCK_DYNAMODB_ENDPOINT is set (e.g. local DynamoDB or\n * jest-dynalite), uses that endpoint with no SSL and region \"local\".\n */\nconst client = new DynamoDBClient({\n ...(process.env.MOCK_DYNAMODB_ENDPOINT && {\n endpoint: process.env.MOCK_DYNAMODB_ENDPOINT,\n sslEnabled: false,\n region: \"local\",\n }),\n});\n\nconst entities = { patient: Patient };\n\n/**\n * ElectroDB Service for the single-table data store. Provides access to Patient\n * and other entities; use with the data store table (PK, SK, GSI1, GSI2, GSI3, GSI4).\n */\nexport const EhrR4DataService = new Service(entities, { table, client });\n\n/**\n * Returns an ElectroDB Service for the data store using the given table name.\n * Use in tests with a dedicated table (e.g. \"data-store-test\" in jest-dynalite).\n */\nexport function getEhrR4DataService(\n tableName: string,\n): typeof EhrR4DataService {\n return new Service(entities, { table: tableName, client });\n}\n","import { Entity } from \"electrodb\";\n\n/**\n * Patient data-store entity based on FHIR (single-table store).\n *\n * Key structure: PK = TID#<tenantId>#WID#<workspaceId>#RT#Patient#ID#<id>, SK = CURRENT.\n * Standard attributes (FHIR source and entity purpose) are documented in the Data-Store Entity Standards (FHIR).\n *\n * @see sites/www-docs/content/reference/data-store-entities.md — Standard attributes (all FHIR domain resources)\n * @see sites/www-docs/content/architecture/dynamodb-single-table-design.md\n */\nexport const Patient = new Entity({\n model: {\n entity: \"patient\",\n service: \"fhir\",\n version: \"01\",\n },\n attributes: {\n /** Sort key. \"CURRENT\" for current version; version history in S3. */\n sk: {\n type: \"string\",\n required: true,\n default: \"CURRENT\",\n },\n tenantId: {\n type: \"string\",\n required: true,\n },\n workspaceId: {\n type: \"string\",\n required: true,\n },\n /** FHIR Resource.id; logical id in URL and PK. */\n id: {\n type: \"string\",\n required: true,\n },\n /** FHIR resource as JSON string. JSON.stringify(resource) on write; JSON.parse(item.resource) on read. */\n resource: {\n type: \"string\",\n required: true,\n },\n /** Version id (e.g. ULID). Tracks current version; S3 history key. */\n vid: {\n type: \"string\",\n required: true,\n },\n lastUpdated: {\n type: \"string\",\n required: true,\n },\n deleted: {\n type: \"boolean\",\n required: false,\n },\n bundleId: {\n type: \"string\",\n required: false,\n },\n msgId: {\n type: \"string\",\n required: false,\n },\n // Audit is in FHIR resource meta (meta.extension), not item attributes. See data-store-entities.md.\n // --- GSI2 (Identifier Lookup): optional; set when indexing this patient by identifier (e.g. MRN)\n /** Identifier system (e.g. MRN system URI). When set with identifierValue, item is written to GSI2. */\n identifierSystem: {\n type: \"string\",\n required: false,\n },\n /** Identifier value (e.g. MRN). When set with identifierSystem, item is written to GSI2. */\n identifierValue: {\n type: \"string\",\n required: false,\n },\n /** For GSI2/GSI3 projection: base table PK/SK so GetItem can be used after query. */\n resourcePk: {\n type: \"string\",\n required: false,\n },\n resourceSk: {\n type: \"string\",\n required: false,\n },\n /** For GSI2 projection: display name for roster/lookup. */\n display: {\n type: \"string\",\n required: false,\n },\n /** For GSI2 projection: resource status if applicable. */\n status: {\n type: \"string\",\n required: false,\n },\n // --- GSI3 (Facility Ops): optional; set when indexing this patient on a facility roster\n /** Facility id. When set with normalizedName, item is written to GSI3. */\n facilityId: {\n type: \"string\",\n required: false,\n },\n /** Normalized display name for roster sort. When set with facilityId, item is written to GSI3. */\n normalizedName: {\n type: \"string\",\n required: false,\n },\n },\n indexes: {\n /** Base table: PK, SK (data store key names). PK is built from tenantId, workspaceId, id; do not supply PK from outside. */\n record: {\n pk: {\n field: \"PK\",\n composite: [\"tenantId\", \"workspaceId\", \"id\"],\n template: \"TID#${tenantId}#WID#${workspaceId}#RT#Patient#ID#${id}\",\n },\n sk: {\n field: \"SK\",\n composite: [\"sk\"],\n },\n },\n\n /**\n * GSI1 — Reverse Reference: query \"what references this patient?\".\n * Patient items are never written to GSI1 (condition: false); reference index items\n * are written by other resources. This index enables querying GSI1 by REFTO#RT#Patient#ID#<id>.\n */\n gsi1: {\n index: \"GSI1\",\n condition: () => false,\n pk: {\n field: \"GSI1PK\",\n composite: [\"tenantId\", \"workspaceId\", \"id\"],\n template:\n \"TID#${tenantId}#WID#${workspaceId}#REFTO#RT#Patient#ID#${id}\",\n },\n sk: {\n field: \"GSI1SK\",\n composite: [],\n },\n },\n\n /** GSI2 — Identifier Lookup: MRN, NPI, member ID, etc. Keys built from identifier components. */\n gsi2: {\n index: \"GSI2\",\n condition: (attr) =>\n attr.identifierSystem != null && attr.identifierValue != null,\n pk: {\n field: \"GSI2PK\",\n composite: [\n \"tenantId\",\n \"workspaceId\",\n \"identifierSystem\",\n \"identifierValue\",\n ],\n template:\n \"TID#${tenantId}#WID#${workspaceId}#IDENT#${identifierSystem}#${identifierValue}\",\n },\n sk: {\n field: \"GSI2SK\",\n composite: [\"id\"],\n template: \"RT#Patient#ID#${id}\",\n },\n },\n /** GSI3 — Facility Ops: facility roster, worklists. Keys built from facility + name. */\n gsi3: {\n index: \"GSI3\",\n condition: (attr) =>\n attr.facilityId != null && attr.normalizedName != null,\n pk: {\n field: \"GSI3PK\",\n composite: [\"tenantId\", \"workspaceId\", \"facilityId\"],\n template: \"TID#${tenantId}#WID#${workspaceId}#FAC#${facilityId}\",\n },\n sk: {\n field: \"GSI3SK\",\n composite: [\"id\", \"normalizedName\"],\n template: \"TYPE#PATIENT#PAT#${id}#NAME#${normalizedName}\",\n },\n },\n /** GSI4 — Resource Type Index: list all Patients in workspace (no scan). */\n gsi4: {\n index: \"GSI4\",\n condition: () => true,\n pk: {\n field: \"GSI4PK\",\n composite: [\"tenantId\", \"workspaceId\"],\n template: \"TID#${tenantId}#WID#${workspaceId}#RT#Patient\",\n },\n sk: {\n field: \"GSI4SK\",\n composite: [\"id\"],\n template: \"ID#${id}\",\n },\n },\n },\n});\n","import { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { compressResource } from \"../lib/compression\";\nimport { getEhrR4DataService } from \"./dynamo/ehr/r4/ehr-r4-data-service\";\n\n/** OpenHI extension URLs for audit in resource meta (per ADR 2026-01-13-06). */\nconst OPENHI_EXT = \"http://openhi.org/fhir/StructureDefinition\";\n\n/** Audit fields stored in FHIR resource meta.extension. */\nexport interface AuditFields {\n createdDate?: string;\n createdById?: string;\n createdByName?: string;\n modifiedDate?: string;\n modifiedById?: string;\n modifiedByName?: string;\n deletedDate?: string;\n deletedById?: string;\n deletedByName?: string;\n}\n\n/** Builds meta.extension entries for audit; merges with existing meta. */\nexport function mergeAuditIntoMeta(\n meta: Record<string, unknown> | undefined,\n audit: AuditFields,\n): Record<string, unknown> {\n const existing = meta ?? {};\n const ext: Array<{\n url: string;\n valueString?: string;\n valueDateTime?: string;\n }> = [\n ...(Array.isArray(existing.extension)\n ? (existing.extension as Array<{\n url: string;\n valueString?: string;\n valueDateTime?: string;\n }>)\n : []),\n ];\n const byUrl = new Map(ext.map((e) => [e.url, e]));\n function set(\n url: string,\n value: string | undefined,\n type: \"valueString\" | \"valueDateTime\",\n ) {\n if (value == null) return;\n byUrl.set(url, { url, [type]: value });\n }\n set(`${OPENHI_EXT}/created-date`, audit.createdDate, \"valueDateTime\");\n set(`${OPENHI_EXT}/created-by-id`, audit.createdById, \"valueString\");\n set(`${OPENHI_EXT}/created-by-name`, audit.createdByName, \"valueString\");\n set(`${OPENHI_EXT}/modified-date`, audit.modifiedDate, \"valueDateTime\");\n set(`${OPENHI_EXT}/modified-by-id`, audit.modifiedById, \"valueString\");\n set(`${OPENHI_EXT}/modified-by-name`, audit.modifiedByName, \"valueString\");\n set(`${OPENHI_EXT}/deleted-date`, audit.deletedDate, \"valueDateTime\");\n set(`${OPENHI_EXT}/deleted-by-id`, audit.deletedById, \"valueString\");\n set(`${OPENHI_EXT}/deleted-by-name`, audit.deletedByName, \"valueString\");\n return { ...existing, extension: Array.from(byUrl.values()) };\n}\n\n/** Minimal FHIR Patient shape needed for import (id and resourceType required). */\ninterface FhirPatientLike {\n resourceType: string;\n id: string;\n meta?: Record<string, unknown>;\n}\n\n/** FHIR Bundle entry (e.g. Synthea transaction bundle). */\ninterface BundleEntry {\n fullUrl?: string;\n resource?: {\n resourceType?: string;\n id?: string;\n meta?: { lastUpdated?: string };\n };\n}\n\n/**\n * Extracts a Patient from parsed JSON. Accepts either:\n * - A standalone Patient resource (root has resourceType \"Patient\"), or\n * - A FHIR Bundle (e.g. Synthea transaction) — uses the first entry whose resource is a Patient.\n */\nfunction extractPatient(parsed: unknown): FhirPatientLike {\n if (parsed && typeof parsed === \"object\" && \"resourceType\" in parsed) {\n const root = parsed as {\n resourceType?: string;\n id?: string;\n meta?: { lastUpdated?: string };\n };\n if (root.resourceType === \"Patient\" && root.id) {\n return root as FhirPatientLike;\n }\n if (root.resourceType === \"Bundle\" && \"entry\" in parsed) {\n const entries = (parsed as { entry?: BundleEntry[] }).entry;\n if (Array.isArray(entries)) {\n const patientEntry = entries.find(\n (e) => e?.resource?.resourceType === \"Patient\" && e.resource.id,\n );\n if (patientEntry?.resource) {\n return patientEntry.resource as FhirPatientLike;\n }\n }\n }\n }\n throw new Error(\n \"File must be a FHIR Patient resource or a Bundle containing at least one Patient entry\",\n );\n}\n\nconst SK = \"CURRENT\";\n\n/** Default audit values for create/modify when importing. */\nconst defaultAudit = {\n createdDate: new Date().toISOString(),\n createdById: \"import\",\n createdByName: \"Bulk import\",\n modifiedDate: new Date().toISOString(),\n modifiedById: \"import\",\n modifiedByName: \"Bulk import\",\n};\n\nexport interface ImportPatientOptions {\n tenantId: string;\n workspaceId: string;\n tableName?: string;\n /** Audit fields at same level as tenantId/workspaceId; merged with defaults. */\n createdDate?: string;\n createdById?: string;\n createdByName?: string;\n modifiedDate?: string;\n modifiedById?: string;\n modifiedByName?: string;\n}\n\n/**\n * Maps a FHIR Patient (from JSON) to the attributes required for Patient.put().\n * Uses placeholder GSI2/GSI3 fields so ElectroDB index conditions are satisfied.\n */\nexport function patientToPutAttrs(\n patient: FhirPatientLike,\n options: ImportPatientOptions,\n): Record<string, unknown> {\n const {\n tenantId,\n workspaceId,\n createdDate,\n createdById,\n createdByName,\n modifiedDate,\n modifiedById,\n modifiedByName,\n } = options;\n const lastUpdated =\n (patient.meta?.lastUpdated as string | undefined) ??\n modifiedDate ??\n defaultAudit.modifiedDate ??\n new Date().toISOString();\n const auditMerged: AuditFields = {\n ...defaultAudit,\n ...(createdDate != null && { createdDate }),\n ...(createdById != null && { createdById }),\n ...(createdByName != null && { createdByName }),\n ...(modifiedDate != null && { modifiedDate }),\n ...(modifiedById != null && { modifiedById }),\n ...(modifiedByName != null && { modifiedByName }),\n };\n\n const patientWithMeta = {\n ...patient,\n meta: mergeAuditIntoMeta(patient.meta, auditMerged),\n };\n if (lastUpdated && !patientWithMeta.meta.lastUpdated) {\n (patientWithMeta.meta as Record<string, unknown>).lastUpdated = lastUpdated;\n }\n\n return {\n sk: SK,\n tenantId,\n workspaceId,\n id: patient.id,\n resource: compressResource(JSON.stringify(patientWithMeta)),\n vid:\n lastUpdated.replace(/[-:T.Z]/g, \"\").slice(0, 12) ||\n Date.now().toString(36),\n lastUpdated,\n identifierSystem: \"\",\n identifierValue: \"\",\n facilityId: \"\",\n normalizedName: \"\",\n };\n}\n\n/**\n * Reads a single Patient JSON file and imports it into the FHIR store.\n * Table name: options.tableName ?? process.env.DYNAMO_TABLE_NAME ?? \"jesttesttable\".\n */\nexport async function importPatientFromFile(\n filePath: string,\n options: ImportPatientOptions,\n): Promise<{ id: string; tenantId: string; workspaceId: string }> {\n const resolved = resolve(filePath);\n const raw = readFileSync(resolved, \"utf-8\");\n const parsed: unknown = JSON.parse(raw);\n const patient = extractPatient(parsed);\n\n const tableName =\n options.tableName ?? process.env.DYNAMO_TABLE_NAME ?? \"jesttesttable\";\n const service = getEhrR4DataService(tableName);\n const attrs = patientToPutAttrs(patient, options);\n\n const result = await service.entities.patient\n .put(attrs as unknown as Parameters<typeof service.entities.patient.put>[0])\n .go();\n\n const data = (\n result as { data?: { id: string; tenantId: string; workspaceId: string } }\n ).data;\n if (!data) {\n throw new Error(`Put failed for Patient ${patient.id}`);\n }\n\n return {\n id: data.id,\n tenantId: data.tenantId,\n workspaceId: data.workspaceId,\n };\n}\n\n/** Run as script: node/ts-node import-patient.ts <path-to-patient.json> [tenantId] [workspaceId] */\nasync function main(): Promise<void> {\n const [, , fileArg, tenantId = \"tenant-1\", workspaceId = \"ws-1\"] =\n process.argv;\n\n if (!fileArg) {\n console.error(\n \"Usage: import-patient.ts <path-to-patient.json> [tenantId] [workspaceId]\",\n );\n process.exit(1);\n }\n\n try {\n const result = await importPatientFromFile(fileArg, {\n tenantId,\n workspaceId,\n });\n console.log(\n `Imported Patient ${result.id} (tenant=${result.tenantId}, workspace=${result.workspaceId})`,\n );\n } catch (err) {\n console.error(err);\n process.exit(1);\n }\n}\n\nif (require.main === module) {\n void main();\n}\n"],"mappings":"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","names":["express","envelope","SK","express"]}
1
+ {"version":3,"sources":["../src/data/lambda/rest-api-lambda.handler.ts","../src/data/rest-api/rest-api.ts","../src/data/middleware/open-hi-context.ts","../src/data/rest-api/ehr/r4/Patient.ts","../src/lib/compression.ts","../src/data/dynamo/ehr/r4/ehr-r4-data-service.ts","../src/data/dynamo/ehr/r4/Patient.ts","../src/data/import-patient.ts","../src/data/rest-api/ohi/Configuration.ts","../src/data/rest-api/ohi/dynamic-configuration.ts","../src/data/dynamo/ohi/ohi-data-service.ts","../src/data/dynamo/ohi/Configuration.ts"],"sourcesContent":["import serverlessExpress from \"@codegenie/serverless-express\";\nimport { app } from \"../rest-api/rest-api\";\n\nexport const handler = serverlessExpress({ app });\n","import path from \"path\";\nimport cors from \"cors\";\nimport express, { Express, Request, Response } from \"express\";\nimport { openHiContextMiddleware } from \"../middleware/open-hi-context\";\nimport { patientRouter } from \"./ehr/r4/Patient\";\nimport { configurationRouter } from \"./ohi/Configuration\";\n\nconst app: Express = express();\n\napp.set(\"view engine\", \"ejs\");\napp.set(\"views\", path.join(__dirname, \"views\"));\n\napp.use(cors());\napp.use(express.json());\napp.use(express.urlencoded({ extended: true }));\n\napp.get(\"/\", (_req: Request, res: Response) => {\n return res.status(200).json({ message: \"POC App is running\" });\n});\n\n// Tenant/workspace resolved from headers/env (or JWT when implemented); attached to req for /ehr and /ohi routes\napp.use(\"/ehr\", openHiContextMiddleware);\napp.use(\"/ohi\", openHiContextMiddleware);\n\n// FHIR R4 EHR endpoints: path structure mirrors folder structure (ehr/r4/Patient.ts → /ehr/r4/Patient)\napp.use(\"/ehr/r4/Patient\", patientRouter);\n\n// Control Plane OHI endpoints (ohi/Configuration.ts → /ohi/Configuration)\napp.use(\"/ohi/Configuration\", configurationRouter);\n\nexport { app };\n","import type { Request, Response, NextFunction } from \"express\";\n\n/** Static context used until JWT claims are implemented. */\nconst STATIC_TENANT_ID = \"tenant-1\";\nconst STATIC_WORKSPACE_ID = \"ws-1\";\nconst STATIC_USER_ID = \"rest-api\";\nconst STATIC_USER_NAME = \"REST API\";\n\n/**\n * Express middleware that sets req.openhiContext for /ehr (and other domain API)\n * routes. Context includes tenantId, workspaceId, and the three standard audit\n * fields (date, userId, username) for audit records and for merging into FHIR\n * data on mutations.\n *\n * **TODO: A future task will populate context from JWT claims** (e.g. after\n * verifying the token). For now, static values are used.\n *\n * @see sites/www-docs/content/packages/@openhi/constructs/rest-api.md — Middleware\n */\nexport function openHiContextMiddleware(\n req: Request,\n _res: Response,\n next: NextFunction,\n): void {\n const now = new Date().toISOString();\n req.openhiContext = {\n tenantId: STATIC_TENANT_ID,\n workspaceId: STATIC_WORKSPACE_ID,\n date: now,\n userId: STATIC_USER_ID,\n username: STATIC_USER_NAME,\n };\n next();\n}\n","import express, { Request, Response } from \"express\";\nimport {\n compressResource,\n decompressResource,\n} from \"../../../../lib/compression\";\nimport { getEhrR4DataService } from \"../../../dynamo/ehr/r4/ehr-r4-data-service\";\nimport {\n mergeAuditIntoMeta,\n patientToPutAttrs,\n type ImportPatientOptions,\n} from \"../../../import-patient\";\n\nconst BASE_PATH = \"/ehr/r4/Patient\";\nconst router: express.Router = express.Router();\nconst SK = \"CURRENT\";\n\nconst TABLE_NAME = process.env.DYNAMO_TABLE_NAME ?? \"jesttesttable\";\n\n/**\n * GET /ehr/r4/Patient — search/list: returns a FHIR Bundle (searchset) from the data store.\n * Uses GSI4 (Resource Type Index) to list all Patients in the workspace without a table scan.\n * @see {@link https://github.com/codedrifters/openhi/blob/main/sites/www-docs/content/architecture/dynamodb-single-table-design.md | DynamoDB Single-Table Design} — GSI4, Query and Access Rules (no scans).\n */\nexport async function listPatients(\n req: Request,\n res: Response,\n): Promise<Response> {\n const { tenantId, workspaceId } = req.openhiContext!;\n const service = getEhrR4DataService(TABLE_NAME);\n\n try {\n const result = await service.entities.patient.query\n .gsi4({ tenantId, workspaceId })\n .go();\n\n const entries = (result.data ?? []).map((item) => {\n const resource = JSON.parse(decompressResource(item.resource)) as Record<\n string,\n unknown\n >;\n return {\n fullUrl: `${BASE_PATH}/${item.id}`,\n resource: { ...resource, id: item.id },\n };\n });\n const bundle = {\n resourceType: \"Bundle\",\n type: \"searchset\",\n total: entries.length,\n link: [{ relation: \"self\", url: BASE_PATH }],\n entry: entries,\n };\n return res.json(bundle);\n } catch (err: unknown) {\n console.error(\"GET /Patient list error:\", err);\n return res.status(500).json({\n resourceType: \"OperationOutcome\",\n issue: [\n {\n severity: \"error\",\n code: \"exception\",\n diagnostics: String(err),\n },\n ],\n });\n }\n}\n\nrouter.get(\"/\", listPatients);\n\nexport async function getPatientById(\n req: Request,\n res: Response,\n): Promise<Response> {\n const id = String(req.params.id);\n const { tenantId, workspaceId } = req.openhiContext!;\n const service = getEhrR4DataService(TABLE_NAME);\n\n try {\n const result = await service.entities.patient\n .get({ tenantId, workspaceId, id, sk: \"CURRENT\" })\n .go();\n\n if (!result.data) {\n return res.status(404).json({\n resourceType: \"OperationOutcome\",\n issue: [\n {\n severity: \"error\",\n code: \"not-found\",\n diagnostics: `Patient ${id} not found`,\n },\n ],\n });\n }\n\n const resource = JSON.parse(\n decompressResource(result.data.resource),\n ) as Record<string, unknown>;\n return res.json({ ...resource, id: result.data.id });\n } catch (err: unknown) {\n console.error(\"GET Patient error:\", err);\n return res.status(500).json({\n resourceType: \"OperationOutcome\",\n issue: [\n {\n severity: \"error\",\n code: \"exception\",\n diagnostics: String(err),\n },\n ],\n });\n }\n}\n\n/** GET /ehr/r4/Patient/:id — read: returns a single Patient resource from the data store or 404. */\nrouter.get(\"/:id\", getPatientById);\n\n/** POST /ehr/r4/Patient — create: accepts Patient in body, persists via data store, returns 201. */\nexport async function createPatient(\n req: Request,\n res: Response,\n): Promise<Response> {\n const ctx = req.openhiContext!;\n const { tenantId, workspaceId, date, userId, username } = ctx;\n const body = req.body as Record<string, unknown>;\n const id = (body?.id as string) ?? `patient-${Date.now()}`;\n const patient = {\n ...body,\n resourceType: \"Patient\",\n id,\n meta: {\n ...((body?.meta as object) ?? {}),\n lastUpdated: date,\n versionId: \"1\",\n },\n } as { resourceType: string; id: string; meta?: { lastUpdated?: string } };\n const options: ImportPatientOptions = {\n tenantId,\n workspaceId,\n createdDate: date,\n createdById: userId,\n createdByName: username,\n modifiedDate: date,\n modifiedById: userId,\n modifiedByName: username,\n };\n const service = getEhrR4DataService(TABLE_NAME);\n\n try {\n const attrs = patientToPutAttrs(patient, options);\n await service.entities.patient\n .put(\n attrs as unknown as Parameters<typeof service.entities.patient.put>[0],\n )\n .go();\n return res.status(201).location(`${BASE_PATH}/${id}`).json(patient);\n } catch (err: unknown) {\n console.error(\"POST Patient error:\", err);\n return res.status(500).json({\n resourceType: \"OperationOutcome\",\n issue: [\n { severity: \"error\", code: \"exception\", diagnostics: String(err) },\n ],\n });\n }\n}\n\nrouter.post(\"/\", createPatient);\n\n/** PUT /ehr/r4/Patient/:id — update: accepts Patient in body, persists via data store, returns 200. */\nexport async function updatePatient(\n req: Request,\n res: Response,\n): Promise<Response> {\n const id = String(req.params.id);\n const ctx = req.openhiContext!;\n const { tenantId, workspaceId, date, userId, username } = ctx;\n const body = req.body as Record<string, unknown>;\n const patient = {\n ...body,\n resourceType: \"Patient\",\n id,\n meta: {\n ...((body?.meta as object) ?? {}),\n lastUpdated: date,\n versionId: \"2\",\n },\n };\n const service = getEhrR4DataService(TABLE_NAME);\n\n try {\n const existing = await service.entities.patient\n .get({ tenantId, workspaceId, id, sk: SK })\n .go();\n if (!existing.data) {\n return res.status(404).json({\n resourceType: \"OperationOutcome\",\n issue: [\n {\n severity: \"error\",\n code: \"not-found\",\n diagnostics: `Patient ${id} not found`,\n },\n ],\n });\n }\n const existingMeta =\n existing.data.resource != null\n ? (\n JSON.parse(decompressResource(existing.data.resource)) as {\n meta?: Record<string, unknown>;\n }\n ).meta\n : undefined;\n const patientWithMeta = {\n ...patient,\n meta: mergeAuditIntoMeta(\n (patient.meta as Record<string, unknown> | undefined) ?? existingMeta,\n {\n modifiedDate: date,\n modifiedById: userId,\n modifiedByName: username,\n },\n ),\n };\n await service.entities.patient\n .patch({ tenantId, workspaceId, id, sk: SK })\n .set({\n resource: compressResource(JSON.stringify(patientWithMeta)),\n lastUpdated: date,\n })\n .go();\n return res.json(patientWithMeta);\n } catch (err: unknown) {\n console.error(\"PUT Patient error:\", err);\n return res.status(500).json({\n resourceType: \"OperationOutcome\",\n issue: [\n { severity: \"error\", code: \"exception\", diagnostics: String(err) },\n ],\n });\n }\n}\n\nrouter.put(\"/:id\", updatePatient);\n\n/** DELETE /ehr/r4/Patient/:id — delete: removes from data store, returns 204. */\nexport async function deletePatient(\n req: Request,\n res: Response,\n): Promise<Response> {\n const id = String(req.params.id);\n const { tenantId, workspaceId } = req.openhiContext!;\n const service = getEhrR4DataService(TABLE_NAME);\n\n try {\n await service.entities.patient\n .delete({ tenantId, workspaceId, id, sk: SK })\n .go();\n return res.status(204).send();\n } catch (err: unknown) {\n console.error(\"DELETE Patient error:\", err);\n return res.status(500).json({\n resourceType: \"OperationOutcome\",\n issue: [\n { severity: \"error\", code: \"exception\", diagnostics: String(err) },\n ],\n });\n }\n}\n\nrouter.delete(\"/:id\", deletePatient);\n\nexport { router as patientRouter };\n","import { gzipSync, gunzipSync } from \"node:zlib\";\n\n/** Envelope format version. See ADR 2026-02-15-02 (data layer compression). */\nconst ENVELOPE_VERSION = 1;\n\n/**\n * Compression algorithm identifiers supported by the envelope (string values).\n * Only algos that Node.js supports out of the box (zlib): gzip, brotli, deflate.\n * \"none\" = uncompressed payload. zstd was considered in the ADR but requires native addon/WASM.\n */\nexport const COMPRESSION_ALGOS = {\n NONE: \"none\",\n GZIP: \"gzip\",\n BROTLI: \"brotli\",\n DEFLATE: \"deflate\",\n} as const;\n\n/** Algorithm value for envelope `algo`; only gzip and none are implemented today. */\nexport type CompressionAlgo =\n (typeof COMPRESSION_ALGOS)[keyof typeof COMPRESSION_ALGOS];\n\n/** Stored value is a JSON string of this envelope. */\ninterface CompressionEnvelope {\n v: number;\n algo: string;\n payload: string;\n}\n\nfunction isEnvelope(obj: unknown): obj is CompressionEnvelope {\n return (\n typeof obj === \"object\" &&\n obj !== null &&\n \"v\" in obj &&\n \"algo\" in obj &&\n \"payload\" in obj &&\n typeof (obj as CompressionEnvelope).payload === \"string\"\n );\n}\n\n/**\n * Compresses a JSON string (e.g. serialized FHIR resource) for storage in DynamoDB.\n * Uses a versioned envelope: { v, algo, payload } with gzip+base64 in payload.\n * Used by the data layer on write; see REST API docs (compression in data layer).\n * Optional compression: pass `{ algo: COMPRESSION_ALGOS.NONE }` to store in envelope without compressing.\n */\nexport function compressResource(\n jsonString: string,\n options?: { algo?: CompressionAlgo },\n): string {\n const algo = options?.algo ?? COMPRESSION_ALGOS.GZIP;\n if (algo === COMPRESSION_ALGOS.NONE) {\n const envelope: CompressionEnvelope = {\n v: ENVELOPE_VERSION,\n algo: COMPRESSION_ALGOS.NONE,\n payload: jsonString,\n };\n return JSON.stringify(envelope);\n }\n const buf = Buffer.from(jsonString, \"utf-8\");\n const payload = gzipSync(buf).toString(\"base64\");\n const envelope: CompressionEnvelope = {\n v: ENVELOPE_VERSION,\n algo: COMPRESSION_ALGOS.GZIP,\n payload,\n };\n return JSON.stringify(envelope);\n}\n\n/**\n * Decompresses a stored value: versioned envelope (v, algo, payload) or legacy gzip+base64 / raw.\n * If the value is not valid envelope JSON, falls back to legacy: try gzip magic on base64, else return as-is.\n */\nexport function decompressResource(compressedOrRaw: string): string {\n try {\n const parsed = JSON.parse(compressedOrRaw) as unknown;\n if (isEnvelope(parsed)) {\n if (parsed.algo === COMPRESSION_ALGOS.GZIP) {\n const buf = Buffer.from(parsed.payload, \"base64\");\n return gunzipSync(buf).toString(\"utf-8\");\n }\n if (parsed.algo === COMPRESSION_ALGOS.NONE) {\n return parsed.payload;\n }\n // Unknown algo: return payload as-is (safe fallback per ADR)\n return parsed.payload;\n }\n } catch {\n // Not valid envelope JSON — legacy path\n }\n\n // Legacy: pre-envelope gzip+base64 or raw\n try {\n const buf = Buffer.from(compressedOrRaw, \"base64\");\n if (buf.length >= 2 && buf[0] === 0x1f && buf[1] === 0x8b) {\n return gunzipSync(buf).toString(\"utf-8\");\n }\n } catch {\n // not base64 or gunzip failed\n }\n return compressedOrRaw;\n}\n","import { DynamoDBClient } from \"@aws-sdk/client-dynamodb\";\nimport { Service } from \"electrodb\";\nimport { Patient } from \"./Patient\";\n\n/**\n * DynamoDB table name for the data store. Set via DYNAMO_TABLE_NAME at runtime\n * (e.g. from Lambda env); defaults for local/test.\n */\nconst table = process.env.DYNAMO_TABLE_NAME ?? \"jesttesttable\";\n\n/**\n * DynamoDB client. When MOCK_DYNAMODB_ENDPOINT is set (e.g. local DynamoDB or\n * jest-dynalite), uses that endpoint with no SSL and region \"local\".\n */\nconst client = new DynamoDBClient({\n ...(process.env.MOCK_DYNAMODB_ENDPOINT && {\n endpoint: process.env.MOCK_DYNAMODB_ENDPOINT,\n sslEnabled: false,\n region: \"local\",\n }),\n});\n\nconst entities = { patient: Patient };\n\n/**\n * ElectroDB Service for the single-table data store. Provides access to Patient\n * and other entities; use with the data store table (PK, SK, GSI1, GSI2, GSI3, GSI4).\n */\nexport const EhrR4DataService = new Service(entities, { table, client });\n\n/**\n * Returns an ElectroDB Service for the data store using the given table name.\n * Use in tests with a dedicated table (e.g. \"data-store-test\" in jest-dynalite).\n */\nexport function getEhrR4DataService(\n tableName: string,\n): typeof EhrR4DataService {\n return new Service(entities, { table: tableName, client });\n}\n","import { Entity } from \"electrodb\";\n\n/**\n * Patient data-store entity based on FHIR (single-table store).\n *\n * Key structure: PK = TID#<tenantId>#WID#<workspaceId>#RT#Patient#ID#<id>, SK = CURRENT.\n * Standard attributes (FHIR source and entity purpose) are documented in the Data-Store Entity Standards (FHIR).\n *\n * @see sites/www-docs/content/reference/data-store-entities.md — Standard attributes (all FHIR domain resources)\n * @see sites/www-docs/content/architecture/dynamodb-single-table-design.md\n */\nexport const Patient = new Entity({\n model: {\n entity: \"patient\",\n service: \"fhir\",\n version: \"01\",\n },\n attributes: {\n /** Sort key. \"CURRENT\" for current version; version history in S3. */\n sk: {\n type: \"string\",\n required: true,\n default: \"CURRENT\",\n },\n tenantId: {\n type: \"string\",\n required: true,\n },\n workspaceId: {\n type: \"string\",\n required: true,\n },\n /** FHIR Resource.id; logical id in URL and PK. */\n id: {\n type: \"string\",\n required: true,\n },\n /** FHIR resource as JSON string. JSON.stringify(resource) on write; JSON.parse(item.resource) on read. */\n resource: {\n type: \"string\",\n required: true,\n },\n /** Version id (e.g. ULID). Tracks current version; S3 history key. */\n vid: {\n type: \"string\",\n required: true,\n },\n lastUpdated: {\n type: \"string\",\n required: true,\n },\n deleted: {\n type: \"boolean\",\n required: false,\n },\n bundleId: {\n type: \"string\",\n required: false,\n },\n msgId: {\n type: \"string\",\n required: false,\n },\n // Audit is in FHIR resource meta (meta.extension), not item attributes. See data-store-entities.md.\n // --- GSI2 (Identifier Lookup): optional; set when indexing this patient by identifier (e.g. MRN)\n /** Identifier system (e.g. MRN system URI). When set with identifierValue, item is written to GSI2. */\n identifierSystem: {\n type: \"string\",\n required: false,\n },\n /** Identifier value (e.g. MRN). When set with identifierSystem, item is written to GSI2. */\n identifierValue: {\n type: \"string\",\n required: false,\n },\n /** For GSI2/GSI3 projection: base table PK/SK so GetItem can be used after query. */\n resourcePk: {\n type: \"string\",\n required: false,\n },\n resourceSk: {\n type: \"string\",\n required: false,\n },\n /** For GSI2 projection: display name for roster/lookup. */\n display: {\n type: \"string\",\n required: false,\n },\n /** For GSI2 projection: resource status if applicable. */\n status: {\n type: \"string\",\n required: false,\n },\n // --- GSI3 (Facility Ops): optional; set when indexing this patient on a facility roster\n /** Facility id. When set with normalizedName, item is written to GSI3. */\n facilityId: {\n type: \"string\",\n required: false,\n },\n /** Normalized display name for roster sort. When set with facilityId, item is written to GSI3. */\n normalizedName: {\n type: \"string\",\n required: false,\n },\n },\n indexes: {\n /** Base table: PK, SK (data store key names). PK is built from tenantId, workspaceId, id; do not supply PK from outside. */\n record: {\n pk: {\n field: \"PK\",\n composite: [\"tenantId\", \"workspaceId\", \"id\"],\n template: \"TID#${tenantId}#WID#${workspaceId}#RT#Patient#ID#${id}\",\n },\n sk: {\n field: \"SK\",\n composite: [\"sk\"],\n },\n },\n\n /**\n * GSI1 — Reverse Reference: query \"what references this patient?\".\n * Patient items are never written to GSI1 (condition: false); reference index items\n * are written by other resources. This index enables querying GSI1 by REFTO#RT#Patient#ID#<id>.\n */\n gsi1: {\n index: \"GSI1\",\n condition: () => false,\n pk: {\n field: \"GSI1PK\",\n composite: [\"tenantId\", \"workspaceId\", \"id\"],\n template:\n \"TID#${tenantId}#WID#${workspaceId}#REFTO#RT#Patient#ID#${id}\",\n },\n sk: {\n field: \"GSI1SK\",\n composite: [],\n },\n },\n\n /** GSI2 — Identifier Lookup: MRN, NPI, member ID, etc. Keys built from identifier components. */\n gsi2: {\n index: \"GSI2\",\n condition: (attr) =>\n attr.identifierSystem != null && attr.identifierValue != null,\n pk: {\n field: \"GSI2PK\",\n composite: [\n \"tenantId\",\n \"workspaceId\",\n \"identifierSystem\",\n \"identifierValue\",\n ],\n template:\n \"TID#${tenantId}#WID#${workspaceId}#IDENT#${identifierSystem}#${identifierValue}\",\n },\n sk: {\n field: \"GSI2SK\",\n composite: [\"id\"],\n template: \"RT#Patient#ID#${id}\",\n },\n },\n /** GSI3 — Facility Ops: facility roster, worklists. Keys built from facility + name. */\n gsi3: {\n index: \"GSI3\",\n condition: (attr) =>\n attr.facilityId != null && attr.normalizedName != null,\n pk: {\n field: \"GSI3PK\",\n composite: [\"tenantId\", \"workspaceId\", \"facilityId\"],\n template: \"TID#${tenantId}#WID#${workspaceId}#FAC#${facilityId}\",\n },\n sk: {\n field: \"GSI3SK\",\n composite: [\"id\", \"normalizedName\"],\n template: \"TYPE#PATIENT#PAT#${id}#NAME#${normalizedName}\",\n },\n },\n /** GSI4 — Resource Type Index: list all Patients in workspace (no scan). */\n gsi4: {\n index: \"GSI4\",\n condition: () => true,\n pk: {\n field: \"GSI4PK\",\n composite: [\"tenantId\", \"workspaceId\"],\n template: \"TID#${tenantId}#WID#${workspaceId}#RT#Patient\",\n },\n sk: {\n field: \"GSI4SK\",\n composite: [\"id\"],\n template: \"ID#${id}\",\n },\n },\n },\n});\n","import { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { compressResource } from \"../lib/compression\";\nimport { getEhrR4DataService } from \"./dynamo/ehr/r4/ehr-r4-data-service\";\n\n/** OpenHI extension URLs for audit in resource meta (per ADR 2026-01-13-06). */\nconst OPENHI_EXT = \"http://openhi.org/fhir/StructureDefinition\";\n\n/** Audit fields stored in FHIR resource meta.extension. */\nexport interface AuditFields {\n createdDate?: string;\n createdById?: string;\n createdByName?: string;\n modifiedDate?: string;\n modifiedById?: string;\n modifiedByName?: string;\n deletedDate?: string;\n deletedById?: string;\n deletedByName?: string;\n}\n\n/** Builds meta.extension entries for audit; merges with existing meta. */\nexport function mergeAuditIntoMeta(\n meta: Record<string, unknown> | undefined,\n audit: AuditFields,\n): Record<string, unknown> {\n const existing = meta ?? {};\n const ext: Array<{\n url: string;\n valueString?: string;\n valueDateTime?: string;\n }> = [\n ...(Array.isArray(existing.extension)\n ? (existing.extension as Array<{\n url: string;\n valueString?: string;\n valueDateTime?: string;\n }>)\n : []),\n ];\n const byUrl = new Map(ext.map((e) => [e.url, e]));\n function set(\n url: string,\n value: string | undefined,\n type: \"valueString\" | \"valueDateTime\",\n ) {\n if (value == null) return;\n byUrl.set(url, { url, [type]: value });\n }\n set(`${OPENHI_EXT}/created-date`, audit.createdDate, \"valueDateTime\");\n set(`${OPENHI_EXT}/created-by-id`, audit.createdById, \"valueString\");\n set(`${OPENHI_EXT}/created-by-name`, audit.createdByName, \"valueString\");\n set(`${OPENHI_EXT}/modified-date`, audit.modifiedDate, \"valueDateTime\");\n set(`${OPENHI_EXT}/modified-by-id`, audit.modifiedById, \"valueString\");\n set(`${OPENHI_EXT}/modified-by-name`, audit.modifiedByName, \"valueString\");\n set(`${OPENHI_EXT}/deleted-date`, audit.deletedDate, \"valueDateTime\");\n set(`${OPENHI_EXT}/deleted-by-id`, audit.deletedById, \"valueString\");\n set(`${OPENHI_EXT}/deleted-by-name`, audit.deletedByName, \"valueString\");\n return { ...existing, extension: Array.from(byUrl.values()) };\n}\n\n/** Minimal FHIR Patient shape needed for import (id and resourceType required). */\ninterface FhirPatientLike {\n resourceType: string;\n id: string;\n meta?: Record<string, unknown>;\n}\n\n/** FHIR Bundle entry (e.g. Synthea transaction bundle). */\ninterface BundleEntry {\n fullUrl?: string;\n resource?: {\n resourceType?: string;\n id?: string;\n meta?: { lastUpdated?: string };\n };\n}\n\n/**\n * Extracts a Patient from parsed JSON. Accepts either:\n * - A standalone Patient resource (root has resourceType \"Patient\"), or\n * - A FHIR Bundle (e.g. Synthea transaction) — uses the first entry whose resource is a Patient.\n */\nfunction extractPatient(parsed: unknown): FhirPatientLike {\n if (parsed && typeof parsed === \"object\" && \"resourceType\" in parsed) {\n const root = parsed as {\n resourceType?: string;\n id?: string;\n meta?: { lastUpdated?: string };\n };\n if (root.resourceType === \"Patient\" && root.id) {\n return root as FhirPatientLike;\n }\n if (root.resourceType === \"Bundle\" && \"entry\" in parsed) {\n const entries = (parsed as { entry?: BundleEntry[] }).entry;\n if (Array.isArray(entries)) {\n const patientEntry = entries.find(\n (e) => e?.resource?.resourceType === \"Patient\" && e.resource.id,\n );\n if (patientEntry?.resource) {\n return patientEntry.resource as FhirPatientLike;\n }\n }\n }\n }\n throw new Error(\n \"File must be a FHIR Patient resource or a Bundle containing at least one Patient entry\",\n );\n}\n\nconst SK = \"CURRENT\";\n\n/** Default audit values for create/modify when importing. */\nconst defaultAudit = {\n createdDate: new Date().toISOString(),\n createdById: \"import\",\n createdByName: \"Bulk import\",\n modifiedDate: new Date().toISOString(),\n modifiedById: \"import\",\n modifiedByName: \"Bulk import\",\n};\n\nexport interface ImportPatientOptions {\n tenantId: string;\n workspaceId: string;\n tableName?: string;\n /** Audit fields at same level as tenantId/workspaceId; merged with defaults. */\n createdDate?: string;\n createdById?: string;\n createdByName?: string;\n modifiedDate?: string;\n modifiedById?: string;\n modifiedByName?: string;\n}\n\n/**\n * Maps a FHIR Patient (from JSON) to the attributes required for Patient.put().\n * Uses placeholder GSI2/GSI3 fields so ElectroDB index conditions are satisfied.\n */\nexport function patientToPutAttrs(\n patient: FhirPatientLike,\n options: ImportPatientOptions,\n): Record<string, unknown> {\n const {\n tenantId,\n workspaceId,\n createdDate,\n createdById,\n createdByName,\n modifiedDate,\n modifiedById,\n modifiedByName,\n } = options;\n const lastUpdated =\n (patient.meta?.lastUpdated as string | undefined) ??\n modifiedDate ??\n defaultAudit.modifiedDate ??\n new Date().toISOString();\n const auditMerged: AuditFields = {\n ...defaultAudit,\n ...(createdDate != null && { createdDate }),\n ...(createdById != null && { createdById }),\n ...(createdByName != null && { createdByName }),\n ...(modifiedDate != null && { modifiedDate }),\n ...(modifiedById != null && { modifiedById }),\n ...(modifiedByName != null && { modifiedByName }),\n };\n\n const patientWithMeta = {\n ...patient,\n meta: mergeAuditIntoMeta(patient.meta, auditMerged),\n };\n if (lastUpdated && !patientWithMeta.meta.lastUpdated) {\n (patientWithMeta.meta as Record<string, unknown>).lastUpdated = lastUpdated;\n }\n\n return {\n sk: SK,\n tenantId,\n workspaceId,\n id: patient.id,\n resource: compressResource(JSON.stringify(patientWithMeta)),\n vid:\n lastUpdated.replace(/[-:T.Z]/g, \"\").slice(0, 12) ||\n Date.now().toString(36),\n lastUpdated,\n identifierSystem: \"\",\n identifierValue: \"\",\n facilityId: \"\",\n normalizedName: \"\",\n };\n}\n\n/**\n * Reads a single Patient JSON file and imports it into the FHIR store.\n * Table name: options.tableName ?? process.env.DYNAMO_TABLE_NAME ?? \"jesttesttable\".\n */\nexport async function importPatientFromFile(\n filePath: string,\n options: ImportPatientOptions,\n): Promise<{ id: string; tenantId: string; workspaceId: string }> {\n const resolved = resolve(filePath);\n const raw = readFileSync(resolved, \"utf-8\");\n const parsed: unknown = JSON.parse(raw);\n const patient = extractPatient(parsed);\n\n const tableName =\n options.tableName ?? process.env.DYNAMO_TABLE_NAME ?? \"jesttesttable\";\n const service = getEhrR4DataService(tableName);\n const attrs = patientToPutAttrs(patient, options);\n\n const result = await service.entities.patient\n .put(attrs as unknown as Parameters<typeof service.entities.patient.put>[0])\n .go();\n\n const data = (\n result as { data?: { id: string; tenantId: string; workspaceId: string } }\n ).data;\n if (!data) {\n throw new Error(`Put failed for Patient ${patient.id}`);\n }\n\n return {\n id: data.id,\n tenantId: data.tenantId,\n workspaceId: data.workspaceId,\n };\n}\n\n/** Run as script: node/ts-node import-patient.ts <path-to-patient.json> [tenantId] [workspaceId] */\nasync function main(): Promise<void> {\n const [, , fileArg, tenantId = \"tenant-1\", workspaceId = \"ws-1\"] =\n process.argv;\n\n if (!fileArg) {\n console.error(\n \"Usage: import-patient.ts <path-to-patient.json> [tenantId] [workspaceId]\",\n );\n process.exit(1);\n }\n\n try {\n const result = await importPatientFromFile(fileArg, {\n tenantId,\n workspaceId,\n });\n console.log(\n `Imported Patient ${result.id} (tenant=${result.tenantId}, workspace=${result.workspaceId})`,\n );\n } catch (err) {\n console.error(err);\n process.exit(1);\n }\n}\n\nif (require.main === module) {\n void main();\n}\n","import express, { Request, Response } from \"express\";\nimport { getDynamicConfigurationEntries } from \"./dynamic-configuration\";\nimport { compressResource, decompressResource } from \"../../../lib/compression\";\nimport { getOhiDataService } from \"../../dynamo/ohi/ohi-data-service\";\n\nconst BASE_PATH = \"/ohi/Configuration\";\nconst router: express.Router = express.Router();\nconst SK = \"CURRENT\";\n\nconst TABLE_NAME = process.env.DYNAMO_TABLE_NAME ?? \"jesttesttable\";\n\n/**\n * GET /ohi/Configuration — list: returns a FHIR Bundle (searchset) of Configuration resources.\n * Uses GSI4 (Resource Type Index) to list all Configuration in the workspace without a table scan.\n * Scope (tenantId, workspaceId) from req.openhiContext; userId/roleId are not used for list partition.\n */\nexport async function listConfigurations(\n req: Request,\n res: Response,\n): Promise<Response> {\n const { tenantId, workspaceId } = req.openhiContext!;\n const service = getOhiDataService(TABLE_NAME);\n\n try {\n const result = await service.entities.configuration.query\n .gsi4({ tenantId, workspaceId })\n .go();\n\n const dynamoEntries = (result.data ?? []).map((item) => {\n const resource = JSON.parse(decompressResource(item.resource)) as Record<\n string,\n unknown\n >;\n return {\n fullUrl: `${BASE_PATH}/${item.key}`,\n resource: { ...resource, id: item.id, key: item.key },\n };\n });\n const dynamicEntries = await getDynamicConfigurationEntries({\n tenantId,\n workspaceId,\n });\n const entries = [...dynamoEntries, ...dynamicEntries];\n const bundle = {\n resourceType: \"Bundle\",\n type: \"searchset\",\n total: entries.length,\n link: [{ relation: \"self\", url: BASE_PATH }],\n entry: entries,\n };\n return res.json(bundle);\n } catch (err: unknown) {\n console.error(\"GET /Configuration list error:\", err);\n return res.status(500).json({\n resourceType: \"OperationOutcome\",\n issue: [\n {\n severity: \"error\",\n code: \"exception\",\n diagnostics: String(err),\n },\n ],\n });\n }\n}\n\nrouter.get(\"/\", listConfigurations);\n\n/**\n * GET /ohi/Configuration/:key — read: returns a single Configuration resource for the key in current scope, or 404.\n */\nexport async function getConfigurationByKey(\n req: Request,\n res: Response,\n): Promise<Response> {\n const key = String(req.params.key);\n const ctx = req.openhiContext!;\n const { tenantId, workspaceId, userId } = ctx;\n const roleId =\n \"roleId\" in ctx && typeof (ctx as { roleId?: string }).roleId === \"string\"\n ? (ctx as { roleId: string }).roleId\n : \"-\";\n const service = getOhiDataService(TABLE_NAME);\n\n try {\n const result = await service.entities.configuration\n .get({ tenantId, workspaceId, userId, roleId, key, sk: SK })\n .go();\n\n if (!result.data) {\n return res.status(404).json({\n resourceType: \"OperationOutcome\",\n issue: [\n {\n severity: \"error\",\n code: \"not-found\",\n diagnostics: `Configuration ${key} not found`,\n },\n ],\n });\n }\n\n const resource = JSON.parse(\n decompressResource(result.data.resource),\n ) as Record<string, unknown>;\n return res.json({\n ...resource,\n resourceType: \"Configuration\",\n id: result.data.id,\n key: result.data.key,\n });\n } catch (err: unknown) {\n console.error(\"GET Configuration error:\", err);\n return res.status(500).json({\n resourceType: \"OperationOutcome\",\n issue: [\n {\n severity: \"error\",\n code: \"exception\",\n diagnostics: String(err),\n },\n ],\n });\n }\n}\n\n/** GET /ohi/Configuration/:key — read: returns a single Configuration resource from the data store or 404. */\nrouter.get(\"/:key\", getConfigurationByKey);\n\n/**\n * POST /ohi/Configuration — create: accepts Configuration in body, persists via data store, returns 201.\n * Scope from body (tenantId, workspaceId, userId, roleId) or req.openhiContext; use BASELINE/- for baseline.\n */\nexport async function createConfiguration(\n req: Request,\n res: Response,\n): Promise<Response> {\n const ctx = req.openhiContext!;\n const {\n tenantId: ctxTenantId,\n workspaceId: ctxWorkspaceId,\n userId: ctxUserId,\n date,\n } = ctx;\n const body = req.body as Record<string, unknown>;\n const key = body?.key as string;\n if (!key || typeof key !== \"string\") {\n return res.status(400).json({\n resourceType: \"OperationOutcome\",\n issue: [\n {\n severity: \"error\",\n code: \"required\",\n diagnostics: \"Configuration key is required\",\n },\n ],\n });\n }\n const id = (body?.id as string) ?? `config-${key}-${Date.now()}`;\n const resourcePayload = body?.resource as Record<string, unknown> | string;\n const resourceStr =\n typeof resourcePayload === \"string\"\n ? resourcePayload\n : JSON.stringify(resourcePayload ?? {});\n const tenantId = (body?.tenantId as string) ?? ctxTenantId;\n const workspaceId = (body?.workspaceId as string) ?? ctxWorkspaceId;\n const userId = (body?.userId as string) ?? ctxUserId ?? \"-\";\n const roleId = (body?.roleId as string) ?? \"-\";\n const vid =\n (body?.vid as string) ??\n (date.replace(/[-:T.Z]/g, \"\").slice(0, 12) || Date.now().toString(36));\n const lastUpdated = (body?.lastUpdated as string) ?? date;\n\n const service = getOhiDataService(TABLE_NAME);\n\n try {\n await service.entities.configuration\n .put({\n tenantId,\n workspaceId,\n userId,\n roleId,\n key,\n id,\n resource: compressResource(resourceStr),\n vid,\n lastUpdated,\n sk: SK,\n })\n .go();\n\n const config = {\n resourceType: \"Configuration\",\n id,\n key,\n resource:\n typeof resourcePayload === \"object\"\n ? resourcePayload\n : JSON.parse(resourceStr),\n meta: { lastUpdated, versionId: vid },\n };\n return res.status(201).location(`${BASE_PATH}/${key}`).json(config);\n } catch (err: unknown) {\n console.error(\"POST Configuration error:\", err);\n return res.status(500).json({\n resourceType: \"OperationOutcome\",\n issue: [\n { severity: \"error\", code: \"exception\", diagnostics: String(err) },\n ],\n });\n }\n}\n\nrouter.post(\"/\", createConfiguration);\n\n/**\n * PUT /ohi/Configuration/:key — update: accepts Configuration body, persists via data store, returns 200.\n */\nexport async function updateConfiguration(\n req: Request,\n res: Response,\n): Promise<Response> {\n const key = String(req.params.key);\n const ctx = req.openhiContext!;\n const { tenantId, workspaceId, userId, date } = ctx;\n const roleId =\n \"roleId\" in ctx && typeof (ctx as { roleId?: string }).roleId === \"string\"\n ? (ctx as { roleId: string }).roleId\n : \"-\";\n const body = req.body as Record<string, unknown>;\n const resourcePayload = body?.resource as Record<string, unknown> | string;\n const resourceStr =\n typeof resourcePayload === \"string\"\n ? resourcePayload\n : JSON.stringify(resourcePayload ?? {});\n const lastUpdated = (body?.lastUpdated as string) ?? date;\n\n const service = getOhiDataService(TABLE_NAME);\n\n try {\n const existing = await service.entities.configuration\n .get({ tenantId, workspaceId, userId, roleId, key, sk: SK })\n .go();\n\n if (!existing.data) {\n return res.status(404).json({\n resourceType: \"OperationOutcome\",\n issue: [\n {\n severity: \"error\",\n code: \"not-found\",\n diagnostics: `Configuration ${key} not found`,\n },\n ],\n });\n }\n\n const nextVid =\n existing.data.vid != null\n ? String(Number(existing.data.vid) + 1)\n : date.replace(/[-:T.Z]/g, \"\").slice(0, 12) || \"2\";\n\n await service.entities.configuration\n .patch({ tenantId, workspaceId, userId, roleId, key, sk: SK })\n .set({\n resource: compressResource(resourceStr),\n lastUpdated,\n vid: nextVid,\n })\n .go();\n\n const config = {\n resourceType: \"Configuration\",\n id: existing.data.id,\n key: existing.data.key,\n resource:\n typeof resourcePayload === \"object\"\n ? resourcePayload\n : JSON.parse(resourceStr),\n meta: { lastUpdated, versionId: nextVid },\n };\n return res.json(config);\n } catch (err: unknown) {\n console.error(\"PUT Configuration error:\", err);\n return res.status(500).json({\n resourceType: \"OperationOutcome\",\n issue: [\n { severity: \"error\", code: \"exception\", diagnostics: String(err) },\n ],\n });\n }\n}\n\nrouter.put(\"/:key\", updateConfiguration);\n\n/**\n * DELETE /ohi/Configuration/:key — delete: removes from data store, returns 204.\n */\nexport async function deleteConfiguration(\n req: Request,\n res: Response,\n): Promise<Response> {\n const key = String(req.params.key);\n const ctx = req.openhiContext!;\n const { tenantId, workspaceId, userId } = ctx;\n const roleId =\n \"roleId\" in ctx && typeof (ctx as { roleId?: string }).roleId === \"string\"\n ? (ctx as { roleId: string }).roleId\n : \"-\";\n const service = getOhiDataService(TABLE_NAME);\n\n try {\n await service.entities.configuration\n .delete({ tenantId, workspaceId, userId, roleId, key, sk: SK })\n .go();\n return res.status(204).send();\n } catch (err: unknown) {\n console.error(\"DELETE Configuration error:\", err);\n return res.status(500).json({\n resourceType: \"OperationOutcome\",\n issue: [\n { severity: \"error\", code: \"exception\", diagnostics: String(err) },\n ],\n });\n }\n}\n\nrouter.delete(\"/:key\", deleteConfiguration);\n\nexport { router as configurationRouter };\n","/**\n * Generates Configuration entries that are not stored in DynamoDB but are\n * included in the list response. Values are retrieved from AWS SSM Parameter\n * Store based on tags. Used by GET /ohi/Configuration to append dynamic\n * configs to Dynamo results.\n *\n * SSM parameters are selected when their tags match the Lambda environment\n * variables BRANCH_TAG_VALUE and HTTP_API_TAG_VALUE (set by RestApiLambda):\n * - Tag OpenHI:Branch must equal BRANCH_TAG_VALUE (e.g. branch name).\n * - Tag OpenHI:HttpApiParam must equal HTTP_API_TAG_VALUE (e.g. ROOT_HTTP_API).\n * If either env var is unset, the static dummy entry is returned.\n *\n * @see sites/www-docs/content/architecture/control-plane/configuration.md\n * @see packages/@openhi/constructs/src/data/lambda/rest-api-lambda.ts — BRANCH_TAG_VALUE, HTTP_API_TAG_VALUE\n */\n\nimport {\n DescribeParametersCommand,\n GetParametersCommand,\n SSMClient,\n} from \"@aws-sdk/client-ssm\";\n\nconst BASE_PATH = \"/ohi/Configuration\";\n\n/** SSM tag key for branch (value must match Lambda env BRANCH_TAG_VALUE). */\nconst TAG_KEY_BRANCH = \"openhi:branch-name\";\n/** SSM tag key for HTTP API param (value must match Lambda env HTTP_API_TAG_VALUE). */\nconst TAG_KEY_HTTP_API_PARAM = \"openhi:param-name\";\n\n/** Shape of a single entry in the list Bundle (fullUrl + resource). */\nexport interface ConfigurationListEntry {\n fullUrl: string;\n resource: {\n resourceType: \"Configuration\";\n id: string;\n key: string;\n resource?: Record<string, unknown>;\n meta?: Record<string, unknown>;\n [k: string]: unknown;\n };\n}\n\n/**\n * Values used to filter SSM parameters by tags. Sourced from Lambda environment\n * (BRANCH_TAG_VALUE, HTTP_API_TAG_VALUE). Both must be set to perform SSM lookup.\n */\nexport interface SsmDynamicConfigEnvFilter {\n /** Value for tag OpenHI:Branch (e.g. branch name). From env BRANCH_TAG_VALUE. */\n branchTagValue: string;\n /** Value for tag OpenHI:HttpApiParam (e.g. ROOT_HTTP_API). From env HTTP_API_TAG_VALUE. */\n httpApiTagValue: string;\n}\n\n/**\n * Resolves the tag filter from Lambda environment variables.\n * Returns null if either BRANCH_TAG_VALUE or HTTP_API_TAG_VALUE is missing.\n */\nexport function getSsmDynamicConfigEnvFilter(): SsmDynamicConfigEnvFilter | null {\n const branchTagValue = process.env.BRANCH_TAG_VALUE;\n const httpApiTagValue = process.env.HTTP_API_TAG_VALUE;\n if (\n branchTagValue == null ||\n branchTagValue === \"\" ||\n httpApiTagValue == null ||\n httpApiTagValue === \"\"\n ) {\n return null;\n }\n return { branchTagValue, httpApiTagValue };\n}\n\n/**\n * Fetches SSM parameter names whose tags match BRANCH_TAG_VALUE and\n * HTTP_API_TAG_VALUE (OpenHI:Branch and OpenHI:HttpApiParam), then\n * retrieves their values and returns a single Configuration list entry\n * whose resource.parameter array is built from name/value pairs.\n *\n * Parameter names are used as the parameter \"name\" in the Configuration\n * (last path segment if the name contains \"/\", otherwise the full name).\n * Values are stored as valueString.\n *\n * If either env var is unset, returns the static dummy entry without calling SSM.\n */\nexport async function getDynamicConfigurationEntries(context?: {\n tenantId?: string;\n workspaceId?: string;\n}): Promise<ConfigurationListEntry[]> {\n const envFilter = getSsmDynamicConfigEnvFilter();\n if (envFilter == null) {\n return getStaticDummyEntry(context);\n }\n\n const region = process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION;\n const client = new SSMClient({ region });\n\n try {\n // DescribeParameters: parameters must have both tags matching Lambda env\n const describeResult = await client.send(\n new DescribeParametersCommand({\n ParameterFilters: [\n {\n Key: `tag:${TAG_KEY_BRANCH}`,\n Option: \"Equals\",\n Values: [envFilter.branchTagValue],\n },\n {\n Key: `tag:${TAG_KEY_HTTP_API_PARAM}`,\n Option: \"Equals\",\n Values: [envFilter.httpApiTagValue],\n },\n ],\n MaxResults: 50,\n }),\n );\n\n const names = (describeResult.Parameters ?? [])\n .map((p: { Name?: string }) => p.Name)\n .filter((n: string | undefined): n is string => n != null);\n\n if (names.length === 0) {\n return getStaticDummyEntry(context);\n }\n\n // GetParameter values in batches of 10 (SSM limit)\n const parameters: Array<{ name: string; value: string }> = [];\n for (let i = 0; i < names.length; i += 10) {\n const batch = names.slice(i, i + 10);\n const getResult = await client.send(\n new GetParametersCommand({\n Names: batch,\n WithDecryption: true,\n }),\n );\n\n for (const p of getResult.Parameters ?? []) {\n const name = (p as { Name?: string }).Name;\n const value = (p as { Value?: string }).Value;\n if (name != null && value != null) {\n parameters.push({ name, value });\n }\n }\n }\n\n const parameterList = parameters.map((p) => {\n const shortName = p.name.includes(\"/\")\n ? p.name.split(\"/\").slice(-1)[0]\n : p.name;\n return { name: shortName, valueString: p.value };\n });\n\n const entry: ConfigurationListEntry = {\n fullUrl: `${BASE_PATH}/ssm-dynamic`,\n resource: {\n resourceType: \"Configuration\",\n id: \"ssm-dynamic\",\n key: \"ssm-dynamic\",\n resource: {\n parameter: parameterList,\n },\n meta: {\n lastUpdated: new Date().toISOString(),\n versionId: \"1\",\n },\n },\n };\n\n return [entry];\n } catch (err) {\n console.error(\"getDynamicConfigurationEntries SSM error:\", err);\n return getStaticDummyEntry(context);\n }\n}\n\n/**\n * Returns a static dummy Configuration entry when SSM is unavailable or\n * returns no parameters (e.g. in tests or fallback).\n */\nfunction getStaticDummyEntry(_context?: {\n tenantId?: string;\n workspaceId?: string;\n}): ConfigurationListEntry[] {\n const dummy: ConfigurationListEntry = {\n fullUrl: `${BASE_PATH}/dynamic-dummy`,\n resource: {\n resourceType: \"Configuration\",\n id: \"dynamic-dummy\",\n key: \"dynamic-dummy\",\n resource: {\n parameter: [\n {\n name: \"description\",\n valueString:\n \"Statically generated dummy configuration (not from DynamoDB).\",\n },\n { name: \"source\", valueString: \"dynamic-configuration\" },\n ],\n },\n meta: {\n lastUpdated: new Date().toISOString(),\n versionId: \"1\",\n },\n },\n };\n\n return [dummy];\n}\n","import { DynamoDBClient } from \"@aws-sdk/client-dynamodb\";\nimport { Service } from \"electrodb\";\nimport { Configuration } from \"./Configuration\";\n\n/**\n * DynamoDB table name for the data store. Set via DYNAMO_TABLE_NAME at runtime\n * (e.g. from Lambda env); defaults for local/test.\n */\nconst table = process.env.DYNAMO_TABLE_NAME ?? \"jesttesttable\";\n\n/**\n * DynamoDB client. When MOCK_DYNAMODB_ENDPOINT is set (e.g. local DynamoDB or\n * jest-dynalite), uses that endpoint with no SSL and region \"local\".\n */\nconst client = new DynamoDBClient({\n ...(process.env.MOCK_DYNAMODB_ENDPOINT && {\n endpoint: process.env.MOCK_DYNAMODB_ENDPOINT,\n sslEnabled: false,\n region: \"local\",\n }),\n});\n\nconst entities = { configuration: Configuration };\n\n/**\n * ElectroDB Service for Control Plane (OHI) entities in the single-table data store.\n * Provides access to Configuration and other OHI entities; use with the data store\n * table (PK, SK). Shares the same table as EHR/FHIR entities.\n *\n * @see sites/www-docs/content/architecture/control-plane/configuration.md\n * @see sites/www-docs/content/architecture/dynamodb-single-table-design.md\n */\nexport const OhiDataService = new Service(entities, { table, client });\n\n/**\n * Returns an ElectroDB Service for OHI entities using the given table name.\n * Use in tests with a dedicated table (e.g. \"data-store-test\" in jest-dynalite).\n */\nexport function getOhiDataService(tableName: string): typeof OhiDataService {\n return new Service(entities, { table: tableName, client });\n}\n","import { Entity } from \"electrodb\";\n\n/**\n * Configuration data-store entity (single-table store).\n *\n * Key structure: PK = OHI#CONFIG#TID#<tenantId>#WID#<workspaceId>#UID#<userId>#RID#<roleId>,\n * SK = KEY#<key>#SK#<sk>. Use tenantId \"BASELINE\" and workspaceId/userId/roleId \"-\" for baseline\n * or absent scope. Uniqueness: one Configuration per (tenantId, workspaceId, userId, roleId, key).\n * Standard attributes and key-building conventions align with Patient (data-store FHIR entities).\n *\n * @see sites/www-docs/content/architecture/control-plane/configuration.md\n * @see sites/www-docs/content/architecture/dynamodb-single-table-design.md\n * @see sites/www-docs/content/reference/data-store-entities.md — Key-building conventions (keys built inside entity)\n */\nexport const Configuration = new Entity({\n model: {\n entity: \"configuration\",\n service: \"ohi\",\n version: \"01\",\n },\n attributes: {\n /** Sort key. \"CURRENT\" for current version; version history in S3. */\n sk: {\n type: \"string\",\n required: true,\n default: \"CURRENT\",\n },\n /** Tenant scope. Use \"BASELINE\" when the config is baseline default (no tenant). */\n tenantId: {\n type: \"string\",\n required: true,\n default: \"BASELINE\",\n },\n /** Workspace scope. Use \"-\" when absent. */\n workspaceId: {\n type: \"string\",\n required: true,\n default: \"-\",\n },\n /** User scope. Use \"-\" when absent. */\n userId: {\n type: \"string\",\n required: true,\n default: \"-\",\n },\n /** Role scope. Use \"-\" when absent. */\n roleId: {\n type: \"string\",\n required: true,\n default: \"-\",\n },\n /** Config type (category), e.g. endpoints, branding, display. */\n key: {\n type: \"string\",\n required: true,\n },\n /** FHIR Resource.id; logical id in URL and for the Configuration resource. */\n id: {\n type: \"string\",\n required: true,\n },\n /** Payload as JSON string. JSON.stringify(resource) on write; JSON.parse(item.resource) on read. */\n resource: {\n type: \"string\",\n required: true,\n },\n /** Version id (e.g. ULID). Tracks current version; S3 history key. */\n vid: {\n type: \"string\",\n required: true,\n },\n lastUpdated: {\n type: \"string\",\n required: true,\n },\n deleted: {\n type: \"boolean\",\n required: false,\n },\n bundleId: {\n type: \"string\",\n required: false,\n },\n msgId: {\n type: \"string\",\n required: false,\n },\n },\n indexes: {\n /** Base table: PK, SK (data store key names). PK is built from tenantId, workspaceId, userId, roleId; SK is built from key and sk. Do not supply PK or SK from outside. */\n record: {\n pk: {\n field: \"PK\",\n composite: [\"tenantId\", \"workspaceId\", \"userId\", \"roleId\"],\n template:\n \"OHI#CONFIG#TID#${tenantId}#WID#${workspaceId}#UID#${userId}#RID#${roleId}\",\n },\n sk: {\n field: \"SK\",\n composite: [\"key\", \"sk\"],\n template: \"KEY#${key}#SK#${sk}\",\n },\n },\n\n /** GSI4 — Resource Type Index: list all Configuration in a tenant or workspace (no scan). Use for \"list configs scoped to this tenant\" (workspaceId = \"-\") or \"list configs scoped to this workspace\". Does not support hierarchical resolution in one query; use base table GetItem in fallback order (user → workspace → tenant → baseline) for that. */\n gsi4: {\n index: \"GSI4\",\n condition: () => true,\n pk: {\n field: \"GSI4PK\",\n composite: [\"tenantId\", \"workspaceId\"],\n template: \"TID#${tenantId}#WID#${workspaceId}#RT#Configuration\",\n },\n sk: {\n field: \"GSI4SK\",\n composite: [\"key\", \"sk\"],\n template: \"KEY#${key}#SK#${sk}\",\n },\n },\n },\n});\n"],"mappings":"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","names":["express","envelope","SK","express","BASE_PATH","client","DynamoDBClient","Service","Entity","table","client","DynamoDBClient","entities","Service","BASE_PATH","router","express","SK","TABLE_NAME","express","router"]}
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "@typescript-eslint/parser": "^8",
12
12
  "aws-cdk-lib": "2.238.0",
13
13
  "commit-and-tag-version": "^12",
14
- "constructs": "10.4.5",
14
+ "constructs": "10.5.0",
15
15
  "copyfiles": "^2.4.1",
16
16
  "eslint": "^9",
17
17
  "eslint-config-prettier": "^10.1.8",
@@ -29,10 +29,11 @@
29
29
  },
30
30
  "peerDependencies": {
31
31
  "aws-cdk-lib": "2.238.0",
32
- "constructs": "10.4.5"
32
+ "constructs": "10.5.0"
33
33
  },
34
34
  "dependencies": {
35
35
  "@aws-sdk/client-dynamodb": "^3.993.0",
36
+ "@aws-sdk/client-ssm": "^3.993.0",
36
37
  "@codedrifters/utils": "^0.0.0",
37
38
  "@codegenie/serverless-express": "^4.17.1",
38
39
  "@types/aws-lambda": "^8.10.160",
@@ -49,7 +50,7 @@
49
50
  "publishConfig": {
50
51
  "access": "public"
51
52
  },
52
- "version": "0.0.5",
53
+ "version": "0.0.7",
53
54
  "types": "lib/index.d.ts",
54
55
  "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"npx projen\".",
55
56
  "scripts": {