fhir-persistence 0.1.0 → 0.4.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +109 -1
  2. package/README.md +47 -14
  3. package/dist/cjs/index.cjs +413 -284
  4. package/dist/cjs/index.cjs.map +4 -4
  5. package/dist/cjs/index.d.ts +191 -40
  6. package/dist/esm/index.d.ts +191 -40
  7. package/dist/esm/index.mjs +410 -283
  8. package/dist/esm/index.mjs.map +4 -4
  9. package/dist/index.d.ts +191 -40
  10. package/dist/lib/db/adapter.d.ts +4 -4
  11. package/dist/lib/db/adapter.d.ts.map +1 -1
  12. package/dist/lib/db/index.d.ts +1 -2
  13. package/dist/lib/db/index.d.ts.map +1 -1
  14. package/dist/lib/db/postgres-adapter.d.ts +1 -19
  15. package/dist/lib/db/postgres-adapter.d.ts.map +1 -1
  16. package/dist/lib/index.d.ts +4 -1
  17. package/dist/lib/index.d.ts.map +1 -1
  18. package/dist/lib/migration/reindex-scheduler.d.ts +3 -1
  19. package/dist/lib/migration/reindex-scheduler.d.ts.map +1 -1
  20. package/dist/lib/migrations/migration-runner.d.ts +3 -1
  21. package/dist/lib/migrations/migration-runner.d.ts.map +1 -1
  22. package/dist/lib/registry/package-registry-repo.d.ts +7 -1
  23. package/dist/lib/registry/package-registry-repo.d.ts.map +1 -1
  24. package/dist/lib/repo/indexing-pipeline.d.ts +3 -0
  25. package/dist/lib/repo/indexing-pipeline.d.ts.map +1 -1
  26. package/dist/lib/repo/lookup-table-writer.d.ts +3 -1
  27. package/dist/lib/repo/lookup-table-writer.d.ts.map +1 -1
  28. package/dist/lib/search/search-executor.d.ts +4 -1
  29. package/dist/lib/search/search-executor.d.ts.map +1 -1
  30. package/dist/lib/search/search-sql-builder.d.ts +4 -3
  31. package/dist/lib/search/search-sql-builder.d.ts.map +1 -1
  32. package/dist/lib/search/where-builder.d.ts +7 -7
  33. package/dist/lib/search/where-builder.d.ts.map +1 -1
  34. package/dist/lib/startup/fhir-system.d.ts.map +1 -1
  35. package/dist/lib/terminology/valueset-repo.d.ts +3 -1
  36. package/dist/lib/terminology/valueset-repo.d.ts.map +1 -1
  37. package/package.json +12 -3
  38. package/dist/lib/db/sqlite-adapter.d.ts +0 -41
  39. package/dist/lib/db/sqlite-adapter.d.ts.map +0 -1
package/CHANGELOG.md CHANGED
@@ -5,17 +5,118 @@ 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.4.0] - 2025-03-15
9
+
10
+ ### Fixed
11
+
12
+ #### PostgreSQL DDL Compatibility (Phase D)
13
+
14
+ - **`migration-runner.ts`** — Tracking table `_migrations` now uses `TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP` on PostgreSQL instead of SQLite-only `datetime('now')`
15
+ - **`package-registry-repo.ts`** — `_packages` and `_schema_version` tables use dialect-aware timestamp defaults; `INSERT OR REPLACE` replaced with `INSERT ... ON CONFLICT ... DO UPDATE` on PostgreSQL
16
+ - **`reindex-scheduler.ts`** — `_reindex_jobs` table uses `SERIAL PRIMARY KEY` on PostgreSQL instead of `AUTOINCREMENT`; `datetime('now')` replaced with `CURRENT_TIMESTAMP`
17
+ - **`valueset-repo.ts`** — `terminology_valuesets` table uses dialect-aware timestamp defaults; `INSERT OR REPLACE` replaced with `INSERT ... ON CONFLICT ... DO UPDATE` on PostgreSQL
18
+ - **`lookup-table-writer.ts`** — 4 global lookup tables (`HumanName`, `Address`, `ContactPoint`, `Identifier`) use `SERIAL PRIMARY KEY` on PostgreSQL instead of `AUTOINCREMENT`
19
+
20
+ ### Changed
21
+
22
+ - **`ig-persistence-manager.ts`** — Now passes `dialect` to `PackageRegistryRepo`, `MigrationRunnerV2`, and `ReindexScheduler` constructors
23
+ - **`indexing-pipeline.ts`** — `IndexingPipelineOptions` accepts `dialect` parameter, passed to `LookupTableWriter`
24
+ - **`fhir-system.ts`** — Passes `dialect` through to `IndexingPipeline` via `FhirPersistence` options
25
+ - All new `dialect` parameters default to `'sqlite'` — fully backward-compatible
26
+
27
+ ### Test Coverage
28
+
29
+ - **1014 total tests** (1006 passing, 8 skipped) across 56 test files — no regressions
30
+
31
+ ## [0.3.0] - 2025-03-15
32
+
33
+ ### Added
34
+
35
+ #### Dual-Backend Validation
36
+
37
+ - Comprehensive dual-backend test suite (`dual-backend-validation.test.ts`) — 41 tests covering SQLite and PostgreSQL
38
+ - **Schema DDL correctness** — generate and execute DDL on both backends, verify tables/columns/indexes
39
+ - **IG lifecycle validation** — `compareSchemas` → `generateMigration` → apply migration on both backends
40
+ - **CRUD correctness** — `FhirStore` create/read/update/delete/history/vread on both SQLite and PostgreSQL
41
+ - Transaction atomicity verification on PostgreSQL
42
+ - Optimistic locking (`ifMatch`) verification on both backends
43
+ - History auto-increment `versionSeq` verification on PostgreSQL
44
+
45
+ #### PostgreSQL Integration Tests
46
+
47
+ - 23 PostgreSQL integration tests (`postgres-adapter.integration.test.ts`) covering CRUD, transactions, DDL generation, migrations, search SQL generation, concurrency, streaming, and NULL handling
48
+
49
+ ### Changed
50
+
51
+ - `buildTableSet` helper now accepts parameterized `resourceType` for test isolation (unique constraint/index names per test run)
52
+ - Schema DDL comparison tests verify CREATE TABLE count parity rather than exact statement count (SQLite generates extra AUTOINCREMENT index)
53
+
54
+ ### Fixed
55
+
56
+ - PostgreSQL test isolation — unique resource type names per run prevent constraint/index name collisions across test runs
57
+
58
+ ### Test Coverage
59
+
60
+ - **1014 total tests** (1006 passing, 8 skipped) across 56 test files
61
+ - Full CRUD verified on both SQLite (in-memory) and PostgreSQL (localhost:5433)
62
+
63
+ ## [0.2.0] - 2025-03-15
64
+
65
+ ### Added
66
+
67
+ #### PostgreSQL Support
68
+
69
+ - `PostgresAdapter` — full `StorageAdapter` implementation for PostgreSQL via `pg` Pool
70
+ - Automatic `?` → `$1, $2, ...` placeholder rewriting
71
+ - Transaction support via pool client + BEGIN/COMMIT/ROLLBACK
72
+ - `queryStream` via cursor-like row iteration
73
+ - Serialization failure retry (40001) with exponential backoff
74
+ - `close()` guard preventing use-after-close
75
+ - `PostgresDialect` — `SqlDialect` implementation for PostgreSQL
76
+ - Native `TEXT[]` array operators (`&&`, `@>`) instead of `json_each()`
77
+ - `GENERATED ALWAYS AS IDENTITY` for history sequence columns
78
+ - PostgreSQL-native type mappings (TIMESTAMPTZ, TEXT[], BOOLEAN, etc.)
79
+ - 23 PostgreSQL integration tests covering CRUD, transactions, DDL, migrations, search SQL, concurrency, streaming, and NULL handling
80
+
81
+ #### SQL Dialect Abstraction
82
+
83
+ - `SqlDialect` interface — abstracts SQL syntax differences between SQLite and PostgreSQL
84
+ - `SQLiteDialect` — `SqlDialect` implementation for SQLite (json_each, AUTOINCREMENT)
85
+ - Dialect-aware WHERE builders: `arrayContainsV2`, `arrayNotContainsV2`, `arrayContainsLikeV2`
86
+ - `buildWhereFragmentV2`, `buildWhereClauseV2` now accept optional `dialect` parameter
87
+ - `buildSearchSQLv2`, `buildCountSQLv2`, `buildTwoPhaseSearchSQLv2` accept optional `dialect`
88
+ - `executeSearchV2` accepts `dialect` via options
89
+ - Compartment filters are dialect-aware (json_each for SQLite, ARRAY operators for PostgreSQL)
90
+
91
+ ### Changed
92
+
93
+ - `TransactionContext` methods (`execute`, `query`, `queryOne`) are now `async` (Promise-based)
94
+ - All transaction callers updated to `await` transaction operations
95
+ - `BetterSqlite3Adapter` transaction wraps sync operations in async interface
96
+
97
+ ### Removed
98
+
99
+ - `sql.js` WASM adapter (`SQLiteAdapter`) — replaced by `BetterSqlite3Adapter`
100
+ - `sql.js` dependency removed from `package.json`
101
+
102
+ ### Dependencies
103
+
104
+ - `pg` ^8.0.0 added as optional peer dependency
105
+ - `pg` ^8.20.0, `@types/pg` ^8.18.0 added as dev dependencies
106
+
8
107
  ## [0.1.0] - 2025-03-13
9
108
 
10
109
  ### Added
11
110
 
12
111
  #### Storage Layer
112
+
13
113
  - `StorageAdapter` interface — unified async database abstraction
14
114
  - `SQLiteAdapter` — sql.js (WASM) implementation for cross-platform use
15
115
  - `BetterSqlite3Adapter` — native better-sqlite3 implementation for production Node.js
16
116
  - `SQLiteDialect` / PostgreSQL dialect support for DDL generation
17
117
 
18
118
  #### CRUD & Persistence
119
+
19
120
  - `FhirStore` — basic CRUD with soft delete and version tracking
20
121
  - `FhirPersistence` — end-to-end facade with automatic search indexing
21
122
  - `ConditionalService` — conditionalCreate / conditionalUpdate / conditionalDelete
@@ -24,6 +125,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
24
125
  - History tracking via dedicated `_History` tables
25
126
 
26
127
  #### Search
128
+
27
129
  - `SearchParameterRegistry` — index FHIR SearchParameter bundles
28
130
  - `parseSearchRequest` — parse FHIR search URL query parameters
29
131
  - `buildSearchSQLv2` — generate SQL with `?` placeholders (SQLite + PG compatible)
@@ -34,6 +136,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
34
136
  - `SearchBundleBuilder` — FHIR Bundle response construction with pagination
35
137
 
36
138
  #### Indexing
139
+
37
140
  - `IndexingPipeline` — automatic search column population on CRUD
38
141
  - `RuntimeProvider` bridge — FHIRPath-driven extraction via `fhir-runtime`
39
142
  - `buildSearchColumns` — fallback property-path extraction
@@ -42,6 +145,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
42
145
  - Column strategy, token-column strategy, lookup-table strategy
43
146
 
44
147
  #### Schema & Migration
148
+
45
149
  - `StructureDefinitionRegistry` — register and resolve FHIR StructureDefinitions
46
150
  - `buildResourceTableSet` — StructureDefinition + SearchParameter → table schema
47
151
  - DDL generators for Main, History, References tables + indexes
@@ -53,25 +157,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
53
157
  - `ReindexScheduler` — schedule reindex jobs for SP expression changes
54
158
 
55
159
  #### Terminology
160
+
56
161
  - `TerminologyCodeRepo` — code system concept storage
57
162
  - `ValueSetRepo` — value set expansion storage
58
163
 
59
164
  #### Platform IG
165
+
60
166
  - Built-in platform resource types: User, Bot, Project, Agent, ClientApplication
61
167
  - `initializePlatformIG` — auto-register platform search parameters
62
168
 
63
169
  #### Provider Bridges (for fhir-engine)
170
+
64
171
  - `FhirDefinitionBridge` — wraps `fhir-definition` `DefinitionRegistry` → `DefinitionProvider`
65
172
  - `FhirRuntimeProvider` — wraps `fhir-runtime` `FhirRuntimeInstance` → `RuntimeProvider`
66
173
  - `FhirSystem` — end-to-end startup orchestrator
67
174
 
68
175
  #### Production
176
+
69
177
  - `ResourceCacheV2` — in-memory resource cache with TTL
70
178
  - `SearchLogger` — search query logging and diagnostics
71
179
  - `reindexResourceTypeV2` / `reindexAllV2` — CLI reindex utilities
72
180
 
73
181
  ### Dependencies
182
+
74
183
  - `fhir-definition` ^0.5.0
75
184
  - `fhir-runtime` ^0.8.1
76
185
  - `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.4.0** — Full PostgreSQL DDL compatibility for all system and lookup tables
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