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.
@@ -51,6 +51,8 @@ __export(index_exports, {
51
51
  PROJECT_ADMIN_RESOURCE_TYPES: () => PROJECT_ADMIN_RESOURCE_TYPES,
52
52
  PROTECTED_RESOURCE_TYPES: () => PROTECTED_RESOURCE_TYPES,
53
53
  PackageRegistryRepo: () => PackageRegistryRepo,
54
+ PostgresAdapter: () => PostgresAdapter,
55
+ PostgresDialect: () => PostgresDialect,
54
56
  ReindexScheduler: () => ReindexScheduler,
55
57
  RepositoryError: () => RepositoryError,
56
58
  ResourceCacheV2: () => ResourceCacheV2,
@@ -59,7 +61,7 @@ __export(index_exports, {
59
61
  ResourceVersionConflictError: () => ResourceVersionConflictError,
60
62
  SCHEMA_VERSION: () => SCHEMA_VERSION,
61
63
  SEARCH_PREFIXES: () => SEARCH_PREFIXES,
62
- SQLiteAdapter: () => SQLiteAdapter,
64
+ SQLiteDialect: () => SQLiteDialect,
63
65
  SearchLogger: () => SearchLogger,
64
66
  SearchParameterRegistry: () => SearchParameterRegistry,
65
67
  StructureDefinitionRegistry: () => StructureDefinitionRegistry,
@@ -5172,7 +5174,35 @@ function quoteColumn(name) {
5172
5174
  function escapeLikeString(value) {
5173
5175
  return value.replace(/[%_\\]/g, "\\$&");
5174
5176
  }
5175
- function buildWhereFragmentV2(impl, param) {
5177
+ function arrayContainsV2(col, values, dialect) {
5178
+ if (dialect?.name === "postgres") {
5179
+ const placeholders2 = values.map(() => "?").join(", ");
5180
+ return { sql: `${col} && ARRAY[${placeholders2}]::text[]`, values: [...values] };
5181
+ }
5182
+ if (values.length === 1) {
5183
+ return { sql: `EXISTS (SELECT 1 FROM json_each(${col}) WHERE json_each.value = ?)`, values: [values[0]] };
5184
+ }
5185
+ const placeholders = values.map(() => "?").join(", ");
5186
+ return { sql: `EXISTS (SELECT 1 FROM json_each(${col}) WHERE json_each.value IN (${placeholders}))`, values: [...values] };
5187
+ }
5188
+ function arrayNotContainsV2(col, values, dialect) {
5189
+ if (dialect?.name === "postgres") {
5190
+ const placeholders2 = values.map(() => "?").join(", ");
5191
+ return { sql: `NOT (${col} && ARRAY[${placeholders2}]::text[])`, values: [...values] };
5192
+ }
5193
+ if (values.length === 1) {
5194
+ return { sql: `NOT EXISTS (SELECT 1 FROM json_each(${col}) WHERE json_each.value = ?)`, values: [values[0]] };
5195
+ }
5196
+ const placeholders = values.map(() => "?").join(", ");
5197
+ return { sql: `NOT EXISTS (SELECT 1 FROM json_each(${col}) WHERE json_each.value IN (${placeholders}))`, values: [...values] };
5198
+ }
5199
+ function arrayContainsLikeV2(col, value, dialect) {
5200
+ if (dialect?.name === "postgres") {
5201
+ return { sql: `EXISTS (SELECT unnest FROM unnest(${col}) WHERE unnest LIKE ?)`, values: [value] };
5202
+ }
5203
+ return { sql: `EXISTS (SELECT 1 FROM json_each(${col}) WHERE json_each.value LIKE ?)`, values: [value] };
5204
+ }
5205
+ function buildWhereFragmentV2(impl, param, dialect) {
5176
5206
  if (param.modifier === "missing") {
5177
5207
  return buildMissingFragmentV2(impl, param);
5178
5208
  }
@@ -5180,7 +5210,7 @@ function buildWhereFragmentV2(impl, param) {
5180
5210
  return buildLookupTableFragmentV2(impl, param);
5181
5211
  }
5182
5212
  if (impl.strategy === "token-column") {
5183
- return buildTokenColumnFragmentV2(impl, param);
5213
+ return buildTokenColumnFragmentV2(impl, param, dialect);
5184
5214
  }
5185
5215
  switch (impl.type) {
5186
5216
  case "string":
@@ -5191,11 +5221,11 @@ function buildWhereFragmentV2(impl, param) {
5191
5221
  case "quantity":
5192
5222
  return buildNumberFragmentV2(impl, param);
5193
5223
  case "reference":
5194
- return buildReferenceFragmentV2(impl, param);
5224
+ return buildReferenceFragmentV2(impl, param, dialect);
5195
5225
  case "uri":
5196
- return buildUriFragmentV2(impl, param);
5226
+ return buildUriFragmentV2(impl, param, dialect);
5197
5227
  case "token":
5198
- return buildTokenColumnFragmentV2(impl, param);
5228
+ return buildTokenColumnFragmentV2(impl, param, dialect);
5199
5229
  default:
5200
5230
  return buildDefaultFragmentV2(impl, param);
5201
5231
  }
@@ -5286,29 +5316,21 @@ function buildNumberFragmentV2(impl, param) {
5286
5316
  });
5287
5317
  return buildOrFragmentV2Raw(col, op, numVals);
5288
5318
  }
5289
- function buildReferenceFragmentV2(impl, param) {
5319
+ function buildReferenceFragmentV2(impl, param, dialect) {
5290
5320
  const col = quoteColumn(impl.columnName);
5291
5321
  if (impl.array) {
5292
- if (param.values.length === 1) {
5293
- return { sql: `EXISTS (SELECT 1 FROM json_each(${col}) WHERE json_each.value = ?)`, values: [param.values[0]] };
5294
- }
5295
- const placeholders = param.values.map(() => "?").join(", ");
5296
- return { sql: `EXISTS (SELECT 1 FROM json_each(${col}) WHERE json_each.value IN (${placeholders}))`, values: [...param.values] };
5322
+ return arrayContainsV2(col, param.values, dialect);
5297
5323
  }
5298
5324
  return buildOrFragmentV2(col, "=", param.values);
5299
5325
  }
5300
- function buildUriFragmentV2(impl, param) {
5326
+ function buildUriFragmentV2(impl, param, dialect) {
5301
5327
  const col = quoteColumn(impl.columnName);
5302
5328
  if (impl.array) {
5303
- if (param.values.length === 1) {
5304
- return { sql: `EXISTS (SELECT 1 FROM json_each(${col}) WHERE json_each.value = ?)`, values: [param.values[0]] };
5305
- }
5306
- const placeholders = param.values.map(() => "?").join(", ");
5307
- return { sql: `EXISTS (SELECT 1 FROM json_each(${col}) WHERE json_each.value IN (${placeholders}))`, values: [...param.values] };
5329
+ return arrayContainsV2(col, param.values, dialect);
5308
5330
  }
5309
5331
  return buildOrFragmentV2(col, "=", param.values);
5310
5332
  }
5311
- function buildTokenColumnFragmentV2(impl, param) {
5333
+ function buildTokenColumnFragmentV2(impl, param, dialect) {
5312
5334
  const textCol = quoteColumn(`__${impl.columnName}Text`);
5313
5335
  const sortCol = quoteColumn(`__${impl.columnName}Sort`);
5314
5336
  if (param.modifier === "text") {
@@ -5320,11 +5342,14 @@ function buildTokenColumnFragmentV2(impl, param) {
5320
5342
  const vals = [];
5321
5343
  for (const value of param.values) {
5322
5344
  if (value.endsWith("|")) {
5323
- conds.push(`EXISTS (SELECT 1 FROM json_each(${textCol}) WHERE json_each.value LIKE ?)`);
5324
- vals.push(value + "%");
5345
+ const frag = arrayContainsLikeV2(textCol, value + "%", dialect);
5346
+ conds.push(frag.sql);
5347
+ vals.push(...frag.values);
5325
5348
  } else {
5326
- conds.push(`EXISTS (SELECT 1 FROM json_each(${textCol}) WHERE json_each.value = ?)`);
5327
- vals.push(value.startsWith("|") ? value.slice(1) : value);
5349
+ const resolved = value.startsWith("|") ? value.slice(1) : value;
5350
+ const frag = arrayContainsV2(textCol, [resolved], dialect);
5351
+ conds.push(frag.sql);
5352
+ vals.push(...frag.values);
5328
5353
  }
5329
5354
  }
5330
5355
  const inner = conds.length === 1 ? conds[0] : `(${conds.join(" OR ")})`;
@@ -5333,17 +5358,9 @@ function buildTokenColumnFragmentV2(impl, param) {
5333
5358
  }
5334
5359
  const resolvedValues = param.values.map((v) => v.startsWith("|") ? v.slice(1) : v);
5335
5360
  if (param.modifier === "not") {
5336
- if (resolvedValues.length === 1) {
5337
- return { sql: `NOT EXISTS (SELECT 1 FROM json_each(${textCol}) WHERE json_each.value = ?)`, values: [resolvedValues[0]] };
5338
- }
5339
- const placeholders2 = resolvedValues.map(() => "?").join(", ");
5340
- return { sql: `NOT EXISTS (SELECT 1 FROM json_each(${textCol}) WHERE json_each.value IN (${placeholders2}))`, values: [...resolvedValues] };
5361
+ return arrayNotContainsV2(textCol, resolvedValues, dialect);
5341
5362
  }
5342
- if (resolvedValues.length === 1) {
5343
- return { sql: `EXISTS (SELECT 1 FROM json_each(${textCol}) WHERE json_each.value = ?)`, values: [resolvedValues[0]] };
5344
- }
5345
- const placeholders = resolvedValues.map(() => "?").join(", ");
5346
- return { sql: `EXISTS (SELECT 1 FROM json_each(${textCol}) WHERE json_each.value IN (${placeholders}))`, values: [...resolvedValues] };
5363
+ return arrayContainsV2(textCol, resolvedValues, dialect);
5347
5364
  }
5348
5365
  function buildDefaultFragmentV2(impl, param) {
5349
5366
  const col = quoteColumn(impl.columnName);
@@ -5373,7 +5390,7 @@ function buildLikeFragmentV2(col, values, prefix, suffix) {
5373
5390
  }
5374
5391
  return { sql: `(${conds.join(" OR ")})`, values: vals };
5375
5392
  }
5376
- function buildChainedFragmentV2(param, chain, registry, sourceResourceType) {
5393
+ function buildChainedFragmentV2(param, chain, registry, sourceResourceType, dialect) {
5377
5394
  const targetImpl = resolveImplV2(
5378
5395
  { code: chain.targetParam, values: param.values, modifier: param.modifier, prefix: param.prefix },
5379
5396
  registry,
@@ -5386,7 +5403,7 @@ function buildChainedFragmentV2(param, chain, registry, sourceResourceType) {
5386
5403
  modifier: param.modifier,
5387
5404
  prefix: param.prefix
5388
5405
  };
5389
- const innerFragment = buildWhereFragmentV2(targetImpl, innerParam);
5406
+ const innerFragment = buildWhereFragmentV2(targetImpl, innerParam, dialect);
5390
5407
  if (!innerFragment) return null;
5391
5408
  const innerSql = rewriteColumnRefsForAlias(innerFragment.sql, "__target");
5392
5409
  const refTable = `${sourceResourceType}_References`;
@@ -5403,11 +5420,11 @@ function buildChainedFragmentV2(param, chain, registry, sourceResourceType) {
5403
5420
  ].join("\n");
5404
5421
  return { sql, values: [param.code, chain.targetType, ...innerFragment.values] };
5405
5422
  }
5406
- function buildWhereClauseV2(params, registry, resourceType) {
5423
+ function buildWhereClauseV2(params, registry, resourceType, dialect) {
5407
5424
  const fragments = [];
5408
5425
  for (const param of params) {
5409
5426
  if (param.chain) {
5410
- const fragment2 = buildChainedFragmentV2(param, param.chain, registry, resourceType);
5427
+ const fragment2 = buildChainedFragmentV2(param, param.chain, registry, resourceType, dialect);
5411
5428
  if (fragment2) {
5412
5429
  fragments.push(fragment2);
5413
5430
  }
@@ -5415,7 +5432,7 @@ function buildWhereClauseV2(params, registry, resourceType) {
5415
5432
  }
5416
5433
  const impl = resolveImplV2(param, registry, resourceType);
5417
5434
  if (!impl) continue;
5418
- const fragment = buildWhereFragmentV2(impl, param);
5435
+ const fragment = buildWhereFragmentV2(impl, param, dialect);
5419
5436
  if (fragment) {
5420
5437
  fragments.push(fragment);
5421
5438
  }
@@ -5469,7 +5486,7 @@ function quoteTable(name) {
5469
5486
  return `"${name}"`;
5470
5487
  }
5471
5488
  var SEARCH_COLUMNS_V2 = ['"id"', '"versionId"', '"content"', '"lastUpdated"', '"deleted"'].join(", ");
5472
- function buildSearchSQLv2(request, registry) {
5489
+ function buildSearchSQLv2(request, registry, dialect) {
5473
5490
  const tableName = quoteTable(request.resourceType);
5474
5491
  const parts = [];
5475
5492
  const allValues = [];
@@ -5478,11 +5495,15 @@ function buildSearchSQLv2(request, registry) {
5478
5495
  const whereConditions = [];
5479
5496
  whereConditions.push('"deleted" = 0');
5480
5497
  if (request.compartment) {
5481
- whereConditions.push('EXISTS (SELECT 1 FROM json_each("compartments") WHERE json_each.value = ?)');
5498
+ if (dialect?.name === "postgres") {
5499
+ whereConditions.push('"compartments" && ARRAY[?]::text[]');
5500
+ } else {
5501
+ whereConditions.push('EXISTS (SELECT 1 FROM json_each("compartments") WHERE json_each.value = ?)');
5502
+ }
5482
5503
  allValues.push(request.compartment.id);
5483
5504
  }
5484
5505
  if (request.params.length > 0) {
5485
- const whereFragment = buildWhereClauseV2(request.params, registry, request.resourceType);
5506
+ const whereFragment = buildWhereClauseV2(request.params, registry, request.resourceType, dialect);
5486
5507
  if (whereFragment) {
5487
5508
  whereConditions.push(whereFragment.sql);
5488
5509
  allValues.push(...whereFragment.values);
@@ -5500,7 +5521,7 @@ function buildSearchSQLv2(request, registry) {
5500
5521
  }
5501
5522
  return { sql: parts.join("\n"), values: allValues };
5502
5523
  }
5503
- function buildCountSQLv2(request, registry) {
5524
+ function buildCountSQLv2(request, registry, dialect) {
5504
5525
  const tableName = quoteTable(request.resourceType);
5505
5526
  const parts = [];
5506
5527
  const allValues = [];
@@ -5509,11 +5530,15 @@ function buildCountSQLv2(request, registry) {
5509
5530
  const whereConditions = [];
5510
5531
  whereConditions.push('"deleted" = 0');
5511
5532
  if (request.compartment) {
5512
- whereConditions.push('EXISTS (SELECT 1 FROM json_each("compartments") WHERE json_each.value = ?)');
5533
+ if (dialect?.name === "postgres") {
5534
+ whereConditions.push('"compartments" && ARRAY[?]::text[]');
5535
+ } else {
5536
+ whereConditions.push('EXISTS (SELECT 1 FROM json_each("compartments") WHERE json_each.value = ?)');
5537
+ }
5513
5538
  allValues.push(request.compartment.id);
5514
5539
  }
5515
5540
  if (request.params.length > 0) {
5516
- const whereFragment = buildWhereClauseV2(request.params, registry, request.resourceType);
5541
+ const whereFragment = buildWhereClauseV2(request.params, registry, request.resourceType, dialect);
5517
5542
  if (whereFragment) {
5518
5543
  whereConditions.push(whereFragment.sql);
5519
5544
  allValues.push(...whereFragment.values);
@@ -5535,7 +5560,7 @@ function buildOrderByV2(sort, registry, resourceType) {
5535
5560
  }
5536
5561
  return clauses.length > 0 ? clauses.join(", ") : '"lastUpdated" DESC';
5537
5562
  }
5538
- function buildTwoPhaseSearchSQLv2(request, registry) {
5563
+ function buildTwoPhaseSearchSQLv2(request, registry, dialect) {
5539
5564
  const tableName = quoteTable(request.resourceType);
5540
5565
  const p1Parts = [];
5541
5566
  const p1Values = [];
@@ -5544,11 +5569,15 @@ function buildTwoPhaseSearchSQLv2(request, registry) {
5544
5569
  const whereConditions = [];
5545
5570
  whereConditions.push('"deleted" = 0');
5546
5571
  if (request.compartment) {
5547
- whereConditions.push('EXISTS (SELECT 1 FROM json_each("compartments") WHERE json_each.value = ?)');
5572
+ if (dialect?.name === "postgres") {
5573
+ whereConditions.push('"compartments" && ARRAY[?]::text[]');
5574
+ } else {
5575
+ whereConditions.push('EXISTS (SELECT 1 FROM json_each("compartments") WHERE json_each.value = ?)');
5576
+ }
5548
5577
  p1Values.push(request.compartment.id);
5549
5578
  }
5550
5579
  if (request.params.length > 0) {
5551
- const whereFragment = buildWhereClauseV2(request.params, registry, request.resourceType);
5580
+ const whereFragment = buildWhereClauseV2(request.params, registry, request.resourceType, dialect);
5552
5581
  if (whereFragment) {
5553
5582
  whereConditions.push(whereFragment.sql);
5554
5583
  p1Values.push(...whereFragment.values);
@@ -5679,12 +5708,13 @@ function buildPaginationContext(baseUrl, resourceType, queryParams, count, offse
5679
5708
 
5680
5709
  // src/search/search-executor.ts
5681
5710
  async function executeSearchV2(adapter, request, registry, options) {
5682
- const searchSQL = buildSearchSQLv2(request, registry);
5711
+ const dialect = options?.dialect;
5712
+ const searchSQL = buildSearchSQLv2(request, registry, dialect);
5683
5713
  const rows = await adapter.query(searchSQL.sql, searchSQL.values);
5684
5714
  const resources = mapRowsToResourcesV2(rows);
5685
5715
  let total;
5686
5716
  if (options?.total === "accurate") {
5687
- const countSQL = buildCountSQLv2(request, registry);
5717
+ const countSQL = buildCountSQLv2(request, registry, dialect);
5688
5718
  const countRow = await adapter.queryOne(countSQL.sql, countSQL.values);
5689
5719
  total = countRow?.count ?? 0;
5690
5720
  }
@@ -5991,166 +6021,6 @@ function planSearch(request, registry, options) {
5991
6021
  };
5992
6022
  }
5993
6023
 
5994
- // src/db/sqlite-adapter.ts
5995
- var import_sql = __toESM(require("sql.js"), 1);
5996
- var sqlJsInitPromise = null;
5997
- async function getSqlJs() {
5998
- if (!sqlJsInitPromise) {
5999
- sqlJsInitPromise = (0, import_sql.default)();
6000
- }
6001
- return sqlJsInitPromise;
6002
- }
6003
- var SQLiteAdapter = class {
6004
- /**
6005
- * @param path - Database file path, or ':memory:' for in-memory database.
6006
- * @param data - Optional Uint8Array of an existing database file to load.
6007
- */
6008
- constructor(_path = ":memory:", data) {
6009
- this.data = data;
6010
- this.initPromise = this.initialize();
6011
- }
6012
- db = null;
6013
- initPromise;
6014
- async initialize() {
6015
- const SQL = await getSqlJs();
6016
- this.db = new SQL.Database(this.data);
6017
- this.db.run("PRAGMA journal_mode = WAL");
6018
- this.db.run("PRAGMA foreign_keys = ON");
6019
- }
6020
- async getDb() {
6021
- await this.initPromise;
6022
- if (!this.db) {
6023
- throw new Error("SQLiteAdapter: database is closed");
6024
- }
6025
- return this.db;
6026
- }
6027
- async execute(sql, params = []) {
6028
- const db = await this.getDb();
6029
- db.run(sql, params);
6030
- const result = db.exec("SELECT changes() as c");
6031
- const changes = result.length > 0 ? result[0].values[0][0] : 0;
6032
- return { changes };
6033
- }
6034
- async query(sql, params = []) {
6035
- const db = await this.getDb();
6036
- const stmt = db.prepare(sql);
6037
- stmt.bind(params);
6038
- const rows = [];
6039
- while (stmt.step()) {
6040
- rows.push(stmt.getAsObject());
6041
- }
6042
- stmt.free();
6043
- return rows;
6044
- }
6045
- async queryOne(sql, params = []) {
6046
- const db = await this.getDb();
6047
- const stmt = db.prepare(sql);
6048
- stmt.bind(params);
6049
- let result;
6050
- if (stmt.step()) {
6051
- result = stmt.getAsObject();
6052
- }
6053
- stmt.free();
6054
- return result;
6055
- }
6056
- async *queryStream(sql, params = []) {
6057
- const db = await this.getDb();
6058
- const stmt = db.prepare(sql);
6059
- stmt.bind(params);
6060
- try {
6061
- while (stmt.step()) {
6062
- yield stmt.getAsObject();
6063
- }
6064
- } finally {
6065
- stmt.free();
6066
- }
6067
- }
6068
- prepare(sql) {
6069
- if (!this.db) {
6070
- throw new Error("SQLiteAdapter: database not initialized. Await an operation first.");
6071
- }
6072
- const db = this.db;
6073
- const stmt = db.prepare(sql);
6074
- return {
6075
- query(params = []) {
6076
- stmt.bind(params);
6077
- const rows = [];
6078
- while (stmt.step()) {
6079
- rows.push(stmt.getAsObject());
6080
- }
6081
- stmt.reset();
6082
- return rows;
6083
- },
6084
- execute(params = []) {
6085
- stmt.bind(params);
6086
- stmt.step();
6087
- stmt.reset();
6088
- const changesResult = db.exec("SELECT changes() as c, last_insert_rowid() as r");
6089
- const changes = changesResult.length > 0 ? changesResult[0].values[0][0] : 0;
6090
- const lastInsertRowid = changesResult.length > 0 ? changesResult[0].values[0][1] : void 0;
6091
- return { changes, lastInsertRowid };
6092
- },
6093
- finalize() {
6094
- stmt.free();
6095
- }
6096
- };
6097
- }
6098
- async transaction(fn) {
6099
- const db = await this.getDb();
6100
- db.run("BEGIN IMMEDIATE");
6101
- const ctx = {
6102
- execute(sql, params = []) {
6103
- db.run(sql, params);
6104
- const result = db.exec("SELECT changes() as c");
6105
- const changes = result.length > 0 ? result[0].values[0][0] : 0;
6106
- return { changes };
6107
- },
6108
- query(sql, params = []) {
6109
- const stmt = db.prepare(sql);
6110
- stmt.bind(params);
6111
- const rows = [];
6112
- while (stmt.step()) {
6113
- rows.push(stmt.getAsObject());
6114
- }
6115
- stmt.free();
6116
- return rows;
6117
- },
6118
- queryOne(sql, params = []) {
6119
- const stmt = db.prepare(sql);
6120
- stmt.bind(params);
6121
- let result;
6122
- if (stmt.step()) {
6123
- result = stmt.getAsObject();
6124
- }
6125
- stmt.free();
6126
- return result;
6127
- }
6128
- };
6129
- try {
6130
- const result = await fn(ctx);
6131
- db.run("COMMIT");
6132
- return result;
6133
- } catch (err) {
6134
- db.run("ROLLBACK");
6135
- throw err;
6136
- }
6137
- }
6138
- async close() {
6139
- await this.initPromise;
6140
- if (this.db) {
6141
- this.db.close();
6142
- this.db = null;
6143
- }
6144
- }
6145
- /**
6146
- * Export the database as a Uint8Array (for file persistence).
6147
- */
6148
- async export() {
6149
- const db = await this.getDb();
6150
- return db.export();
6151
- }
6152
- };
6153
-
6154
6024
  // src/db/better-sqlite3-adapter.ts
6155
6025
  var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
6156
6026
  var BetterSqlite3Adapter = class {
@@ -6228,14 +6098,14 @@ var BetterSqlite3Adapter = class {
6228
6098
  async transaction(fn) {
6229
6099
  const db = this.ensureOpen();
6230
6100
  const ctx = {
6231
- execute(sql, params = []) {
6101
+ async execute(sql, params = []) {
6232
6102
  const result = db.prepare(sql).run(...params);
6233
6103
  return { changes: result.changes };
6234
6104
  },
6235
- query(sql, params = []) {
6105
+ async query(sql, params = []) {
6236
6106
  return db.prepare(sql).all(...params);
6237
6107
  },
6238
- queryOne(sql, params = []) {
6108
+ async queryOne(sql, params = []) {
6239
6109
  return db.prepare(sql).get(...params);
6240
6110
  }
6241
6111
  };
@@ -6280,6 +6150,202 @@ var BetterSqlite3Adapter = class {
6280
6150
  }
6281
6151
  };
6282
6152
 
6153
+ // src/db/postgres-adapter.ts
6154
+ function rewritePlaceholders(sql) {
6155
+ let result = "";
6156
+ let paramIndex = 1;
6157
+ let inString = false;
6158
+ for (let i = 0; i < sql.length; i++) {
6159
+ const ch = sql[i];
6160
+ if (ch === "'" && !inString) {
6161
+ inString = true;
6162
+ result += ch;
6163
+ } else if (ch === "'" && inString) {
6164
+ if (i + 1 < sql.length && sql[i + 1] === "'") {
6165
+ result += "''";
6166
+ i++;
6167
+ } else {
6168
+ inString = false;
6169
+ result += ch;
6170
+ }
6171
+ } else if (ch === "?" && !inString) {
6172
+ result += `$${paramIndex}`;
6173
+ paramIndex++;
6174
+ } else {
6175
+ result += ch;
6176
+ }
6177
+ }
6178
+ return result;
6179
+ }
6180
+ var PostgresAdapter = class {
6181
+ pool;
6182
+ closed = false;
6183
+ constructor(pool) {
6184
+ this.pool = pool;
6185
+ }
6186
+ async execute(sql, params = []) {
6187
+ this.ensureNotClosed();
6188
+ const pgSql = rewritePlaceholders(sql);
6189
+ const result = await this.pool.query(pgSql, params);
6190
+ return { changes: result.rowCount ?? 0 };
6191
+ }
6192
+ async query(sql, params = []) {
6193
+ this.ensureNotClosed();
6194
+ const pgSql = rewritePlaceholders(sql);
6195
+ const result = await this.pool.query(pgSql, params);
6196
+ return result.rows;
6197
+ }
6198
+ async queryOne(sql, params = []) {
6199
+ this.ensureNotClosed();
6200
+ const pgSql = rewritePlaceholders(sql);
6201
+ const result = await this.pool.query(pgSql, params);
6202
+ return result.rows[0] ?? void 0;
6203
+ }
6204
+ async *queryStream(sql, params = []) {
6205
+ this.ensureNotClosed();
6206
+ const pgSql = rewritePlaceholders(sql);
6207
+ const result = await this.pool.query(pgSql, params);
6208
+ for (const row of result.rows) {
6209
+ yield row;
6210
+ }
6211
+ }
6212
+ prepare(_sql) {
6213
+ throw new Error("PostgresAdapter.prepare() is not supported. Use query() or execute() instead.");
6214
+ }
6215
+ async transaction(fn) {
6216
+ this.ensureNotClosed();
6217
+ const client = await this.pool.connect();
6218
+ try {
6219
+ await client.query("BEGIN");
6220
+ const ctx = {
6221
+ execute: async (sql, params = []) => {
6222
+ const pgSql = rewritePlaceholders(sql);
6223
+ const result2 = await client.query(pgSql, params);
6224
+ return { changes: result2.rowCount ?? 0 };
6225
+ },
6226
+ query: async (sql, params = []) => {
6227
+ const pgSql = rewritePlaceholders(sql);
6228
+ const result2 = await client.query(pgSql, params);
6229
+ return result2.rows;
6230
+ },
6231
+ queryOne: async (sql, params = []) => {
6232
+ const pgSql = rewritePlaceholders(sql);
6233
+ const result2 = await client.query(pgSql, params);
6234
+ return result2.rows[0] ?? void 0;
6235
+ }
6236
+ };
6237
+ const result = await fn(ctx);
6238
+ await client.query("COMMIT");
6239
+ return result;
6240
+ } catch (err) {
6241
+ await client.query("ROLLBACK");
6242
+ throw err;
6243
+ } finally {
6244
+ client.release();
6245
+ }
6246
+ }
6247
+ async close() {
6248
+ if (!this.closed) {
6249
+ this.closed = true;
6250
+ await this.pool.end();
6251
+ }
6252
+ }
6253
+ ensureNotClosed() {
6254
+ if (this.closed) {
6255
+ throw new Error("PostgresAdapter: pool is closed");
6256
+ }
6257
+ }
6258
+ };
6259
+
6260
+ // src/db/sqlite-dialect.ts
6261
+ var SQLiteDialect = class {
6262
+ name = "sqlite";
6263
+ placeholder(_index) {
6264
+ return "?";
6265
+ }
6266
+ textArrayContains(column, paramCount, _paramStartIndex) {
6267
+ const placeholders = Array.from({ length: paramCount }, () => "?").join(", ");
6268
+ return {
6269
+ sql: `EXISTS (SELECT 1 FROM json_each("${column}") WHERE value IN (${placeholders}))`,
6270
+ values: []
6271
+ // caller supplies values separately
6272
+ };
6273
+ }
6274
+ like(column, _paramIndex) {
6275
+ return `"${column}" LIKE ? ESCAPE '\\'`;
6276
+ }
6277
+ limitOffset(_paramStartIndex) {
6278
+ return { sql: "LIMIT ? OFFSET ?" };
6279
+ }
6280
+ arrayLiteral(values) {
6281
+ return JSON.stringify(values);
6282
+ }
6283
+ timestampType() {
6284
+ return "TEXT";
6285
+ }
6286
+ booleanType() {
6287
+ return "INTEGER";
6288
+ }
6289
+ textArrayType() {
6290
+ return "TEXT";
6291
+ }
6292
+ upsertSuffix(conflictColumn, updateColumns) {
6293
+ const sets = updateColumns.map((c) => `"${c}" = excluded."${c}"`).join(", ");
6294
+ return `ON CONFLICT("${conflictColumn}") DO UPDATE SET ${sets}`;
6295
+ }
6296
+ autoIncrementPK() {
6297
+ return "INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT";
6298
+ }
6299
+ };
6300
+
6301
+ // src/db/postgres-dialect.ts
6302
+ var PostgresDialect = class {
6303
+ name = "postgres";
6304
+ placeholder(index) {
6305
+ return `$${index}`;
6306
+ }
6307
+ textArrayContains(column, paramCount, paramStartIndex) {
6308
+ const placeholders = Array.from(
6309
+ { length: paramCount },
6310
+ (_, i) => `$${paramStartIndex + i}`
6311
+ ).join(", ");
6312
+ return {
6313
+ sql: `"${column}" && ARRAY[${placeholders}]::text[]`,
6314
+ values: []
6315
+ // caller supplies values separately
6316
+ };
6317
+ }
6318
+ like(column, paramIndex) {
6319
+ return `"${column}" LIKE $${paramIndex}`;
6320
+ }
6321
+ limitOffset(paramStartIndex) {
6322
+ return { sql: `LIMIT $${paramStartIndex} OFFSET $${paramStartIndex + 1}` };
6323
+ }
6324
+ arrayLiteral(values) {
6325
+ if (values.length === 0) {
6326
+ return "ARRAY[]::text[]";
6327
+ }
6328
+ const escaped = values.map((v) => `'${v.replace(/'/g, "''")}'`).join(", ");
6329
+ return `ARRAY[${escaped}]::text[]`;
6330
+ }
6331
+ timestampType() {
6332
+ return "TIMESTAMPTZ";
6333
+ }
6334
+ booleanType() {
6335
+ return "BOOLEAN";
6336
+ }
6337
+ textArrayType() {
6338
+ return "TEXT[]";
6339
+ }
6340
+ upsertSuffix(conflictColumn, updateColumns) {
6341
+ const sets = updateColumns.map((c) => `"${c}" = excluded."${c}"`).join(", ");
6342
+ return `ON CONFLICT("${conflictColumn}") DO UPDATE SET ${sets}`;
6343
+ }
6344
+ autoIncrementPK() {
6345
+ return "INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY";
6346
+ }
6347
+ };
6348
+
6283
6349
  // src/store/fhir-store.ts
6284
6350
  var import_node_crypto4 = require("node:crypto");
6285
6351
  var FhirStore = class {
@@ -6320,11 +6386,11 @@ var FhirStore = class {
6320
6386
  lastUpdated: now,
6321
6387
  deleted: 0
6322
6388
  };
6323
- await this.adapter.transaction((tx) => {
6389
+ await this.adapter.transaction(async (tx) => {
6324
6390
  const mainSQL = buildInsertMainSQLv2(resourceType, mainRow);
6325
- tx.execute(mainSQL.sql, mainSQL.values);
6391
+ await tx.execute(mainSQL.sql, mainSQL.values);
6326
6392
  const histSQL = buildInsertHistorySQLv2(`${resourceType}_History`, historyRow);
6327
- tx.execute(histSQL.sql, histSQL.values);
6393
+ await tx.execute(histSQL.sql, histSQL.values);
6328
6394
  });
6329
6395
  return persisted;
6330
6396
  }
@@ -6398,13 +6464,13 @@ var FhirStore = class {
6398
6464
  lastUpdated: now,
6399
6465
  deleted: 0
6400
6466
  };
6401
- await this.adapter.transaction((tx) => {
6467
+ await this.adapter.transaction(async (tx) => {
6402
6468
  const updateSQL = buildUpdateMainSQLv2(resourceType, mainRow);
6403
- tx.execute(updateSQL.sql, updateSQL.values);
6469
+ await tx.execute(updateSQL.sql, updateSQL.values);
6404
6470
  const histSQL = buildInsertHistorySQLv2(`${resourceType}_History`, historyRow);
6405
- tx.execute(histSQL.sql, histSQL.values);
6471
+ await tx.execute(histSQL.sql, histSQL.values);
6406
6472
  const delRefSQL = buildDeleteReferencesSQLv2(`${resourceType}_References`);
6407
- tx.execute(delRefSQL, [id]);
6473
+ await tx.execute(delRefSQL, [id]);
6408
6474
  });
6409
6475
  return persisted;
6410
6476
  }
@@ -6436,13 +6502,13 @@ var FhirStore = class {
6436
6502
  lastUpdated: now,
6437
6503
  deleted: 1
6438
6504
  };
6439
- await this.adapter.transaction((tx) => {
6505
+ await this.adapter.transaction(async (tx) => {
6440
6506
  const updateSQL = buildUpdateMainSQLv2(resourceType, deleteRow);
6441
- tx.execute(updateSQL.sql, updateSQL.values);
6507
+ await tx.execute(updateSQL.sql, updateSQL.values);
6442
6508
  const histSQL = buildInsertHistorySQLv2(`${resourceType}_History`, historyRow);
6443
- tx.execute(histSQL.sql, histSQL.values);
6509
+ await tx.execute(histSQL.sql, histSQL.values);
6444
6510
  const delRefSQL = buildDeleteReferencesSQLv2(`${resourceType}_References`);
6445
- tx.execute(delRefSQL, [id]);
6511
+ await tx.execute(delRefSQL, [id]);
6446
6512
  });
6447
6513
  }
6448
6514
  // ---------------------------------------------------------------------------
@@ -6885,11 +6951,11 @@ var FhirPersistence = class {
6885
6951
  lastUpdated: now,
6886
6952
  deleted: 0
6887
6953
  };
6888
- await this.adapter.transaction((tx) => {
6954
+ await this.adapter.transaction(async (tx) => {
6889
6955
  const mainSQL = buildInsertMainSQLv2(resourceType, mainRow);
6890
- tx.execute(mainSQL.sql, mainSQL.values);
6956
+ await tx.execute(mainSQL.sql, mainSQL.values);
6891
6957
  const histSQL = buildInsertHistorySQLv2(`${resourceType}_History`, historyRow);
6892
- tx.execute(histSQL.sql, histSQL.values);
6958
+ await tx.execute(histSQL.sql, histSQL.values);
6893
6959
  });
6894
6960
  return persisted;
6895
6961
  }
@@ -6966,11 +7032,11 @@ var FhirPersistence = class {
6966
7032
  lastUpdated: now,
6967
7033
  deleted: 0
6968
7034
  };
6969
- await this.adapter.transaction((tx) => {
7035
+ await this.adapter.transaction(async (tx) => {
6970
7036
  const updateSQL = buildUpdateMainSQLv2(resourceType, mainRow);
6971
- tx.execute(updateSQL.sql, updateSQL.values);
7037
+ await tx.execute(updateSQL.sql, updateSQL.values);
6972
7038
  const histSQL = buildInsertHistorySQLv2(`${resourceType}_History`, historyRow);
6973
- tx.execute(histSQL.sql, histSQL.values);
7039
+ await tx.execute(histSQL.sql, histSQL.values);
6974
7040
  });
6975
7041
  return persisted;
6976
7042
  }
@@ -7003,11 +7069,11 @@ var FhirPersistence = class {
7003
7069
  deleted: 1
7004
7070
  };
7005
7071
  await this.pipeline.deleteIndex(resourceType, id);
7006
- await this.adapter.transaction((tx) => {
7072
+ await this.adapter.transaction(async (tx) => {
7007
7073
  const updateSQL = buildUpdateMainSQLv2(resourceType, deleteRow);
7008
- tx.execute(updateSQL.sql, updateSQL.values);
7074
+ await tx.execute(updateSQL.sql, updateSQL.values);
7009
7075
  const histSQL = buildInsertHistorySQLv2(`${resourceType}_History`, historyRow);
7010
- tx.execute(histSQL.sql, histSQL.values);
7076
+ await tx.execute(histSQL.sql, histSQL.values);
7011
7077
  });
7012
7078
  }
7013
7079
  // ---------------------------------------------------------------------------
@@ -7673,22 +7739,22 @@ var MigrationRunnerV2 = class {
7673
7739
  return new Set(rows.map((r) => r.version));
7674
7740
  }
7675
7741
  async applyMigration(migration) {
7676
- await this.adapter.transaction((tx) => {
7742
+ await this.adapter.transaction(async (tx) => {
7677
7743
  for (const sql of migration.up) {
7678
- tx.execute(sql);
7744
+ await tx.execute(sql);
7679
7745
  }
7680
- tx.execute(
7746
+ await tx.execute(
7681
7747
  `INSERT INTO "${TRACKING_TABLE_V2}" ("version", "description", "type") VALUES (?, ?, ?)`,
7682
7748
  [migration.version, migration.description, migration.type]
7683
7749
  );
7684
7750
  });
7685
7751
  }
7686
7752
  async revertMigration(migration) {
7687
- await this.adapter.transaction((tx) => {
7753
+ await this.adapter.transaction(async (tx) => {
7688
7754
  for (const sql of migration.down) {
7689
- tx.execute(sql);
7755
+ await tx.execute(sql);
7690
7756
  }
7691
- tx.execute(
7757
+ await tx.execute(
7692
7758
  `DELETE FROM "${TRACKING_TABLE_V2}" WHERE "version" = ?`,
7693
7759
  [migration.version]
7694
7760
  );
@@ -7944,10 +8010,10 @@ var TerminologyCodeRepo = class {
7944
8010
  const chunkSize = 100;
7945
8011
  for (let i = 0; i < codes.length; i += chunkSize) {
7946
8012
  const chunk = codes.slice(i, i + chunkSize);
7947
- const result = await this.adapter.transaction((tx) => {
8013
+ const result = await this.adapter.transaction(async (tx) => {
7948
8014
  let count = 0;
7949
8015
  for (const c of chunk) {
7950
- const r = tx.execute(
8016
+ const r = await tx.execute(
7951
8017
  `INSERT OR IGNORE INTO "${CODES_TABLE}" ("system", "code", "display") VALUES (?, ?, ?)`,
7952
8018
  [c.system, c.code, c.display]
7953
8019
  );
@@ -8798,6 +8864,8 @@ function createFhirRuntimeProvider(options) {
8798
8864
  PROJECT_ADMIN_RESOURCE_TYPES,
8799
8865
  PROTECTED_RESOURCE_TYPES,
8800
8866
  PackageRegistryRepo,
8867
+ PostgresAdapter,
8868
+ PostgresDialect,
8801
8869
  ReindexScheduler,
8802
8870
  RepositoryError,
8803
8871
  ResourceCacheV2,
@@ -8806,7 +8874,7 @@ function createFhirRuntimeProvider(options) {
8806
8874
  ResourceVersionConflictError,
8807
8875
  SCHEMA_VERSION,
8808
8876
  SEARCH_PREFIXES,
8809
- SQLiteAdapter,
8877
+ SQLiteDialect,
8810
8878
  SearchLogger,
8811
8879
  SearchParameterRegistry,
8812
8880
  StructureDefinitionRegistry,