Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
319 changes: 319 additions & 0 deletions docs/design/12-connector-parity-gap-analysis.md

Large diffs are not rendered by default.

239 changes: 239 additions & 0 deletions docs/design/13-non-connector-gap-analysis.md

Large diffs are not rendered by default.

236 changes: 236 additions & 0 deletions docs/design/14-beyond-mirth-competitive-gaps.md

Large diffs are not rendered by default.

59 changes: 59 additions & 0 deletions docs/progress/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,65 @@ The foundation. A single-server deployment that can replace Mirth Connect for co
- [x] DICOM (source + destination) — C-STORE via dcmtk.js
- [x] Email/IMAP (source) — folder polling with filters

### Connector Parity Gaps vs Mirth/OIE 4.6.0

> Full analysis + fix specs + migration matrix: [`docs/design/12-connector-parity-gap-analysis.md`](../design/12-connector-parity-gap-analysis.md).
> The connectors above exist but several are **partial** — these gaps block whole classes of channel migration. Ranked by channels unblocked, not effort.

- [ ] **[1] Multi-vendor Database connector** — driver abstraction (pg/mysql2/tedious/oracledb) + JS mode. 🔴 Postgres-only today; Oracle/SQL Server/MySQL DB channels **cannot migrate**.
- [ ] **[2] File scheme abstraction** — FTP/FTPS, SMB, S3; fold standalone SFTP in as a scheme. 🔴 Local-disk-only today.
- [ ] **[3] Configurable TCP framing + Basic mode** — 🔴 start/end bytes hardcoded VT/FS+CR; no non-standard/delimited framing.
- [ ] **[4] Surface hidden runtime options in forms** — TCP TLS/charset/responseMode exist in runtime but not the UI; audit all connectors. 🟡 cheapest wins.
- [ ] **[5] HTTP outbound auth (Basic/Digest) + query-parameters table + `${}` substitution** — 🟡 common REST requirement.
- [ ] **[6] Web Service / SOAP connector** (source + dispatcher) — 🔴 blocks brownfield SOAP hospital installs.
- [ ] **[7] Document Writer** (PDF/RTF destination) — 🟡 no workaround for rendered clinical docs.
- [ ] **[8] DICOM TLS** (cert/key/trust) — 🟡 secured PACS. (Assoc-rejection bug already fixed, D-181.)
- [ ] **[9] SMTP multi-attachment** — 🟡 replace single hardcoded `message.txt`.
- [ ] **[10] Binary data type** across TCP/HTTP — 🟡 imaging/PDF/non-text feeds.
- [ ] **[11] JMS connector** — 🟡 defer unless a customer needs it.
- [ ] **[12] WebDAV File scheme** — 🟢 rare; do last alongside File schemes.

### Non-Connector Parity Gaps vs Mirth/OIE 4.6.0

> Full analysis: [`docs/design/13-non-connector-gap-analysis.md`](../design/13-non-connector-gap-analysis.md).
> Core lifecycle/alerts/monitoring are at parity or ahead. Gaps cluster in data types, scripting API, and a few "half-built" engine features (schema exists, runtime doesn't use it). Ranked by impact, not effort.

- [ ] **[N1] EDI/X12 + NCPDP serializers** (+ enum entries) — 🔴 blocks X12 billing/eligibility + pharmacy channels.
- [ ] **[N2] Finish declared-but-pass-through datatypes** — Delimited, HL7v3, DICOM→XML. 🔴 `msg` mapping impossible on them today.
- [ ] **[N3] Fix `$gc` map-shortcut collision** (means configMap; Mirth = globalChannelMap) + add `$cfg`/`$c`/`$co`/`$s` — 🔴 silent-wrong-map footgun for ported scripts.
- [ ] **[N4] Message search operators** — metadata-column search, regex, error, send-attempt filters. 🔴 daily-use triage gap.
- [ ] **[N5] Populate custom metadata columns + make searchable** — 🔴 currently stored but never populated (half-built).
- [ ] **[N6] Dependency-ordered deploy** (topological sort in `autoDeployChannels`) — 🔴 graph stored/validated but not applied; channel can start before its dependency.
- [ ] **[N7] Destination-queue threading** (threadCount/groupBy/rotate) or remove dead schema fields — 🔴 throughput/ordering at volume.
- [ ] **[N8] Pruner archiver + content/metadata-day split** — 🟡 `pruningArchiveEnabled` flag has no archiver (half-built); HIPAA retention.
- [ ] **[N9] JS API breadth** — DateUtil, ChannelUtil, AttachmentUtil, SerializerFactory, getArrayOrXmlLength, etc. (~10 of Mirth's ~40 userutil objects). 🟡
- [ ] **[N10] Per-connector connection monitoring** (Mirth dashboardstatus) + per-channel Prometheus labels — 🔴 ops triage visibility.
- [ ] **[N11] Iterator transformer step** (+ XSLT / External Script) — 🟡 repeating-segment mapping UX.
- [ ] **[N12] Code-template per-channel library scoping** — 🟡 flat today (all templates to all channels).
- [ ] **[N13] Custom RBAC roles** (roles as permission sets vs 4 fixed) — 🟡 "dev who can't deploy to prod".
- [ ] **[N14] Export completeness** (templates/deps/groups/tags + set export) — 🟡
- [ ] **[N15] Alert per-connector scope + error-stage granularity** — 🟡
- [ ] **[DECIDE] Extension/plugin platform** — 🔴 strategic: Mirth's third-party plugin loader has no equivalent. Decide curated-built-in vs a modern manifest-based SPI. Not a scheduled task until decided.

### Beyond-Mirth Horizon (vs the broader integration landscape)

> Full analysis + pursue/consider/partner/non-goal calls: [`docs/design/14-beyond-mirth-competitive-gaps.md`](../design/14-beyond-mirth-competitive-gaps.md).
> These are **platform expansions beyond Mirth** (Mirth lacks most of them too), NOT parity gaps. A strategy menu, not a defect list — act only after the doc 12/13 blockers clear. Top PURSUE picks:

- [ ] **[B1] Silent-interface / SLA / heartbeat alerting** — 🟢 "no message in N min" + throughput/queue-depth thresholds. Deadliest failure mode; alert engine currently fires only on CHANNEL_ERROR.
- [ ] **[B2] HA / clustering / automatic failover** — 🟢 genuine production gap; multi-node today duplicates inbound on singleton sources.
- [ ] **[B3] Keyed partitioning / per-key ordering + scale** — 🟢 per-patient order + horizontal scale; the real fix for doc 13 N7 (dead threadCount/groupBy).
- [ ] **[B4] End-to-end lineage + OpenTelemetry tracing** — 🟢 cross-channel message journeys.
- [ ] **[B5] Interface test framework** — 🟢 assertion-based per-channel regression in CI; on-brand with testing mandate.
- [ ] **[B6] Environment promotion / config-as-code** — 🟢 formalize JSON export into dev→test→prod.
- [ ] **[B7] FHIR-native transform + IG/US-Core validation (in-pipeline) + HL7v2→FHIR mapping templates** — 🟢 biggest strategic direction; engine work, not becoming a FHIR server.
- [ ] **[B8] AI-assisted mapping + first-class LLM step** — 🟢 modern differentiator; PHI governance required for runtime use.
- [ ] **[B9] EIP primitives**: aggregator (on Collections), wire-tap, dead-letter queue — 🟢 engine-native.
- [ ] **[B10] OAuth2 credential vault** (handshake + auto token refresh) — 🟢 enables FHIR/SMART direction.
- [ ] **[PARTNER] Kafka connector, EMPI, terminology hosting, TEFCA/QHIN, Temporal, Debezium/CDC** — 🔗 integrate best-of-breed, don't rebuild.
- [ ] **[NON-GOAL] FHIR datastore, API gateway, SaaS marketplace, dev portal, streaming internals** — ⛔ front/beside a real tool.
- [ ] **[DECIDE] Multi-tenancy (SaaS vs on-prem), low-code visual canvas, how far up the FHIR-server stack** — business/identity calls, not engineering.

### API & Auth (done)
- [x] 131 REST endpoints across 37 route files
- [x] JWT + refresh tokens + session management
Expand Down
5 changes: 4 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
{
ignores: ['**/dist/**', '**/node_modules/**', '**/coverage/**'],
// Ambient declaration files legitimately use `declare var` for global
// (globalThis) augmentation — `no-var` is a false positive there — and are
// otherwise type-only, so exclude them from linting.
ignores: ['**/dist/**', '**/node_modules/**', '**/coverage/**', '**/*.d.ts'],
},
{
rules: {
Expand Down
15 changes: 13 additions & 2 deletions packages/connectors/src/http/http-dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ export interface HttpDispatcherConfig {
readonly headers: Readonly<Record<string, string>>;
readonly contentType: string;
readonly responseTimeout: number;
/**
* Explicit transport scheme. When 'HTTPS' the node:https path is authoritative
* (rather than inferring purely from the URL), so a configured HTTPS
* destination always applies its TLS options. Absent → legacy URL inference.
*/
readonly scheme?: string | undefined;
/**
* Optional client-side TLS material for HTTPS destinations: a custom/internal
* CA to trust, a client certificate + key for mutual TLS, and verification
Expand Down Expand Up @@ -91,8 +97,13 @@ export class HttpDispatcher implements DestinationConnectorRuntime {

// HTTPS with custom TLS material can't go through fetch (needs an undici
// Agent we don't depend on) — use node:https so ca/cert/key/reject apply.
const isHttps = new URL(this.config.url).protocol === 'https:';
if (isHttps && hasActiveTlsOptions(this.config.tls)) {
// scheme==='HTTPS' is authoritative: a configured HTTPS destination always
// takes the node:https path so its TLS options are honored, even when the
// material alone (e.g. a public-CA server with default verification) would
// otherwise have fallen back to fetch. Absent scheme → legacy URL inference.
const schemeHttps = this.config.scheme === 'HTTPS';
const isHttps = schemeHttps || new URL(this.config.url).protocol === 'https:';
if (isHttps && (schemeHttps || hasActiveTlsOptions(this.config.tls))) {
Comment on lines +104 to +106
return this.sendViaHttps(message, combinedSignal, bodyAllowed);
}

Expand Down
1 change: 1 addition & 0 deletions packages/connectors/src/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ const destinationFactories = new Map<string, DestinationFactory>([
headers: (props['headers'] as Record<string, string> | undefined) ?? {},
contentType: (props['contentType'] as string | undefined) ?? 'text/plain',
responseTimeout: num(props, 'responseTimeout', 30_000),
scheme: props['scheme'] as string | undefined,
tls: readTlsClientOptions(props),
})],
['FILE', (props): DestinationConnectorRuntime => new FileDispatcher({
Expand Down
2 changes: 1 addition & 1 deletion packages/engine/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "src/**/*.test.ts"]
"exclude": ["node_modules", "dist", "src/**/*.test.ts", "src/**/*.itest.ts", "src/__tests__/integration/**"]
}
5 changes: 5 additions & 0 deletions packages/server/src/db/seeds/run-seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,11 @@ async function seed(): Promise<void> {
// Seed showcase example data for otherwise-empty feature pages
const { seedShowcase } = await import('./seed-showcase.js');
await seedShowcase(exampleDb);

// Seed browsable example messages for the example channels (uses the
// MessageService write-path via the server db singleton — no db argument).
const { seedExampleMessages } = await import('./seed-example-messages.js');
await seedExampleMessages();
} finally {
await pool.end();
}
Expand Down
Loading
Loading