fhir-persistence 0.6.0 → 0.7.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.
@@ -31,14 +31,19 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  BetterSqlite3Adapter: () => BetterSqlite3Adapter,
34
+ ConceptHierarchyRepo: () => ConceptHierarchyRepo,
34
35
  DEFAULT_SEARCH_COUNT: () => DEFAULT_SEARCH_COUNT,
35
36
  DELETED_SCHEMA_VERSION: () => DELETED_SCHEMA_VERSION,
37
+ ElementIndexRepo: () => ElementIndexRepo,
38
+ ExpansionCacheRepo: () => ExpansionCacheRepo,
36
39
  FhirDefinitionBridge: () => FhirDefinitionBridge,
37
40
  FhirPersistence: () => FhirPersistence,
38
41
  FhirRuntimeProvider: () => FhirRuntimeProvider,
39
42
  FhirStore: () => FhirStore,
40
43
  FhirSystem: () => FhirSystem,
44
+ IGImportOrchestrator: () => IGImportOrchestrator,
41
45
  IGPersistenceManager: () => IGPersistenceManager,
46
+ IGResourceMapRepo: () => IGResourceMapRepo,
42
47
  IndexingPipeline: () => IndexingPipeline,
43
48
  LookupTableWriter: () => LookupTableWriter,
44
49
  MAX_SEARCH_COUNT: () => MAX_SEARCH_COUNT,
@@ -60,9 +65,11 @@ __export(index_exports, {
60
65
  ResourceNotFoundError: () => ResourceNotFoundError,
61
66
  ResourceVersionConflictError: () => ResourceVersionConflictError,
62
67
  SCHEMA_VERSION: () => SCHEMA_VERSION,
68
+ SDIndexRepo: () => SDIndexRepo,
63
69
  SEARCH_PREFIXES: () => SEARCH_PREFIXES,
64
70
  SQLiteDialect: () => SQLiteDialect,
65
71
  SearchLogger: () => SearchLogger,
72
+ SearchParamIndexRepo: () => SearchParamIndexRepo,
66
73
  SearchParameterRegistry: () => SearchParameterRegistry,
67
74
  StructureDefinitionRegistry: () => StructureDefinitionRegistry,
68
75
  TerminologyCodeRepo: () => TerminologyCodeRepo,
@@ -8911,17 +8918,727 @@ var FhirRuntimeProvider = class {
8911
8918
  function createFhirRuntimeProvider(options) {
8912
8919
  return new FhirRuntimeProvider(options);
8913
8920
  }
8921
+
8922
+ // src/conformance/ig-resource-map-repo.ts
8923
+ var TABLE = "ig_resource_map";
8924
+ var CREATE_TABLE_DDL = `
8925
+ CREATE TABLE IF NOT EXISTS "${TABLE}" (
8926
+ "ig_id" TEXT NOT NULL,
8927
+ "resource_type" TEXT NOT NULL,
8928
+ "resource_id" TEXT NOT NULL,
8929
+ "resource_url" TEXT,
8930
+ "resource_name" TEXT,
8931
+ "base_type" TEXT,
8932
+ PRIMARY KEY ("ig_id", "resource_type", "resource_id")
8933
+ );
8934
+ `;
8935
+ var CREATE_INDEX_IG = `CREATE INDEX IF NOT EXISTS idx_ig_resource_map_ig ON "${TABLE}"("ig_id")`;
8936
+ var CREATE_INDEX_TYPE = `CREATE INDEX IF NOT EXISTS idx_ig_resource_map_type ON "${TABLE}"("ig_id", "resource_type")`;
8937
+ var IGResourceMapRepo = class {
8938
+ constructor(adapter, dialect = "sqlite") {
8939
+ this.adapter = adapter;
8940
+ this.dialect = dialect;
8941
+ }
8942
+ async ensureTable() {
8943
+ await this.adapter.execute(CREATE_TABLE_DDL);
8944
+ await this.adapter.execute(CREATE_INDEX_IG);
8945
+ await this.adapter.execute(CREATE_INDEX_TYPE);
8946
+ }
8947
+ /** Batch insert resource map entries for an IG. */
8948
+ async batchInsert(igId, entries) {
8949
+ await this.ensureTable();
8950
+ let count = 0;
8951
+ const sql = this.dialect === "postgres" ? `INSERT INTO "${TABLE}" ("ig_id", "resource_type", "resource_id", "resource_url", "resource_name", "base_type") VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT ("ig_id", "resource_type", "resource_id") DO UPDATE SET "resource_url" = EXCLUDED."resource_url", "resource_name" = EXCLUDED."resource_name", "base_type" = EXCLUDED."base_type"` : `INSERT OR REPLACE INTO "${TABLE}" ("ig_id", "resource_type", "resource_id", "resource_url", "resource_name", "base_type") VALUES (?, ?, ?, ?, ?, ?)`;
8952
+ for (const e of entries) {
8953
+ await this.adapter.execute(sql, [
8954
+ igId,
8955
+ e.resourceType,
8956
+ e.resourceId,
8957
+ e.resourceUrl ?? null,
8958
+ e.resourceName ?? null,
8959
+ e.baseType ?? null
8960
+ ]);
8961
+ count++;
8962
+ }
8963
+ return count;
8964
+ }
8965
+ /** Get grouped IG index. */
8966
+ async getIGIndex(igId) {
8967
+ await this.ensureTable();
8968
+ const rows = await this.adapter.query(
8969
+ `SELECT "ig_id", "resource_type", "resource_id", "resource_url", "resource_name", "base_type" FROM "${TABLE}" WHERE "ig_id" = ? ORDER BY "resource_type", "resource_id"`,
8970
+ [igId]
8971
+ );
8972
+ const index = {
8973
+ profiles: [],
8974
+ extensions: [],
8975
+ valueSets: [],
8976
+ codeSystems: [],
8977
+ searchParameters: []
8978
+ };
8979
+ for (const r of rows) {
8980
+ const entry = {
8981
+ igId: r.ig_id,
8982
+ resourceType: r.resource_type,
8983
+ resourceId: r.resource_id,
8984
+ resourceUrl: r.resource_url ?? void 0,
8985
+ resourceName: r.resource_name ?? void 0,
8986
+ baseType: r.base_type ?? void 0
8987
+ };
8988
+ switch (r.resource_type) {
8989
+ case "StructureDefinition":
8990
+ if (r.base_type === "Extension") {
8991
+ index.extensions.push(entry);
8992
+ } else {
8993
+ index.profiles.push(entry);
8994
+ }
8995
+ break;
8996
+ case "ValueSet":
8997
+ index.valueSets.push(entry);
8998
+ break;
8999
+ case "CodeSystem":
9000
+ index.codeSystems.push(entry);
9001
+ break;
9002
+ case "SearchParameter":
9003
+ index.searchParameters.push(entry);
9004
+ break;
9005
+ default:
9006
+ break;
9007
+ }
9008
+ }
9009
+ return index;
9010
+ }
9011
+ /** Get resources of a specific type within an IG. */
9012
+ async getByType(igId, resourceType) {
9013
+ await this.ensureTable();
9014
+ const rows = await this.adapter.query(
9015
+ `SELECT "ig_id", "resource_type", "resource_id", "resource_url", "resource_name", "base_type" FROM "${TABLE}" WHERE "ig_id" = ? AND "resource_type" = ? ORDER BY "resource_id"`,
9016
+ [igId, resourceType]
9017
+ );
9018
+ return rows.map((r) => ({
9019
+ igId: r.ig_id,
9020
+ resourceType: r.resource_type,
9021
+ resourceId: r.resource_id,
9022
+ resourceUrl: r.resource_url ?? void 0,
9023
+ resourceName: r.resource_name ?? void 0,
9024
+ baseType: r.base_type ?? void 0
9025
+ }));
9026
+ }
9027
+ /** Remove all resource mappings for an IG. */
9028
+ async removeIG(igId) {
9029
+ await this.ensureTable();
9030
+ await this.adapter.execute(`DELETE FROM "${TABLE}" WHERE "ig_id" = ?`, [igId]);
9031
+ }
9032
+ };
9033
+
9034
+ // src/conformance/sd-index-repo.ts
9035
+ var TABLE2 = "structure_definition_index";
9036
+ var CREATE_TABLE_DDL2 = `
9037
+ CREATE TABLE IF NOT EXISTS "${TABLE2}" (
9038
+ "id" TEXT PRIMARY KEY,
9039
+ "url" TEXT,
9040
+ "version" TEXT,
9041
+ "type" TEXT,
9042
+ "kind" TEXT,
9043
+ "base_definition" TEXT,
9044
+ "derivation" TEXT,
9045
+ "snapshot_hash" TEXT
9046
+ );
9047
+ `;
9048
+ var CREATE_INDEX_TYPE2 = `CREATE INDEX IF NOT EXISTS idx_sdi_type ON "${TABLE2}"("type")`;
9049
+ var CREATE_INDEX_KIND = `CREATE INDEX IF NOT EXISTS idx_sdi_kind ON "${TABLE2}"("kind")`;
9050
+ var CREATE_INDEX_BASE = `CREATE INDEX IF NOT EXISTS idx_sdi_base ON "${TABLE2}"("base_definition")`;
9051
+ var SDIndexRepo = class {
9052
+ constructor(adapter, dialect = "sqlite") {
9053
+ this.adapter = adapter;
9054
+ this.dialect = dialect;
9055
+ }
9056
+ async ensureTable() {
9057
+ await this.adapter.execute(CREATE_TABLE_DDL2);
9058
+ await this.adapter.execute(CREATE_INDEX_TYPE2);
9059
+ await this.adapter.execute(CREATE_INDEX_KIND);
9060
+ await this.adapter.execute(CREATE_INDEX_BASE);
9061
+ }
9062
+ async upsert(entry) {
9063
+ await this.ensureTable();
9064
+ const sql = this.dialect === "postgres" ? `INSERT INTO "${TABLE2}" ("id", "url", "version", "type", "kind", "base_definition", "derivation", "snapshot_hash") VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT ("id") DO UPDATE SET "url" = EXCLUDED."url", "version" = EXCLUDED."version", "type" = EXCLUDED."type", "kind" = EXCLUDED."kind", "base_definition" = EXCLUDED."base_definition", "derivation" = EXCLUDED."derivation", "snapshot_hash" = EXCLUDED."snapshot_hash"` : `INSERT OR REPLACE INTO "${TABLE2}" ("id", "url", "version", "type", "kind", "base_definition", "derivation", "snapshot_hash") VALUES (?, ?, ?, ?, ?, ?, ?, ?)`;
9065
+ await this.adapter.execute(sql, [
9066
+ entry.id,
9067
+ entry.url ?? null,
9068
+ entry.version ?? null,
9069
+ entry.type ?? null,
9070
+ entry.kind ?? null,
9071
+ entry.baseDefinition ?? null,
9072
+ entry.derivation ?? null,
9073
+ entry.snapshotHash ?? null
9074
+ ]);
9075
+ }
9076
+ async batchUpsert(entries) {
9077
+ let count = 0;
9078
+ for (const entry of entries) {
9079
+ await this.upsert(entry);
9080
+ count++;
9081
+ }
9082
+ return count;
9083
+ }
9084
+ async getById(id) {
9085
+ await this.ensureTable();
9086
+ const row = await this.adapter.queryOne(
9087
+ `SELECT "id", "url", "version", "type", "kind", "base_definition", "derivation", "snapshot_hash" FROM "${TABLE2}" WHERE "id" = ?`,
9088
+ [id]
9089
+ );
9090
+ return row ? this.mapRow(row) : void 0;
9091
+ }
9092
+ async getByUrl(url) {
9093
+ await this.ensureTable();
9094
+ const rows = await this.adapter.query(
9095
+ `SELECT "id", "url", "version", "type", "kind", "base_definition", "derivation", "snapshot_hash" FROM "${TABLE2}" WHERE "url" = ? ORDER BY "version"`,
9096
+ [url]
9097
+ );
9098
+ return rows.map((r) => this.mapRow(r));
9099
+ }
9100
+ async getByType(type) {
9101
+ await this.ensureTable();
9102
+ const rows = await this.adapter.query(
9103
+ `SELECT "id", "url", "version", "type", "kind", "base_definition", "derivation", "snapshot_hash" FROM "${TABLE2}" WHERE "type" = ? ORDER BY "id"`,
9104
+ [type]
9105
+ );
9106
+ return rows.map((r) => this.mapRow(r));
9107
+ }
9108
+ async getByBaseDefinition(baseUrl) {
9109
+ await this.ensureTable();
9110
+ const rows = await this.adapter.query(
9111
+ `SELECT "id", "url", "version", "type", "kind", "base_definition", "derivation", "snapshot_hash" FROM "${TABLE2}" WHERE "base_definition" = ? ORDER BY "id"`,
9112
+ [baseUrl]
9113
+ );
9114
+ return rows.map((r) => this.mapRow(r));
9115
+ }
9116
+ async remove(id) {
9117
+ await this.ensureTable();
9118
+ await this.adapter.execute(`DELETE FROM "${TABLE2}" WHERE "id" = ?`, [id]);
9119
+ }
9120
+ mapRow(r) {
9121
+ return {
9122
+ id: r.id,
9123
+ url: r.url ?? void 0,
9124
+ version: r.version ?? void 0,
9125
+ type: r.type ?? void 0,
9126
+ kind: r.kind ?? void 0,
9127
+ baseDefinition: r.base_definition ?? void 0,
9128
+ derivation: r.derivation ?? void 0,
9129
+ snapshotHash: r.snapshot_hash ?? void 0
9130
+ };
9131
+ }
9132
+ };
9133
+
9134
+ // src/conformance/element-index-repo.ts
9135
+ var TABLE3 = "structure_element_index";
9136
+ function createTableDDL(dialect) {
9137
+ if (dialect === "postgres") {
9138
+ return `
9139
+ CREATE TABLE IF NOT EXISTS "${TABLE3}" (
9140
+ "id" TEXT PRIMARY KEY,
9141
+ "structure_id" TEXT NOT NULL,
9142
+ "path" TEXT NOT NULL,
9143
+ "min" INTEGER,
9144
+ "max" TEXT,
9145
+ "type_codes" JSONB,
9146
+ "is_slice" BOOLEAN DEFAULT FALSE,
9147
+ "slice_name" TEXT,
9148
+ "is_extension" BOOLEAN DEFAULT FALSE,
9149
+ "binding_value_set" TEXT,
9150
+ "must_support" BOOLEAN DEFAULT FALSE
9151
+ );
9152
+ `;
9153
+ }
9154
+ return `
9155
+ CREATE TABLE IF NOT EXISTS "${TABLE3}" (
9156
+ "id" TEXT PRIMARY KEY,
9157
+ "structure_id" TEXT NOT NULL,
9158
+ "path" TEXT NOT NULL,
9159
+ "min" INTEGER,
9160
+ "max" TEXT,
9161
+ "type_codes" TEXT,
9162
+ "is_slice" INTEGER DEFAULT 0,
9163
+ "slice_name" TEXT,
9164
+ "is_extension" INTEGER DEFAULT 0,
9165
+ "binding_value_set" TEXT,
9166
+ "must_support" INTEGER DEFAULT 0
9167
+ );
9168
+ `;
9169
+ }
9170
+ var CREATE_INDEX_STRUCTURE = `CREATE INDEX IF NOT EXISTS idx_sei_structure ON "${TABLE3}"("structure_id")`;
9171
+ var CREATE_INDEX_PATH = `CREATE INDEX IF NOT EXISTS idx_sei_path ON "${TABLE3}"("path")`;
9172
+ var CREATE_INDEX_SLICE = `CREATE INDEX IF NOT EXISTS idx_sei_slice ON "${TABLE3}"("structure_id", "is_slice")`;
9173
+ var ElementIndexRepo = class {
9174
+ constructor(adapter, dialect = "sqlite") {
9175
+ this.adapter = adapter;
9176
+ this.dialect = dialect;
9177
+ }
9178
+ async ensureTable() {
9179
+ await this.adapter.execute(createTableDDL(this.dialect));
9180
+ await this.adapter.execute(CREATE_INDEX_STRUCTURE);
9181
+ await this.adapter.execute(CREATE_INDEX_PATH);
9182
+ await this.adapter.execute(CREATE_INDEX_SLICE);
9183
+ }
9184
+ /** Batch insert element index entries for a StructureDefinition. */
9185
+ async batchInsert(structureId, elements) {
9186
+ await this.ensureTable();
9187
+ let count = 0;
9188
+ const sql = this.dialect === "postgres" ? `INSERT INTO "${TABLE3}" ("id", "structure_id", "path", "min", "max", "type_codes", "is_slice", "slice_name", "is_extension", "binding_value_set", "must_support") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT ("id") DO UPDATE SET "path" = EXCLUDED."path", "min" = EXCLUDED."min", "max" = EXCLUDED."max", "type_codes" = EXCLUDED."type_codes", "is_slice" = EXCLUDED."is_slice", "slice_name" = EXCLUDED."slice_name", "is_extension" = EXCLUDED."is_extension", "binding_value_set" = EXCLUDED."binding_value_set", "must_support" = EXCLUDED."must_support"` : `INSERT OR REPLACE INTO "${TABLE3}" ("id", "structure_id", "path", "min", "max", "type_codes", "is_slice", "slice_name", "is_extension", "binding_value_set", "must_support") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
9189
+ for (const e of elements) {
9190
+ const typeCodes = e.typeCodes ? this.dialect === "postgres" ? JSON.stringify(e.typeCodes) : JSON.stringify(e.typeCodes) : null;
9191
+ const isSlice = this.dialect === "postgres" ? e.isSlice ?? false : e.isSlice ? 1 : 0;
9192
+ const isExtension = this.dialect === "postgres" ? e.isExtension ?? false : e.isExtension ? 1 : 0;
9193
+ const mustSupport = this.dialect === "postgres" ? e.mustSupport ?? false : e.mustSupport ? 1 : 0;
9194
+ await this.adapter.execute(sql, [
9195
+ e.id,
9196
+ structureId,
9197
+ e.path,
9198
+ e.min ?? null,
9199
+ e.max ?? null,
9200
+ typeCodes,
9201
+ isSlice,
9202
+ e.sliceName ?? null,
9203
+ isExtension,
9204
+ e.bindingValueSet ?? null,
9205
+ mustSupport
9206
+ ]);
9207
+ count++;
9208
+ }
9209
+ return count;
9210
+ }
9211
+ /** Get all elements for a StructureDefinition. */
9212
+ async getByStructureId(structureId) {
9213
+ await this.ensureTable();
9214
+ const rows = await this.adapter.query(
9215
+ `SELECT "id", "structure_id", "path", "min", "max", "type_codes", "is_slice", "slice_name", "is_extension", "binding_value_set", "must_support" FROM "${TABLE3}" WHERE "structure_id" = ? ORDER BY "id"`,
9216
+ [structureId]
9217
+ );
9218
+ return rows.map((r) => this.mapRow(r));
9219
+ }
9220
+ /** Search elements by path pattern (LIKE). */
9221
+ async searchByPath(pathPattern) {
9222
+ await this.ensureTable();
9223
+ const rows = await this.adapter.query(
9224
+ `SELECT "id", "structure_id", "path", "min", "max", "type_codes", "is_slice", "slice_name", "is_extension", "binding_value_set", "must_support" FROM "${TABLE3}" WHERE "path" LIKE ? ORDER BY "structure_id", "path"`,
9225
+ [pathPattern]
9226
+ );
9227
+ return rows.map((r) => this.mapRow(r));
9228
+ }
9229
+ /** Remove all element indexes for a StructureDefinition. */
9230
+ async removeByStructureId(structureId) {
9231
+ await this.ensureTable();
9232
+ await this.adapter.execute(`DELETE FROM "${TABLE3}" WHERE "structure_id" = ?`, [structureId]);
9233
+ }
9234
+ mapRow(r) {
9235
+ const typeCodes = r.type_codes ? typeof r.type_codes === "string" ? JSON.parse(r.type_codes) : r.type_codes : void 0;
9236
+ return {
9237
+ id: r.id,
9238
+ structureId: r.structure_id,
9239
+ path: r.path,
9240
+ min: r.min != null ? Number(r.min) : void 0,
9241
+ max: r.max ?? void 0,
9242
+ typeCodes,
9243
+ isSlice: Boolean(r.is_slice),
9244
+ sliceName: r.slice_name ?? void 0,
9245
+ isExtension: Boolean(r.is_extension),
9246
+ bindingValueSet: r.binding_value_set ?? void 0,
9247
+ mustSupport: Boolean(r.must_support)
9248
+ };
9249
+ }
9250
+ };
9251
+
9252
+ // src/conformance/expansion-cache-repo.ts
9253
+ var TABLE4 = "value_set_expansion";
9254
+ function createTableDDL2(dialect) {
9255
+ const ts = dialect === "postgres" ? "TIMESTAMPTZ DEFAULT NOW()" : "TEXT DEFAULT (datetime('now'))";
9256
+ const jsonType = dialect === "postgres" ? "JSONB NOT NULL" : "TEXT NOT NULL";
9257
+ return `
9258
+ CREATE TABLE IF NOT EXISTS "${TABLE4}" (
9259
+ "valueset_url" TEXT NOT NULL,
9260
+ "version" TEXT NOT NULL DEFAULT '',
9261
+ "expanded_at" ${ts},
9262
+ "code_count" INTEGER,
9263
+ "expansion_json" ${jsonType},
9264
+ PRIMARY KEY ("valueset_url", "version")
9265
+ );
9266
+ `;
9267
+ }
9268
+ var ExpansionCacheRepo = class {
9269
+ constructor(adapter, dialect = "sqlite") {
9270
+ this.adapter = adapter;
9271
+ this.dialect = dialect;
9272
+ }
9273
+ async ensureTable() {
9274
+ await this.adapter.execute(createTableDDL2(this.dialect));
9275
+ }
9276
+ /** Write or update an expansion cache entry. */
9277
+ async upsert(url, version, expansionJson, codeCount) {
9278
+ await this.ensureTable();
9279
+ const sql = this.dialect === "postgres" ? `INSERT INTO "${TABLE4}" ("valueset_url", "version", "expansion_json", "code_count") VALUES (?, ?, ?, ?) ON CONFLICT ("valueset_url", "version") DO UPDATE SET "expansion_json" = EXCLUDED."expansion_json", "code_count" = EXCLUDED."code_count", "expanded_at" = NOW()` : `INSERT OR REPLACE INTO "${TABLE4}" ("valueset_url", "version", "expansion_json", "code_count") VALUES (?, ?, ?, ?)`;
9280
+ await this.adapter.execute(sql, [url, version, expansionJson, codeCount]);
9281
+ }
9282
+ /** Get a cached expansion by URL and version. */
9283
+ async get(url, version) {
9284
+ await this.ensureTable();
9285
+ const row = await this.adapter.queryOne(
9286
+ `SELECT "valueset_url", "version", "expanded_at", "code_count", "expansion_json" FROM "${TABLE4}" WHERE "valueset_url" = ? AND "version" = ?`,
9287
+ [url, version]
9288
+ );
9289
+ if (!row) return void 0;
9290
+ return {
9291
+ valuesetUrl: row.valueset_url,
9292
+ version: row.version,
9293
+ expandedAt: row.expanded_at,
9294
+ codeCount: row.code_count,
9295
+ expansionJson: row.expansion_json
9296
+ };
9297
+ }
9298
+ /** Invalidate a specific expansion cache entry. */
9299
+ async invalidate(url, version) {
9300
+ await this.ensureTable();
9301
+ await this.adapter.execute(
9302
+ `DELETE FROM "${TABLE4}" WHERE "valueset_url" = ? AND "version" = ?`,
9303
+ [url, version]
9304
+ );
9305
+ }
9306
+ /** Clear all expansion caches. */
9307
+ async clear() {
9308
+ await this.ensureTable();
9309
+ await this.adapter.execute(`DELETE FROM "${TABLE4}"`);
9310
+ }
9311
+ };
9312
+
9313
+ // src/conformance/concept-hierarchy-repo.ts
9314
+ var TABLE5 = "code_system_concept";
9315
+ var CREATE_TABLE_DDL3 = `
9316
+ CREATE TABLE IF NOT EXISTS "${TABLE5}" (
9317
+ "id" TEXT PRIMARY KEY,
9318
+ "code_system_url" TEXT NOT NULL,
9319
+ "code_system_version" TEXT,
9320
+ "code" TEXT NOT NULL,
9321
+ "display" TEXT,
9322
+ "parent_code" TEXT,
9323
+ "level" INTEGER DEFAULT 0
9324
+ );
9325
+ `;
9326
+ var CREATE_INDEX_URL = `CREATE INDEX IF NOT EXISTS idx_csc_url ON "${TABLE5}"("code_system_url")`;
9327
+ var CREATE_INDEX_CODE = `CREATE INDEX IF NOT EXISTS idx_csc_code ON "${TABLE5}"("code_system_url", "code")`;
9328
+ var CREATE_INDEX_PARENT = `CREATE INDEX IF NOT EXISTS idx_csc_parent ON "${TABLE5}"("code_system_url", "parent_code")`;
9329
+ var ConceptHierarchyRepo = class {
9330
+ constructor(adapter, dialect = "sqlite") {
9331
+ this.adapter = adapter;
9332
+ this.dialect = dialect;
9333
+ }
9334
+ async ensureTable() {
9335
+ await this.adapter.execute(CREATE_TABLE_DDL3);
9336
+ await this.adapter.execute(CREATE_INDEX_URL);
9337
+ await this.adapter.execute(CREATE_INDEX_CODE);
9338
+ await this.adapter.execute(CREATE_INDEX_PARENT);
9339
+ }
9340
+ /** Batch insert hierarchical concept entries. */
9341
+ async batchInsert(entries) {
9342
+ await this.ensureTable();
9343
+ let count = 0;
9344
+ const sql = this.dialect === "postgres" ? `INSERT INTO "${TABLE5}" ("id", "code_system_url", "code_system_version", "code", "display", "parent_code", "level") VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT ("id") DO UPDATE SET "display" = EXCLUDED."display", "parent_code" = EXCLUDED."parent_code", "level" = EXCLUDED."level"` : `INSERT OR REPLACE INTO "${TABLE5}" ("id", "code_system_url", "code_system_version", "code", "display", "parent_code", "level") VALUES (?, ?, ?, ?, ?, ?, ?)`;
9345
+ for (const e of entries) {
9346
+ await this.adapter.execute(sql, [
9347
+ e.id,
9348
+ e.codeSystemUrl,
9349
+ e.codeSystemVersion ?? null,
9350
+ e.code,
9351
+ e.display ?? null,
9352
+ e.parentCode ?? null,
9353
+ e.level
9354
+ ]);
9355
+ count++;
9356
+ }
9357
+ return count;
9358
+ }
9359
+ /** Get all concepts for a CodeSystem (tree order by level). */
9360
+ async getTree(codeSystemUrl) {
9361
+ await this.ensureTable();
9362
+ const rows = await this.adapter.query(
9363
+ `SELECT "id", "code_system_url", "code_system_version", "code", "display", "parent_code", "level" FROM "${TABLE5}" WHERE "code_system_url" = ? ORDER BY "level", "code"`,
9364
+ [codeSystemUrl]
9365
+ );
9366
+ return rows.map((r) => this.mapRow(r));
9367
+ }
9368
+ /** Get direct children of a concept. */
9369
+ async getChildren(codeSystemUrl, parentCode) {
9370
+ await this.ensureTable();
9371
+ const rows = await this.adapter.query(
9372
+ `SELECT "id", "code_system_url", "code_system_version", "code", "display", "parent_code", "level" FROM "${TABLE5}" WHERE "code_system_url" = ? AND "parent_code" = ? ORDER BY "code"`,
9373
+ [codeSystemUrl, parentCode]
9374
+ );
9375
+ return rows.map((r) => this.mapRow(r));
9376
+ }
9377
+ /** Lookup a single concept by code. */
9378
+ async lookup(codeSystemUrl, code) {
9379
+ await this.ensureTable();
9380
+ const row = await this.adapter.queryOne(
9381
+ `SELECT "id", "code_system_url", "code_system_version", "code", "display", "parent_code", "level" FROM "${TABLE5}" WHERE "code_system_url" = ? AND "code" = ?`,
9382
+ [codeSystemUrl, code]
9383
+ );
9384
+ return row ? this.mapRow(row) : void 0;
9385
+ }
9386
+ /** Remove all concepts for a CodeSystem. */
9387
+ async removeByCodeSystem(codeSystemUrl) {
9388
+ await this.ensureTable();
9389
+ await this.adapter.execute(`DELETE FROM "${TABLE5}" WHERE "code_system_url" = ?`, [codeSystemUrl]);
9390
+ }
9391
+ mapRow(r) {
9392
+ return {
9393
+ id: r.id,
9394
+ codeSystemUrl: r.code_system_url,
9395
+ codeSystemVersion: r.code_system_version ?? void 0,
9396
+ code: r.code,
9397
+ display: r.display ?? void 0,
9398
+ parentCode: r.parent_code ?? void 0,
9399
+ level: Number(r.level)
9400
+ };
9401
+ }
9402
+ };
9403
+
9404
+ // src/conformance/search-param-index-repo.ts
9405
+ var TABLE6 = "search_parameter_index";
9406
+ function createTableDDL3(dialect) {
9407
+ const baseType = dialect === "postgres" ? "JSONB" : "TEXT";
9408
+ return `
9409
+ CREATE TABLE IF NOT EXISTS "${TABLE6}" (
9410
+ "id" TEXT PRIMARY KEY,
9411
+ "ig_id" TEXT NOT NULL,
9412
+ "url" TEXT,
9413
+ "code" TEXT NOT NULL,
9414
+ "type" TEXT NOT NULL,
9415
+ "base" ${baseType},
9416
+ "expression" TEXT
9417
+ );
9418
+ `;
9419
+ }
9420
+ var CREATE_INDEX_IG2 = `CREATE INDEX IF NOT EXISTS idx_spi_ig ON "${TABLE6}"("ig_id")`;
9421
+ var CREATE_INDEX_CODE2 = `CREATE INDEX IF NOT EXISTS idx_spi_code ON "${TABLE6}"("code")`;
9422
+ var SearchParamIndexRepo = class {
9423
+ constructor(adapter, dialect = "sqlite") {
9424
+ this.adapter = adapter;
9425
+ this.dialect = dialect;
9426
+ }
9427
+ async ensureTable() {
9428
+ await this.adapter.execute(createTableDDL3(this.dialect));
9429
+ await this.adapter.execute(CREATE_INDEX_IG2);
9430
+ await this.adapter.execute(CREATE_INDEX_CODE2);
9431
+ }
9432
+ async upsert(entry) {
9433
+ await this.ensureTable();
9434
+ const baseJson = JSON.stringify(entry.base);
9435
+ const sql = this.dialect === "postgres" ? `INSERT INTO "${TABLE6}" ("id", "ig_id", "url", "code", "type", "base", "expression") VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT ("id") DO UPDATE SET "url" = EXCLUDED."url", "code" = EXCLUDED."code", "type" = EXCLUDED."type", "base" = EXCLUDED."base", "expression" = EXCLUDED."expression"` : `INSERT OR REPLACE INTO "${TABLE6}" ("id", "ig_id", "url", "code", "type", "base", "expression") VALUES (?, ?, ?, ?, ?, ?, ?)`;
9436
+ await this.adapter.execute(sql, [
9437
+ entry.id,
9438
+ entry.igId,
9439
+ entry.url ?? null,
9440
+ entry.code,
9441
+ entry.type,
9442
+ baseJson,
9443
+ entry.expression ?? null
9444
+ ]);
9445
+ }
9446
+ async batchUpsert(entries) {
9447
+ let count = 0;
9448
+ for (const entry of entries) {
9449
+ await this.upsert(entry);
9450
+ count++;
9451
+ }
9452
+ return count;
9453
+ }
9454
+ async getByIG(igId) {
9455
+ await this.ensureTable();
9456
+ const rows = await this.adapter.query(
9457
+ `SELECT "id", "ig_id", "url", "code", "type", "base", "expression" FROM "${TABLE6}" WHERE "ig_id" = ? ORDER BY "code"`,
9458
+ [igId]
9459
+ );
9460
+ return rows.map((r) => this.mapRow(r));
9461
+ }
9462
+ async getByCode(code) {
9463
+ await this.ensureTable();
9464
+ const rows = await this.adapter.query(
9465
+ `SELECT "id", "ig_id", "url", "code", "type", "base", "expression" FROM "${TABLE6}" WHERE "code" = ? ORDER BY "ig_id"`,
9466
+ [code]
9467
+ );
9468
+ return rows.map((r) => this.mapRow(r));
9469
+ }
9470
+ async remove(id) {
9471
+ await this.ensureTable();
9472
+ await this.adapter.execute(`DELETE FROM "${TABLE6}" WHERE "id" = ?`, [id]);
9473
+ }
9474
+ async removeByIG(igId) {
9475
+ await this.ensureTable();
9476
+ await this.adapter.execute(`DELETE FROM "${TABLE6}" WHERE "ig_id" = ?`, [igId]);
9477
+ }
9478
+ mapRow(r) {
9479
+ const base = r.base ? typeof r.base === "string" ? JSON.parse(r.base) : r.base : [];
9480
+ return {
9481
+ id: r.id,
9482
+ igId: r.ig_id,
9483
+ url: r.url ?? void 0,
9484
+ code: r.code,
9485
+ type: r.type,
9486
+ base,
9487
+ expression: r.expression ?? void 0
9488
+ };
9489
+ }
9490
+ };
9491
+
9492
+ // src/conformance/ig-import-orchestrator.ts
9493
+ var IGImportOrchestrator = class {
9494
+ resourceMapRepo;
9495
+ sdIndexRepo;
9496
+ elementIndexRepo;
9497
+ expansionCacheRepo;
9498
+ conceptRepo;
9499
+ spIndexRepo;
9500
+ opts;
9501
+ constructor(adapter, dialect = "sqlite", options) {
9502
+ this.resourceMapRepo = new IGResourceMapRepo(adapter, dialect);
9503
+ this.sdIndexRepo = new SDIndexRepo(adapter, dialect);
9504
+ this.elementIndexRepo = new ElementIndexRepo(adapter, dialect);
9505
+ this.expansionCacheRepo = new ExpansionCacheRepo(adapter, dialect);
9506
+ this.conceptRepo = new ConceptHierarchyRepo(adapter, dialect);
9507
+ this.spIndexRepo = new SearchParamIndexRepo(adapter, dialect);
9508
+ this.opts = options ?? {};
9509
+ }
9510
+ /** Ensure all conformance tables exist. */
9511
+ async ensureAllTables() {
9512
+ await this.resourceMapRepo.ensureTable();
9513
+ await this.sdIndexRepo.ensureTable();
9514
+ await this.elementIndexRepo.ensureTable();
9515
+ await this.expansionCacheRepo.ensureTable();
9516
+ await this.conceptRepo.ensureTable();
9517
+ await this.spIndexRepo.ensureTable();
9518
+ }
9519
+ /** Execute a complete IG import from a FHIR Bundle. */
9520
+ async importIG(igId, bundle) {
9521
+ await this.ensureAllTables();
9522
+ const result = {
9523
+ igId,
9524
+ resourceCount: 0,
9525
+ sdIndexCount: 0,
9526
+ elementIndexCount: 0,
9527
+ conceptCount: 0,
9528
+ spIndexCount: 0,
9529
+ errors: []
9530
+ };
9531
+ const entries = bundle.entry ?? [];
9532
+ const resourceMapEntries = [];
9533
+ const structureDefs = [];
9534
+ const codeSystems = [];
9535
+ const searchParams = [];
9536
+ for (const entry of entries) {
9537
+ const resource = entry.resource;
9538
+ if (!resource || !resource.resourceType || !resource.id) continue;
9539
+ const resourceType = resource.resourceType;
9540
+ const resourceId = resource.id;
9541
+ const mapEntry = {
9542
+ resourceType,
9543
+ resourceId,
9544
+ resourceUrl: resource.url ?? void 0,
9545
+ resourceName: resource.name ?? void 0
9546
+ };
9547
+ if (resourceType === "StructureDefinition") {
9548
+ mapEntry.baseType = resource.type ?? void 0;
9549
+ structureDefs.push(resource);
9550
+ } else if (resourceType === "CodeSystem") {
9551
+ codeSystems.push(resource);
9552
+ } else if (resourceType === "SearchParameter") {
9553
+ searchParams.push(resource);
9554
+ }
9555
+ resourceMapEntries.push(mapEntry);
9556
+ }
9557
+ try {
9558
+ result.resourceCount = await this.resourceMapRepo.batchInsert(igId, resourceMapEntries);
9559
+ } catch (err) {
9560
+ result.errors.push(`Resource map insert failed: ${String(err)}`);
9561
+ }
9562
+ for (const sd of structureDefs) {
9563
+ try {
9564
+ const sdEntry = {
9565
+ id: sd.id,
9566
+ url: sd.url ?? void 0,
9567
+ version: sd.version ?? void 0,
9568
+ type: sd.type ?? void 0,
9569
+ kind: sd.kind ?? void 0,
9570
+ baseDefinition: sd.baseDefinition ?? void 0,
9571
+ derivation: sd.derivation ?? void 0
9572
+ };
9573
+ await this.sdIndexRepo.upsert(sdEntry);
9574
+ result.sdIndexCount++;
9575
+ if (this.opts.extractElementIndex) {
9576
+ const elements = this.opts.extractElementIndex(sd);
9577
+ const count = await this.elementIndexRepo.batchInsert(sd.id, elements);
9578
+ result.elementIndexCount += count;
9579
+ }
9580
+ } catch (err) {
9581
+ result.errors.push(`SD processing failed for ${sd.id}: ${String(err)}`);
9582
+ }
9583
+ }
9584
+ for (const cs of codeSystems) {
9585
+ try {
9586
+ if (this.opts.flattenConcepts) {
9587
+ const concepts = this.opts.flattenConcepts(cs);
9588
+ const count = await this.conceptRepo.batchInsert(concepts);
9589
+ result.conceptCount += count;
9590
+ }
9591
+ } catch (err) {
9592
+ result.errors.push(`CodeSystem processing failed for ${cs.id}: ${String(err)}`);
9593
+ }
9594
+ }
9595
+ for (const sp of searchParams) {
9596
+ try {
9597
+ const spEntry = {
9598
+ id: sp.id,
9599
+ igId,
9600
+ url: sp.url ?? void 0,
9601
+ code: sp.code ?? "",
9602
+ type: sp.type ?? "",
9603
+ base: Array.isArray(sp.base) ? sp.base : [],
9604
+ expression: sp.expression ?? void 0
9605
+ };
9606
+ await this.spIndexRepo.upsert(spEntry);
9607
+ result.spIndexCount++;
9608
+ } catch (err) {
9609
+ result.errors.push(`SearchParameter processing failed for ${sp.id}: ${String(err)}`);
9610
+ }
9611
+ }
9612
+ return result;
9613
+ }
9614
+ /** Get individual repos for direct access. */
9615
+ get repos() {
9616
+ return {
9617
+ resourceMap: this.resourceMapRepo,
9618
+ sdIndex: this.sdIndexRepo,
9619
+ elementIndex: this.elementIndexRepo,
9620
+ expansionCache: this.expansionCacheRepo,
9621
+ conceptHierarchy: this.conceptRepo,
9622
+ searchParamIndex: this.spIndexRepo
9623
+ };
9624
+ }
9625
+ };
8914
9626
  // Annotate the CommonJS export names for ESM import in node:
8915
9627
  0 && (module.exports = {
8916
9628
  BetterSqlite3Adapter,
9629
+ ConceptHierarchyRepo,
8917
9630
  DEFAULT_SEARCH_COUNT,
8918
9631
  DELETED_SCHEMA_VERSION,
9632
+ ElementIndexRepo,
9633
+ ExpansionCacheRepo,
8919
9634
  FhirDefinitionBridge,
8920
9635
  FhirPersistence,
8921
9636
  FhirRuntimeProvider,
8922
9637
  FhirStore,
8923
9638
  FhirSystem,
9639
+ IGImportOrchestrator,
8924
9640
  IGPersistenceManager,
9641
+ IGResourceMapRepo,
8925
9642
  IndexingPipeline,
8926
9643
  LookupTableWriter,
8927
9644
  MAX_SEARCH_COUNT,
@@ -8943,9 +9660,11 @@ function createFhirRuntimeProvider(options) {
8943
9660
  ResourceNotFoundError,
8944
9661
  ResourceVersionConflictError,
8945
9662
  SCHEMA_VERSION,
9663
+ SDIndexRepo,
8946
9664
  SEARCH_PREFIXES,
8947
9665
  SQLiteDialect,
8948
9666
  SearchLogger,
9667
+ SearchParamIndexRepo,
8949
9668
  SearchParameterRegistry,
8950
9669
  StructureDefinitionRegistry,
8951
9670
  TerminologyCodeRepo,