fhir-persistence 0.3.0 → 0.5.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 +43 -0
- package/README.md +1 -1
- package/dist/cjs/index.cjs +123 -53
- package/dist/cjs/index.cjs.map +2 -2
- package/dist/cjs/index.d.ts +17 -6
- package/dist/esm/index.d.ts +17 -6
- package/dist/esm/index.mjs +123 -53
- package/dist/esm/index.mjs.map +2 -2
- package/dist/index.d.ts +17 -6
- package/dist/lib/migration/migration-generator.d.ts.map +1 -1
- package/dist/lib/migration/reindex-scheduler.d.ts +3 -1
- package/dist/lib/migration/reindex-scheduler.d.ts.map +1 -1
- package/dist/lib/migrations/migration-runner.d.ts +3 -1
- package/dist/lib/migrations/migration-runner.d.ts.map +1 -1
- package/dist/lib/registry/package-registry-repo.d.ts +7 -1
- package/dist/lib/registry/package-registry-repo.d.ts.map +1 -1
- package/dist/lib/repo/indexing-pipeline.d.ts +3 -0
- package/dist/lib/repo/indexing-pipeline.d.ts.map +1 -1
- package/dist/lib/repo/lookup-table-writer.d.ts +3 -1
- package/dist/lib/repo/lookup-table-writer.d.ts.map +1 -1
- package/dist/lib/search/where-builder.d.ts +2 -2
- package/dist/lib/search/where-builder.d.ts.map +1 -1
- package/dist/lib/startup/fhir-system.d.ts.map +1 -1
- package/dist/lib/terminology/valueset-repo.d.ts +3 -1
- package/dist/lib/terminology/valueset-repo.d.ts.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,49 @@ 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.5.0] - 2025-03-16
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
#### PostgreSQL Migration Path Fixes
|
|
13
|
+
|
|
14
|
+
- **`migration-generator.ts`** — IG migration path now creates PostgreSQL extensions (`pg_trgm`, `btree_gin`) and helper function (`token_array_to_text`) before generating GIN indexes, fixing "no default operator class for access method gin" error
|
|
15
|
+
- **`where-builder.ts`** — Lookup table EXISTS subqueries now use fully-qualified `"ResourceType"."id"` instead of ambiguous `"id"`, fixing PostgreSQL "operator does not exist: text = integer" error in name/address/telecom searches
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- **`buildWhereFragment` (v1)** — Added optional `resourceType` parameter, passed to `buildLookupTableFragment`
|
|
20
|
+
- **`buildWhereFragmentV2` (v2)** — Added optional `resourceType` parameter, passed to `buildLookupTableFragmentV2`
|
|
21
|
+
- Both `buildLookupTableFragment*` functions now generate `outerIdRef = "ResourceType"."id"` to eliminate column name ambiguity in PostgreSQL
|
|
22
|
+
|
|
23
|
+
### Test Coverage
|
|
24
|
+
|
|
25
|
+
- **1014 total tests** (1006 passing, 8 skipped) across 56 test files — no regressions
|
|
26
|
+
- Updated 7 lookup table test assertions to use qualified column references
|
|
27
|
+
|
|
28
|
+
## [0.4.0] - 2025-03-15
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
#### PostgreSQL DDL Compatibility (Phase D)
|
|
33
|
+
|
|
34
|
+
- **`migration-runner.ts`** — Tracking table `_migrations` now uses `TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP` on PostgreSQL instead of SQLite-only `datetime('now')`
|
|
35
|
+
- **`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
|
|
36
|
+
- **`reindex-scheduler.ts`** — `_reindex_jobs` table uses `SERIAL PRIMARY KEY` on PostgreSQL instead of `AUTOINCREMENT`; `datetime('now')` replaced with `CURRENT_TIMESTAMP`
|
|
37
|
+
- **`valueset-repo.ts`** — `terminology_valuesets` table uses dialect-aware timestamp defaults; `INSERT OR REPLACE` replaced with `INSERT ... ON CONFLICT ... DO UPDATE` on PostgreSQL
|
|
38
|
+
- **`lookup-table-writer.ts`** — 4 global lookup tables (`HumanName`, `Address`, `ContactPoint`, `Identifier`) use `SERIAL PRIMARY KEY` on PostgreSQL instead of `AUTOINCREMENT`
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
|
|
42
|
+
- **`ig-persistence-manager.ts`** — Now passes `dialect` to `PackageRegistryRepo`, `MigrationRunnerV2`, and `ReindexScheduler` constructors
|
|
43
|
+
- **`indexing-pipeline.ts`** — `IndexingPipelineOptions` accepts `dialect` parameter, passed to `LookupTableWriter`
|
|
44
|
+
- **`fhir-system.ts`** — Passes `dialect` through to `IndexingPipeline` via `FhirPersistence` options
|
|
45
|
+
- All new `dialect` parameters default to `'sqlite'` — fully backward-compatible
|
|
46
|
+
|
|
47
|
+
### Test Coverage
|
|
48
|
+
|
|
49
|
+
- **1014 total tests** (1006 passing, 8 skipped) across 56 test files — no regressions
|
|
50
|
+
|
|
8
51
|
## [0.3.0] - 2025-03-15
|
|
9
52
|
|
|
10
53
|
### Added
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ Embedded FHIR R4 persistence layer — CRUD, search, indexing, and schema migrat
|
|
|
5
5
|
[](https://www.npmjs.com/package/fhir-persistence)
|
|
6
6
|
[](./LICENSE)
|
|
7
7
|
|
|
8
|
-
> **v0.
|
|
8
|
+
> **v0.5.0** — PostgreSQL migration path complete: GIN extensions + lookup table fixes
|
|
9
9
|
|
|
10
10
|
## Features
|
|
11
11
|
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -5202,12 +5202,12 @@ function arrayContainsLikeV2(col, value, dialect) {
|
|
|
5202
5202
|
}
|
|
5203
5203
|
return { sql: `EXISTS (SELECT 1 FROM json_each(${col}) WHERE json_each.value LIKE ?)`, values: [value] };
|
|
5204
5204
|
}
|
|
5205
|
-
function buildWhereFragmentV2(impl, param, dialect) {
|
|
5205
|
+
function buildWhereFragmentV2(impl, param, dialect, resourceType) {
|
|
5206
5206
|
if (param.modifier === "missing") {
|
|
5207
5207
|
return buildMissingFragmentV2(impl, param);
|
|
5208
5208
|
}
|
|
5209
5209
|
if (impl.strategy === "lookup-table") {
|
|
5210
|
-
return buildLookupTableFragmentV2(impl, param);
|
|
5210
|
+
return buildLookupTableFragmentV2(impl, param, resourceType ?? "Resource");
|
|
5211
5211
|
}
|
|
5212
5212
|
if (impl.strategy === "token-column") {
|
|
5213
5213
|
return buildTokenColumnFragmentV2(impl, param, dialect);
|
|
@@ -5235,7 +5235,7 @@ function buildMissingFragmentV2(impl, param) {
|
|
|
5235
5235
|
const col = quoteColumn(impl.columnName);
|
|
5236
5236
|
return { sql: isMissing ? `${col} IS NULL` : `${col} IS NOT NULL`, values: [] };
|
|
5237
5237
|
}
|
|
5238
|
-
function buildLookupTableFragmentV2(impl, param) {
|
|
5238
|
+
function buildLookupTableFragmentV2(impl, param, resourceType) {
|
|
5239
5239
|
const mapping = LOOKUP_TABLE_MAP[impl.code];
|
|
5240
5240
|
if (!mapping) {
|
|
5241
5241
|
const sortCol = quoteColumn(`__${impl.columnName}Sort`);
|
|
@@ -5245,25 +5245,26 @@ function buildLookupTableFragmentV2(impl, param) {
|
|
|
5245
5245
|
}
|
|
5246
5246
|
const { table, column } = mapping;
|
|
5247
5247
|
const colRef = `__lookup."${column}"`;
|
|
5248
|
+
const outerIdRef = `"${resourceType}"."id"`;
|
|
5248
5249
|
if (param.modifier === "exact") {
|
|
5249
5250
|
if (param.values.length === 1) {
|
|
5250
|
-
return { sql: `EXISTS (SELECT 1 FROM "${table}" __lookup WHERE __lookup."resourceId" =
|
|
5251
|
+
return { sql: `EXISTS (SELECT 1 FROM "${table}" __lookup WHERE __lookup."resourceId" = ${outerIdRef} AND ${colRef} = ?)`, values: [param.values[0]] };
|
|
5251
5252
|
}
|
|
5252
5253
|
const conds2 = param.values.map(() => `${colRef} = ?`);
|
|
5253
|
-
return { sql: `EXISTS (SELECT 1 FROM "${table}" __lookup WHERE __lookup."resourceId" =
|
|
5254
|
+
return { sql: `EXISTS (SELECT 1 FROM "${table}" __lookup WHERE __lookup."resourceId" = ${outerIdRef} AND (${conds2.join(" OR ")}))`, values: [...param.values] };
|
|
5254
5255
|
}
|
|
5255
5256
|
if (param.modifier === "contains") {
|
|
5256
5257
|
if (param.values.length === 1) {
|
|
5257
|
-
return { sql: `EXISTS (SELECT 1 FROM "${table}" __lookup WHERE __lookup."resourceId" =
|
|
5258
|
+
return { sql: `EXISTS (SELECT 1 FROM "${table}" __lookup WHERE __lookup."resourceId" = ${outerIdRef} AND LOWER(${colRef}) LIKE ?)`, values: [`%${param.values[0].toLowerCase()}%`] };
|
|
5258
5259
|
}
|
|
5259
5260
|
const conds2 = param.values.map(() => `LOWER(${colRef}) LIKE ?`);
|
|
5260
|
-
return { sql: `EXISTS (SELECT 1 FROM "${table}" __lookup WHERE __lookup."resourceId" =
|
|
5261
|
+
return { sql: `EXISTS (SELECT 1 FROM "${table}" __lookup WHERE __lookup."resourceId" = ${outerIdRef} AND (${conds2.join(" OR ")}))`, values: param.values.map((v) => `%${v.toLowerCase()}%`) };
|
|
5261
5262
|
}
|
|
5262
5263
|
if (param.values.length === 1) {
|
|
5263
|
-
return { sql: `EXISTS (SELECT 1 FROM "${table}" __lookup WHERE __lookup."resourceId" =
|
|
5264
|
+
return { sql: `EXISTS (SELECT 1 FROM "${table}" __lookup WHERE __lookup."resourceId" = ${outerIdRef} AND LOWER(${colRef}) LIKE ?)`, values: [`${param.values[0].toLowerCase()}%`] };
|
|
5264
5265
|
}
|
|
5265
5266
|
const conds = param.values.map(() => `LOWER(${colRef}) LIKE ?`);
|
|
5266
|
-
return { sql: `EXISTS (SELECT 1 FROM "${table}" __lookup WHERE __lookup."resourceId" =
|
|
5267
|
+
return { sql: `EXISTS (SELECT 1 FROM "${table}" __lookup WHERE __lookup."resourceId" = ${outerIdRef} AND (${conds.join(" OR ")}))`, values: param.values.map((v) => `${v.toLowerCase()}%`) };
|
|
5267
5268
|
}
|
|
5268
5269
|
function buildStringFragmentV2(impl, param) {
|
|
5269
5270
|
const col = quoteColumn(impl.columnName);
|
|
@@ -5432,7 +5433,7 @@ function buildWhereClauseV2(params, registry, resourceType, dialect) {
|
|
|
5432
5433
|
}
|
|
5433
5434
|
const impl = resolveImplV2(param, registry, resourceType);
|
|
5434
5435
|
if (!impl) continue;
|
|
5435
|
-
const fragment = buildWhereFragmentV2(impl, param, dialect);
|
|
5436
|
+
const fragment = buildWhereFragmentV2(impl, param, dialect, resourceType);
|
|
5436
5437
|
if (fragment) {
|
|
5437
5438
|
fragments.push(fragment);
|
|
5438
5439
|
}
|
|
@@ -6560,16 +6561,18 @@ var FhirStore = class {
|
|
|
6560
6561
|
var import_node_crypto5 = require("node:crypto");
|
|
6561
6562
|
|
|
6562
6563
|
// src/repo/lookup-table-writer.ts
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6564
|
+
function buildLookupTableDDL(dialect) {
|
|
6565
|
+
const pk = dialect === "postgres" ? '"id" SERIAL PRIMARY KEY' : '"id" INTEGER PRIMARY KEY AUTOINCREMENT';
|
|
6566
|
+
return {
|
|
6567
|
+
HumanName: `CREATE TABLE IF NOT EXISTS "HumanName" (
|
|
6568
|
+
${pk},
|
|
6566
6569
|
"resourceId" TEXT NOT NULL,
|
|
6567
6570
|
"name" TEXT,
|
|
6568
6571
|
"given" TEXT,
|
|
6569
6572
|
"family" TEXT
|
|
6570
6573
|
)`,
|
|
6571
|
-
|
|
6572
|
-
|
|
6574
|
+
Address: `CREATE TABLE IF NOT EXISTS "Address" (
|
|
6575
|
+
${pk},
|
|
6573
6576
|
"resourceId" TEXT NOT NULL,
|
|
6574
6577
|
"address" TEXT,
|
|
6575
6578
|
"city" TEXT,
|
|
@@ -6578,20 +6581,21 @@ var LOOKUP_TABLE_DDL = {
|
|
|
6578
6581
|
"state" TEXT,
|
|
6579
6582
|
"use" TEXT
|
|
6580
6583
|
)`,
|
|
6581
|
-
|
|
6582
|
-
|
|
6584
|
+
ContactPoint: `CREATE TABLE IF NOT EXISTS "ContactPoint" (
|
|
6585
|
+
${pk},
|
|
6583
6586
|
"resourceId" TEXT NOT NULL,
|
|
6584
6587
|
"system" TEXT,
|
|
6585
6588
|
"value" TEXT,
|
|
6586
6589
|
"use" TEXT
|
|
6587
6590
|
)`,
|
|
6588
|
-
|
|
6589
|
-
|
|
6591
|
+
Identifier: `CREATE TABLE IF NOT EXISTS "Identifier" (
|
|
6592
|
+
${pk},
|
|
6590
6593
|
"resourceId" TEXT NOT NULL,
|
|
6591
6594
|
"system" TEXT,
|
|
6592
6595
|
"value" TEXT
|
|
6593
6596
|
)`
|
|
6594
|
-
};
|
|
6597
|
+
};
|
|
6598
|
+
}
|
|
6595
6599
|
var LOOKUP_TABLE_INDEXES = {
|
|
6596
6600
|
HumanName: [
|
|
6597
6601
|
'CREATE INDEX IF NOT EXISTS "HumanName_resourceId_idx" ON "HumanName" ("resourceId")',
|
|
@@ -6617,10 +6621,12 @@ var LOOKUP_COLUMNS = {
|
|
|
6617
6621
|
Identifier: ["resourceId", "system", "value"]
|
|
6618
6622
|
};
|
|
6619
6623
|
var LookupTableWriter = class {
|
|
6620
|
-
constructor(adapter) {
|
|
6624
|
+
constructor(adapter, dialect = "sqlite") {
|
|
6621
6625
|
this.adapter = adapter;
|
|
6626
|
+
this.ddl = buildLookupTableDDL(dialect);
|
|
6622
6627
|
}
|
|
6623
6628
|
initialized = false;
|
|
6629
|
+
ddl;
|
|
6624
6630
|
// ---------------------------------------------------------------------------
|
|
6625
6631
|
// DDL
|
|
6626
6632
|
// ---------------------------------------------------------------------------
|
|
@@ -6629,8 +6635,8 @@ var LookupTableWriter = class {
|
|
|
6629
6635
|
*/
|
|
6630
6636
|
async ensureTables() {
|
|
6631
6637
|
if (this.initialized) return;
|
|
6632
|
-
for (const table of Object.keys(
|
|
6633
|
-
await this.adapter.execute(
|
|
6638
|
+
for (const table of Object.keys(this.ddl)) {
|
|
6639
|
+
await this.adapter.execute(this.ddl[table]);
|
|
6634
6640
|
for (const idx of LOOKUP_TABLE_INDEXES[table]) {
|
|
6635
6641
|
await this.adapter.execute(idx);
|
|
6636
6642
|
}
|
|
@@ -6679,7 +6685,7 @@ var LookupTableWriter = class {
|
|
|
6679
6685
|
*/
|
|
6680
6686
|
async deleteRows(resourceId) {
|
|
6681
6687
|
await this.ensureTables();
|
|
6682
|
-
for (const table of Object.keys(
|
|
6688
|
+
for (const table of Object.keys(this.ddl)) {
|
|
6683
6689
|
await this.adapter.execute(
|
|
6684
6690
|
`DELETE FROM "${table}" WHERE "resourceId" = ?`,
|
|
6685
6691
|
[resourceId]
|
|
@@ -6705,7 +6711,7 @@ var LookupTableWriter = class {
|
|
|
6705
6711
|
var IndexingPipeline = class {
|
|
6706
6712
|
constructor(adapter, options) {
|
|
6707
6713
|
this.adapter = adapter;
|
|
6708
|
-
this.lookupWriter = new LookupTableWriter(adapter);
|
|
6714
|
+
this.lookupWriter = new LookupTableWriter(adapter, options?.dialect ?? "sqlite");
|
|
6709
6715
|
this.runtimeProvider = options?.runtimeProvider;
|
|
6710
6716
|
this.options = {
|
|
6711
6717
|
enableLookupTables: options?.enableLookupTables ?? true,
|
|
@@ -7275,6 +7281,14 @@ function generateMigration(deltas, dialect) {
|
|
|
7275
7281
|
const down = [];
|
|
7276
7282
|
const reindexDeltas = [];
|
|
7277
7283
|
const descriptions = [];
|
|
7284
|
+
const needsPgExtensions = deltas.some((d) => d.kind === "ADD_TABLE" || d.kind === "ADD_INDEX");
|
|
7285
|
+
if (dialect === "postgres" && needsPgExtensions) {
|
|
7286
|
+
up.push("CREATE EXTENSION IF NOT EXISTS pg_trgm;");
|
|
7287
|
+
up.push("CREATE EXTENSION IF NOT EXISTS btree_gin;");
|
|
7288
|
+
up.push(
|
|
7289
|
+
`CREATE OR REPLACE FUNCTION token_array_to_text(arr text[]) RETURNS text LANGUAGE sql IMMUTABLE AS $$ SELECT array_to_string(arr, ' ') $$;`
|
|
7290
|
+
);
|
|
7291
|
+
}
|
|
7278
7292
|
for (const delta of deltas) {
|
|
7279
7293
|
switch (delta.kind) {
|
|
7280
7294
|
case "ADD_TABLE": {
|
|
@@ -7403,35 +7417,43 @@ function mapColumnTypeForDialect(type, dialect) {
|
|
|
7403
7417
|
|
|
7404
7418
|
// src/registry/package-registry-repo.ts
|
|
7405
7419
|
var PACKAGES_TABLE = "_packages";
|
|
7406
|
-
var
|
|
7420
|
+
var SCHEMA_VERSION_TABLE = "_schema_version";
|
|
7421
|
+
function createPackagesTableDDL(dialect) {
|
|
7422
|
+
const ts = dialect === "postgres" ? "TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP" : "TEXT NOT NULL DEFAULT (datetime('now'))";
|
|
7423
|
+
return `
|
|
7407
7424
|
CREATE TABLE IF NOT EXISTS "${PACKAGES_TABLE}" (
|
|
7408
7425
|
"name" TEXT NOT NULL,
|
|
7409
7426
|
"version" TEXT NOT NULL,
|
|
7410
7427
|
"checksum" TEXT NOT NULL,
|
|
7411
7428
|
"schemaSnapshot" TEXT,
|
|
7412
|
-
"installedAt"
|
|
7429
|
+
"installedAt" ${ts},
|
|
7413
7430
|
"status" TEXT NOT NULL DEFAULT 'active',
|
|
7414
7431
|
PRIMARY KEY ("name", "version")
|
|
7415
7432
|
);
|
|
7416
7433
|
`;
|
|
7417
|
-
|
|
7418
|
-
|
|
7434
|
+
}
|
|
7435
|
+
function createSchemaVersionTableDDL(dialect) {
|
|
7436
|
+
const ts = dialect === "postgres" ? "TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP" : "TEXT NOT NULL DEFAULT (datetime('now'))";
|
|
7437
|
+
return `
|
|
7419
7438
|
CREATE TABLE IF NOT EXISTS "${SCHEMA_VERSION_TABLE}" (
|
|
7420
7439
|
"version" INTEGER NOT NULL PRIMARY KEY,
|
|
7421
7440
|
"packageList" TEXT NOT NULL,
|
|
7422
7441
|
"description" TEXT NOT NULL DEFAULT '',
|
|
7423
|
-
"appliedAt"
|
|
7442
|
+
"appliedAt" ${ts}
|
|
7424
7443
|
);
|
|
7425
7444
|
`;
|
|
7445
|
+
}
|
|
7426
7446
|
var PackageRegistryRepo = class {
|
|
7427
|
-
constructor(adapter) {
|
|
7447
|
+
constructor(adapter, dialect = "sqlite") {
|
|
7428
7448
|
this.adapter = adapter;
|
|
7449
|
+
this.dialect = dialect;
|
|
7429
7450
|
}
|
|
7451
|
+
dialect;
|
|
7430
7452
|
/**
|
|
7431
7453
|
* Ensure the packages tracking table exists.
|
|
7432
7454
|
*/
|
|
7433
7455
|
async ensureTable() {
|
|
7434
|
-
await this.adapter.execute(
|
|
7456
|
+
await this.adapter.execute(createPackagesTableDDL(this.dialect));
|
|
7435
7457
|
}
|
|
7436
7458
|
/**
|
|
7437
7459
|
* Get the active version of a package by name.
|
|
@@ -7476,7 +7498,7 @@ var PackageRegistryRepo = class {
|
|
|
7476
7498
|
[pkg.name]
|
|
7477
7499
|
);
|
|
7478
7500
|
await this.adapter.execute(
|
|
7479
|
-
|
|
7501
|
+
this.upsertSQL(),
|
|
7480
7502
|
[pkg.name, pkg.version, pkg.checksum, pkg.schemaSnapshot ?? null]
|
|
7481
7503
|
);
|
|
7482
7504
|
await this.recordSchemaVersion(description ?? `Register ${pkg.name}@${pkg.version}`);
|
|
@@ -7493,10 +7515,19 @@ var PackageRegistryRepo = class {
|
|
|
7493
7515
|
[pkg.name]
|
|
7494
7516
|
);
|
|
7495
7517
|
await this.adapter.execute(
|
|
7496
|
-
|
|
7518
|
+
this.upsertSQL(),
|
|
7497
7519
|
[pkg.name, pkg.version, pkg.checksum, pkg.schemaSnapshot ?? null]
|
|
7498
7520
|
);
|
|
7499
7521
|
}
|
|
7522
|
+
/**
|
|
7523
|
+
* Generate dialect-aware UPSERT SQL.
|
|
7524
|
+
*/
|
|
7525
|
+
upsertSQL() {
|
|
7526
|
+
if (this.dialect === "postgres") {
|
|
7527
|
+
return `INSERT INTO "${PACKAGES_TABLE}" ("name", "version", "checksum", "schemaSnapshot", "status") VALUES (?, ?, ?, ?, 'active') ON CONFLICT ("name", "version") DO UPDATE SET "checksum" = EXCLUDED."checksum", "schemaSnapshot" = EXCLUDED."schemaSnapshot", "status" = 'active'`;
|
|
7528
|
+
}
|
|
7529
|
+
return `INSERT OR REPLACE INTO "${PACKAGES_TABLE}" ("name", "version", "checksum", "schemaSnapshot", "status") VALUES (?, ?, ?, ?, 'active')`;
|
|
7530
|
+
}
|
|
7500
7531
|
/**
|
|
7501
7532
|
* Remove all versions of a package.
|
|
7502
7533
|
*/
|
|
@@ -7528,7 +7559,7 @@ var PackageRegistryRepo = class {
|
|
|
7528
7559
|
* Ensure the schema version table exists.
|
|
7529
7560
|
*/
|
|
7530
7561
|
async ensureSchemaVersionTable() {
|
|
7531
|
-
await this.adapter.execute(
|
|
7562
|
+
await this.adapter.execute(createSchemaVersionTableDDL(this.dialect));
|
|
7532
7563
|
}
|
|
7533
7564
|
/**
|
|
7534
7565
|
* Record a schema version with the current active package list.
|
|
@@ -7578,12 +7609,25 @@ CREATE TABLE IF NOT EXISTS "${TRACKING_TABLE_V2}" (
|
|
|
7578
7609
|
"applied_at" TEXT NOT NULL DEFAULT (datetime('now'))
|
|
7579
7610
|
);
|
|
7580
7611
|
`;
|
|
7612
|
+
var CREATE_TRACKING_TABLE_V2_POSTGRES = `
|
|
7613
|
+
CREATE TABLE IF NOT EXISTS "${TRACKING_TABLE_V2}" (
|
|
7614
|
+
"version" INTEGER PRIMARY KEY,
|
|
7615
|
+
"description" TEXT NOT NULL,
|
|
7616
|
+
"type" TEXT NOT NULL DEFAULT 'file',
|
|
7617
|
+
"applied_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
7618
|
+
);
|
|
7619
|
+
`;
|
|
7620
|
+
function createTrackingDDL(dialect) {
|
|
7621
|
+
return dialect === "postgres" ? CREATE_TRACKING_TABLE_V2_POSTGRES : CREATE_TRACKING_TABLE_V2_SQLITE;
|
|
7622
|
+
}
|
|
7581
7623
|
var MigrationRunnerV2 = class {
|
|
7582
7624
|
adapter;
|
|
7583
7625
|
migrations;
|
|
7584
|
-
|
|
7626
|
+
dialect;
|
|
7627
|
+
constructor(adapter, migrations = [], dialect = "sqlite") {
|
|
7585
7628
|
this.adapter = adapter;
|
|
7586
7629
|
this.migrations = [...migrations].sort((a, b) => a.version - b.version);
|
|
7630
|
+
this.dialect = dialect;
|
|
7587
7631
|
}
|
|
7588
7632
|
// ---------------------------------------------------------------------------
|
|
7589
7633
|
// Public API
|
|
@@ -7592,7 +7636,7 @@ var MigrationRunnerV2 = class {
|
|
|
7592
7636
|
* Ensure the tracking table exists.
|
|
7593
7637
|
*/
|
|
7594
7638
|
async ensureTrackingTable() {
|
|
7595
|
-
await this.adapter.execute(
|
|
7639
|
+
await this.adapter.execute(createTrackingDDL(this.dialect));
|
|
7596
7640
|
}
|
|
7597
7641
|
/**
|
|
7598
7642
|
* Apply all pending migrations (or up to a target version).
|
|
@@ -7768,7 +7812,23 @@ var MigrationRunnerV2 = class {
|
|
|
7768
7812
|
|
|
7769
7813
|
// src/migration/reindex-scheduler.ts
|
|
7770
7814
|
var REINDEX_JOBS_TABLE = "_reindex_jobs";
|
|
7771
|
-
|
|
7815
|
+
function createReindexJobsTableDDL(dialect) {
|
|
7816
|
+
if (dialect === "postgres") {
|
|
7817
|
+
return `
|
|
7818
|
+
CREATE TABLE IF NOT EXISTS "${REINDEX_JOBS_TABLE}" (
|
|
7819
|
+
"id" SERIAL PRIMARY KEY,
|
|
7820
|
+
"resourceType" TEXT NOT NULL,
|
|
7821
|
+
"searchParamCode" TEXT NOT NULL,
|
|
7822
|
+
"expression" TEXT NOT NULL,
|
|
7823
|
+
"status" TEXT NOT NULL DEFAULT 'pending',
|
|
7824
|
+
"cursor" TEXT,
|
|
7825
|
+
"processedCount" INTEGER NOT NULL DEFAULT 0,
|
|
7826
|
+
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
7827
|
+
"updatedAt" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
7828
|
+
);
|
|
7829
|
+
`;
|
|
7830
|
+
}
|
|
7831
|
+
return `
|
|
7772
7832
|
CREATE TABLE IF NOT EXISTS "${REINDEX_JOBS_TABLE}" (
|
|
7773
7833
|
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
7774
7834
|
"resourceType" TEXT NOT NULL,
|
|
@@ -7781,15 +7841,21 @@ CREATE TABLE IF NOT EXISTS "${REINDEX_JOBS_TABLE}" (
|
|
|
7781
7841
|
"updatedAt" TEXT NOT NULL DEFAULT (datetime('now'))
|
|
7782
7842
|
);
|
|
7783
7843
|
`;
|
|
7844
|
+
}
|
|
7845
|
+
function nowExpression(dialect) {
|
|
7846
|
+
return dialect === "postgres" ? "CURRENT_TIMESTAMP" : "datetime('now')";
|
|
7847
|
+
}
|
|
7784
7848
|
var ReindexScheduler = class {
|
|
7785
|
-
constructor(adapter) {
|
|
7849
|
+
constructor(adapter, dialect = "sqlite") {
|
|
7786
7850
|
this.adapter = adapter;
|
|
7851
|
+
this.dialect = dialect;
|
|
7787
7852
|
}
|
|
7853
|
+
dialect;
|
|
7788
7854
|
/**
|
|
7789
7855
|
* Ensure the reindex jobs table exists.
|
|
7790
7856
|
*/
|
|
7791
7857
|
async ensureTable() {
|
|
7792
|
-
await this.adapter.execute(
|
|
7858
|
+
await this.adapter.execute(createReindexJobsTableDDL(this.dialect));
|
|
7793
7859
|
}
|
|
7794
7860
|
/**
|
|
7795
7861
|
* Schedule reindex jobs from REINDEX deltas.
|
|
@@ -7856,7 +7922,7 @@ var ReindexScheduler = class {
|
|
|
7856
7922
|
sets.push('"processedCount" = ?');
|
|
7857
7923
|
values.push(update.processedCount);
|
|
7858
7924
|
}
|
|
7859
|
-
sets.push(`"updatedAt" =
|
|
7925
|
+
sets.push(`"updatedAt" = ${nowExpression(this.dialect)}`);
|
|
7860
7926
|
values.push(id);
|
|
7861
7927
|
await this.adapter.execute(
|
|
7862
7928
|
`UPDATE "${REINDEX_JOBS_TABLE}" SET ${sets.join(", ")} WHERE "id" = ?`,
|
|
@@ -7889,9 +7955,9 @@ var IGPersistenceManager = class {
|
|
|
7889
7955
|
reindexScheduler;
|
|
7890
7956
|
constructor(adapter, dialect = "sqlite") {
|
|
7891
7957
|
this.dialect = dialect;
|
|
7892
|
-
this.packageRepo = new PackageRegistryRepo(adapter);
|
|
7893
|
-
this.migrationRunner = new MigrationRunnerV2(adapter);
|
|
7894
|
-
this.reindexScheduler = new ReindexScheduler(adapter);
|
|
7958
|
+
this.packageRepo = new PackageRegistryRepo(adapter, dialect);
|
|
7959
|
+
this.migrationRunner = new MigrationRunnerV2(adapter, [], dialect);
|
|
7960
|
+
this.reindexScheduler = new ReindexScheduler(adapter, dialect);
|
|
7895
7961
|
}
|
|
7896
7962
|
/**
|
|
7897
7963
|
* Initialize an IG package — the main entry point.
|
|
@@ -8072,25 +8138,30 @@ var TerminologyCodeRepo = class {
|
|
|
8072
8138
|
|
|
8073
8139
|
// src/terminology/valueset-repo.ts
|
|
8074
8140
|
var VALUESETS_TABLE = "terminology_valuesets";
|
|
8075
|
-
|
|
8141
|
+
function createValuesetsTableDDL(dialect) {
|
|
8142
|
+
const ts = dialect === "postgres" ? "TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP" : "TEXT NOT NULL DEFAULT (datetime('now'))";
|
|
8143
|
+
return `
|
|
8076
8144
|
CREATE TABLE IF NOT EXISTS "${VALUESETS_TABLE}" (
|
|
8077
8145
|
"url" TEXT NOT NULL,
|
|
8078
8146
|
"version" TEXT NOT NULL,
|
|
8079
8147
|
"name" TEXT,
|
|
8080
8148
|
"content" TEXT NOT NULL,
|
|
8081
|
-
"storedAt"
|
|
8149
|
+
"storedAt" ${ts},
|
|
8082
8150
|
PRIMARY KEY ("url", "version")
|
|
8083
8151
|
);
|
|
8084
8152
|
`;
|
|
8153
|
+
}
|
|
8085
8154
|
var ValueSetRepo = class {
|
|
8086
|
-
constructor(adapter) {
|
|
8155
|
+
constructor(adapter, dialect = "sqlite") {
|
|
8087
8156
|
this.adapter = adapter;
|
|
8157
|
+
this.dialect = dialect;
|
|
8088
8158
|
}
|
|
8159
|
+
dialect;
|
|
8089
8160
|
/**
|
|
8090
8161
|
* Ensure the terminology_valuesets table exists.
|
|
8091
8162
|
*/
|
|
8092
8163
|
async ensureTable() {
|
|
8093
|
-
await this.adapter.execute(
|
|
8164
|
+
await this.adapter.execute(createValuesetsTableDDL(this.dialect));
|
|
8094
8165
|
}
|
|
8095
8166
|
/**
|
|
8096
8167
|
* Insert or update a ValueSet.
|
|
@@ -8098,10 +8169,8 @@ var ValueSetRepo = class {
|
|
|
8098
8169
|
*/
|
|
8099
8170
|
async upsert(input) {
|
|
8100
8171
|
await this.ensureTable();
|
|
8101
|
-
|
|
8102
|
-
|
|
8103
|
-
[input.url, input.version, input.name ?? null, input.content]
|
|
8104
|
-
);
|
|
8172
|
+
const sql = this.dialect === "postgres" ? `INSERT INTO "${VALUESETS_TABLE}" ("url", "version", "name", "content") VALUES (?, ?, ?, ?) ON CONFLICT ("url", "version") DO UPDATE SET "name" = EXCLUDED."name", "content" = EXCLUDED."content"` : `INSERT OR REPLACE INTO "${VALUESETS_TABLE}" ("url", "version", "name", "content") VALUES (?, ?, ?, ?)`;
|
|
8173
|
+
await this.adapter.execute(sql, [input.url, input.version, input.name ?? null, input.content]);
|
|
8105
8174
|
}
|
|
8106
8175
|
/**
|
|
8107
8176
|
* Get a specific ValueSet by url and version.
|
|
@@ -8560,7 +8629,8 @@ var FhirSystem = class {
|
|
|
8560
8629
|
indexing: {
|
|
8561
8630
|
enableLookupTables: this.options.enableLookupTables,
|
|
8562
8631
|
enableReferences: this.options.enableReferences,
|
|
8563
|
-
runtimeProvider: this.options.runtimeProvider
|
|
8632
|
+
runtimeProvider: this.options.runtimeProvider,
|
|
8633
|
+
dialect: this.dialect
|
|
8564
8634
|
}
|
|
8565
8635
|
});
|
|
8566
8636
|
return {
|