2026-03-26 16:52:54 +07:00
# Workstation Middleware Plan (Node.js + SQLite)
## Goal
Build a lightweight Node.js service that:
- receives laboratory messages from instruments
- normalizes every protocol into a single JSON payload
- queues payloads in SQLite for durability
- reliably delivers results to CLQMS with retries
## Responsibilities
2026-04-07 11:30:11 +07:00
- **Middleware:** connector protocols (HTTP JSON, HL7 TCP, ASTM serial), raw payload capture + translation/normalization, schema checks, durable queue, retries, dead-letter, logging, health endpoints.
2026-03-26 16:52:54 +07:00
- **CLQMS:** domain validation, mapping rules, result persistence, workflow/flags/audit.
## Flow
1. Connector captures raw message and writes to `inbox_raw` .
2026-04-07 11:30:11 +07:00
2. Pipeline wraps raw payloads into a canonical envelope.
2026-03-26 16:52:54 +07:00
3. Normalizer maps the object to canonical JSON.
4. Payload lands in `outbox_result` as `pending` .
5. Delivery worker sends to CLQMS and logs attempts.
6. On success mark `processed` ; on failure mark `retrying` or `dead_letter` after max retries.
## Tech Stack (JS only)
- Node.js 20+
- SQLite via `sqlite3`
- `zod` for validation
- `pino` for logs
- `undici` (or `axios` ) for HTTP delivery
## Suggested Layout
```text
2026-04-07 11:30:11 +07:00
core/
2026-03-26 16:52:54 +07:00
src/
connectors/
2026-04-07 11:30:11 +07:00
pipeline/
2026-04-07 07:30:07 +07:00
domain/
runtime/
2026-03-26 16:52:54 +07:00
storage/
2026-04-07 07:30:07 +07:00
scripts/
2026-03-26 16:52:54 +07:00
utils/
index.js
db/migrations/
config/
logs/
```
## Connector Contract
Each connector exposes:
```js
{
name: () => string,
type: () => "hl7-tcp" | "astm-serial" | "http-json",
start: async () => {},
stop: async () => {},
health: () => ({ status: "up" | "down" | "degraded" }),
onMessage: async (msg) => {},
onError: (handler) => {}
}
```
## Canonical Payload
Required fields: `instrument_id` , `sample_id` , `result_time` , `results[]` (each with `test_code` and `value` ).
Optional: `unit` , `flag` , `patient_id` , `operator_id` , `meta` .
Example:
```json
{
"instrument_id": "SYSMEX_XN1000",
"sample_id": "SMP-20260326-001",
"result_time": "2026-03-26T10:20:00Z",
"results": [
{
"test_code": "WBC",
"value": "8.2",
"unit": "10^3/uL",
"flag": "N"
}
],
"meta": {
"source_protocol": "HL7",
"message_id": "msg-123",
"connector": "hl7-tcp"
}
}
```
## SQLite Tables
- `inbox_raw` : raw payloads, connector, parse status.
- `outbox_result` : canonical payload, delivery status, retry metadata, `dedupe_key` .
- `delivery_log` : attempt history, latency, responses.
- `instrument_config` : connector settings, enabled flag.
- `dead_letter` : failed payloads, reason, timestamp.
## Retry + Idempotency
- Backoff: `30s -> 2m -> 10m -> 30m -> 2h -> 6h` , max 10 attempts.
- Retry transient failures (timeouts, DNS/connection, HTTP 5xx); skip HTTP 400/422 or validation errors.
- After max attempts move to `dead_letter` with payload + last error.
- `dedupe_key` = hash of `instrument_id + sample_id + test_code + result_time` ; unique index on `outbox_result.dedupe_key` ensures idempotent replay.
## Security & Observability
- CLQMS auth token stored in env, never hardcoded.
- Optional IP allowlist, strict TLS + request timeouts, mask sensitive logs.
- Health: `GET /health` , `GET /health/ready` (DB + workers).
- Metrics: pending size, retrying count, dead letters, last successful delivery.
- Alerts: backlog limits, dead letters increase, stale success timestamp.
## Delivery Phases
1. **Phase 1 (MVP):** scaffold Node app, add SQLite + migrations, build `http-json` connector, parser/normalizer, outbox worker, retries, dead-letter, pilot with one instrument.
2. **Phase 2:** add HL7 TCP and ASTM connectors, parser/normalizer per connector, instrument-specific config.
3. **Phase 3:** richer metrics/dashboards, backup + maintenance scripts, integration/failure tests, runbook + incident checklist.
## MVP Done When
- One instrument end-to-end with no loss during CLQMS downtime.
- Retries, dead-letter, and duplicate protection verified.
- Health checks and logs available for operations.
## Phase 2 Completion Notes
2026-04-07 11:30:11 +07:00
- Instruments are provisioned from `config/app.yaml` , a single file containing `host` runtime settings and `instruments[]` entries with embedded connector, match, config, and translator settings.
2026-04-06 16:50:17 +07:00
- The `/instruments` route is read-only for visibility; instrument onboarding is file-driven.
- Each connector validates against loaded instrument files so only known, enabled equipment is accepted.
2026-03-26 16:52:54 +07:00
- Deduplication now guarded by SHA-256 `dedupe_key` , and instrument metadata is carried through the pipeline.
## Metrics & Observability
- Health router provides `/health` (status) and `/health/ready` (DB + worker) plus metrics per connector.
- Prometheus-friendly `/metrics` exports pending/retrying/dead-letter counts, delivery attempts, last success timestamp, and average latency.
- Logs/pino already mask PII by design; connectors emit structured errors and handshake timing for alerts.
## Maintenance, Runbook & Automation
2026-04-07 11:30:11 +07:00
- SQLite maintenance script (`node core/maintenance/maintenance.js` ) supports `backup` , `vacuum` , and `prune --days=<n>` to keep the DB performant and reproducible.
2026-03-26 16:52:54 +07:00
- Daily/weekly checklist: run backup before deployments, vacuum monthly, and prune `delivery_log` older than 30 days (configurable via CLI).
- Incident checklist: 1) check `/health/ready` ; 2) inspect `outbox_result` + `dead_letter` ; 3) replay payloads with `pending` or `retrying` status; 4) rotate CLQMS token via env + restart; 5) escalate when dead letters spike or metrics show stale success timestamp.
## Testing & Validation
2026-04-07 11:30:11 +07:00
- Raw payload smoke tests under `core/rawPipeline.test.js` verify canonical output and keep `normalize()` coverage intact. Run via `npm test` .
2026-04-06 16:50:17 +07:00
- Instrument config integrity check runs via `npm run instrument:check` ; startup performs the same validation and fails fast on errors.
2026-03-26 16:52:54 +07:00
- Future CI can run the same script plus `npm run migrate` ahead of any pull request to ensure schema/queue logic still applies.