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.
@@ -324,7 +324,7 @@ export declare function buildWhereClauseV2(params: ParsedSearchParam[], registry
324
324
  * - SQLite: json_each() for array columns
325
325
  * - PostgreSQL: native ARRAY operators
326
326
  */
327
- export declare function buildWhereFragmentV2(impl: SearchParameterImpl, param: ParsedSearchParam, dialect?: SqlDialect): WhereFragment | null;
327
+ export declare function buildWhereFragmentV2(impl: SearchParameterImpl, param: ParsedSearchParam, dialect?: SqlDialect, resourceType?: string): WhereFragment | null;
328
328
 
329
329
  declare interface BundleLink {
330
330
  relation: string;
@@ -1194,6 +1194,8 @@ export declare interface IndexingPipelineOptions {
1194
1194
  enableReferences?: boolean;
1195
1195
  /** Optional RuntimeProvider for FHIRPath-driven extraction (B3). */
1196
1196
  runtimeProvider?: RuntimeProvider;
1197
+ /** SQL dialect for lookup table DDL (default: 'sqlite'). */
1198
+ dialect?: DDLDialect;
1197
1199
  }
1198
1200
 
1199
1201
  /**
@@ -1306,7 +1308,8 @@ declare type LookupTableType = 'HumanName' | 'Address' | 'ContactPoint' | 'Ident
1306
1308
  export declare class LookupTableWriter {
1307
1309
  private readonly adapter;
1308
1310
  private initialized;
1309
- constructor(adapter: StorageAdapter);
1311
+ private readonly ddl;
1312
+ constructor(adapter: StorageAdapter, dialect?: DDLDialect);
1310
1313
  /**
1311
1314
  * Create all 4 lookup tables + indexes if they don't exist.
1312
1315
  */
@@ -1390,7 +1393,8 @@ export declare interface MigrationResultV2 {
1390
1393
  export declare class MigrationRunnerV2 {
1391
1394
  private readonly adapter;
1392
1395
  private readonly migrations;
1393
- constructor(adapter: StorageAdapter, migrations?: MigrationV2[]);
1396
+ private readonly dialect;
1397
+ constructor(adapter: StorageAdapter, migrations?: MigrationV2[], dialect?: DDLDialect);
1394
1398
  /**
1395
1399
  * Ensure the tracking table exists.
1396
1400
  */
@@ -1481,7 +1485,8 @@ export declare interface OperationContext {
1481
1485
 
1482
1486
  export declare class PackageRegistryRepo {
1483
1487
  private readonly adapter;
1484
- constructor(adapter: StorageAdapter);
1488
+ private readonly dialect;
1489
+ constructor(adapter: StorageAdapter, dialect?: DDLDialect);
1485
1490
  /**
1486
1491
  * Ensure the packages tracking table exists.
1487
1492
  */
@@ -1513,6 +1518,10 @@ export declare class PackageRegistryRepo {
1513
1518
  * Uses INSERT OR REPLACE (SQLite UPSERT) to handle both new and upgraded packages.
1514
1519
  */
1515
1520
  upsertPackage(pkg: Omit<InstalledPackage, 'installedAt' | 'status'>): Promise<void>;
1521
+ /**
1522
+ * Generate dialect-aware UPSERT SQL.
1523
+ */
1524
+ private upsertSQL;
1516
1525
  /**
1517
1526
  * Remove all versions of a package.
1518
1527
  */
@@ -1900,7 +1909,8 @@ declare interface ReindexResultV2 {
1900
1909
 
1901
1910
  export declare class ReindexScheduler {
1902
1911
  private readonly adapter;
1903
- constructor(adapter: StorageAdapter);
1912
+ private readonly dialect;
1913
+ constructor(adapter: StorageAdapter, dialect?: DDLDialect);
1904
1914
  /**
1905
1915
  * Ensure the reindex jobs table exists.
1906
1916
  */
@@ -3199,7 +3209,8 @@ declare interface ValueSetInput {
3199
3209
 
3200
3210
  export declare class ValueSetRepo {
3201
3211
  private readonly adapter;
3202
- constructor(adapter: StorageAdapter);
3212
+ private readonly dialect;
3213
+ constructor(adapter: StorageAdapter, dialect?: DDLDialect);
3203
3214
  /**
3204
3215
  * Ensure the terminology_valuesets table exists.
3205
3216
  */
@@ -324,7 +324,7 @@ export declare function buildWhereClauseV2(params: ParsedSearchParam[], registry
324
324
  * - SQLite: json_each() for array columns
325
325
  * - PostgreSQL: native ARRAY operators
326
326
  */
327
- export declare function buildWhereFragmentV2(impl: SearchParameterImpl, param: ParsedSearchParam, dialect?: SqlDialect): WhereFragment | null;
327
+ export declare function buildWhereFragmentV2(impl: SearchParameterImpl, param: ParsedSearchParam, dialect?: SqlDialect, resourceType?: string): WhereFragment | null;
328
328
 
329
329
  declare interface BundleLink {
330
330
  relation: string;
@@ -1194,6 +1194,8 @@ export declare interface IndexingPipelineOptions {
1194
1194
  enableReferences?: boolean;
1195
1195
  /** Optional RuntimeProvider for FHIRPath-driven extraction (B3). */
1196
1196
  runtimeProvider?: RuntimeProvider;
1197
+ /** SQL dialect for lookup table DDL (default: 'sqlite'). */
1198
+ dialect?: DDLDialect;
1197
1199
  }
1198
1200
 
1199
1201
  /**
@@ -1306,7 +1308,8 @@ declare type LookupTableType = 'HumanName' | 'Address' | 'ContactPoint' | 'Ident
1306
1308
  export declare class LookupTableWriter {
1307
1309
  private readonly adapter;
1308
1310
  private initialized;
1309
- constructor(adapter: StorageAdapter);
1311
+ private readonly ddl;
1312
+ constructor(adapter: StorageAdapter, dialect?: DDLDialect);
1310
1313
  /**
1311
1314
  * Create all 4 lookup tables + indexes if they don't exist.
1312
1315
  */
@@ -1390,7 +1393,8 @@ export declare interface MigrationResultV2 {
1390
1393
  export declare class MigrationRunnerV2 {
1391
1394
  private readonly adapter;
1392
1395
  private readonly migrations;
1393
- constructor(adapter: StorageAdapter, migrations?: MigrationV2[]);
1396
+ private readonly dialect;
1397
+ constructor(adapter: StorageAdapter, migrations?: MigrationV2[], dialect?: DDLDialect);
1394
1398
  /**
1395
1399
  * Ensure the tracking table exists.
1396
1400
  */
@@ -1481,7 +1485,8 @@ export declare interface OperationContext {
1481
1485
 
1482
1486
  export declare class PackageRegistryRepo {
1483
1487
  private readonly adapter;
1484
- constructor(adapter: StorageAdapter);
1488
+ private readonly dialect;
1489
+ constructor(adapter: StorageAdapter, dialect?: DDLDialect);
1485
1490
  /**
1486
1491
  * Ensure the packages tracking table exists.
1487
1492
  */
@@ -1513,6 +1518,10 @@ export declare class PackageRegistryRepo {
1513
1518
  * Uses INSERT OR REPLACE (SQLite UPSERT) to handle both new and upgraded packages.
1514
1519
  */
1515
1520
  upsertPackage(pkg: Omit<InstalledPackage, 'installedAt' | 'status'>): Promise<void>;
1521
+ /**
1522
+ * Generate dialect-aware UPSERT SQL.
1523
+ */
1524
+ private upsertSQL;
1516
1525
  /**
1517
1526
  * Remove all versions of a package.
1518
1527
  */
@@ -1900,7 +1909,8 @@ declare interface ReindexResultV2 {
1900
1909
 
1901
1910
  export declare class ReindexScheduler {
1902
1911
  private readonly adapter;
1903
- constructor(adapter: StorageAdapter);
1912
+ private readonly dialect;
1913
+ constructor(adapter: StorageAdapter, dialect?: DDLDialect);
1904
1914
  /**
1905
1915
  * Ensure the reindex jobs table exists.
1906
1916
  */
@@ -3199,7 +3209,8 @@ declare interface ValueSetInput {
3199
3209
 
3200
3210
  export declare class ValueSetRepo {
3201
3211
  private readonly adapter;
3202
- constructor(adapter: StorageAdapter);
3212
+ private readonly dialect;
3213
+ constructor(adapter: StorageAdapter, dialect?: DDLDialect);
3203
3214
  /**
3204
3215
  * Ensure the terminology_valuesets table exists.
3205
3216
  */
@@ -5077,12 +5077,12 @@ function arrayContainsLikeV2(col, value, dialect) {
5077
5077
  }
5078
5078
  return { sql: `EXISTS (SELECT 1 FROM json_each(${col}) WHERE json_each.value LIKE ?)`, values: [value] };
5079
5079
  }
5080
- function buildWhereFragmentV2(impl, param, dialect) {
5080
+ function buildWhereFragmentV2(impl, param, dialect, resourceType) {
5081
5081
  if (param.modifier === "missing") {
5082
5082
  return buildMissingFragmentV2(impl, param);
5083
5083
  }
5084
5084
  if (impl.strategy === "lookup-table") {
5085
- return buildLookupTableFragmentV2(impl, param);
5085
+ return buildLookupTableFragmentV2(impl, param, resourceType ?? "Resource");
5086
5086
  }
5087
5087
  if (impl.strategy === "token-column") {
5088
5088
  return buildTokenColumnFragmentV2(impl, param, dialect);
@@ -5110,7 +5110,7 @@ function buildMissingFragmentV2(impl, param) {
5110
5110
  const col = quoteColumn(impl.columnName);
5111
5111
  return { sql: isMissing ? `${col} IS NULL` : `${col} IS NOT NULL`, values: [] };
5112
5112
  }
5113
- function buildLookupTableFragmentV2(impl, param) {
5113
+ function buildLookupTableFragmentV2(impl, param, resourceType) {
5114
5114
  const mapping = LOOKUP_TABLE_MAP[impl.code];
5115
5115
  if (!mapping) {
5116
5116
  const sortCol = quoteColumn(`__${impl.columnName}Sort`);
@@ -5120,25 +5120,26 @@ function buildLookupTableFragmentV2(impl, param) {
5120
5120
  }
5121
5121
  const { table, column } = mapping;
5122
5122
  const colRef = `__lookup."${column}"`;
5123
+ const outerIdRef = `"${resourceType}"."id"`;
5123
5124
  if (param.modifier === "exact") {
5124
5125
  if (param.values.length === 1) {
5125
- return { sql: `EXISTS (SELECT 1 FROM "${table}" __lookup WHERE __lookup."resourceId" = "id" AND ${colRef} = ?)`, values: [param.values[0]] };
5126
+ return { sql: `EXISTS (SELECT 1 FROM "${table}" __lookup WHERE __lookup."resourceId" = ${outerIdRef} AND ${colRef} = ?)`, values: [param.values[0]] };
5126
5127
  }
5127
5128
  const conds2 = param.values.map(() => `${colRef} = ?`);
5128
- return { sql: `EXISTS (SELECT 1 FROM "${table}" __lookup WHERE __lookup."resourceId" = "id" AND (${conds2.join(" OR ")}))`, values: [...param.values] };
5129
+ return { sql: `EXISTS (SELECT 1 FROM "${table}" __lookup WHERE __lookup."resourceId" = ${outerIdRef} AND (${conds2.join(" OR ")}))`, values: [...param.values] };
5129
5130
  }
5130
5131
  if (param.modifier === "contains") {
5131
5132
  if (param.values.length === 1) {
5132
- return { sql: `EXISTS (SELECT 1 FROM "${table}" __lookup WHERE __lookup."resourceId" = "id" AND LOWER(${colRef}) LIKE ?)`, values: [`%${param.values[0].toLowerCase()}%`] };
5133
+ return { sql: `EXISTS (SELECT 1 FROM "${table}" __lookup WHERE __lookup."resourceId" = ${outerIdRef} AND LOWER(${colRef}) LIKE ?)`, values: [`%${param.values[0].toLowerCase()}%`] };
5133
5134
  }
5134
5135
  const conds2 = param.values.map(() => `LOWER(${colRef}) LIKE ?`);
5135
- return { sql: `EXISTS (SELECT 1 FROM "${table}" __lookup WHERE __lookup."resourceId" = "id" AND (${conds2.join(" OR ")}))`, values: param.values.map((v) => `%${v.toLowerCase()}%`) };
5136
+ return { sql: `EXISTS (SELECT 1 FROM "${table}" __lookup WHERE __lookup."resourceId" = ${outerIdRef} AND (${conds2.join(" OR ")}))`, values: param.values.map((v) => `%${v.toLowerCase()}%`) };
5136
5137
  }
5137
5138
  if (param.values.length === 1) {
5138
- return { sql: `EXISTS (SELECT 1 FROM "${table}" __lookup WHERE __lookup."resourceId" = "id" AND LOWER(${colRef}) LIKE ?)`, values: [`${param.values[0].toLowerCase()}%`] };
5139
+ return { sql: `EXISTS (SELECT 1 FROM "${table}" __lookup WHERE __lookup."resourceId" = ${outerIdRef} AND LOWER(${colRef}) LIKE ?)`, values: [`${param.values[0].toLowerCase()}%`] };
5139
5140
  }
5140
5141
  const conds = param.values.map(() => `LOWER(${colRef}) LIKE ?`);
5141
- return { sql: `EXISTS (SELECT 1 FROM "${table}" __lookup WHERE __lookup."resourceId" = "id" AND (${conds.join(" OR ")}))`, values: param.values.map((v) => `${v.toLowerCase()}%`) };
5142
+ return { sql: `EXISTS (SELECT 1 FROM "${table}" __lookup WHERE __lookup."resourceId" = ${outerIdRef} AND (${conds.join(" OR ")}))`, values: param.values.map((v) => `${v.toLowerCase()}%`) };
5142
5143
  }
5143
5144
  function buildStringFragmentV2(impl, param) {
5144
5145
  const col = quoteColumn(impl.columnName);
@@ -5307,7 +5308,7 @@ function buildWhereClauseV2(params, registry, resourceType, dialect) {
5307
5308
  }
5308
5309
  const impl = resolveImplV2(param, registry, resourceType);
5309
5310
  if (!impl) continue;
5310
- const fragment = buildWhereFragmentV2(impl, param, dialect);
5311
+ const fragment = buildWhereFragmentV2(impl, param, dialect, resourceType);
5311
5312
  if (fragment) {
5312
5313
  fragments.push(fragment);
5313
5314
  }
@@ -6435,16 +6436,18 @@ var FhirStore = class {
6435
6436
  import { randomUUID as randomUUID4 } from "node:crypto";
6436
6437
 
6437
6438
  // src/repo/lookup-table-writer.ts
6438
- var LOOKUP_TABLE_DDL = {
6439
- HumanName: `CREATE TABLE IF NOT EXISTS "HumanName" (
6440
- "id" INTEGER PRIMARY KEY AUTOINCREMENT,
6439
+ function buildLookupTableDDL(dialect) {
6440
+ const pk = dialect === "postgres" ? '"id" SERIAL PRIMARY KEY' : '"id" INTEGER PRIMARY KEY AUTOINCREMENT';
6441
+ return {
6442
+ HumanName: `CREATE TABLE IF NOT EXISTS "HumanName" (
6443
+ ${pk},
6441
6444
  "resourceId" TEXT NOT NULL,
6442
6445
  "name" TEXT,
6443
6446
  "given" TEXT,
6444
6447
  "family" TEXT
6445
6448
  )`,
6446
- Address: `CREATE TABLE IF NOT EXISTS "Address" (
6447
- "id" INTEGER PRIMARY KEY AUTOINCREMENT,
6449
+ Address: `CREATE TABLE IF NOT EXISTS "Address" (
6450
+ ${pk},
6448
6451
  "resourceId" TEXT NOT NULL,
6449
6452
  "address" TEXT,
6450
6453
  "city" TEXT,
@@ -6453,20 +6456,21 @@ var LOOKUP_TABLE_DDL = {
6453
6456
  "state" TEXT,
6454
6457
  "use" TEXT
6455
6458
  )`,
6456
- ContactPoint: `CREATE TABLE IF NOT EXISTS "ContactPoint" (
6457
- "id" INTEGER PRIMARY KEY AUTOINCREMENT,
6459
+ ContactPoint: `CREATE TABLE IF NOT EXISTS "ContactPoint" (
6460
+ ${pk},
6458
6461
  "resourceId" TEXT NOT NULL,
6459
6462
  "system" TEXT,
6460
6463
  "value" TEXT,
6461
6464
  "use" TEXT
6462
6465
  )`,
6463
- Identifier: `CREATE TABLE IF NOT EXISTS "Identifier" (
6464
- "id" INTEGER PRIMARY KEY AUTOINCREMENT,
6466
+ Identifier: `CREATE TABLE IF NOT EXISTS "Identifier" (
6467
+ ${pk},
6465
6468
  "resourceId" TEXT NOT NULL,
6466
6469
  "system" TEXT,
6467
6470
  "value" TEXT
6468
6471
  )`
6469
- };
6472
+ };
6473
+ }
6470
6474
  var LOOKUP_TABLE_INDEXES = {
6471
6475
  HumanName: [
6472
6476
  'CREATE INDEX IF NOT EXISTS "HumanName_resourceId_idx" ON "HumanName" ("resourceId")',
@@ -6492,10 +6496,12 @@ var LOOKUP_COLUMNS = {
6492
6496
  Identifier: ["resourceId", "system", "value"]
6493
6497
  };
6494
6498
  var LookupTableWriter = class {
6495
- constructor(adapter) {
6499
+ constructor(adapter, dialect = "sqlite") {
6496
6500
  this.adapter = adapter;
6501
+ this.ddl = buildLookupTableDDL(dialect);
6497
6502
  }
6498
6503
  initialized = false;
6504
+ ddl;
6499
6505
  // ---------------------------------------------------------------------------
6500
6506
  // DDL
6501
6507
  // ---------------------------------------------------------------------------
@@ -6504,8 +6510,8 @@ var LookupTableWriter = class {
6504
6510
  */
6505
6511
  async ensureTables() {
6506
6512
  if (this.initialized) return;
6507
- for (const table of Object.keys(LOOKUP_TABLE_DDL)) {
6508
- await this.adapter.execute(LOOKUP_TABLE_DDL[table]);
6513
+ for (const table of Object.keys(this.ddl)) {
6514
+ await this.adapter.execute(this.ddl[table]);
6509
6515
  for (const idx of LOOKUP_TABLE_INDEXES[table]) {
6510
6516
  await this.adapter.execute(idx);
6511
6517
  }
@@ -6554,7 +6560,7 @@ var LookupTableWriter = class {
6554
6560
  */
6555
6561
  async deleteRows(resourceId) {
6556
6562
  await this.ensureTables();
6557
- for (const table of Object.keys(LOOKUP_TABLE_DDL)) {
6563
+ for (const table of Object.keys(this.ddl)) {
6558
6564
  await this.adapter.execute(
6559
6565
  `DELETE FROM "${table}" WHERE "resourceId" = ?`,
6560
6566
  [resourceId]
@@ -6580,7 +6586,7 @@ var LookupTableWriter = class {
6580
6586
  var IndexingPipeline = class {
6581
6587
  constructor(adapter, options) {
6582
6588
  this.adapter = adapter;
6583
- this.lookupWriter = new LookupTableWriter(adapter);
6589
+ this.lookupWriter = new LookupTableWriter(adapter, options?.dialect ?? "sqlite");
6584
6590
  this.runtimeProvider = options?.runtimeProvider;
6585
6591
  this.options = {
6586
6592
  enableLookupTables: options?.enableLookupTables ?? true,
@@ -7150,6 +7156,14 @@ function generateMigration(deltas, dialect) {
7150
7156
  const down = [];
7151
7157
  const reindexDeltas = [];
7152
7158
  const descriptions = [];
7159
+ const needsPgExtensions = deltas.some((d) => d.kind === "ADD_TABLE" || d.kind === "ADD_INDEX");
7160
+ if (dialect === "postgres" && needsPgExtensions) {
7161
+ up.push("CREATE EXTENSION IF NOT EXISTS pg_trgm;");
7162
+ up.push("CREATE EXTENSION IF NOT EXISTS btree_gin;");
7163
+ up.push(
7164
+ `CREATE OR REPLACE FUNCTION token_array_to_text(arr text[]) RETURNS text LANGUAGE sql IMMUTABLE AS $$ SELECT array_to_string(arr, ' ') $$;`
7165
+ );
7166
+ }
7153
7167
  for (const delta of deltas) {
7154
7168
  switch (delta.kind) {
7155
7169
  case "ADD_TABLE": {
@@ -7278,35 +7292,43 @@ function mapColumnTypeForDialect(type, dialect) {
7278
7292
 
7279
7293
  // src/registry/package-registry-repo.ts
7280
7294
  var PACKAGES_TABLE = "_packages";
7281
- var CREATE_PACKAGES_TABLE = `
7295
+ var SCHEMA_VERSION_TABLE = "_schema_version";
7296
+ function createPackagesTableDDL(dialect) {
7297
+ const ts = dialect === "postgres" ? "TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP" : "TEXT NOT NULL DEFAULT (datetime('now'))";
7298
+ return `
7282
7299
  CREATE TABLE IF NOT EXISTS "${PACKAGES_TABLE}" (
7283
7300
  "name" TEXT NOT NULL,
7284
7301
  "version" TEXT NOT NULL,
7285
7302
  "checksum" TEXT NOT NULL,
7286
7303
  "schemaSnapshot" TEXT,
7287
- "installedAt" TEXT NOT NULL DEFAULT (datetime('now')),
7304
+ "installedAt" ${ts},
7288
7305
  "status" TEXT NOT NULL DEFAULT 'active',
7289
7306
  PRIMARY KEY ("name", "version")
7290
7307
  );
7291
7308
  `;
7292
- var SCHEMA_VERSION_TABLE = "_schema_version";
7293
- var CREATE_SCHEMA_VERSION_TABLE = `
7309
+ }
7310
+ function createSchemaVersionTableDDL(dialect) {
7311
+ const ts = dialect === "postgres" ? "TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP" : "TEXT NOT NULL DEFAULT (datetime('now'))";
7312
+ return `
7294
7313
  CREATE TABLE IF NOT EXISTS "${SCHEMA_VERSION_TABLE}" (
7295
7314
  "version" INTEGER NOT NULL PRIMARY KEY,
7296
7315
  "packageList" TEXT NOT NULL,
7297
7316
  "description" TEXT NOT NULL DEFAULT '',
7298
- "appliedAt" TEXT NOT NULL DEFAULT (datetime('now'))
7317
+ "appliedAt" ${ts}
7299
7318
  );
7300
7319
  `;
7320
+ }
7301
7321
  var PackageRegistryRepo = class {
7302
- constructor(adapter) {
7322
+ constructor(adapter, dialect = "sqlite") {
7303
7323
  this.adapter = adapter;
7324
+ this.dialect = dialect;
7304
7325
  }
7326
+ dialect;
7305
7327
  /**
7306
7328
  * Ensure the packages tracking table exists.
7307
7329
  */
7308
7330
  async ensureTable() {
7309
- await this.adapter.execute(CREATE_PACKAGES_TABLE);
7331
+ await this.adapter.execute(createPackagesTableDDL(this.dialect));
7310
7332
  }
7311
7333
  /**
7312
7334
  * Get the active version of a package by name.
@@ -7351,7 +7373,7 @@ var PackageRegistryRepo = class {
7351
7373
  [pkg.name]
7352
7374
  );
7353
7375
  await this.adapter.execute(
7354
- `INSERT OR REPLACE INTO "${PACKAGES_TABLE}" ("name", "version", "checksum", "schemaSnapshot", "status") VALUES (?, ?, ?, ?, 'active')`,
7376
+ this.upsertSQL(),
7355
7377
  [pkg.name, pkg.version, pkg.checksum, pkg.schemaSnapshot ?? null]
7356
7378
  );
7357
7379
  await this.recordSchemaVersion(description ?? `Register ${pkg.name}@${pkg.version}`);
@@ -7368,10 +7390,19 @@ var PackageRegistryRepo = class {
7368
7390
  [pkg.name]
7369
7391
  );
7370
7392
  await this.adapter.execute(
7371
- `INSERT OR REPLACE INTO "${PACKAGES_TABLE}" ("name", "version", "checksum", "schemaSnapshot", "status") VALUES (?, ?, ?, ?, 'active')`,
7393
+ this.upsertSQL(),
7372
7394
  [pkg.name, pkg.version, pkg.checksum, pkg.schemaSnapshot ?? null]
7373
7395
  );
7374
7396
  }
7397
+ /**
7398
+ * Generate dialect-aware UPSERT SQL.
7399
+ */
7400
+ upsertSQL() {
7401
+ if (this.dialect === "postgres") {
7402
+ 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'`;
7403
+ }
7404
+ return `INSERT OR REPLACE INTO "${PACKAGES_TABLE}" ("name", "version", "checksum", "schemaSnapshot", "status") VALUES (?, ?, ?, ?, 'active')`;
7405
+ }
7375
7406
  /**
7376
7407
  * Remove all versions of a package.
7377
7408
  */
@@ -7403,7 +7434,7 @@ var PackageRegistryRepo = class {
7403
7434
  * Ensure the schema version table exists.
7404
7435
  */
7405
7436
  async ensureSchemaVersionTable() {
7406
- await this.adapter.execute(CREATE_SCHEMA_VERSION_TABLE);
7437
+ await this.adapter.execute(createSchemaVersionTableDDL(this.dialect));
7407
7438
  }
7408
7439
  /**
7409
7440
  * Record a schema version with the current active package list.
@@ -7453,12 +7484,25 @@ CREATE TABLE IF NOT EXISTS "${TRACKING_TABLE_V2}" (
7453
7484
  "applied_at" TEXT NOT NULL DEFAULT (datetime('now'))
7454
7485
  );
7455
7486
  `;
7487
+ var CREATE_TRACKING_TABLE_V2_POSTGRES = `
7488
+ CREATE TABLE IF NOT EXISTS "${TRACKING_TABLE_V2}" (
7489
+ "version" INTEGER PRIMARY KEY,
7490
+ "description" TEXT NOT NULL,
7491
+ "type" TEXT NOT NULL DEFAULT 'file',
7492
+ "applied_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
7493
+ );
7494
+ `;
7495
+ function createTrackingDDL(dialect) {
7496
+ return dialect === "postgres" ? CREATE_TRACKING_TABLE_V2_POSTGRES : CREATE_TRACKING_TABLE_V2_SQLITE;
7497
+ }
7456
7498
  var MigrationRunnerV2 = class {
7457
7499
  adapter;
7458
7500
  migrations;
7459
- constructor(adapter, migrations = []) {
7501
+ dialect;
7502
+ constructor(adapter, migrations = [], dialect = "sqlite") {
7460
7503
  this.adapter = adapter;
7461
7504
  this.migrations = [...migrations].sort((a, b) => a.version - b.version);
7505
+ this.dialect = dialect;
7462
7506
  }
7463
7507
  // ---------------------------------------------------------------------------
7464
7508
  // Public API
@@ -7467,7 +7511,7 @@ var MigrationRunnerV2 = class {
7467
7511
  * Ensure the tracking table exists.
7468
7512
  */
7469
7513
  async ensureTrackingTable() {
7470
- await this.adapter.execute(CREATE_TRACKING_TABLE_V2_SQLITE);
7514
+ await this.adapter.execute(createTrackingDDL(this.dialect));
7471
7515
  }
7472
7516
  /**
7473
7517
  * Apply all pending migrations (or up to a target version).
@@ -7643,7 +7687,23 @@ var MigrationRunnerV2 = class {
7643
7687
 
7644
7688
  // src/migration/reindex-scheduler.ts
7645
7689
  var REINDEX_JOBS_TABLE = "_reindex_jobs";
7646
- var CREATE_REINDEX_JOBS_TABLE = `
7690
+ function createReindexJobsTableDDL(dialect) {
7691
+ if (dialect === "postgres") {
7692
+ return `
7693
+ CREATE TABLE IF NOT EXISTS "${REINDEX_JOBS_TABLE}" (
7694
+ "id" SERIAL PRIMARY KEY,
7695
+ "resourceType" TEXT NOT NULL,
7696
+ "searchParamCode" TEXT NOT NULL,
7697
+ "expression" TEXT NOT NULL,
7698
+ "status" TEXT NOT NULL DEFAULT 'pending',
7699
+ "cursor" TEXT,
7700
+ "processedCount" INTEGER NOT NULL DEFAULT 0,
7701
+ "createdAt" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
7702
+ "updatedAt" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
7703
+ );
7704
+ `;
7705
+ }
7706
+ return `
7647
7707
  CREATE TABLE IF NOT EXISTS "${REINDEX_JOBS_TABLE}" (
7648
7708
  "id" INTEGER PRIMARY KEY AUTOINCREMENT,
7649
7709
  "resourceType" TEXT NOT NULL,
@@ -7656,15 +7716,21 @@ CREATE TABLE IF NOT EXISTS "${REINDEX_JOBS_TABLE}" (
7656
7716
  "updatedAt" TEXT NOT NULL DEFAULT (datetime('now'))
7657
7717
  );
7658
7718
  `;
7719
+ }
7720
+ function nowExpression(dialect) {
7721
+ return dialect === "postgres" ? "CURRENT_TIMESTAMP" : "datetime('now')";
7722
+ }
7659
7723
  var ReindexScheduler = class {
7660
- constructor(adapter) {
7724
+ constructor(adapter, dialect = "sqlite") {
7661
7725
  this.adapter = adapter;
7726
+ this.dialect = dialect;
7662
7727
  }
7728
+ dialect;
7663
7729
  /**
7664
7730
  * Ensure the reindex jobs table exists.
7665
7731
  */
7666
7732
  async ensureTable() {
7667
- await this.adapter.execute(CREATE_REINDEX_JOBS_TABLE);
7733
+ await this.adapter.execute(createReindexJobsTableDDL(this.dialect));
7668
7734
  }
7669
7735
  /**
7670
7736
  * Schedule reindex jobs from REINDEX deltas.
@@ -7731,7 +7797,7 @@ var ReindexScheduler = class {
7731
7797
  sets.push('"processedCount" = ?');
7732
7798
  values.push(update.processedCount);
7733
7799
  }
7734
- sets.push(`"updatedAt" = datetime('now')`);
7800
+ sets.push(`"updatedAt" = ${nowExpression(this.dialect)}`);
7735
7801
  values.push(id);
7736
7802
  await this.adapter.execute(
7737
7803
  `UPDATE "${REINDEX_JOBS_TABLE}" SET ${sets.join(", ")} WHERE "id" = ?`,
@@ -7764,9 +7830,9 @@ var IGPersistenceManager = class {
7764
7830
  reindexScheduler;
7765
7831
  constructor(adapter, dialect = "sqlite") {
7766
7832
  this.dialect = dialect;
7767
- this.packageRepo = new PackageRegistryRepo(adapter);
7768
- this.migrationRunner = new MigrationRunnerV2(adapter);
7769
- this.reindexScheduler = new ReindexScheduler(adapter);
7833
+ this.packageRepo = new PackageRegistryRepo(adapter, dialect);
7834
+ this.migrationRunner = new MigrationRunnerV2(adapter, [], dialect);
7835
+ this.reindexScheduler = new ReindexScheduler(adapter, dialect);
7770
7836
  }
7771
7837
  /**
7772
7838
  * Initialize an IG package — the main entry point.
@@ -7947,25 +8013,30 @@ var TerminologyCodeRepo = class {
7947
8013
 
7948
8014
  // src/terminology/valueset-repo.ts
7949
8015
  var VALUESETS_TABLE = "terminology_valuesets";
7950
- var CREATE_VALUESETS_TABLE = `
8016
+ function createValuesetsTableDDL(dialect) {
8017
+ const ts = dialect === "postgres" ? "TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP" : "TEXT NOT NULL DEFAULT (datetime('now'))";
8018
+ return `
7951
8019
  CREATE TABLE IF NOT EXISTS "${VALUESETS_TABLE}" (
7952
8020
  "url" TEXT NOT NULL,
7953
8021
  "version" TEXT NOT NULL,
7954
8022
  "name" TEXT,
7955
8023
  "content" TEXT NOT NULL,
7956
- "storedAt" TEXT NOT NULL DEFAULT (datetime('now')),
8024
+ "storedAt" ${ts},
7957
8025
  PRIMARY KEY ("url", "version")
7958
8026
  );
7959
8027
  `;
8028
+ }
7960
8029
  var ValueSetRepo = class {
7961
- constructor(adapter) {
8030
+ constructor(adapter, dialect = "sqlite") {
7962
8031
  this.adapter = adapter;
8032
+ this.dialect = dialect;
7963
8033
  }
8034
+ dialect;
7964
8035
  /**
7965
8036
  * Ensure the terminology_valuesets table exists.
7966
8037
  */
7967
8038
  async ensureTable() {
7968
- await this.adapter.execute(CREATE_VALUESETS_TABLE);
8039
+ await this.adapter.execute(createValuesetsTableDDL(this.dialect));
7969
8040
  }
7970
8041
  /**
7971
8042
  * Insert or update a ValueSet.
@@ -7973,10 +8044,8 @@ var ValueSetRepo = class {
7973
8044
  */
7974
8045
  async upsert(input) {
7975
8046
  await this.ensureTable();
7976
- await this.adapter.execute(
7977
- `INSERT OR REPLACE INTO "${VALUESETS_TABLE}" ("url", "version", "name", "content") VALUES (?, ?, ?, ?)`,
7978
- [input.url, input.version, input.name ?? null, input.content]
7979
- );
8047
+ 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 (?, ?, ?, ?)`;
8048
+ await this.adapter.execute(sql, [input.url, input.version, input.name ?? null, input.content]);
7980
8049
  }
7981
8050
  /**
7982
8051
  * Get a specific ValueSet by url and version.
@@ -8435,7 +8504,8 @@ var FhirSystem = class {
8435
8504
  indexing: {
8436
8505
  enableLookupTables: this.options.enableLookupTables,
8437
8506
  enableReferences: this.options.enableReferences,
8438
- runtimeProvider: this.options.runtimeProvider
8507
+ runtimeProvider: this.options.runtimeProvider,
8508
+ dialect: this.dialect
8439
8509
  }
8440
8510
  });
8441
8511
  return {