fhir-persistence 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,17 +5,95 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.3.0] - 2025-03-15
9
+
10
+ ### Added
11
+
12
+ #### Dual-Backend Validation
13
+
14
+ - Comprehensive dual-backend test suite (`dual-backend-validation.test.ts`) — 41 tests covering SQLite and PostgreSQL
15
+ - **Schema DDL correctness** — generate and execute DDL on both backends, verify tables/columns/indexes
16
+ - **IG lifecycle validation** — `compareSchemas` → `generateMigration` → apply migration on both backends
17
+ - **CRUD correctness** — `FhirStore` create/read/update/delete/history/vread on both SQLite and PostgreSQL
18
+ - Transaction atomicity verification on PostgreSQL
19
+ - Optimistic locking (`ifMatch`) verification on both backends
20
+ - History auto-increment `versionSeq` verification on PostgreSQL
21
+
22
+ #### PostgreSQL Integration Tests
23
+
24
+ - 23 PostgreSQL integration tests (`postgres-adapter.integration.test.ts`) covering CRUD, transactions, DDL generation, migrations, search SQL generation, concurrency, streaming, and NULL handling
25
+
26
+ ### Changed
27
+
28
+ - `buildTableSet` helper now accepts parameterized `resourceType` for test isolation (unique constraint/index names per test run)
29
+ - Schema DDL comparison tests verify CREATE TABLE count parity rather than exact statement count (SQLite generates extra AUTOINCREMENT index)
30
+
31
+ ### Fixed
32
+
33
+ - PostgreSQL test isolation — unique resource type names per run prevent constraint/index name collisions across test runs
34
+
35
+ ### Test Coverage
36
+
37
+ - **1014 total tests** (1006 passing, 8 skipped) across 56 test files
38
+ - Full CRUD verified on both SQLite (in-memory) and PostgreSQL (localhost:5433)
39
+
40
+ ## [0.2.0] - 2025-03-15
41
+
42
+ ### Added
43
+
44
+ #### PostgreSQL Support
45
+
46
+ - `PostgresAdapter` — full `StorageAdapter` implementation for PostgreSQL via `pg` Pool
47
+ - Automatic `?` → `$1, $2, ...` placeholder rewriting
48
+ - Transaction support via pool client + BEGIN/COMMIT/ROLLBACK
49
+ - `queryStream` via cursor-like row iteration
50
+ - Serialization failure retry (40001) with exponential backoff
51
+ - `close()` guard preventing use-after-close
52
+ - `PostgresDialect` — `SqlDialect` implementation for PostgreSQL
53
+ - Native `TEXT[]` array operators (`&&`, `@>`) instead of `json_each()`
54
+ - `GENERATED ALWAYS AS IDENTITY` for history sequence columns
55
+ - PostgreSQL-native type mappings (TIMESTAMPTZ, TEXT[], BOOLEAN, etc.)
56
+ - 23 PostgreSQL integration tests covering CRUD, transactions, DDL, migrations, search SQL, concurrency, streaming, and NULL handling
57
+
58
+ #### SQL Dialect Abstraction
59
+
60
+ - `SqlDialect` interface — abstracts SQL syntax differences between SQLite and PostgreSQL
61
+ - `SQLiteDialect` — `SqlDialect` implementation for SQLite (json_each, AUTOINCREMENT)
62
+ - Dialect-aware WHERE builders: `arrayContainsV2`, `arrayNotContainsV2`, `arrayContainsLikeV2`
63
+ - `buildWhereFragmentV2`, `buildWhereClauseV2` now accept optional `dialect` parameter
64
+ - `buildSearchSQLv2`, `buildCountSQLv2`, `buildTwoPhaseSearchSQLv2` accept optional `dialect`
65
+ - `executeSearchV2` accepts `dialect` via options
66
+ - Compartment filters are dialect-aware (json_each for SQLite, ARRAY operators for PostgreSQL)
67
+
68
+ ### Changed
69
+
70
+ - `TransactionContext` methods (`execute`, `query`, `queryOne`) are now `async` (Promise-based)
71
+ - All transaction callers updated to `await` transaction operations
72
+ - `BetterSqlite3Adapter` transaction wraps sync operations in async interface
73
+
74
+ ### Removed
75
+
76
+ - `sql.js` WASM adapter (`SQLiteAdapter`) — replaced by `BetterSqlite3Adapter`
77
+ - `sql.js` dependency removed from `package.json`
78
+
79
+ ### Dependencies
80
+
81
+ - `pg` ^8.0.0 added as optional peer dependency
82
+ - `pg` ^8.20.0, `@types/pg` ^8.18.0 added as dev dependencies
83
+
8
84
  ## [0.1.0] - 2025-03-13
9
85
 
10
86
  ### Added
11
87
 
12
88
  #### Storage Layer
89
+
13
90
  - `StorageAdapter` interface — unified async database abstraction
14
91
  - `SQLiteAdapter` — sql.js (WASM) implementation for cross-platform use
15
92
  - `BetterSqlite3Adapter` — native better-sqlite3 implementation for production Node.js
16
93
  - `SQLiteDialect` / PostgreSQL dialect support for DDL generation
17
94
 
18
95
  #### CRUD & Persistence
96
+
19
97
  - `FhirStore` — basic CRUD with soft delete and version tracking
20
98
  - `FhirPersistence` — end-to-end facade with automatic search indexing
21
99
  - `ConditionalService` — conditionalCreate / conditionalUpdate / conditionalDelete
@@ -24,6 +102,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
24
102
  - History tracking via dedicated `_History` tables
25
103
 
26
104
  #### Search
105
+
27
106
  - `SearchParameterRegistry` — index FHIR SearchParameter bundles
28
107
  - `parseSearchRequest` — parse FHIR search URL query parameters
29
108
  - `buildSearchSQLv2` — generate SQL with `?` placeholders (SQLite + PG compatible)
@@ -34,6 +113,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
34
113
  - `SearchBundleBuilder` — FHIR Bundle response construction with pagination
35
114
 
36
115
  #### Indexing
116
+
37
117
  - `IndexingPipeline` — automatic search column population on CRUD
38
118
  - `RuntimeProvider` bridge — FHIRPath-driven extraction via `fhir-runtime`
39
119
  - `buildSearchColumns` — fallback property-path extraction
@@ -42,6 +122,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
42
122
  - Column strategy, token-column strategy, lookup-table strategy
43
123
 
44
124
  #### Schema & Migration
125
+
45
126
  - `StructureDefinitionRegistry` — register and resolve FHIR StructureDefinitions
46
127
  - `buildResourceTableSet` — StructureDefinition + SearchParameter → table schema
47
128
  - DDL generators for Main, History, References tables + indexes
@@ -53,25 +134,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
53
134
  - `ReindexScheduler` — schedule reindex jobs for SP expression changes
54
135
 
55
136
  #### Terminology
137
+
56
138
  - `TerminologyCodeRepo` — code system concept storage
57
139
  - `ValueSetRepo` — value set expansion storage
58
140
 
59
141
  #### Platform IG
142
+
60
143
  - Built-in platform resource types: User, Bot, Project, Agent, ClientApplication
61
144
  - `initializePlatformIG` — auto-register platform search parameters
62
145
 
63
146
  #### Provider Bridges (for fhir-engine)
147
+
64
148
  - `FhirDefinitionBridge` — wraps `fhir-definition` `DefinitionRegistry` → `DefinitionProvider`
65
149
  - `FhirRuntimeProvider` — wraps `fhir-runtime` `FhirRuntimeInstance` → `RuntimeProvider`
66
150
  - `FhirSystem` — end-to-end startup orchestrator
67
151
 
68
152
  #### Production
153
+
69
154
  - `ResourceCacheV2` — in-memory resource cache with TTL
70
155
  - `SearchLogger` — search query logging and diagnostics
71
156
  - `reindexResourceTypeV2` / `reindexAllV2` — CLI reindex utilities
72
157
 
73
158
  ### Dependencies
159
+
74
160
  - `fhir-definition` ^0.5.0
75
161
  - `fhir-runtime` ^0.8.1
76
162
  - `better-sqlite3` ^12.6.2
77
- - `sql.js` ^1.14.1
package/README.md CHANGED
@@ -5,10 +5,13 @@ Embedded FHIR R4 persistence layer — CRUD, search, indexing, and schema migrat
5
5
  [![npm version](https://img.shields.io/npm/v/fhir-persistence)](https://www.npmjs.com/package/fhir-persistence)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE)
7
7
 
8
+ > **v0.3.0** — Dual-backend validated: 1014 tests across SQLite and PostgreSQL
9
+
8
10
  ## Features
9
11
 
10
12
  - **StorageAdapter abstraction** — unified async interface for SQLite and PostgreSQL
11
- - **Two SQLite adapters** — `BetterSqlite3Adapter` (native, production) + `SQLiteAdapter` (sql.js WASM, cross-platform)
13
+ - **Dual-backend support** — `BetterSqlite3Adapter` (native SQLite) + `PostgresAdapter` (pg)
14
+ - **SqlDialect abstraction** — dialect-aware SQL generation for array operators, DDL, upsert
12
15
  - **3-table-per-resource pattern** — Main + History + References tables per FHIR resource type
13
16
  - **Automatic search indexing** — column, token-column, and lookup-table strategies
14
17
  - **FHIRPath-driven extraction** — optional `RuntimeProvider` bridge for `fhir-runtime` powered indexing
@@ -34,21 +37,24 @@ npm install fhir-persistence
34
37
 
35
38
  ```bash
36
39
  npm install fhir-definition fhir-runtime
40
+
41
+ # For PostgreSQL support (optional):
42
+ npm install pg
37
43
  ```
38
44
 
39
45
  ## Quick Start
40
46
 
41
- ### Standalone (low-level)
47
+ ### SQLite (standalone)
42
48
 
43
49
  ```typescript
44
50
  import {
45
- SQLiteAdapter,
51
+ BetterSqlite3Adapter,
46
52
  FhirPersistence,
47
53
  SearchParameterRegistry,
48
54
  } from "fhir-persistence";
49
55
 
50
56
  // 1. Create storage adapter
51
- const adapter = new SQLiteAdapter(":memory:");
57
+ const adapter = new BetterSqlite3Adapter({ path: "./fhir.db" });
52
58
 
53
59
  // 2. Create search parameter registry
54
60
  const spRegistry = new SearchParameterRegistry();
@@ -78,6 +84,29 @@ const result = await persistence.searchResources({
78
84
  });
79
85
  ```
80
86
 
87
+ ### PostgreSQL
88
+
89
+ ```typescript
90
+ import { PostgresAdapter, FhirStore } from "fhir-persistence";
91
+ import { Pool } from "pg";
92
+
93
+ const pool = new Pool({
94
+ host: "localhost",
95
+ port: 5432,
96
+ database: "fhir_db",
97
+ user: "postgres",
98
+ password: "secret",
99
+ });
100
+ const adapter = new PostgresAdapter(pool);
101
+ const store = new FhirStore(adapter);
102
+
103
+ // Same CRUD API as SQLite — no code changes needed
104
+ const patient = await store.createResource("Patient", {
105
+ resourceType: "Patient",
106
+ name: [{ family: "Smith" }],
107
+ });
108
+ ```
109
+
81
110
  ### With FhirSystem (recommended for fhir-engine)
82
111
 
83
112
  ```typescript
@@ -96,7 +125,7 @@ const adapter = new BetterSqlite3Adapter({ path: './fhir.db' });
96
125
  const definitionBridge = new FhirDefinitionBridge(registry);
97
126
 
98
127
  // 4. Bootstrap via FhirSystem
99
- const system = new FhirSystem(adapter, { dialect: 'sqlite' });
128
+ const system = new FhirSystem(adapter, { dialect: 'sqlite' }); // or 'postgres'
100
129
  const { persistence, sdRegistry, spRegistry, igResult } =
101
130
  await system.initialize(definitionBridge);
102
131
 
@@ -107,9 +136,13 @@ const patient = await persistence.createResource('Patient', { resourceType: 'Pat
107
136
  ## Architecture
108
137
 
109
138
  ```
110
- StorageAdapter (SQLite / better-sqlite3 / PostgreSQL)
139
+ StorageAdapter (BetterSqlite3Adapter / PostgresAdapter)
140
+
141
+ ├── SqlDialect (SQLiteDialect / PostgresDialect)
142
+ │ └── Dialect-aware: DDL, array operators, upsert, timestamps
143
+
111
144
  └── FhirPersistence (end-to-end facade)
112
- ├── FhirStore (basic CRUD + soft delete + versioning)
145
+ ├── FhirStore (basic CRUD + soft delete + optimistic locking + versioning)
113
146
  ├── IndexingPipeline
114
147
  │ ├── RuntimeProvider (FHIRPath extraction, optional)
115
148
  │ ├── buildSearchColumns (fallback row indexer)
@@ -119,7 +152,7 @@ StorageAdapter (SQLite / better-sqlite3 / PostgreSQL)
119
152
  ├── BundleProcessorV2 (transaction / batch)
120
153
  ├── SearchParameterRegistry
121
154
  └── Search Engine
122
- ├── WhereBuilder v2 (chain search, ? placeholders)
155
+ ├── WhereBuilder v2 (chain search, dialect-aware)
123
156
  ├── SearchPlanner (filter reorder, two-phase recommendation)
124
157
  ├── SearchSQLBuilder v2 (single-phase + two-phase)
125
158
  ├── SearchExecutor (_include / _revinclude / _include:iterate)
@@ -131,8 +164,7 @@ StorageAdapter (SQLite / better-sqlite3 / PostgreSQL)
131
164
  | Adapter | Backend | Use Case |
132
165
  | ---------------------- | ----------------------- | --------------------------------- |
133
166
  | `BetterSqlite3Adapter` | better-sqlite3 (native) | Production Node.js, CLI, Electron |
134
- | `SQLiteAdapter` | sql.js (WASM) | Browser, cross-platform, testing |
135
- | `PostgresAdapter` | pg | Production server |
167
+ | `PostgresAdapter` | pg (connection pool) | Production server |
136
168
 
137
169
  ## Search
138
170
 
@@ -155,7 +187,7 @@ const request = parseSearchRequest(
155
187
  registry,
156
188
  );
157
189
 
158
- // Single-phase SQL
190
+ // Single-phase SQL (dialect-aware)
159
191
  const sql = buildSearchSQLv2(request, registry);
160
192
 
161
193
  // Two-phase SQL for large tables
@@ -184,7 +216,8 @@ The IG persistence manager automatically handles schema evolution:
184
216
  ```typescript
185
217
  import { IGPersistenceManager } from "fhir-persistence";
186
218
 
187
- const igManager = new IGPersistenceManager(adapter, "sqlite");
219
+ // Dialect: 'sqlite' or 'postgres'
220
+ const igManager = new IGPersistenceManager(adapter, "postgres");
188
221
  const result = await igManager.initialize({
189
222
  name: "hl7.fhir.r4.core",
190
223
  version: "4.0.1",
@@ -202,7 +235,7 @@ const result = await igManager.initialize({
202
235
  import { createFhirEngine } from "fhir-engine";
203
236
 
204
237
  const engine = await createFhirEngine({
205
- database: { type: "sqlite", url: "./fhir.db" },
238
+ database: { type: "sqlite", url: "./fhir.db" }, // or { type: 'postgres', ... }
206
239
  packages: { path: "./fhir-packages" },
207
240
  });
208
241
 
@@ -218,7 +251,7 @@ const engine = await createFhirEngine({
218
251
  | `fhir-definition` | StructureDefinition, SearchParameter, ValueSet, CodeSystem definitions |
219
252
  | `fhir-runtime` | FHIRPath evaluation, validation, search value extraction |
220
253
  | `better-sqlite3` | Native SQLite bindings (production) |
221
- | `sql.js` | WebAssembly SQLite (cross-platform) |
254
+ | `pg` | PostgreSQL client (optional peer dependency) |
222
255
 
223
256
  ## License
224
257