@openhi/constructs 0.0.27 → 0.0.29

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.
@@ -37,7 +37,6 @@ var import_serverless_express2 = __toESM(require("@codegenie/serverless-express"
37
37
 
38
38
  // src/data/rest-api/rest-api.ts
39
39
  var import_node_path2 = __toESM(require("path"));
40
- var import_cors = __toESM(require("cors"));
41
40
  var import_express3 = __toESM(require("express"));
42
41
 
43
42
  // src/data/middleware/normalize-json-body.ts
@@ -1192,10 +1191,16 @@ router2.delete("/:id", deletePatient);
1192
1191
  var app = (0, import_express3.default)();
1193
1192
  app.set("view engine", "ejs");
1194
1193
  app.set("views", import_node_path2.default.join(__dirname, "views"));
1195
- app.use((0, import_cors.default)());
1196
1194
  app.use(import_express3.default.json());
1197
1195
  app.use(import_express3.default.urlencoded({ extended: true }));
1198
1196
  app.use(normalizeJsonBodyMiddleware);
1197
+ app.use((req, res, next) => {
1198
+ if (req.method === "OPTIONS") {
1199
+ res.status(204).end();
1200
+ return;
1201
+ }
1202
+ next();
1203
+ });
1199
1204
  app.get("/", (_req, res) => {
1200
1205
  return res.status(200).json({ message: "POC App is running" });
1201
1206
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/data/lambda/rest-api-lambda.handler.ts","../src/data/rest-api/rest-api.ts","../src/data/middleware/normalize-json-body.ts","../src/data/middleware/open-hi-context.ts","../src/data/rest-api/routes/configuration/configuration.ts","../src/data/rest-api/routes/configuration/configuration-common.ts","../src/lib/compression.ts","../src/data/dynamo/dynamo-service.ts","../src/data/dynamo/entities/configuration.ts","../src/data/dynamo/entities/patient.ts","../src/data/rest-api/routes/configuration/configuration-create.ts","../src/data/rest-api/routes/configuration/configuration-delete.ts","../src/data/rest-api/routes/configuration/configuration-get-by-key.ts","../src/data/rest-api/dynamic-configuration.ts","../src/data/rest-api/routes/configuration/configuration-list.ts","../src/data/rest-api/routes/configuration/configuration-update.ts","../src/data/rest-api/routes/patient/patient.ts","../src/data/rest-api/routes/patient/patient-common.ts","../src/data/import-patient.ts","../src/data/rest-api/routes/patient/patient-create.ts","../src/data/rest-api/routes/patient/patient-delete.ts","../src/data/rest-api/routes/patient/patient-get-by-id.ts","../src/data/rest-api/routes/patient/patient-list.ts","../src/data/rest-api/routes/patient/patient-update.ts"],"sourcesContent":["import serverlessExpress from \"@codegenie/serverless-express\";\nimport { app } from \"../rest-api/rest-api\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/lambda/rest-api-lambda.md\n */\n\nexport const handler = serverlessExpress({ app });\n","import path from \"node:path\";\nimport cors from \"cors\";\nimport express, { Express, Request, Response } from \"express\";\nimport { normalizeJsonBodyMiddleware } from \"../middleware/normalize-json-body\";\nimport { openHiContextMiddleware } from \"../middleware/open-hi-context\";\nimport { configurationRouter } from \"./routes/configuration/configuration\";\nimport { patientRouter } from \"./routes/patient/patient\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/rest-api/rest-api.md\n */\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// Normalize body when it arrives as Buffer/string (e.g. Lambda/API Gateway); all routes then see a plain object\napp.use(normalizeJsonBodyMiddleware);\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 JWT (or headers/env when implemented); attached to req for resource routes\napp.use([\"/Patient\", \"/Configuration\"], openHiContextMiddleware);\n\n// FHIR R4 Patient resource\napp.use(\"/Patient\", patientRouter);\n\n// Control Plane Configuration resource\napp.use(\"/Configuration\", configurationRouter);\n\nexport { app };\n","import type { Request, Response, NextFunction } from \"express\";\n\n/**\n * Normalize req.body so it is always a plain object when it was sent as JSON.\n * Runs after express.json(); handles Lambda/API Gateway cases where the body\n * may be attached as a Buffer or string (e.g. from event.body) instead of\n * being parsed by express.json(). Prevents handlers from spreading a Buffer\n * into resources (which would produce numeric keys from byte indices).\n */\nexport function normalizeJsonBodyMiddleware(\n req: Request,\n _res: Response,\n next: NextFunction,\n): void {\n const raw = req.body;\n if (Buffer.isBuffer(raw)) {\n try {\n req.body = JSON.parse(raw.toString(\"utf-8\")) as Request[\"body\"];\n } catch {\n // Leave body as-is; handler or validation can return 400\n }\n } else if (typeof raw === \"string\") {\n try {\n req.body = JSON.parse(raw) as Request[\"body\"];\n } catch {\n // Leave body as-is\n }\n }\n next();\n}\n","import { getCurrentInvoke } from \"@codegenie/serverless-express\";\nimport type { Request, Response, NextFunction } from \"express\";\nimport type { ApiGatewayJwtClaims } from \"./express\";\n\nconst REQUIRED_CLAIMS = [\n \"openhi_tenant_id\",\n \"openhi_workspace_id\",\n \"openhi_user_id\",\n \"openhi_user_name\",\n] as const;\n\nfunction getJwtClaims(req: Request): ApiGatewayJwtClaims | undefined {\n const invoke = getCurrentInvoke();\n const event =\n invoke?.event ??\n (req as Request & { apiGateway?: { event?: unknown } }).apiGateway?.event;\n\n const claims = (\n event as\n | {\n requestContext?: {\n authorizer?: { jwt?: { claims?: ApiGatewayJwtClaims } };\n };\n }\n | undefined\n )?.requestContext?.authorizer?.jwt?.claims;\n\n return claims;\n}\n\nfunction hasRequiredClaims(claims: ApiGatewayJwtClaims): boolean {\n return REQUIRED_CLAIMS.every(\n (key) =>\n typeof claims[key] === \"string\" && (claims[key] as string).trim() !== \"\",\n );\n}\n\n/**\n * Express middleware that sets req.openhiContext for /ehr and /ohi domain API routes.\n * Context is populated from JWT claims (openhi_tenant_id, openhi_workspace_id, openhi_user_id, openhi_user_name)\n * added by the Cognito pre-token generation Lambda. Missing required claims result in 403 Forbidden.\n *\n * @see ADR 2026-02-13-02 (JWT claim design)\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 claims = getJwtClaims(req);\n if (!claims || !hasRequiredClaims(claims)) {\n res.status(403).json({\n error: \"Forbidden\",\n message:\n \"Missing or invalid OpenHI JWT claims (tenant, workspace, or audit context).\",\n });\n return;\n }\n\n const invoke = getCurrentInvoke();\n const event = (invoke?.event ??\n (req as Request & { apiGateway?: { event?: unknown } }).apiGateway\n ?.event) as { requestContext?: { requestId?: string } } | undefined;\n const requestId =\n typeof event?.requestContext?.requestId === \"string\"\n ? event.requestContext.requestId\n : undefined;\n\n req.openhiContext = {\n tenantId: claims.openhi_tenant_id as string,\n workspaceId: claims.openhi_workspace_id as string,\n date: new Date().toISOString(),\n actorId: claims.openhi_user_id as string,\n actorName: claims.openhi_user_name as string,\n actorType: \"human\",\n roleId:\n typeof claims.openhi_role_id === \"string\" && claims.openhi_role_id !== \"\"\n ? claims.openhi_role_id\n : undefined,\n requestId,\n source: \"rest\",\n clientId:\n typeof claims.openhi_client_id === \"string\" &&\n claims.openhi_client_id !== \"\"\n ? claims.openhi_client_id\n : undefined,\n };\n\n next();\n}\n","import express from \"express\";\nimport { createConfiguration } from \"./configuration-create\";\nimport { deleteConfiguration } from \"./configuration-delete\";\nimport { getConfigurationByKey } from \"./configuration-get-by-key\";\nimport { listConfigurations } from \"./configuration-list\";\nimport { updateConfiguration } from \"./configuration-update\";\n\n/**\n * Configuration REST router: /Configuration\n * @see sites/www-docs/content/packages/@openhi/constructs/data/rest-api/routes/configuration.md\n */\nconst router: express.Router = express.Router();\n\nrouter.get(\"/\", listConfigurations);\nrouter.get(\"/:key\", getConfigurationByKey);\nrouter.post(\"/\", createConfiguration);\nrouter.put(\"/:key\", updateConfiguration);\nrouter.delete(\"/:key\", deleteConfiguration);\n\nexport { router as configurationRouter };\n","/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/rest-api/routes/configuration.md\n */\n\nexport const BASE_PATH = \"/Configuration\";\nexport const SK = \"CURRENT\";\n","import { gzipSync, gunzipSync } from \"node:zlib\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/lib/compression.md\n */\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 { Configuration } from \"./entities/configuration\";\nimport { Patient } from \"./entities/patient\";\n\n/**\n * Single ElectroDB service for the data store (EHR + OHI entities in one table).\n * @see sites/www-docs/content/architecture/dynamodb-single-table-design.md\n */\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, configuration: Configuration };\n\n/**\n * ElectroDB Service for the single-table data store. Provides access to Patient\n * (EHR/FHIR) and Configuration (OHI); use with the data store table (PK, SK, GSI1–GSI4).\n */\nexport const DynamoDataService = new Service(entities, { table, client });\n\n/**\n * Returns the data store service. Table name is resolved from tableName (optional override),\n * then DYNAMO_TABLE_NAME, then \"jesttesttable\".\n */\nexport function getDynamoDataService(\n tableName?: string,\n): typeof DynamoDataService {\n const resolved = tableName ?? table;\n return new Service(entities, { table: resolved, client });\n}\n","import { Entity } from \"electrodb\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/ohi/Configuration.md\n */\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: \"openhi\",\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","import { Entity } from \"electrodb\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/ehr/r4/Patient.md\n */\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: \"openhi\",\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 type { Request, Response } from \"express\";\nimport { BASE_PATH, SK } from \"./configuration-common\";\nimport { compressResource } from \"../../../../lib/compression\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\n\n/**\n * POST /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 actorId: ctxActorId,\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) ?? ctxActorId ?? \"-\";\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 = getDynamoDataService();\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","import type { Request, Response } from \"express\";\nimport { SK } from \"./configuration-common\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\n\n/**\n * DELETE /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, actorId, roleId: ctxRoleId } = ctx;\n const roleId = ctxRoleId ?? \"-\";\n const service = getDynamoDataService();\n\n try {\n await service.entities.configuration\n .delete({ tenantId, workspaceId, userId: actorId, 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","import type { Request, Response } from \"express\";\nimport { SK } from \"./configuration-common\";\nimport { decompressResource } from \"../../../../lib/compression\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\n\n/**\n * GET /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, actorId, roleId: ctxRoleId } = ctx;\n const roleId = ctxRoleId ?? \"-\";\n const service = getDynamoDataService();\n\n try {\n const result = await service.entities.configuration\n .get({ tenantId, workspaceId, userId: actorId, 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 * 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 /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\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/rest-api/dynamic-configuration.md\n */\n\nconst BASE_PATH = \"/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<Array<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}): Array<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 type { Request, Response } from \"express\";\nimport { BASE_PATH } from \"./configuration-common\";\nimport { decompressResource } from \"../../../../lib/compression\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\nimport { getDynamicConfigurationEntries } from \"../../dynamic-configuration\";\n\n/**\n * GET /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; actorId/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 = getDynamoDataService();\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","import type { Request, Response } from \"express\";\nimport { SK } from \"./configuration-common\";\nimport { compressResource } from \"../../../../lib/compression\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\n\n/**\n * PUT /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, actorId, date, roleId: ctxRoleId } = ctx;\n const roleId = ctxRoleId ?? \"-\";\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 = getDynamoDataService();\n\n try {\n const existing = await service.entities.configuration\n .get({ tenantId, workspaceId, userId: actorId, 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: actorId, 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","import express from \"express\";\nimport { createPatient } from \"./patient-create\";\nimport { deletePatient } from \"./patient-delete\";\nimport { getPatientById } from \"./patient-get-by-id\";\nimport { listPatients } from \"./patient-list\";\nimport { updatePatient } from \"./patient-update\";\n\n/**\n * Patient REST router: /Patient\n * @see sites/www-docs/content/packages/@openhi/constructs/data/rest-api/routes/patient.md\n */\nconst router: express.Router = express.Router();\n\nrouter.get(\"/\", listPatients);\nrouter.get(\"/:id\", getPatientById);\nrouter.post(\"/\", createPatient);\nrouter.put(\"/:id\", updatePatient);\nrouter.delete(\"/:id\", deletePatient);\n\nexport { router as patientRouter };\n","import type { Request, Response } from \"express\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/rest-api/routes/patient.md\n */\n\nexport const BASE_PATH = \"/Patient\";\nexport const SK = \"CURRENT\";\n\n/**\n * Require req.body to be a plain object (set by express.json() + normalizeJsonBodyMiddleware).\n * Returns the body for use, or sends 400 and returns the response so the handler can return.\n */\nexport function requireJsonBody(\n req: Request,\n res: Response,\n): { body: Record<string, unknown> } | { errorResponse: Response } {\n const raw = req.body;\n if (raw == null || typeof raw !== \"object\" || Array.isArray(raw)) {\n return {\n errorResponse: res.status(400).json({\n resourceType: \"OperationOutcome\",\n issue: [\n {\n severity: \"error\",\n code: \"invalid\",\n diagnostics: \"Request body must be a JSON object.\",\n },\n ],\n }),\n };\n }\n return { body: raw as Record<string, unknown> };\n}\n","import { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport type { Extension, Meta } from \"@openhi/types\";\nimport { compressResource } from \"../lib/compression\";\nimport { getDynamoDataService } from \"./dynamo/dynamo-service\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/import-patient.md\n */\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/** Meta with optional OpenHI audit extensions (created/modified by, etc.). */\nexport type MetaWithExtensions = Meta & { extension?: Extension[] };\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/** Audit extension entry shape (subset of Extension used by OpenHI audit). */\ntype AuditExtensionEntry = Pick<\n Extension,\n \"url\" | \"valueString\" | \"valueDateTime\"\n>;\n\n/** Builds meta.extension entries for audit; merges with existing meta. */\nexport function mergeAuditIntoMeta(\n meta: MetaWithExtensions | Record<string, unknown> | undefined,\n audit: AuditFields,\n): MetaWithExtensions {\n const existing = (meta ?? {}) as MetaWithExtensions;\n const ext: AuditExtensionEntry[] = [\n ...(Array.isArray(existing.extension)\n ? (existing.extension as AuditExtensionEntry[])\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?: 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?: Array<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 is passed through; data service resolves from DYNAMO_TABLE_NAME when omitted.\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 service = getDynamoDataService(options.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 type { Request, Response } from \"express\";\nimport { BASE_PATH, requireJsonBody } from \"./patient-common\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\nimport {\n patientToPutAttrs,\n type ImportPatientOptions,\n} from \"../../../import-patient\";\n\n/** POST /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 bodyResult = requireJsonBody(req, res);\n if (\"errorResponse\" in bodyResult) return bodyResult.errorResponse;\n\n const ctx = req.openhiContext!;\n const { tenantId, workspaceId, date, actorId, actorName } = ctx;\n const body = bodyResult.body;\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: actorId,\n createdByName: actorName,\n modifiedDate: date,\n modifiedById: actorId,\n modifiedByName: actorName,\n };\n const service = getDynamoDataService();\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","import type { Request, Response } from \"express\";\nimport { SK } from \"./patient-common\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\n\n/** DELETE /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 = getDynamoDataService();\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","import type { Request, Response } from \"express\";\nimport { SK } from \"./patient-common\";\nimport { decompressResource } from \"../../../../lib/compression\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\n\n/** GET /Patient/:id — read: returns a single Patient resource from the data store or 404. */\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 = getDynamoDataService();\n\n try {\n const result = await service.entities.patient\n .get({ tenantId, workspaceId, id, 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: `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","import type { Request, Response } from \"express\";\nimport { BASE_PATH } from \"./patient-common\";\nimport { decompressResource } from \"../../../../lib/compression\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\n\n/**\n * GET /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 = getDynamoDataService();\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","import type { Request, Response } from \"express\";\nimport { requireJsonBody, SK } from \"./patient-common\";\nimport {\n compressResource,\n decompressResource,\n} from \"../../../../lib/compression\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\nimport { mergeAuditIntoMeta } from \"../../../import-patient\";\n\n/** PUT /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 bodyResult = requireJsonBody(req, res);\n if (\"errorResponse\" in bodyResult) return bodyResult.errorResponse;\n\n const id = String(req.params.id);\n const ctx = req.openhiContext!;\n const { tenantId, workspaceId, date, actorId, actorName } = ctx;\n const body = bodyResult.body;\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 = getDynamoDataService();\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: actorId,\n modifiedByName: actorName,\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"],"mappings":"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","names":["import_serverless_express","import_node_path","import_express","envelope","import_electrodb","import_electrodb","BASE_PATH","client","express","import_express","BASE_PATH","SK","SK","BASE_PATH","SK","SK","BASE_PATH","SK","router","express","express","path","cors","router","serverlessExpress"]}
1
+ {"version":3,"sources":["../src/data/lambda/rest-api-lambda.handler.ts","../src/data/rest-api/rest-api.ts","../src/data/middleware/normalize-json-body.ts","../src/data/middleware/open-hi-context.ts","../src/data/rest-api/routes/configuration/configuration.ts","../src/data/rest-api/routes/configuration/configuration-common.ts","../src/lib/compression.ts","../src/data/dynamo/dynamo-service.ts","../src/data/dynamo/entities/configuration.ts","../src/data/dynamo/entities/patient.ts","../src/data/rest-api/routes/configuration/configuration-create.ts","../src/data/rest-api/routes/configuration/configuration-delete.ts","../src/data/rest-api/routes/configuration/configuration-get-by-key.ts","../src/data/rest-api/dynamic-configuration.ts","../src/data/rest-api/routes/configuration/configuration-list.ts","../src/data/rest-api/routes/configuration/configuration-update.ts","../src/data/rest-api/routes/patient/patient.ts","../src/data/rest-api/routes/patient/patient-common.ts","../src/data/import-patient.ts","../src/data/rest-api/routes/patient/patient-create.ts","../src/data/rest-api/routes/patient/patient-delete.ts","../src/data/rest-api/routes/patient/patient-get-by-id.ts","../src/data/rest-api/routes/patient/patient-list.ts","../src/data/rest-api/routes/patient/patient-update.ts"],"sourcesContent":["import serverlessExpress from \"@codegenie/serverless-express\";\nimport { app } from \"../rest-api/rest-api\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/lambda/rest-api-lambda.md\n */\n\nexport const handler = serverlessExpress({ app });\n","import path from \"node:path\";\nimport express, { Express, NextFunction, Request, Response } from \"express\";\nimport { normalizeJsonBodyMiddleware } from \"../middleware/normalize-json-body\";\nimport { openHiContextMiddleware } from \"../middleware/open-hi-context\";\nimport { configurationRouter } from \"./routes/configuration/configuration\";\nimport { patientRouter } from \"./routes/patient/patient\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/rest-api/rest-api.md\n */\n\nconst app: Express = express();\n\napp.set(\"view engine\", \"ejs\");\napp.set(\"views\", path.join(__dirname, \"views\"));\n\n// CORS is handled exclusively by API Gateway when the REST API is deployed with cors.allowOrigins\n// (see OpenHiRestApiService). Configure CORS there; do not add Express CORS middleware here.\napp.use(express.json());\napp.use(express.urlencoded({ extended: true }));\n// Normalize body when it arrives as Buffer/string (e.g. Lambda/API Gateway); all routes then see a plain object\napp.use(normalizeJsonBodyMiddleware);\n\n// Respond to CORS preflight (OPTIONS) before auth so preflight succeeds without JWT (see #692).\napp.use((req: Request, res: Response, next: NextFunction) => {\n if (req.method === \"OPTIONS\") {\n res.status(204).end();\n return;\n }\n next();\n});\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 JWT (or headers/env when implemented); attached to req for resource routes\napp.use([\"/Patient\", \"/Configuration\"], openHiContextMiddleware);\n\n// FHIR R4 Patient resource\napp.use(\"/Patient\", patientRouter);\n\n// Control Plane Configuration resource\napp.use(\"/Configuration\", configurationRouter);\n\nexport { app };\n","import type { Request, Response, NextFunction } from \"express\";\n\n/**\n * Normalize req.body so it is always a plain object when it was sent as JSON.\n * Runs after express.json(); handles Lambda/API Gateway cases where the body\n * may be attached as a Buffer or string (e.g. from event.body) instead of\n * being parsed by express.json(). Prevents handlers from spreading a Buffer\n * into resources (which would produce numeric keys from byte indices).\n */\nexport function normalizeJsonBodyMiddleware(\n req: Request,\n _res: Response,\n next: NextFunction,\n): void {\n const raw = req.body;\n if (Buffer.isBuffer(raw)) {\n try {\n req.body = JSON.parse(raw.toString(\"utf-8\")) as Request[\"body\"];\n } catch {\n // Leave body as-is; handler or validation can return 400\n }\n } else if (typeof raw === \"string\") {\n try {\n req.body = JSON.parse(raw) as Request[\"body\"];\n } catch {\n // Leave body as-is\n }\n }\n next();\n}\n","import { getCurrentInvoke } from \"@codegenie/serverless-express\";\nimport type { Request, Response, NextFunction } from \"express\";\nimport type { ApiGatewayJwtClaims } from \"./express\";\n\nconst REQUIRED_CLAIMS = [\n \"openhi_tenant_id\",\n \"openhi_workspace_id\",\n \"openhi_user_id\",\n \"openhi_user_name\",\n] as const;\n\nfunction getJwtClaims(req: Request): ApiGatewayJwtClaims | undefined {\n const invoke = getCurrentInvoke();\n const event =\n invoke?.event ??\n (req as Request & { apiGateway?: { event?: unknown } }).apiGateway?.event;\n\n const claims = (\n event as\n | {\n requestContext?: {\n authorizer?: { jwt?: { claims?: ApiGatewayJwtClaims } };\n };\n }\n | undefined\n )?.requestContext?.authorizer?.jwt?.claims;\n\n return claims;\n}\n\nfunction hasRequiredClaims(claims: ApiGatewayJwtClaims): boolean {\n return REQUIRED_CLAIMS.every(\n (key) =>\n typeof claims[key] === \"string\" && (claims[key] as string).trim() !== \"\",\n );\n}\n\n/**\n * Express middleware that sets req.openhiContext for /ehr and /ohi domain API routes.\n * Context is populated from JWT claims (openhi_tenant_id, openhi_workspace_id, openhi_user_id, openhi_user_name)\n * added by the Cognito pre-token generation Lambda. Missing required claims result in 403 Forbidden.\n *\n * @see ADR 2026-02-13-02 (JWT claim design)\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 claims = getJwtClaims(req);\n if (!claims || !hasRequiredClaims(claims)) {\n res.status(403).json({\n error: \"Forbidden\",\n message:\n \"Missing or invalid OpenHI JWT claims (tenant, workspace, or audit context).\",\n });\n return;\n }\n\n const invoke = getCurrentInvoke();\n const event = (invoke?.event ??\n (req as Request & { apiGateway?: { event?: unknown } }).apiGateway\n ?.event) as { requestContext?: { requestId?: string } } | undefined;\n const requestId =\n typeof event?.requestContext?.requestId === \"string\"\n ? event.requestContext.requestId\n : undefined;\n\n req.openhiContext = {\n tenantId: claims.openhi_tenant_id as string,\n workspaceId: claims.openhi_workspace_id as string,\n date: new Date().toISOString(),\n actorId: claims.openhi_user_id as string,\n actorName: claims.openhi_user_name as string,\n actorType: \"human\",\n roleId:\n typeof claims.openhi_role_id === \"string\" && claims.openhi_role_id !== \"\"\n ? claims.openhi_role_id\n : undefined,\n requestId,\n source: \"rest\",\n clientId:\n typeof claims.openhi_client_id === \"string\" &&\n claims.openhi_client_id !== \"\"\n ? claims.openhi_client_id\n : undefined,\n };\n\n next();\n}\n","import express from \"express\";\nimport { createConfiguration } from \"./configuration-create\";\nimport { deleteConfiguration } from \"./configuration-delete\";\nimport { getConfigurationByKey } from \"./configuration-get-by-key\";\nimport { listConfigurations } from \"./configuration-list\";\nimport { updateConfiguration } from \"./configuration-update\";\n\n/**\n * Configuration REST router: /Configuration\n * @see sites/www-docs/content/packages/@openhi/constructs/data/rest-api/routes/configuration.md\n */\nconst router: express.Router = express.Router();\n\nrouter.get(\"/\", listConfigurations);\nrouter.get(\"/:key\", getConfigurationByKey);\nrouter.post(\"/\", createConfiguration);\nrouter.put(\"/:key\", updateConfiguration);\nrouter.delete(\"/:key\", deleteConfiguration);\n\nexport { router as configurationRouter };\n","/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/rest-api/routes/configuration.md\n */\n\nexport const BASE_PATH = \"/Configuration\";\nexport const SK = \"CURRENT\";\n","import { gzipSync, gunzipSync } from \"node:zlib\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/lib/compression.md\n */\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 { Configuration } from \"./entities/configuration\";\nimport { Patient } from \"./entities/patient\";\n\n/**\n * Single ElectroDB service for the data store (EHR + OHI entities in one table).\n * @see sites/www-docs/content/architecture/dynamodb-single-table-design.md\n */\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, configuration: Configuration };\n\n/**\n * ElectroDB Service for the single-table data store. Provides access to Patient\n * (EHR/FHIR) and Configuration (OHI); use with the data store table (PK, SK, GSI1–GSI4).\n */\nexport const DynamoDataService = new Service(entities, { table, client });\n\n/**\n * Returns the data store service. Table name is resolved from tableName (optional override),\n * then DYNAMO_TABLE_NAME, then \"jesttesttable\".\n */\nexport function getDynamoDataService(\n tableName?: string,\n): typeof DynamoDataService {\n const resolved = tableName ?? table;\n return new Service(entities, { table: resolved, client });\n}\n","import { Entity } from \"electrodb\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/ohi/Configuration.md\n */\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: \"openhi\",\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","import { Entity } from \"electrodb\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/ehr/r4/Patient.md\n */\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: \"openhi\",\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 type { Request, Response } from \"express\";\nimport { BASE_PATH, SK } from \"./configuration-common\";\nimport { compressResource } from \"../../../../lib/compression\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\n\n/**\n * POST /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 actorId: ctxActorId,\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) ?? ctxActorId ?? \"-\";\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 = getDynamoDataService();\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","import type { Request, Response } from \"express\";\nimport { SK } from \"./configuration-common\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\n\n/**\n * DELETE /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, actorId, roleId: ctxRoleId } = ctx;\n const roleId = ctxRoleId ?? \"-\";\n const service = getDynamoDataService();\n\n try {\n await service.entities.configuration\n .delete({ tenantId, workspaceId, userId: actorId, 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","import type { Request, Response } from \"express\";\nimport { SK } from \"./configuration-common\";\nimport { decompressResource } from \"../../../../lib/compression\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\n\n/**\n * GET /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, actorId, roleId: ctxRoleId } = ctx;\n const roleId = ctxRoleId ?? \"-\";\n const service = getDynamoDataService();\n\n try {\n const result = await service.entities.configuration\n .get({ tenantId, workspaceId, userId: actorId, 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 * 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 /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\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/rest-api/dynamic-configuration.md\n */\n\nconst BASE_PATH = \"/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<Array<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}): Array<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 type { Request, Response } from \"express\";\nimport { BASE_PATH } from \"./configuration-common\";\nimport { decompressResource } from \"../../../../lib/compression\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\nimport { getDynamicConfigurationEntries } from \"../../dynamic-configuration\";\n\n/**\n * GET /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; actorId/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 = getDynamoDataService();\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","import type { Request, Response } from \"express\";\nimport { SK } from \"./configuration-common\";\nimport { compressResource } from \"../../../../lib/compression\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\n\n/**\n * PUT /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, actorId, date, roleId: ctxRoleId } = ctx;\n const roleId = ctxRoleId ?? \"-\";\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 = getDynamoDataService();\n\n try {\n const existing = await service.entities.configuration\n .get({ tenantId, workspaceId, userId: actorId, 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: actorId, 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","import express from \"express\";\nimport { createPatient } from \"./patient-create\";\nimport { deletePatient } from \"./patient-delete\";\nimport { getPatientById } from \"./patient-get-by-id\";\nimport { listPatients } from \"./patient-list\";\nimport { updatePatient } from \"./patient-update\";\n\n/**\n * Patient REST router: /Patient\n * @see sites/www-docs/content/packages/@openhi/constructs/data/rest-api/routes/patient.md\n */\nconst router: express.Router = express.Router();\n\nrouter.get(\"/\", listPatients);\nrouter.get(\"/:id\", getPatientById);\nrouter.post(\"/\", createPatient);\nrouter.put(\"/:id\", updatePatient);\nrouter.delete(\"/:id\", deletePatient);\n\nexport { router as patientRouter };\n","import type { Request, Response } from \"express\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/rest-api/routes/patient.md\n */\n\nexport const BASE_PATH = \"/Patient\";\nexport const SK = \"CURRENT\";\n\n/**\n * Require req.body to be a plain object (set by express.json() + normalizeJsonBodyMiddleware).\n * Returns the body for use, or sends 400 and returns the response so the handler can return.\n */\nexport function requireJsonBody(\n req: Request,\n res: Response,\n): { body: Record<string, unknown> } | { errorResponse: Response } {\n const raw = req.body;\n if (raw == null || typeof raw !== \"object\" || Array.isArray(raw)) {\n return {\n errorResponse: res.status(400).json({\n resourceType: \"OperationOutcome\",\n issue: [\n {\n severity: \"error\",\n code: \"invalid\",\n diagnostics: \"Request body must be a JSON object.\",\n },\n ],\n }),\n };\n }\n return { body: raw as Record<string, unknown> };\n}\n","import { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport type { Extension, Meta } from \"@openhi/types\";\nimport { compressResource } from \"../lib/compression\";\nimport { getDynamoDataService } from \"./dynamo/dynamo-service\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/import-patient.md\n */\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/** Meta with optional OpenHI audit extensions (created/modified by, etc.). */\nexport type MetaWithExtensions = Meta & { extension?: Extension[] };\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/** Audit extension entry shape (subset of Extension used by OpenHI audit). */\ntype AuditExtensionEntry = Pick<\n Extension,\n \"url\" | \"valueString\" | \"valueDateTime\"\n>;\n\n/** Builds meta.extension entries for audit; merges with existing meta. */\nexport function mergeAuditIntoMeta(\n meta: MetaWithExtensions | Record<string, unknown> | undefined,\n audit: AuditFields,\n): MetaWithExtensions {\n const existing = (meta ?? {}) as MetaWithExtensions;\n const ext: AuditExtensionEntry[] = [\n ...(Array.isArray(existing.extension)\n ? (existing.extension as AuditExtensionEntry[])\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?: 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?: Array<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 is passed through; data service resolves from DYNAMO_TABLE_NAME when omitted.\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 service = getDynamoDataService(options.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 type { Request, Response } from \"express\";\nimport { BASE_PATH, requireJsonBody } from \"./patient-common\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\nimport {\n patientToPutAttrs,\n type ImportPatientOptions,\n} from \"../../../import-patient\";\n\n/** POST /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 bodyResult = requireJsonBody(req, res);\n if (\"errorResponse\" in bodyResult) return bodyResult.errorResponse;\n\n const ctx = req.openhiContext!;\n const { tenantId, workspaceId, date, actorId, actorName } = ctx;\n const body = bodyResult.body;\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: actorId,\n createdByName: actorName,\n modifiedDate: date,\n modifiedById: actorId,\n modifiedByName: actorName,\n };\n const service = getDynamoDataService();\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","import type { Request, Response } from \"express\";\nimport { SK } from \"./patient-common\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\n\n/** DELETE /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 = getDynamoDataService();\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","import type { Request, Response } from \"express\";\nimport { SK } from \"./patient-common\";\nimport { decompressResource } from \"../../../../lib/compression\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\n\n/** GET /Patient/:id — read: returns a single Patient resource from the data store or 404. */\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 = getDynamoDataService();\n\n try {\n const result = await service.entities.patient\n .get({ tenantId, workspaceId, id, 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: `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","import type { Request, Response } from \"express\";\nimport { BASE_PATH } from \"./patient-common\";\nimport { decompressResource } from \"../../../../lib/compression\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\n\n/**\n * GET /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 = getDynamoDataService();\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","import type { Request, Response } from \"express\";\nimport { requireJsonBody, SK } from \"./patient-common\";\nimport {\n compressResource,\n decompressResource,\n} from \"../../../../lib/compression\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\nimport { mergeAuditIntoMeta } from \"../../../import-patient\";\n\n/** PUT /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 bodyResult = requireJsonBody(req, res);\n if (\"errorResponse\" in bodyResult) return bodyResult.errorResponse;\n\n const id = String(req.params.id);\n const ctx = req.openhiContext!;\n const { tenantId, workspaceId, date, actorId, actorName } = ctx;\n const body = bodyResult.body;\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 = getDynamoDataService();\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: actorId,\n modifiedByName: actorName,\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"],"mappings":"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","names":["import_serverless_express","import_node_path","import_express","envelope","import_electrodb","import_electrodb","BASE_PATH","client","express","import_express","BASE_PATH","SK","SK","BASE_PATH","SK","SK","BASE_PATH","SK","router","express","express","path","router","serverlessExpress"]}
@@ -7,7 +7,6 @@ import serverlessExpress from "@codegenie/serverless-express";
7
7
 
8
8
  // src/data/rest-api/rest-api.ts
9
9
  import path from "path";
10
- import cors from "cors";
11
10
  import express3 from "express";
12
11
 
13
12
  // src/data/middleware/normalize-json-body.ts
@@ -1166,10 +1165,16 @@ router2.delete("/:id", deletePatient);
1166
1165
  var app = express3();
1167
1166
  app.set("view engine", "ejs");
1168
1167
  app.set("views", path.join(__dirname, "views"));
1169
- app.use(cors());
1170
1168
  app.use(express3.json());
1171
1169
  app.use(express3.urlencoded({ extended: true }));
1172
1170
  app.use(normalizeJsonBodyMiddleware);
1171
+ app.use((req, res, next) => {
1172
+ if (req.method === "OPTIONS") {
1173
+ res.status(204).end();
1174
+ return;
1175
+ }
1176
+ next();
1177
+ });
1173
1178
  app.get("/", (_req, res) => {
1174
1179
  return res.status(200).json({ message: "POC App is running" });
1175
1180
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/data/lambda/rest-api-lambda.handler.ts","../src/data/rest-api/rest-api.ts","../src/data/middleware/normalize-json-body.ts","../src/data/middleware/open-hi-context.ts","../src/data/rest-api/routes/configuration/configuration.ts","../src/data/rest-api/routes/configuration/configuration-common.ts","../src/lib/compression.ts","../src/data/dynamo/dynamo-service.ts","../src/data/dynamo/entities/configuration.ts","../src/data/dynamo/entities/patient.ts","../src/data/rest-api/routes/configuration/configuration-create.ts","../src/data/rest-api/routes/configuration/configuration-delete.ts","../src/data/rest-api/routes/configuration/configuration-get-by-key.ts","../src/data/rest-api/dynamic-configuration.ts","../src/data/rest-api/routes/configuration/configuration-list.ts","../src/data/rest-api/routes/configuration/configuration-update.ts","../src/data/rest-api/routes/patient/patient.ts","../src/data/rest-api/routes/patient/patient-common.ts","../src/data/import-patient.ts","../src/data/rest-api/routes/patient/patient-create.ts","../src/data/rest-api/routes/patient/patient-delete.ts","../src/data/rest-api/routes/patient/patient-get-by-id.ts","../src/data/rest-api/routes/patient/patient-list.ts","../src/data/rest-api/routes/patient/patient-update.ts"],"sourcesContent":["import serverlessExpress from \"@codegenie/serverless-express\";\nimport { app } from \"../rest-api/rest-api\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/lambda/rest-api-lambda.md\n */\n\nexport const handler = serverlessExpress({ app });\n","import path from \"node:path\";\nimport cors from \"cors\";\nimport express, { Express, Request, Response } from \"express\";\nimport { normalizeJsonBodyMiddleware } from \"../middleware/normalize-json-body\";\nimport { openHiContextMiddleware } from \"../middleware/open-hi-context\";\nimport { configurationRouter } from \"./routes/configuration/configuration\";\nimport { patientRouter } from \"./routes/patient/patient\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/rest-api/rest-api.md\n */\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// Normalize body when it arrives as Buffer/string (e.g. Lambda/API Gateway); all routes then see a plain object\napp.use(normalizeJsonBodyMiddleware);\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 JWT (or headers/env when implemented); attached to req for resource routes\napp.use([\"/Patient\", \"/Configuration\"], openHiContextMiddleware);\n\n// FHIR R4 Patient resource\napp.use(\"/Patient\", patientRouter);\n\n// Control Plane Configuration resource\napp.use(\"/Configuration\", configurationRouter);\n\nexport { app };\n","import type { Request, Response, NextFunction } from \"express\";\n\n/**\n * Normalize req.body so it is always a plain object when it was sent as JSON.\n * Runs after express.json(); handles Lambda/API Gateway cases where the body\n * may be attached as a Buffer or string (e.g. from event.body) instead of\n * being parsed by express.json(). Prevents handlers from spreading a Buffer\n * into resources (which would produce numeric keys from byte indices).\n */\nexport function normalizeJsonBodyMiddleware(\n req: Request,\n _res: Response,\n next: NextFunction,\n): void {\n const raw = req.body;\n if (Buffer.isBuffer(raw)) {\n try {\n req.body = JSON.parse(raw.toString(\"utf-8\")) as Request[\"body\"];\n } catch {\n // Leave body as-is; handler or validation can return 400\n }\n } else if (typeof raw === \"string\") {\n try {\n req.body = JSON.parse(raw) as Request[\"body\"];\n } catch {\n // Leave body as-is\n }\n }\n next();\n}\n","import { getCurrentInvoke } from \"@codegenie/serverless-express\";\nimport type { Request, Response, NextFunction } from \"express\";\nimport type { ApiGatewayJwtClaims } from \"./express\";\n\nconst REQUIRED_CLAIMS = [\n \"openhi_tenant_id\",\n \"openhi_workspace_id\",\n \"openhi_user_id\",\n \"openhi_user_name\",\n] as const;\n\nfunction getJwtClaims(req: Request): ApiGatewayJwtClaims | undefined {\n const invoke = getCurrentInvoke();\n const event =\n invoke?.event ??\n (req as Request & { apiGateway?: { event?: unknown } }).apiGateway?.event;\n\n const claims = (\n event as\n | {\n requestContext?: {\n authorizer?: { jwt?: { claims?: ApiGatewayJwtClaims } };\n };\n }\n | undefined\n )?.requestContext?.authorizer?.jwt?.claims;\n\n return claims;\n}\n\nfunction hasRequiredClaims(claims: ApiGatewayJwtClaims): boolean {\n return REQUIRED_CLAIMS.every(\n (key) =>\n typeof claims[key] === \"string\" && (claims[key] as string).trim() !== \"\",\n );\n}\n\n/**\n * Express middleware that sets req.openhiContext for /ehr and /ohi domain API routes.\n * Context is populated from JWT claims (openhi_tenant_id, openhi_workspace_id, openhi_user_id, openhi_user_name)\n * added by the Cognito pre-token generation Lambda. Missing required claims result in 403 Forbidden.\n *\n * @see ADR 2026-02-13-02 (JWT claim design)\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 claims = getJwtClaims(req);\n if (!claims || !hasRequiredClaims(claims)) {\n res.status(403).json({\n error: \"Forbidden\",\n message:\n \"Missing or invalid OpenHI JWT claims (tenant, workspace, or audit context).\",\n });\n return;\n }\n\n const invoke = getCurrentInvoke();\n const event = (invoke?.event ??\n (req as Request & { apiGateway?: { event?: unknown } }).apiGateway\n ?.event) as { requestContext?: { requestId?: string } } | undefined;\n const requestId =\n typeof event?.requestContext?.requestId === \"string\"\n ? event.requestContext.requestId\n : undefined;\n\n req.openhiContext = {\n tenantId: claims.openhi_tenant_id as string,\n workspaceId: claims.openhi_workspace_id as string,\n date: new Date().toISOString(),\n actorId: claims.openhi_user_id as string,\n actorName: claims.openhi_user_name as string,\n actorType: \"human\",\n roleId:\n typeof claims.openhi_role_id === \"string\" && claims.openhi_role_id !== \"\"\n ? claims.openhi_role_id\n : undefined,\n requestId,\n source: \"rest\",\n clientId:\n typeof claims.openhi_client_id === \"string\" &&\n claims.openhi_client_id !== \"\"\n ? claims.openhi_client_id\n : undefined,\n };\n\n next();\n}\n","import express from \"express\";\nimport { createConfiguration } from \"./configuration-create\";\nimport { deleteConfiguration } from \"./configuration-delete\";\nimport { getConfigurationByKey } from \"./configuration-get-by-key\";\nimport { listConfigurations } from \"./configuration-list\";\nimport { updateConfiguration } from \"./configuration-update\";\n\n/**\n * Configuration REST router: /Configuration\n * @see sites/www-docs/content/packages/@openhi/constructs/data/rest-api/routes/configuration.md\n */\nconst router: express.Router = express.Router();\n\nrouter.get(\"/\", listConfigurations);\nrouter.get(\"/:key\", getConfigurationByKey);\nrouter.post(\"/\", createConfiguration);\nrouter.put(\"/:key\", updateConfiguration);\nrouter.delete(\"/:key\", deleteConfiguration);\n\nexport { router as configurationRouter };\n","/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/rest-api/routes/configuration.md\n */\n\nexport const BASE_PATH = \"/Configuration\";\nexport const SK = \"CURRENT\";\n","import { gzipSync, gunzipSync } from \"node:zlib\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/lib/compression.md\n */\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 { Configuration } from \"./entities/configuration\";\nimport { Patient } from \"./entities/patient\";\n\n/**\n * Single ElectroDB service for the data store (EHR + OHI entities in one table).\n * @see sites/www-docs/content/architecture/dynamodb-single-table-design.md\n */\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, configuration: Configuration };\n\n/**\n * ElectroDB Service for the single-table data store. Provides access to Patient\n * (EHR/FHIR) and Configuration (OHI); use with the data store table (PK, SK, GSI1–GSI4).\n */\nexport const DynamoDataService = new Service(entities, { table, client });\n\n/**\n * Returns the data store service. Table name is resolved from tableName (optional override),\n * then DYNAMO_TABLE_NAME, then \"jesttesttable\".\n */\nexport function getDynamoDataService(\n tableName?: string,\n): typeof DynamoDataService {\n const resolved = tableName ?? table;\n return new Service(entities, { table: resolved, client });\n}\n","import { Entity } from \"electrodb\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/ohi/Configuration.md\n */\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: \"openhi\",\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","import { Entity } from \"electrodb\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/ehr/r4/Patient.md\n */\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: \"openhi\",\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 type { Request, Response } from \"express\";\nimport { BASE_PATH, SK } from \"./configuration-common\";\nimport { compressResource } from \"../../../../lib/compression\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\n\n/**\n * POST /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 actorId: ctxActorId,\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) ?? ctxActorId ?? \"-\";\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 = getDynamoDataService();\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","import type { Request, Response } from \"express\";\nimport { SK } from \"./configuration-common\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\n\n/**\n * DELETE /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, actorId, roleId: ctxRoleId } = ctx;\n const roleId = ctxRoleId ?? \"-\";\n const service = getDynamoDataService();\n\n try {\n await service.entities.configuration\n .delete({ tenantId, workspaceId, userId: actorId, 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","import type { Request, Response } from \"express\";\nimport { SK } from \"./configuration-common\";\nimport { decompressResource } from \"../../../../lib/compression\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\n\n/**\n * GET /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, actorId, roleId: ctxRoleId } = ctx;\n const roleId = ctxRoleId ?? \"-\";\n const service = getDynamoDataService();\n\n try {\n const result = await service.entities.configuration\n .get({ tenantId, workspaceId, userId: actorId, 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 * 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 /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\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/rest-api/dynamic-configuration.md\n */\n\nconst BASE_PATH = \"/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<Array<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}): Array<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 type { Request, Response } from \"express\";\nimport { BASE_PATH } from \"./configuration-common\";\nimport { decompressResource } from \"../../../../lib/compression\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\nimport { getDynamicConfigurationEntries } from \"../../dynamic-configuration\";\n\n/**\n * GET /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; actorId/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 = getDynamoDataService();\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","import type { Request, Response } from \"express\";\nimport { SK } from \"./configuration-common\";\nimport { compressResource } from \"../../../../lib/compression\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\n\n/**\n * PUT /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, actorId, date, roleId: ctxRoleId } = ctx;\n const roleId = ctxRoleId ?? \"-\";\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 = getDynamoDataService();\n\n try {\n const existing = await service.entities.configuration\n .get({ tenantId, workspaceId, userId: actorId, 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: actorId, 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","import express from \"express\";\nimport { createPatient } from \"./patient-create\";\nimport { deletePatient } from \"./patient-delete\";\nimport { getPatientById } from \"./patient-get-by-id\";\nimport { listPatients } from \"./patient-list\";\nimport { updatePatient } from \"./patient-update\";\n\n/**\n * Patient REST router: /Patient\n * @see sites/www-docs/content/packages/@openhi/constructs/data/rest-api/routes/patient.md\n */\nconst router: express.Router = express.Router();\n\nrouter.get(\"/\", listPatients);\nrouter.get(\"/:id\", getPatientById);\nrouter.post(\"/\", createPatient);\nrouter.put(\"/:id\", updatePatient);\nrouter.delete(\"/:id\", deletePatient);\n\nexport { router as patientRouter };\n","import type { Request, Response } from \"express\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/rest-api/routes/patient.md\n */\n\nexport const BASE_PATH = \"/Patient\";\nexport const SK = \"CURRENT\";\n\n/**\n * Require req.body to be a plain object (set by express.json() + normalizeJsonBodyMiddleware).\n * Returns the body for use, or sends 400 and returns the response so the handler can return.\n */\nexport function requireJsonBody(\n req: Request,\n res: Response,\n): { body: Record<string, unknown> } | { errorResponse: Response } {\n const raw = req.body;\n if (raw == null || typeof raw !== \"object\" || Array.isArray(raw)) {\n return {\n errorResponse: res.status(400).json({\n resourceType: \"OperationOutcome\",\n issue: [\n {\n severity: \"error\",\n code: \"invalid\",\n diagnostics: \"Request body must be a JSON object.\",\n },\n ],\n }),\n };\n }\n return { body: raw as Record<string, unknown> };\n}\n","import { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport type { Extension, Meta } from \"@openhi/types\";\nimport { compressResource } from \"../lib/compression\";\nimport { getDynamoDataService } from \"./dynamo/dynamo-service\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/import-patient.md\n */\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/** Meta with optional OpenHI audit extensions (created/modified by, etc.). */\nexport type MetaWithExtensions = Meta & { extension?: Extension[] };\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/** Audit extension entry shape (subset of Extension used by OpenHI audit). */\ntype AuditExtensionEntry = Pick<\n Extension,\n \"url\" | \"valueString\" | \"valueDateTime\"\n>;\n\n/** Builds meta.extension entries for audit; merges with existing meta. */\nexport function mergeAuditIntoMeta(\n meta: MetaWithExtensions | Record<string, unknown> | undefined,\n audit: AuditFields,\n): MetaWithExtensions {\n const existing = (meta ?? {}) as MetaWithExtensions;\n const ext: AuditExtensionEntry[] = [\n ...(Array.isArray(existing.extension)\n ? (existing.extension as AuditExtensionEntry[])\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?: 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?: Array<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 is passed through; data service resolves from DYNAMO_TABLE_NAME when omitted.\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 service = getDynamoDataService(options.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 type { Request, Response } from \"express\";\nimport { BASE_PATH, requireJsonBody } from \"./patient-common\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\nimport {\n patientToPutAttrs,\n type ImportPatientOptions,\n} from \"../../../import-patient\";\n\n/** POST /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 bodyResult = requireJsonBody(req, res);\n if (\"errorResponse\" in bodyResult) return bodyResult.errorResponse;\n\n const ctx = req.openhiContext!;\n const { tenantId, workspaceId, date, actorId, actorName } = ctx;\n const body = bodyResult.body;\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: actorId,\n createdByName: actorName,\n modifiedDate: date,\n modifiedById: actorId,\n modifiedByName: actorName,\n };\n const service = getDynamoDataService();\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","import type { Request, Response } from \"express\";\nimport { SK } from \"./patient-common\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\n\n/** DELETE /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 = getDynamoDataService();\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","import type { Request, Response } from \"express\";\nimport { SK } from \"./patient-common\";\nimport { decompressResource } from \"../../../../lib/compression\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\n\n/** GET /Patient/:id — read: returns a single Patient resource from the data store or 404. */\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 = getDynamoDataService();\n\n try {\n const result = await service.entities.patient\n .get({ tenantId, workspaceId, id, 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: `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","import type { Request, Response } from \"express\";\nimport { BASE_PATH } from \"./patient-common\";\nimport { decompressResource } from \"../../../../lib/compression\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\n\n/**\n * GET /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 = getDynamoDataService();\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","import type { Request, Response } from \"express\";\nimport { requireJsonBody, SK } from \"./patient-common\";\nimport {\n compressResource,\n decompressResource,\n} from \"../../../../lib/compression\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\nimport { mergeAuditIntoMeta } from \"../../../import-patient\";\n\n/** PUT /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 bodyResult = requireJsonBody(req, res);\n if (\"errorResponse\" in bodyResult) return bodyResult.errorResponse;\n\n const id = String(req.params.id);\n const ctx = req.openhiContext!;\n const { tenantId, workspaceId, date, actorId, actorName } = ctx;\n const body = bodyResult.body;\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 = getDynamoDataService();\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: actorId,\n modifiedByName: actorName,\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"],"mappings":"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","names":["express","envelope","Entity","BASE_PATH","client","express","BASE_PATH","SK","SK","BASE_PATH","SK","SK","BASE_PATH","SK","router","express","express","router"]}
1
+ {"version":3,"sources":["../src/data/lambda/rest-api-lambda.handler.ts","../src/data/rest-api/rest-api.ts","../src/data/middleware/normalize-json-body.ts","../src/data/middleware/open-hi-context.ts","../src/data/rest-api/routes/configuration/configuration.ts","../src/data/rest-api/routes/configuration/configuration-common.ts","../src/lib/compression.ts","../src/data/dynamo/dynamo-service.ts","../src/data/dynamo/entities/configuration.ts","../src/data/dynamo/entities/patient.ts","../src/data/rest-api/routes/configuration/configuration-create.ts","../src/data/rest-api/routes/configuration/configuration-delete.ts","../src/data/rest-api/routes/configuration/configuration-get-by-key.ts","../src/data/rest-api/dynamic-configuration.ts","../src/data/rest-api/routes/configuration/configuration-list.ts","../src/data/rest-api/routes/configuration/configuration-update.ts","../src/data/rest-api/routes/patient/patient.ts","../src/data/rest-api/routes/patient/patient-common.ts","../src/data/import-patient.ts","../src/data/rest-api/routes/patient/patient-create.ts","../src/data/rest-api/routes/patient/patient-delete.ts","../src/data/rest-api/routes/patient/patient-get-by-id.ts","../src/data/rest-api/routes/patient/patient-list.ts","../src/data/rest-api/routes/patient/patient-update.ts"],"sourcesContent":["import serverlessExpress from \"@codegenie/serverless-express\";\nimport { app } from \"../rest-api/rest-api\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/lambda/rest-api-lambda.md\n */\n\nexport const handler = serverlessExpress({ app });\n","import path from \"node:path\";\nimport express, { Express, NextFunction, Request, Response } from \"express\";\nimport { normalizeJsonBodyMiddleware } from \"../middleware/normalize-json-body\";\nimport { openHiContextMiddleware } from \"../middleware/open-hi-context\";\nimport { configurationRouter } from \"./routes/configuration/configuration\";\nimport { patientRouter } from \"./routes/patient/patient\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/rest-api/rest-api.md\n */\n\nconst app: Express = express();\n\napp.set(\"view engine\", \"ejs\");\napp.set(\"views\", path.join(__dirname, \"views\"));\n\n// CORS is handled exclusively by API Gateway when the REST API is deployed with cors.allowOrigins\n// (see OpenHiRestApiService). Configure CORS there; do not add Express CORS middleware here.\napp.use(express.json());\napp.use(express.urlencoded({ extended: true }));\n// Normalize body when it arrives as Buffer/string (e.g. Lambda/API Gateway); all routes then see a plain object\napp.use(normalizeJsonBodyMiddleware);\n\n// Respond to CORS preflight (OPTIONS) before auth so preflight succeeds without JWT (see #692).\napp.use((req: Request, res: Response, next: NextFunction) => {\n if (req.method === \"OPTIONS\") {\n res.status(204).end();\n return;\n }\n next();\n});\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 JWT (or headers/env when implemented); attached to req for resource routes\napp.use([\"/Patient\", \"/Configuration\"], openHiContextMiddleware);\n\n// FHIR R4 Patient resource\napp.use(\"/Patient\", patientRouter);\n\n// Control Plane Configuration resource\napp.use(\"/Configuration\", configurationRouter);\n\nexport { app };\n","import type { Request, Response, NextFunction } from \"express\";\n\n/**\n * Normalize req.body so it is always a plain object when it was sent as JSON.\n * Runs after express.json(); handles Lambda/API Gateway cases where the body\n * may be attached as a Buffer or string (e.g. from event.body) instead of\n * being parsed by express.json(). Prevents handlers from spreading a Buffer\n * into resources (which would produce numeric keys from byte indices).\n */\nexport function normalizeJsonBodyMiddleware(\n req: Request,\n _res: Response,\n next: NextFunction,\n): void {\n const raw = req.body;\n if (Buffer.isBuffer(raw)) {\n try {\n req.body = JSON.parse(raw.toString(\"utf-8\")) as Request[\"body\"];\n } catch {\n // Leave body as-is; handler or validation can return 400\n }\n } else if (typeof raw === \"string\") {\n try {\n req.body = JSON.parse(raw) as Request[\"body\"];\n } catch {\n // Leave body as-is\n }\n }\n next();\n}\n","import { getCurrentInvoke } from \"@codegenie/serverless-express\";\nimport type { Request, Response, NextFunction } from \"express\";\nimport type { ApiGatewayJwtClaims } from \"./express\";\n\nconst REQUIRED_CLAIMS = [\n \"openhi_tenant_id\",\n \"openhi_workspace_id\",\n \"openhi_user_id\",\n \"openhi_user_name\",\n] as const;\n\nfunction getJwtClaims(req: Request): ApiGatewayJwtClaims | undefined {\n const invoke = getCurrentInvoke();\n const event =\n invoke?.event ??\n (req as Request & { apiGateway?: { event?: unknown } }).apiGateway?.event;\n\n const claims = (\n event as\n | {\n requestContext?: {\n authorizer?: { jwt?: { claims?: ApiGatewayJwtClaims } };\n };\n }\n | undefined\n )?.requestContext?.authorizer?.jwt?.claims;\n\n return claims;\n}\n\nfunction hasRequiredClaims(claims: ApiGatewayJwtClaims): boolean {\n return REQUIRED_CLAIMS.every(\n (key) =>\n typeof claims[key] === \"string\" && (claims[key] as string).trim() !== \"\",\n );\n}\n\n/**\n * Express middleware that sets req.openhiContext for /ehr and /ohi domain API routes.\n * Context is populated from JWT claims (openhi_tenant_id, openhi_workspace_id, openhi_user_id, openhi_user_name)\n * added by the Cognito pre-token generation Lambda. Missing required claims result in 403 Forbidden.\n *\n * @see ADR 2026-02-13-02 (JWT claim design)\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 claims = getJwtClaims(req);\n if (!claims || !hasRequiredClaims(claims)) {\n res.status(403).json({\n error: \"Forbidden\",\n message:\n \"Missing or invalid OpenHI JWT claims (tenant, workspace, or audit context).\",\n });\n return;\n }\n\n const invoke = getCurrentInvoke();\n const event = (invoke?.event ??\n (req as Request & { apiGateway?: { event?: unknown } }).apiGateway\n ?.event) as { requestContext?: { requestId?: string } } | undefined;\n const requestId =\n typeof event?.requestContext?.requestId === \"string\"\n ? event.requestContext.requestId\n : undefined;\n\n req.openhiContext = {\n tenantId: claims.openhi_tenant_id as string,\n workspaceId: claims.openhi_workspace_id as string,\n date: new Date().toISOString(),\n actorId: claims.openhi_user_id as string,\n actorName: claims.openhi_user_name as string,\n actorType: \"human\",\n roleId:\n typeof claims.openhi_role_id === \"string\" && claims.openhi_role_id !== \"\"\n ? claims.openhi_role_id\n : undefined,\n requestId,\n source: \"rest\",\n clientId:\n typeof claims.openhi_client_id === \"string\" &&\n claims.openhi_client_id !== \"\"\n ? claims.openhi_client_id\n : undefined,\n };\n\n next();\n}\n","import express from \"express\";\nimport { createConfiguration } from \"./configuration-create\";\nimport { deleteConfiguration } from \"./configuration-delete\";\nimport { getConfigurationByKey } from \"./configuration-get-by-key\";\nimport { listConfigurations } from \"./configuration-list\";\nimport { updateConfiguration } from \"./configuration-update\";\n\n/**\n * Configuration REST router: /Configuration\n * @see sites/www-docs/content/packages/@openhi/constructs/data/rest-api/routes/configuration.md\n */\nconst router: express.Router = express.Router();\n\nrouter.get(\"/\", listConfigurations);\nrouter.get(\"/:key\", getConfigurationByKey);\nrouter.post(\"/\", createConfiguration);\nrouter.put(\"/:key\", updateConfiguration);\nrouter.delete(\"/:key\", deleteConfiguration);\n\nexport { router as configurationRouter };\n","/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/rest-api/routes/configuration.md\n */\n\nexport const BASE_PATH = \"/Configuration\";\nexport const SK = \"CURRENT\";\n","import { gzipSync, gunzipSync } from \"node:zlib\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/lib/compression.md\n */\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 { Configuration } from \"./entities/configuration\";\nimport { Patient } from \"./entities/patient\";\n\n/**\n * Single ElectroDB service for the data store (EHR + OHI entities in one table).\n * @see sites/www-docs/content/architecture/dynamodb-single-table-design.md\n */\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, configuration: Configuration };\n\n/**\n * ElectroDB Service for the single-table data store. Provides access to Patient\n * (EHR/FHIR) and Configuration (OHI); use with the data store table (PK, SK, GSI1–GSI4).\n */\nexport const DynamoDataService = new Service(entities, { table, client });\n\n/**\n * Returns the data store service. Table name is resolved from tableName (optional override),\n * then DYNAMO_TABLE_NAME, then \"jesttesttable\".\n */\nexport function getDynamoDataService(\n tableName?: string,\n): typeof DynamoDataService {\n const resolved = tableName ?? table;\n return new Service(entities, { table: resolved, client });\n}\n","import { Entity } from \"electrodb\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/ohi/Configuration.md\n */\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: \"openhi\",\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","import { Entity } from \"electrodb\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/dynamo/ehr/r4/Patient.md\n */\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: \"openhi\",\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 type { Request, Response } from \"express\";\nimport { BASE_PATH, SK } from \"./configuration-common\";\nimport { compressResource } from \"../../../../lib/compression\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\n\n/**\n * POST /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 actorId: ctxActorId,\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) ?? ctxActorId ?? \"-\";\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 = getDynamoDataService();\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","import type { Request, Response } from \"express\";\nimport { SK } from \"./configuration-common\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\n\n/**\n * DELETE /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, actorId, roleId: ctxRoleId } = ctx;\n const roleId = ctxRoleId ?? \"-\";\n const service = getDynamoDataService();\n\n try {\n await service.entities.configuration\n .delete({ tenantId, workspaceId, userId: actorId, 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","import type { Request, Response } from \"express\";\nimport { SK } from \"./configuration-common\";\nimport { decompressResource } from \"../../../../lib/compression\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\n\n/**\n * GET /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, actorId, roleId: ctxRoleId } = ctx;\n const roleId = ctxRoleId ?? \"-\";\n const service = getDynamoDataService();\n\n try {\n const result = await service.entities.configuration\n .get({ tenantId, workspaceId, userId: actorId, 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 * 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 /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\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/rest-api/dynamic-configuration.md\n */\n\nconst BASE_PATH = \"/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<Array<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}): Array<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 type { Request, Response } from \"express\";\nimport { BASE_PATH } from \"./configuration-common\";\nimport { decompressResource } from \"../../../../lib/compression\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\nimport { getDynamicConfigurationEntries } from \"../../dynamic-configuration\";\n\n/**\n * GET /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; actorId/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 = getDynamoDataService();\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","import type { Request, Response } from \"express\";\nimport { SK } from \"./configuration-common\";\nimport { compressResource } from \"../../../../lib/compression\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\n\n/**\n * PUT /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, actorId, date, roleId: ctxRoleId } = ctx;\n const roleId = ctxRoleId ?? \"-\";\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 = getDynamoDataService();\n\n try {\n const existing = await service.entities.configuration\n .get({ tenantId, workspaceId, userId: actorId, 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: actorId, 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","import express from \"express\";\nimport { createPatient } from \"./patient-create\";\nimport { deletePatient } from \"./patient-delete\";\nimport { getPatientById } from \"./patient-get-by-id\";\nimport { listPatients } from \"./patient-list\";\nimport { updatePatient } from \"./patient-update\";\n\n/**\n * Patient REST router: /Patient\n * @see sites/www-docs/content/packages/@openhi/constructs/data/rest-api/routes/patient.md\n */\nconst router: express.Router = express.Router();\n\nrouter.get(\"/\", listPatients);\nrouter.get(\"/:id\", getPatientById);\nrouter.post(\"/\", createPatient);\nrouter.put(\"/:id\", updatePatient);\nrouter.delete(\"/:id\", deletePatient);\n\nexport { router as patientRouter };\n","import type { Request, Response } from \"express\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/rest-api/routes/patient.md\n */\n\nexport const BASE_PATH = \"/Patient\";\nexport const SK = \"CURRENT\";\n\n/**\n * Require req.body to be a plain object (set by express.json() + normalizeJsonBodyMiddleware).\n * Returns the body for use, or sends 400 and returns the response so the handler can return.\n */\nexport function requireJsonBody(\n req: Request,\n res: Response,\n): { body: Record<string, unknown> } | { errorResponse: Response } {\n const raw = req.body;\n if (raw == null || typeof raw !== \"object\" || Array.isArray(raw)) {\n return {\n errorResponse: res.status(400).json({\n resourceType: \"OperationOutcome\",\n issue: [\n {\n severity: \"error\",\n code: \"invalid\",\n diagnostics: \"Request body must be a JSON object.\",\n },\n ],\n }),\n };\n }\n return { body: raw as Record<string, unknown> };\n}\n","import { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport type { Extension, Meta } from \"@openhi/types\";\nimport { compressResource } from \"../lib/compression\";\nimport { getDynamoDataService } from \"./dynamo/dynamo-service\";\n\n/**\n * @see sites/www-docs/content/packages/@openhi/constructs/data/import-patient.md\n */\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/** Meta with optional OpenHI audit extensions (created/modified by, etc.). */\nexport type MetaWithExtensions = Meta & { extension?: Extension[] };\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/** Audit extension entry shape (subset of Extension used by OpenHI audit). */\ntype AuditExtensionEntry = Pick<\n Extension,\n \"url\" | \"valueString\" | \"valueDateTime\"\n>;\n\n/** Builds meta.extension entries for audit; merges with existing meta. */\nexport function mergeAuditIntoMeta(\n meta: MetaWithExtensions | Record<string, unknown> | undefined,\n audit: AuditFields,\n): MetaWithExtensions {\n const existing = (meta ?? {}) as MetaWithExtensions;\n const ext: AuditExtensionEntry[] = [\n ...(Array.isArray(existing.extension)\n ? (existing.extension as AuditExtensionEntry[])\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?: 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?: Array<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 is passed through; data service resolves from DYNAMO_TABLE_NAME when omitted.\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 service = getDynamoDataService(options.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 type { Request, Response } from \"express\";\nimport { BASE_PATH, requireJsonBody } from \"./patient-common\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\nimport {\n patientToPutAttrs,\n type ImportPatientOptions,\n} from \"../../../import-patient\";\n\n/** POST /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 bodyResult = requireJsonBody(req, res);\n if (\"errorResponse\" in bodyResult) return bodyResult.errorResponse;\n\n const ctx = req.openhiContext!;\n const { tenantId, workspaceId, date, actorId, actorName } = ctx;\n const body = bodyResult.body;\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: actorId,\n createdByName: actorName,\n modifiedDate: date,\n modifiedById: actorId,\n modifiedByName: actorName,\n };\n const service = getDynamoDataService();\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","import type { Request, Response } from \"express\";\nimport { SK } from \"./patient-common\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\n\n/** DELETE /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 = getDynamoDataService();\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","import type { Request, Response } from \"express\";\nimport { SK } from \"./patient-common\";\nimport { decompressResource } from \"../../../../lib/compression\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\n\n/** GET /Patient/:id — read: returns a single Patient resource from the data store or 404. */\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 = getDynamoDataService();\n\n try {\n const result = await service.entities.patient\n .get({ tenantId, workspaceId, id, 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: `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","import type { Request, Response } from \"express\";\nimport { BASE_PATH } from \"./patient-common\";\nimport { decompressResource } from \"../../../../lib/compression\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\n\n/**\n * GET /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 = getDynamoDataService();\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","import type { Request, Response } from \"express\";\nimport { requireJsonBody, SK } from \"./patient-common\";\nimport {\n compressResource,\n decompressResource,\n} from \"../../../../lib/compression\";\nimport { getDynamoDataService } from \"../../../dynamo/dynamo-service\";\nimport { mergeAuditIntoMeta } from \"../../../import-patient\";\n\n/** PUT /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 bodyResult = requireJsonBody(req, res);\n if (\"errorResponse\" in bodyResult) return bodyResult.errorResponse;\n\n const id = String(req.params.id);\n const ctx = req.openhiContext!;\n const { tenantId, workspaceId, date, actorId, actorName } = ctx;\n const body = bodyResult.body;\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 = getDynamoDataService();\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: actorId,\n modifiedByName: actorName,\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"],"mappings":";;;;;AAAA,OAAO,uBAAuB;;;ACA9B,OAAO,UAAU;AACjB,OAAOA,cAA2D;;;ACQ3D,SAAS,4BACd,KACA,MACA,MACM;AACN,QAAM,MAAM,IAAI;AAChB,MAAI,OAAO,SAAS,GAAG,GAAG;AACxB,QAAI;AACF,UAAI,OAAO,KAAK,MAAM,IAAI,SAAS,OAAO,CAAC;AAAA,IAC7C,QAAQ;AAAA,IAER;AAAA,EACF,WAAW,OAAO,QAAQ,UAAU;AAClC,QAAI;AACF,UAAI,OAAO,KAAK,MAAM,GAAG;AAAA,IAC3B,QAAQ;AAAA,IAER;AAAA,EACF;AACA,OAAK;AACP;;;AC7BA,SAAS,wBAAwB;AAIjC,IAAM,kBAAkB;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,SAAS,aAAa,KAA+C;AACnE,QAAM,SAAS,iBAAiB;AAChC,QAAM,QACJ,QAAQ,SACP,IAAuD,YAAY;AAEtE,QAAM,SACJ,OAOC,gBAAgB,YAAY,KAAK;AAEpC,SAAO;AACT;AAEA,SAAS,kBAAkB,QAAsC;AAC/D,SAAO,gBAAgB;AAAA,IACrB,CAAC,QACC,OAAO,OAAO,GAAG,MAAM,YAAa,OAAO,GAAG,EAAa,KAAK,MAAM;AAAA,EAC1E;AACF;AAUO,SAAS,wBACd,KACA,KACA,MACM;AACN,QAAM,SAAS,aAAa,GAAG;AAC/B,MAAI,CAAC,UAAU,CAAC,kBAAkB,MAAM,GAAG;AACzC,QAAI,OAAO,GAAG,EAAE,KAAK;AAAA,MACnB,OAAO;AAAA,MACP,SACE;AAAA,IACJ,CAAC;AACD;AAAA,EACF;AAEA,QAAM,SAAS,iBAAiB;AAChC,QAAM,QAAS,QAAQ,SACpB,IAAuD,YACpD;AACN,QAAM,YACJ,OAAO,OAAO,gBAAgB,cAAc,WACxC,MAAM,eAAe,YACrB;AAEN,MAAI,gBAAgB;AAAA,IAClB,UAAU,OAAO;AAAA,IACjB,aAAa,OAAO;AAAA,IACpB,OAAM,oBAAI,KAAK,GAAE,YAAY;AAAA,IAC7B,SAAS,OAAO;AAAA,IAChB,WAAW,OAAO;AAAA,IAClB,WAAW;AAAA,IACX,QACE,OAAO,OAAO,mBAAmB,YAAY,OAAO,mBAAmB,KACnE,OAAO,iBACP;AAAA,IACN;AAAA,IACA,QAAQ;AAAA,IACR,UACE,OAAO,OAAO,qBAAqB,YACnC,OAAO,qBAAqB,KACxB,OAAO,mBACP;AAAA,EACR;AAEA,OAAK;AACP;;;AC1FA,OAAO,aAAa;;;ACIb,IAAM,YAAY;AAClB,IAAM,KAAK;;;ACLlB,SAAS,UAAU,kBAAkB;AAOrC,IAAM,mBAAmB;AAOlB,IAAM,oBAAoB;AAAA,EAC/B,MAAM;AAAA,EACN,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,SAAS;AACX;AAaA,SAAS,WAAW,KAA0C;AAC5D,SACE,OAAO,QAAQ,YACf,QAAQ,QACR,OAAO,OACP,UAAU,OACV,aAAa,OACb,OAAQ,IAA4B,YAAY;AAEpD;AAQO,SAAS,iBACd,YACA,SACQ;AACR,QAAM,OAAO,SAAS,QAAQ,kBAAkB;AAChD,MAAI,SAAS,kBAAkB,MAAM;AACnC,UAAMC,YAAgC;AAAA,MACpC,GAAG;AAAA,MACH,MAAM,kBAAkB;AAAA,MACxB,SAAS;AAAA,IACX;AACA,WAAO,KAAK,UAAUA,SAAQ;AAAA,EAChC;AACA,QAAM,MAAM,OAAO,KAAK,YAAY,OAAO;AAC3C,QAAM,UAAU,SAAS,GAAG,EAAE,SAAS,QAAQ;AAC/C,QAAM,WAAgC;AAAA,IACpC,GAAG;AAAA,IACH,MAAM,kBAAkB;AAAA,IACxB;AAAA,EACF;AACA,SAAO,KAAK,UAAU,QAAQ;AAChC;AAMO,SAAS,mBAAmB,iBAAiC;AAClE,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,eAAe;AACzC,QAAI,WAAW,MAAM,GAAG;AACtB,UAAI,OAAO,SAAS,kBAAkB,MAAM;AAC1C,cAAM,MAAM,OAAO,KAAK,OAAO,SAAS,QAAQ;AAChD,eAAO,WAAW,GAAG,EAAE,SAAS,OAAO;AAAA,MACzC;AACA,UAAI,OAAO,SAAS,kBAAkB,MAAM;AAC1C,eAAO,OAAO;AAAA,MAChB;AAEA,aAAO,OAAO;AAAA,IAChB;AAAA,EACF,QAAQ;AAAA,EAER;AAGA,MAAI;AACF,UAAM,MAAM,OAAO,KAAK,iBAAiB,QAAQ;AACjD,QAAI,IAAI,UAAU,KAAK,IAAI,CAAC,MAAM,MAAQ,IAAI,CAAC,MAAM,KAAM;AACzD,aAAO,WAAW,GAAG,EAAE,SAAS,OAAO;AAAA,IACzC;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO;AACT;;;ACxGA,SAAS,sBAAsB;AAC/B,SAAS,eAAe;;;ACDxB,SAAS,cAAc;AAkBhB,IAAM,gBAAgB,IAAI,OAAO;AAAA,EACtC,OAAO;AAAA,IACL,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,SAAS;AAAA,EACX;AAAA,EACA,YAAY;AAAA;AAAA,IAEV,IAAI;AAAA,MACF,MAAM;AAAA,MACN,UAAU;AAAA,MACV,SAAS;AAAA,IACX;AAAA;AAAA,IAEA,UAAU;AAAA,MACR,MAAM;AAAA,MACN,UAAU;AAAA,MACV,SAAS;AAAA,IACX;AAAA;AAAA,IAEA,aAAa;AAAA,MACX,MAAM;AAAA,MACN,UAAU;AAAA,MACV,SAAS;AAAA,IACX;AAAA;AAAA,IAEA,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,UAAU;AAAA,MACV,SAAS;AAAA,IACX;AAAA;AAAA,IAEA,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,UAAU;AAAA,MACV,SAAS;AAAA,IACX;AAAA;AAAA,IAEA,KAAK;AAAA,MACH,MAAM;AAAA,MACN,UAAU;AAAA,IACZ;AAAA;AAAA,IAEA,IAAI;AAAA,MACF,MAAM;AAAA,MACN,UAAU;AAAA,IACZ;AAAA;AAAA,IAEA,UAAU;AAAA,MACR,MAAM;AAAA,MACN,UAAU;AAAA,IACZ;AAAA;AAAA,IAEA,KAAK;AAAA,MACH,MAAM;AAAA,MACN,UAAU;AAAA,IACZ;AAAA,IACA,aAAa;AAAA,MACX,MAAM;AAAA,MACN,UAAU;AAAA,IACZ;AAAA,IACA,SAAS;AAAA,MACP,MAAM;AAAA,MACN,UAAU;AAAA,IACZ;AAAA,IACA,UAAU;AAAA,MACR,MAAM;AAAA,MACN,UAAU;AAAA,IACZ;AAAA,IACA,OAAO;AAAA,MACL,MAAM;AAAA,MACN,UAAU;AAAA,IACZ;AAAA,EACF;AAAA,EACA,SAAS;AAAA;AAAA,IAEP,QAAQ;AAAA,MACN,IAAI;AAAA,QACF,OAAO;AAAA,QACP,WAAW,CAAC,YAAY,eAAe,UAAU,QAAQ;AAAA,QACzD,UACE;AAAA,MACJ;AAAA,MACA,IAAI;AAAA,QACF,OAAO;AAAA,QACP,WAAW,CAAC,OAAO,IAAI;AAAA,QACvB,UAAU;AAAA,MACZ;AAAA,IACF;AAAA;AAAA,IAGA,MAAM;AAAA,MACJ,OAAO;AAAA,MACP,WAAW,MAAM;AAAA,MACjB,IAAI;AAAA,QACF,OAAO;AAAA,QACP,WAAW,CAAC,YAAY,aAAa;AAAA,QACrC,UAAU;AAAA,MACZ;AAAA,MACA,IAAI;AAAA,QACF,OAAO;AAAA,QACP,WAAW,CAAC,OAAO,IAAI;AAAA,QACvB,UAAU;AAAA,MACZ;AAAA,IACF;AAAA,EACF;AACF,CAAC;;;AC5HD,SAAS,UAAAC,eAAc;AAehB,IAAM,UAAU,IAAIA,QAAO;AAAA,EAChC,OAAO;AAAA,IACL,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,SAAS;AAAA,EACX;AAAA,EACA,YAAY;AAAA;AAAA,IAEV,IAAI;AAAA,MACF,MAAM;AAAA,MACN,UAAU;AAAA,MACV,SAAS;AAAA,IACX;AAAA,IACA,UAAU;AAAA,MACR,MAAM;AAAA,MACN,UAAU;AAAA,IACZ;AAAA,IACA,aAAa;AAAA,MACX,MAAM;AAAA,MACN,UAAU;AAAA,IACZ;AAAA;AAAA,IAEA,IAAI;AAAA,MACF,MAAM;AAAA,MACN,UAAU;AAAA,IACZ;AAAA;AAAA,IAEA,UAAU;AAAA,MACR,MAAM;AAAA,MACN,UAAU;AAAA,IACZ;AAAA;AAAA,IAEA,KAAK;AAAA,MACH,MAAM;AAAA,MACN,UAAU;AAAA,IACZ;AAAA,IACA,aAAa;AAAA,MACX,MAAM;AAAA,MACN,UAAU;AAAA,IACZ;AAAA,IACA,SAAS;AAAA,MACP,MAAM;AAAA,MACN,UAAU;AAAA,IACZ;AAAA,IACA,UAAU;AAAA,MACR,MAAM;AAAA,MACN,UAAU;AAAA,IACZ;AAAA,IACA,OAAO;AAAA,MACL,MAAM;AAAA,MACN,UAAU;AAAA,IACZ;AAAA;AAAA;AAAA;AAAA,IAIA,kBAAkB;AAAA,MAChB,MAAM;AAAA,MACN,UAAU;AAAA,IACZ;AAAA;AAAA,IAEA,iBAAiB;AAAA,MACf,MAAM;AAAA,MACN,UAAU;AAAA,IACZ;AAAA;AAAA,IAEA,YAAY;AAAA,MACV,MAAM;AAAA,MACN,UAAU;AAAA,IACZ;AAAA,IACA,YAAY;AAAA,MACV,MAAM;AAAA,MACN,UAAU;AAAA,IACZ;AAAA;AAAA,IAEA,SAAS;AAAA,MACP,MAAM;AAAA,MACN,UAAU;AAAA,IACZ;AAAA;AAAA,IAEA,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,UAAU;AAAA,IACZ;AAAA;AAAA;AAAA,IAGA,YAAY;AAAA,MACV,MAAM;AAAA,MACN,UAAU;AAAA,IACZ;AAAA;AAAA,IAEA,gBAAgB;AAAA,MACd,MAAM;AAAA,MACN,UAAU;AAAA,IACZ;AAAA,EACF;AAAA,EACA,SAAS;AAAA;AAAA,IAEP,QAAQ;AAAA,MACN,IAAI;AAAA,QACF,OAAO;AAAA,QACP,WAAW,CAAC,YAAY,eAAe,IAAI;AAAA,QAC3C,UAAU;AAAA,MACZ;AAAA,MACA,IAAI;AAAA,QACF,OAAO;AAAA,QACP,WAAW,CAAC,IAAI;AAAA,MAClB;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOA,MAAM;AAAA,MACJ,OAAO;AAAA,MACP,WAAW,MAAM;AAAA,MACjB,IAAI;AAAA,QACF,OAAO;AAAA,QACP,WAAW,CAAC,YAAY,eAAe,IAAI;AAAA,QAC3C,UACE;AAAA,MACJ;AAAA,MACA,IAAI;AAAA,QACF,OAAO;AAAA,QACP,WAAW,CAAC;AAAA,MACd;AAAA,IACF;AAAA;AAAA,IAGA,MAAM;AAAA,MACJ,OAAO;AAAA,MACP,WAAW,CAAC,SACV,KAAK,oBAAoB,QAAQ,KAAK,mBAAmB;AAAA,MAC3D,IAAI;AAAA,QACF,OAAO;AAAA,QACP,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,QACA,UACE;AAAA,MACJ;AAAA,MACA,IAAI;AAAA,QACF,OAAO;AAAA,QACP,WAAW,CAAC,IAAI;AAAA,QAChB,UAAU;AAAA,MACZ;AAAA,IACF;AAAA;AAAA,IAEA,MAAM;AAAA,MACJ,OAAO;AAAA,MACP,WAAW,CAAC,SACV,KAAK,cAAc,QAAQ,KAAK,kBAAkB;AAAA,MACpD,IAAI;AAAA,QACF,OAAO;AAAA,QACP,WAAW,CAAC,YAAY,eAAe,YAAY;AAAA,QACnD,UAAU;AAAA,MACZ;AAAA,MACA,IAAI;AAAA,QACF,OAAO;AAAA,QACP,WAAW,CAAC,MAAM,gBAAgB;AAAA,QAClC,UAAU;AAAA,MACZ;AAAA,IACF;AAAA;AAAA,IAEA,MAAM;AAAA,MACJ,OAAO;AAAA,MACP,WAAW,MAAM;AAAA,MACjB,IAAI;AAAA,QACF,OAAO;AAAA,QACP,WAAW,CAAC,YAAY,aAAa;AAAA,QACrC,UAAU;AAAA,MACZ;AAAA,MACA,IAAI;AAAA,QACF,OAAO;AAAA,QACP,WAAW,CAAC,IAAI;AAAA,QAChB,UAAU;AAAA,MACZ;AAAA,IACF;AAAA,EACF;AACF,CAAC;;;AFxLD,IAAM,QAAQ,QAAQ,IAAI,qBAAqB;AAM/C,IAAM,SAAS,IAAI,eAAe;AAAA,EAChC,GAAI,QAAQ,IAAI,0BAA0B;AAAA,IACxC,UAAU,QAAQ,IAAI;AAAA,IACtB,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV;AACF,CAAC;AAED,IAAM,WAAW,EAAE,SAAS,SAAS,eAAe,cAAc;AAM3D,IAAM,oBAAoB,IAAI,QAAQ,UAAU,EAAE,OAAO,OAAO,CAAC;AAMjE,SAAS,qBACd,WAC0B;AAC1B,QAAM,WAAW,aAAa;AAC9B,SAAO,IAAI,QAAQ,UAAU,EAAE,OAAO,UAAU,OAAO,CAAC;AAC1D;;;AGpCA,eAAsB,oBACpB,KACA,KACmB;AACnB,QAAM,MAAM,IAAI;AAChB,QAAM;AAAA,IACJ,UAAU;AAAA,IACV,aAAa;AAAA,IACb,SAAS;AAAA,IACT;AAAA,EACF,IAAI;AACJ,QAAM,OAAO,IAAI;AACjB,QAAM,MAAM,MAAM;AAClB,MAAI,CAAC,OAAO,OAAO,QAAQ,UAAU;AACnC,WAAO,IAAI,OAAO,GAAG,EAAE,KAAK;AAAA,MAC1B,cAAc;AAAA,MACd,OAAO;AAAA,QACL;AAAA,UACE,UAAU;AAAA,UACV,MAAM;AAAA,UACN,aAAa;AAAA,QACf;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AACA,QAAM,KAAM,MAAM,MAAiB,UAAU,GAAG,IAAI,KAAK,IAAI,CAAC;AAC9D,QAAM,kBAAkB,MAAM;AAC9B,QAAM,cACJ,OAAO,oBAAoB,WACvB,kBACA,KAAK,UAAU,mBAAmB,CAAC,CAAC;AAC1C,QAAM,WAAY,MAAM,YAAuB;AAC/C,QAAM,cAAe,MAAM,eAA0B;AACrD,QAAM,SAAU,MAAM,UAAqB,cAAc;AACzD,QAAM,SAAU,MAAM,UAAqB;AAC3C,QAAM,MACH,MAAM,QACN,KAAK,QAAQ,YAAY,EAAE,EAAE,MAAM,GAAG,EAAE,KAAK,KAAK,IAAI,EAAE,SAAS,EAAE;AACtE,QAAM,cAAe,MAAM,eAA0B;AAErD,QAAM,UAAU,qBAAqB;AAErC,MAAI;AACF,UAAM,QAAQ,SAAS,cACpB,IAAI;AAAA,MACH;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU,iBAAiB,WAAW;AAAA,MACtC;AAAA,MACA;AAAA,MACA,IAAI;AAAA,IACN,CAAC,EACA,GAAG;AAEN,UAAM,SAAS;AAAA,MACb,cAAc;AAAA,MACd;AAAA,MACA;AAAA,MACA,UACE,OAAO,oBAAoB,WACvB,kBACA,KAAK,MAAM,WAAW;AAAA,MAC5B,MAAM,EAAE,aAAa,WAAW,IAAI;AAAA,IACtC;AACA,WAAO,IAAI,OAAO,GAAG,EAAE,SAAS,GAAG,SAAS,IAAI,GAAG,EAAE,EAAE,KAAK,MAAM;AAAA,EACpE,SAAS,KAAc;AACrB,YAAQ,MAAM,6BAA6B,GAAG;AAC9C,WAAO,IAAI,OAAO,GAAG,EAAE,KAAK;AAAA,MAC1B,cAAc;AAAA,MACd,OAAO;AAAA,QACL,EAAE,UAAU,SAAS,MAAM,aAAa,aAAa,OAAO,GAAG,EAAE;AAAA,MACnE;AAAA,IACF,CAAC;AAAA,EACH;AACF;;;AChFA,eAAsB,oBACpB,KACA,KACmB;AACnB,QAAM,MAAM,OAAO,IAAI,OAAO,GAAG;AACjC,QAAM,MAAM,IAAI;AAChB,QAAM,EAAE,UAAU,aAAa,SAAS,QAAQ,UAAU,IAAI;AAC9D,QAAM,SAAS,aAAa;AAC5B,QAAM,UAAU,qBAAqB;AAErC,MAAI;AACF,UAAM,QAAQ,SAAS,cACpB,OAAO,EAAE,UAAU,aAAa,QAAQ,SAAS,QAAQ,KAAK,IAAI,GAAG,CAAC,EACtE,GAAG;AACN,WAAO,IAAI,OAAO,GAAG,EAAE,KAAK;AAAA,EAC9B,SAAS,KAAc;AACrB,YAAQ,MAAM,+BAA+B,GAAG;AAChD,WAAO,IAAI,OAAO,GAAG,EAAE,KAAK;AAAA,MAC1B,cAAc;AAAA,MACd,OAAO;AAAA,QACL,EAAE,UAAU,SAAS,MAAM,aAAa,aAAa,OAAO,GAAG,EAAE;AAAA,MACnE;AAAA,IACF,CAAC;AAAA,EACH;AACF;;;ACvBA,eAAsB,sBACpB,KACA,KACmB;AACnB,QAAM,MAAM,OAAO,IAAI,OAAO,GAAG;AACjC,QAAM,MAAM,IAAI;AAChB,QAAM,EAAE,UAAU,aAAa,SAAS,QAAQ,UAAU,IAAI;AAC9D,QAAM,SAAS,aAAa;AAC5B,QAAM,UAAU,qBAAqB;AAErC,MAAI;AACF,UAAM,SAAS,MAAM,QAAQ,SAAS,cACnC,IAAI,EAAE,UAAU,aAAa,QAAQ,SAAS,QAAQ,KAAK,IAAI,GAAG,CAAC,EACnE,GAAG;AAEN,QAAI,CAAC,OAAO,MAAM;AAChB,aAAO,IAAI,OAAO,GAAG,EAAE,KAAK;AAAA,QAC1B,cAAc;AAAA,QACd,OAAO;AAAA,UACL;AAAA,YACE,UAAU;AAAA,YACV,MAAM;AAAA,YACN,aAAa,iBAAiB,GAAG;AAAA,UACnC;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAEA,UAAM,WAAW,KAAK;AAAA,MACpB,mBAAmB,OAAO,KAAK,QAAQ;AAAA,IACzC;AACA,WAAO,IAAI,KAAK;AAAA,MACd,GAAG;AAAA,MACH,cAAc;AAAA,MACd,IAAI,OAAO,KAAK;AAAA,MAChB,KAAK,OAAO,KAAK;AAAA,IACnB,CAAC;AAAA,EACH,SAAS,KAAc;AACrB,YAAQ,MAAM,4BAA4B,GAAG;AAC7C,WAAO,IAAI,OAAO,GAAG,EAAE,KAAK;AAAA,MAC1B,cAAc;AAAA,MACd,OAAO;AAAA,QACL;AAAA,UACE,UAAU;AAAA,UACV,MAAM;AAAA,UACN,aAAa,OAAO,GAAG;AAAA,QACzB;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AACF;;;AC1CA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAMP,IAAMC,aAAY;AAGlB,IAAM,iBAAiB;AAEvB,IAAM,yBAAyB;AA8BxB,SAAS,+BAAiE;AAC/E,QAAM,iBAAiB,QAAQ,IAAI;AACnC,QAAM,kBAAkB,QAAQ,IAAI;AACpC,MACE,kBAAkB,QAClB,mBAAmB,MACnB,mBAAmB,QACnB,oBAAoB,IACpB;AACA,WAAO;AAAA,EACT;AACA,SAAO,EAAE,gBAAgB,gBAAgB;AAC3C;AAcA,eAAsB,+BAA+B,SAGV;AACzC,QAAM,YAAY,6BAA6B;AAC/C,MAAI,aAAa,MAAM;AACrB,WAAO,oBAAoB,OAAO;AAAA,EACpC;AAEA,QAAM,SAAS,QAAQ,IAAI,cAAc,QAAQ,IAAI;AACrD,QAAMC,UAAS,IAAI,UAAU,EAAE,OAAO,CAAC;AAEvC,MAAI;AAEF,UAAM,iBAAiB,MAAMA,QAAO;AAAA,MAClC,IAAI,0BAA0B;AAAA,QAC5B,kBAAkB;AAAA,UAChB;AAAA,YACE,KAAK,OAAO,cAAc;AAAA,YAC1B,QAAQ;AAAA,YACR,QAAQ,CAAC,UAAU,cAAc;AAAA,UACnC;AAAA,UACA;AAAA,YACE,KAAK,OAAO,sBAAsB;AAAA,YAClC,QAAQ;AAAA,YACR,QAAQ,CAAC,UAAU,eAAe;AAAA,UACpC;AAAA,QACF;AAAA,QACA,YAAY;AAAA,MACd,CAAC;AAAA,IACH;AAEA,UAAM,SAAS,eAAe,cAAc,CAAC,GAC1C,IAAI,CAAC,MAAyB,EAAE,IAAI,EACpC,OAAO,CAAC,MAAuC,KAAK,IAAI;AAE3D,QAAI,MAAM,WAAW,GAAG;AACtB,aAAO,oBAAoB,OAAO;AAAA,IACpC;AAGA,UAAM,aAAqD,CAAC;AAC5D,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,IAAI;AACzC,YAAM,QAAQ,MAAM,MAAM,GAAG,IAAI,EAAE;AACnC,YAAM,YAAY,MAAMA,QAAO;AAAA,QAC7B,IAAI,qBAAqB;AAAA,UACvB,OAAO;AAAA,UACP,gBAAgB;AAAA,QAClB,CAAC;AAAA,MACH;AAEA,iBAAW,KAAK,UAAU,cAAc,CAAC,GAAG;AAC1C,cAAM,OAAQ,EAAwB;AACtC,cAAM,QAAS,EAAyB;AACxC,YAAI,QAAQ,QAAQ,SAAS,MAAM;AACjC,qBAAW,KAAK,EAAE,MAAM,MAAM,CAAC;AAAA,QACjC;AAAA,MACF;AAAA,IACF;AAEA,UAAM,gBAAgB,WAAW,IAAI,CAAC,MAAM;AAC1C,YAAM,YAAY,EAAE,KAAK,SAAS,GAAG,IACjC,EAAE,KAAK,MAAM,GAAG,EAAE,MAAM,EAAE,EAAE,CAAC,IAC7B,EAAE;AACN,aAAO,EAAE,MAAM,WAAW,aAAa,EAAE,MAAM;AAAA,IACjD,CAAC;AAED,UAAM,QAAgC;AAAA,MACpC,SAAS,GAAGD,UAAS;AAAA,MACrB,UAAU;AAAA,QACR,cAAc;AAAA,QACd,IAAI;AAAA,QACJ,KAAK;AAAA,QACL,UAAU;AAAA,UACR,WAAW;AAAA,QACb;AAAA,QACA,MAAM;AAAA,UACJ,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,UACpC,WAAW;AAAA,QACb;AAAA,MACF;AAAA,IACF;AAEA,WAAO,CAAC,KAAK;AAAA,EACf,SAAS,KAAK;AACZ,YAAQ,MAAM,6CAA6C,GAAG;AAC9D,WAAO,oBAAoB,OAAO;AAAA,EACpC;AACF;AAMA,SAAS,oBAAoB,UAGK;AAChC,QAAM,QAAgC;AAAA,IACpC,SAAS,GAAGA,UAAS;AAAA,IACrB,UAAU;AAAA,MACR,cAAc;AAAA,MACd,IAAI;AAAA,MACJ,KAAK;AAAA,MACL,UAAU;AAAA,QACR,WAAW;AAAA,UACT;AAAA,YACE,MAAM;AAAA,YACN,aACE;AAAA,UACJ;AAAA,UACA,EAAE,MAAM,UAAU,aAAa,wBAAwB;AAAA,QACzD;AAAA,MACF;AAAA,MACA,MAAM;AAAA,QACJ,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,QACpC,WAAW;AAAA,MACb;AAAA,IACF;AAAA,EACF;AAEA,SAAO,CAAC,KAAK;AACf;;;ACtMA,eAAsB,mBACpB,KACA,KACmB;AACnB,QAAM,EAAE,UAAU,YAAY,IAAI,IAAI;AACtC,QAAM,UAAU,qBAAqB;AAErC,MAAI;AACF,UAAM,SAAS,MAAM,QAAQ,SAAS,cAAc,MACjD,KAAK,EAAE,UAAU,YAAY,CAAC,EAC9B,GAAG;AAEN,UAAM,iBAAiB,OAAO,QAAQ,CAAC,GAAG,IAAI,CAAC,SAAS;AACtD,YAAM,WAAW,KAAK,MAAM,mBAAmB,KAAK,QAAQ,CAAC;AAI7D,aAAO;AAAA,QACL,SAAS,GAAG,SAAS,IAAI,KAAK,GAAG;AAAA,QACjC,UAAU,EAAE,GAAG,UAAU,IAAI,KAAK,IAAI,KAAK,KAAK,IAAI;AAAA,MACtD;AAAA,IACF,CAAC;AACD,UAAM,iBAAiB,MAAM,+BAA+B;AAAA,MAC1D;AAAA,MACA;AAAA,IACF,CAAC;AACD,UAAM,UAAU,CAAC,GAAG,eAAe,GAAG,cAAc;AACpD,UAAM,SAAS;AAAA,MACb,cAAc;AAAA,MACd,MAAM;AAAA,MACN,OAAO,QAAQ;AAAA,MACf,MAAM,CAAC,EAAE,UAAU,QAAQ,KAAK,UAAU,CAAC;AAAA,MAC3C,OAAO;AAAA,IACT;AACA,WAAO,IAAI,KAAK,MAAM;AAAA,EACxB,SAAS,KAAc;AACrB,YAAQ,MAAM,kCAAkC,GAAG;AACnD,WAAO,IAAI,OAAO,GAAG,EAAE,KAAK;AAAA,MAC1B,cAAc;AAAA,MACd,OAAO;AAAA,QACL;AAAA,UACE,UAAU;AAAA,UACV,MAAM;AAAA,UACN,aAAa,OAAO,GAAG;AAAA,QACzB;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AACF;;;ACnDA,eAAsB,oBACpB,KACA,KACmB;AACnB,QAAM,MAAM,OAAO,IAAI,OAAO,GAAG;AACjC,QAAM,MAAM,IAAI;AAChB,QAAM,EAAE,UAAU,aAAa,SAAS,MAAM,QAAQ,UAAU,IAAI;AACpE,QAAM,SAAS,aAAa;AAC5B,QAAM,OAAO,IAAI;AACjB,QAAM,kBAAkB,MAAM;AAC9B,QAAM,cACJ,OAAO,oBAAoB,WACvB,kBACA,KAAK,UAAU,mBAAmB,CAAC,CAAC;AAC1C,QAAM,cAAe,MAAM,eAA0B;AAErD,QAAM,UAAU,qBAAqB;AAErC,MAAI;AACF,UAAM,WAAW,MAAM,QAAQ,SAAS,cACrC,IAAI,EAAE,UAAU,aAAa,QAAQ,SAAS,QAAQ,KAAK,IAAI,GAAG,CAAC,EACnE,GAAG;AAEN,QAAI,CAAC,SAAS,MAAM;AAClB,aAAO,IAAI,OAAO,GAAG,EAAE,KAAK;AAAA,QAC1B,cAAc;AAAA,QACd,OAAO;AAAA,UACL;AAAA,YACE,UAAU;AAAA,YACV,MAAM;AAAA,YACN,aAAa,iBAAiB,GAAG;AAAA,UACnC;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAEA,UAAM,UACJ,SAAS,KAAK,OAAO,OACjB,OAAO,OAAO,SAAS,KAAK,GAAG,IAAI,CAAC,IACpC,KAAK,QAAQ,YAAY,EAAE,EAAE,MAAM,GAAG,EAAE,KAAK;AAEnD,UAAM,QAAQ,SAAS,cACpB,MAAM,EAAE,UAAU,aAAa,QAAQ,SAAS,QAAQ,KAAK,IAAI,GAAG,CAAC,EACrE,IAAI;AAAA,MACH,UAAU,iBAAiB,WAAW;AAAA,MACtC;AAAA,MACA,KAAK;AAAA,IACP,CAAC,EACA,GAAG;AAEN,UAAM,SAAS;AAAA,MACb,cAAc;AAAA,MACd,IAAI,SAAS,KAAK;AAAA,MAClB,KAAK,SAAS,KAAK;AAAA,MACnB,UACE,OAAO,oBAAoB,WACvB,kBACA,KAAK,MAAM,WAAW;AAAA,MAC5B,MAAM,EAAE,aAAa,WAAW,QAAQ;AAAA,IAC1C;AACA,WAAO,IAAI,KAAK,MAAM;AAAA,EACxB,SAAS,KAAc;AACrB,YAAQ,MAAM,4BAA4B,GAAG;AAC7C,WAAO,IAAI,OAAO,GAAG,EAAE,KAAK;AAAA,MAC1B,cAAc;AAAA,MACd,OAAO;AAAA,QACL,EAAE,UAAU,SAAS,MAAM,aAAa,aAAa,OAAO,GAAG,EAAE;AAAA,MACnE;AAAA,IACF,CAAC;AAAA,EACH;AACF;;;AXnEA,IAAM,SAAyB,QAAQ,OAAO;AAE9C,OAAO,IAAI,KAAK,kBAAkB;AAClC,OAAO,IAAI,SAAS,qBAAqB;AACzC,OAAO,KAAK,KAAK,mBAAmB;AACpC,OAAO,IAAI,SAAS,mBAAmB;AACvC,OAAO,OAAO,SAAS,mBAAmB;;;AYjB1C,OAAOE,cAAa;;;ACMb,IAAMC,aAAY;AAClB,IAAMC,MAAK;AAMX,SAAS,gBACd,KACA,KACiE;AACjE,QAAM,MAAM,IAAI;AAChB,MAAI,OAAO,QAAQ,OAAO,QAAQ,YAAY,MAAM,QAAQ,GAAG,GAAG;AAChE,WAAO;AAAA,MACL,eAAe,IAAI,OAAO,GAAG,EAAE,KAAK;AAAA,QAClC,cAAc;AAAA,QACd,OAAO;AAAA,UACL;AAAA,YACE,UAAU;AAAA,YACV,MAAM;AAAA,YACN,aAAa;AAAA,UACf;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACA,SAAO,EAAE,MAAM,IAA+B;AAChD;;;ACjCA,SAAS,oBAAoB;AAC7B,SAAS,eAAe;AAUxB,IAAM,aAAa;AAyBZ,SAAS,mBACd,MACA,OACoB;AACpB,QAAM,WAAY,QAAQ,CAAC;AAC3B,QAAM,MAA6B;AAAA,IACjC,GAAI,MAAM,QAAQ,SAAS,SAAS,IAC/B,SAAS,YACV,CAAC;AAAA,EACP;AACA,QAAM,QAAQ,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;AAChD,WAAS,IACP,KACA,OACA,MACA;AACA,QAAI,SAAS,KAAM;AACnB,UAAM,IAAI,KAAK,EAAE,KAAK,CAAC,IAAI,GAAG,MAAM,CAAC;AAAA,EACvC;AACA,MAAI,GAAG,UAAU,iBAAiB,MAAM,aAAa,eAAe;AACpE,MAAI,GAAG,UAAU,kBAAkB,MAAM,aAAa,aAAa;AACnE,MAAI,GAAG,UAAU,oBAAoB,MAAM,eAAe,aAAa;AACvE,MAAI,GAAG,UAAU,kBAAkB,MAAM,cAAc,eAAe;AACtE,MAAI,GAAG,UAAU,mBAAmB,MAAM,cAAc,aAAa;AACrE,MAAI,GAAG,UAAU,qBAAqB,MAAM,gBAAgB,aAAa;AACzE,MAAI,GAAG,UAAU,iBAAiB,MAAM,aAAa,eAAe;AACpE,MAAI,GAAG,UAAU,kBAAkB,MAAM,aAAa,aAAa;AACnE,MAAI,GAAG,UAAU,oBAAoB,MAAM,eAAe,aAAa;AACvE,SAAO,EAAE,GAAG,UAAU,WAAW,MAAM,KAAK,MAAM,OAAO,CAAC,EAAE;AAC9D;AAwBA,SAAS,eAAe,QAAkC;AACxD,MAAI,UAAU,OAAO,WAAW,YAAY,kBAAkB,QAAQ;AACpE,UAAM,OAAO;AAKb,QAAI,KAAK,iBAAiB,aAAa,KAAK,IAAI;AAC9C,aAAO;AAAA,IACT;AACA,QAAI,KAAK,iBAAiB,YAAY,WAAW,QAAQ;AACvD,YAAM,UAAW,OAA0C;AAC3D,UAAI,MAAM,QAAQ,OAAO,GAAG;AAC1B,cAAM,eAAe,QAAQ;AAAA,UAC3B,CAAC,MAAM,GAAG,UAAU,iBAAiB,aAAa,EAAE,SAAS;AAAA,QAC/D;AACA,YAAI,cAAc,UAAU;AAC1B,iBAAO,aAAa;AAAA,QACtB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,QAAM,IAAI;AAAA,IACR;AAAA,EACF;AACF;AAEA,IAAMC,MAAK;AAGX,IAAM,eAAe;AAAA,EACnB,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,EACpC,aAAa;AAAA,EACb,eAAe;AAAA,EACf,eAAc,oBAAI,KAAK,GAAE,YAAY;AAAA,EACrC,cAAc;AAAA,EACd,gBAAgB;AAClB;AAmBO,SAAS,kBACd,SACA,SACyB;AACzB,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AACJ,QAAM,cACH,QAAQ,MAAM,eACf,gBACA,aAAa,iBACb,oBAAI,KAAK,GAAE,YAAY;AACzB,QAAM,cAA2B;AAAA,IAC/B,GAAG;AAAA,IACH,GAAI,eAAe,QAAQ,EAAE,YAAY;AAAA,IACzC,GAAI,eAAe,QAAQ,EAAE,YAAY;AAAA,IACzC,GAAI,iBAAiB,QAAQ,EAAE,cAAc;AAAA,IAC7C,GAAI,gBAAgB,QAAQ,EAAE,aAAa;AAAA,IAC3C,GAAI,gBAAgB,QAAQ,EAAE,aAAa;AAAA,IAC3C,GAAI,kBAAkB,QAAQ,EAAE,eAAe;AAAA,EACjD;AAEA,QAAM,kBAAkB;AAAA,IACtB,GAAG;AAAA,IACH,MAAM,mBAAmB,QAAQ,MAAM,WAAW;AAAA,EACpD;AACA,MAAI,eAAe,CAAC,gBAAgB,KAAK,aAAa;AACpD,IAAC,gBAAgB,KAAiC,cAAc;AAAA,EAClE;AAEA,SAAO;AAAA,IACL,IAAIA;AAAA,IACJ;AAAA,IACA;AAAA,IACA,IAAI,QAAQ;AAAA,IACZ,UAAU,iBAAiB,KAAK,UAAU,eAAe,CAAC;AAAA,IAC1D,KACE,YAAY,QAAQ,YAAY,EAAE,EAAE,MAAM,GAAG,EAAE,KAC/C,KAAK,IAAI,EAAE,SAAS,EAAE;AAAA,IACxB;AAAA,IACA,kBAAkB;AAAA,IAClB,iBAAiB;AAAA,IACjB,YAAY;AAAA,IACZ,gBAAgB;AAAA,EAClB;AACF;AAMA,eAAsB,sBACpB,UACA,SACgE;AAChE,QAAM,WAAW,QAAQ,QAAQ;AACjC,QAAM,MAAM,aAAa,UAAU,OAAO;AAC1C,QAAM,SAAkB,KAAK,MAAM,GAAG;AACtC,QAAM,UAAU,eAAe,MAAM;AAErC,QAAM,UAAU,qBAAqB,QAAQ,SAAS;AACtD,QAAM,QAAQ,kBAAkB,SAAS,OAAO;AAEhD,QAAM,SAAS,MAAM,QAAQ,SAAS,QACnC,IAAI,KAAsE,EAC1E,GAAG;AAEN,QAAM,OACJ,OACA;AACF,MAAI,CAAC,MAAM;AACT,UAAM,IAAI,MAAM,0BAA0B,QAAQ,EAAE,EAAE;AAAA,EACxD;AAEA,SAAO;AAAA,IACL,IAAI,KAAK;AAAA,IACT,UAAU,KAAK;AAAA,IACf,aAAa,KAAK;AAAA,EACpB;AACF;AAGA,eAAe,OAAsB;AACnC,QAAM,CAAC,EAAE,EAAE,SAAS,WAAW,YAAY,cAAc,MAAM,IAC7D,QAAQ;AAEV,MAAI,CAAC,SAAS;AACZ,YAAQ;AAAA,MACN;AAAA,IACF;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI;AACF,UAAM,SAAS,MAAM,sBAAsB,SAAS;AAAA,MAClD;AAAA,MACA;AAAA,IACF,CAAC;AACD,YAAQ;AAAA,MACN,oBAAoB,OAAO,EAAE,YAAY,OAAO,QAAQ,eAAe,OAAO,WAAW;AAAA,IAC3F;AAAA,EACF,SAAS,KAAK;AACZ,YAAQ,MAAM,GAAG;AACjB,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAEA,IAAI,UAAQ,SAAS,QAAQ;AAC3B,OAAK,KAAK;AACZ;;;AC5PA,eAAsB,cACpB,KACA,KACmB;AACnB,QAAM,aAAa,gBAAgB,KAAK,GAAG;AAC3C,MAAI,mBAAmB,WAAY,QAAO,WAAW;AAErD,QAAM,MAAM,IAAI;AAChB,QAAM,EAAE,UAAU,aAAa,MAAM,SAAS,UAAU,IAAI;AAC5D,QAAM,OAAO,WAAW;AACxB,QAAM,KAAM,MAAM,MAAiB,WAAW,KAAK,IAAI,CAAC;AACxD,QAAM,UAAU;AAAA,IACd,GAAG;AAAA,IACH,cAAc;AAAA,IACd;AAAA,IACA,MAAM;AAAA,MACJ,GAAK,MAAM,QAAmB,CAAC;AAAA,MAC/B,aAAa;AAAA,MACb,WAAW;AAAA,IACb;AAAA,EACF;AACA,QAAM,UAAgC;AAAA,IACpC;AAAA,IACA;AAAA,IACA,aAAa;AAAA,IACb,aAAa;AAAA,IACb,eAAe;AAAA,IACf,cAAc;AAAA,IACd,cAAc;AAAA,IACd,gBAAgB;AAAA,EAClB;AACA,QAAM,UAAU,qBAAqB;AAErC,MAAI;AACF,UAAM,QAAQ,kBAAkB,SAAS,OAAO;AAChD,UAAM,QAAQ,SAAS,QACpB;AAAA,MACC;AAAA,IACF,EACC,GAAG;AACN,WAAO,IAAI,OAAO,GAAG,EAAE,SAAS,GAAGC,UAAS,IAAI,EAAE,EAAE,EAAE,KAAK,OAAO;AAAA,EACpE,SAAS,KAAc;AACrB,YAAQ,MAAM,uBAAuB,GAAG;AACxC,WAAO,IAAI,OAAO,GAAG,EAAE,KAAK;AAAA,MAC1B,cAAc;AAAA,MACd,OAAO;AAAA,QACL,EAAE,UAAU,SAAS,MAAM,aAAa,aAAa,OAAO,GAAG,EAAE;AAAA,MACnE;AAAA,IACF,CAAC;AAAA,EACH;AACF;;;ACtDA,eAAsB,cACpB,KACA,KACmB;AACnB,QAAM,KAAK,OAAO,IAAI,OAAO,EAAE;AAC/B,QAAM,EAAE,UAAU,YAAY,IAAI,IAAI;AACtC,QAAM,UAAU,qBAAqB;AAErC,MAAI;AACF,UAAM,QAAQ,SAAS,QACpB,OAAO,EAAE,UAAU,aAAa,IAAI,IAAIC,IAAG,CAAC,EAC5C,GAAG;AACN,WAAO,IAAI,OAAO,GAAG,EAAE,KAAK;AAAA,EAC9B,SAAS,KAAc;AACrB,YAAQ,MAAM,yBAAyB,GAAG;AAC1C,WAAO,IAAI,OAAO,GAAG,EAAE,KAAK;AAAA,MAC1B,cAAc;AAAA,MACd,OAAO;AAAA,QACL,EAAE,UAAU,SAAS,MAAM,aAAa,aAAa,OAAO,GAAG,EAAE;AAAA,MACnE;AAAA,IACF,CAAC;AAAA,EACH;AACF;;;ACrBA,eAAsB,eACpB,KACA,KACmB;AACnB,QAAM,KAAK,OAAO,IAAI,OAAO,EAAE;AAC/B,QAAM,EAAE,UAAU,YAAY,IAAI,IAAI;AACtC,QAAM,UAAU,qBAAqB;AAErC,MAAI;AACF,UAAM,SAAS,MAAM,QAAQ,SAAS,QACnC,IAAI,EAAE,UAAU,aAAa,IAAI,IAAIC,IAAG,CAAC,EACzC,GAAG;AAEN,QAAI,CAAC,OAAO,MAAM;AAChB,aAAO,IAAI,OAAO,GAAG,EAAE,KAAK;AAAA,QAC1B,cAAc;AAAA,QACd,OAAO;AAAA,UACL;AAAA,YACE,UAAU;AAAA,YACV,MAAM;AAAA,YACN,aAAa,WAAW,EAAE;AAAA,UAC5B;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAEA,UAAM,WAAW,KAAK;AAAA,MACpB,mBAAmB,OAAO,KAAK,QAAQ;AAAA,IACzC;AACA,WAAO,IAAI,KAAK,EAAE,GAAG,UAAU,IAAI,OAAO,KAAK,GAAG,CAAC;AAAA,EACrD,SAAS,KAAc;AACrB,YAAQ,MAAM,sBAAsB,GAAG;AACvC,WAAO,IAAI,OAAO,GAAG,EAAE,KAAK;AAAA,MAC1B,cAAc;AAAA,MACd,OAAO;AAAA,QACL;AAAA,UACE,UAAU;AAAA,UACV,MAAM;AAAA,UACN,aAAa,OAAO,GAAG;AAAA,QACzB;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AACF;;;ACvCA,eAAsB,aACpB,KACA,KACmB;AACnB,QAAM,EAAE,UAAU,YAAY,IAAI,IAAI;AACtC,QAAM,UAAU,qBAAqB;AAErC,MAAI;AACF,UAAM,SAAS,MAAM,QAAQ,SAAS,QAAQ,MAC3C,KAAK,EAAE,UAAU,YAAY,CAAC,EAC9B,GAAG;AAEN,UAAM,WAAW,OAAO,QAAQ,CAAC,GAAG,IAAI,CAAC,SAAS;AAChD,YAAM,WAAW,KAAK,MAAM,mBAAmB,KAAK,QAAQ,CAAC;AAI7D,aAAO;AAAA,QACL,SAAS,GAAGC,UAAS,IAAI,KAAK,EAAE;AAAA,QAChC,UAAU,EAAE,GAAG,UAAU,IAAI,KAAK,GAAG;AAAA,MACvC;AAAA,IACF,CAAC;AACD,UAAM,SAAS;AAAA,MACb,cAAc;AAAA,MACd,MAAM;AAAA,MACN,OAAO,QAAQ;AAAA,MACf,MAAM,CAAC,EAAE,UAAU,QAAQ,KAAKA,WAAU,CAAC;AAAA,MAC3C,OAAO;AAAA,IACT;AACA,WAAO,IAAI,KAAK,MAAM;AAAA,EACxB,SAAS,KAAc;AACrB,YAAQ,MAAM,4BAA4B,GAAG;AAC7C,WAAO,IAAI,OAAO,GAAG,EAAE,KAAK;AAAA,MAC1B,cAAc;AAAA,MACd,OAAO;AAAA,QACL;AAAA,UACE,UAAU;AAAA,UACV,MAAM;AAAA,UACN,aAAa,OAAO,GAAG;AAAA,QACzB;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AACF;;;AC3CA,eAAsB,cACpB,KACA,KACmB;AACnB,QAAM,aAAa,gBAAgB,KAAK,GAAG;AAC3C,MAAI,mBAAmB,WAAY,QAAO,WAAW;AAErD,QAAM,KAAK,OAAO,IAAI,OAAO,EAAE;AAC/B,QAAM,MAAM,IAAI;AAChB,QAAM,EAAE,UAAU,aAAa,MAAM,SAAS,UAAU,IAAI;AAC5D,QAAM,OAAO,WAAW;AACxB,QAAM,UAAU;AAAA,IACd,GAAG;AAAA,IACH,cAAc;AAAA,IACd;AAAA,IACA,MAAM;AAAA,MACJ,GAAK,MAAM,QAAmB,CAAC;AAAA,MAC/B,aAAa;AAAA,MACb,WAAW;AAAA,IACb;AAAA,EACF;AACA,QAAM,UAAU,qBAAqB;AAErC,MAAI;AACF,UAAM,WAAW,MAAM,QAAQ,SAAS,QACrC,IAAI,EAAE,UAAU,aAAa,IAAI,IAAIC,IAAG,CAAC,EACzC,GAAG;AACN,QAAI,CAAC,SAAS,MAAM;AAClB,aAAO,IAAI,OAAO,GAAG,EAAE,KAAK;AAAA,QAC1B,cAAc;AAAA,QACd,OAAO;AAAA,UACL;AAAA,YACE,UAAU;AAAA,YACV,MAAM;AAAA,YACN,aAAa,WAAW,EAAE;AAAA,UAC5B;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AACA,UAAM,eACJ,SAAS,KAAK,YAAY,OAEpB,KAAK,MAAM,mBAAmB,SAAS,KAAK,QAAQ,CAAC,EAGrD,OACF;AACN,UAAM,kBAAkB;AAAA,MACtB,GAAG;AAAA,MACH,MAAM;AAAA,QACH,QAAQ,QAAgD;AAAA,QACzD;AAAA,UACE,cAAc;AAAA,UACd,cAAc;AAAA,UACd,gBAAgB;AAAA,QAClB;AAAA,MACF;AAAA,IACF;AACA,UAAM,QAAQ,SAAS,QACpB,MAAM,EAAE,UAAU,aAAa,IAAI,IAAIA,IAAG,CAAC,EAC3C,IAAI;AAAA,MACH,UAAU,iBAAiB,KAAK,UAAU,eAAe,CAAC;AAAA,MAC1D,aAAa;AAAA,IACf,CAAC,EACA,GAAG;AACN,WAAO,IAAI,KAAK,eAAe;AAAA,EACjC,SAAS,KAAc;AACrB,YAAQ,MAAM,sBAAsB,GAAG;AACvC,WAAO,IAAI,OAAO,GAAG,EAAE,KAAK;AAAA,MAC1B,cAAc;AAAA,MACd,OAAO;AAAA,QACL,EAAE,UAAU,SAAS,MAAM,aAAa,aAAa,OAAO,GAAG,EAAE;AAAA,MACnE;AAAA,IACF,CAAC;AAAA,EACH;AACF;;;AP1EA,IAAMC,UAAyBC,SAAQ,OAAO;AAE9CD,QAAO,IAAI,KAAK,YAAY;AAC5BA,QAAO,IAAI,QAAQ,cAAc;AACjCA,QAAO,KAAK,KAAK,aAAa;AAC9BA,QAAO,IAAI,QAAQ,aAAa;AAChCA,QAAO,OAAO,QAAQ,aAAa;;;AfNnC,IAAM,MAAeE,SAAQ;AAE7B,IAAI,IAAI,eAAe,KAAK;AAC5B,IAAI,IAAI,SAAS,KAAK,KAAK,WAAW,OAAO,CAAC;AAI9C,IAAI,IAAIA,SAAQ,KAAK,CAAC;AACtB,IAAI,IAAIA,SAAQ,WAAW,EAAE,UAAU,KAAK,CAAC,CAAC;AAE9C,IAAI,IAAI,2BAA2B;AAGnC,IAAI,IAAI,CAAC,KAAc,KAAe,SAAuB;AAC3D,MAAI,IAAI,WAAW,WAAW;AAC5B,QAAI,OAAO,GAAG,EAAE,IAAI;AACpB;AAAA,EACF;AACA,OAAK;AACP,CAAC;AAED,IAAI,IAAI,KAAK,CAAC,MAAe,QAAkB;AAC7C,SAAO,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,SAAS,qBAAqB,CAAC;AAC/D,CAAC;AAGD,IAAI,IAAI,CAAC,YAAY,gBAAgB,GAAG,uBAAuB;AAG/D,IAAI,IAAI,YAAYC,OAAa;AAGjC,IAAI,IAAI,kBAAkB,MAAmB;;;ADpCtC,IAAM,UAAU,kBAAkB,EAAE,IAAI,CAAC;","names":["express","envelope","Entity","BASE_PATH","client","express","BASE_PATH","SK","SK","BASE_PATH","SK","SK","BASE_PATH","SK","router","express","express","router"]}
package/package.json CHANGED
@@ -3,7 +3,6 @@
3
3
  "devDependencies": {
4
4
  "@swc/core": "^1.15.13",
5
5
  "@swc/jest": "^0.2.39",
6
- "@types/cors": "^2.8.19",
7
6
  "@types/express": "5.0.6",
8
7
  "@types/jest": "^30.0.0",
9
8
  "@types/node": "^22.19.12",
@@ -39,21 +38,20 @@
39
38
  "@types/aws-lambda": "^8.10.160",
40
39
  "awscdk-appsync-utils": "^0.0.850",
41
40
  "change-case": "^4.0",
42
- "cors": "^2.8.6",
43
41
  "electrodb": "^3.6.2",
44
42
  "esbuild": "^0.27.3",
45
43
  "express": "^5.2.1",
46
44
  "type-fest": "^4",
47
45
  "ulid": "*",
48
- "@openhi/config": "0.0.0",
49
- "@openhi/types": "0.0.0"
46
+ "@openhi/types": "0.0.0",
47
+ "@openhi/config": "0.0.0"
50
48
  },
51
49
  "main": "lib/index.js",
52
50
  "license": "UNLICENSED",
53
51
  "publishConfig": {
54
52
  "access": "public"
55
53
  },
56
- "version": "0.0.27",
54
+ "version": "0.0.29",
57
55
  "types": "lib/index.d.ts",
58
56
  "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"npx projen\".",
59
57
  "scripts": {