fhir-persistence 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/README.md +7 -4
- package/dist/cjs/index.cjs +786 -3
- package/dist/cjs/index.cjs.map +4 -4
- package/dist/cjs/index.d.ts +221 -12
- package/dist/esm/index.d.ts +221 -12
- package/dist/esm/index.mjs +788 -3
- package/dist/esm/index.mjs.map +4 -4
- package/dist/index.d.ts +221 -12
- package/dist/lib/index.d.ts +8 -1
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/search/where-builder.d.ts.map +1 -1
- package/dist/lib/store/fhir-persistence.d.ts +20 -2
- package/dist/lib/store/fhir-persistence.d.ts.map +1 -1
- package/dist/lib/store/fhir-store.d.ts +10 -1
- package/dist/lib/store/fhir-store.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/esm/index.mjs
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
1
8
|
// src/registry/structure-definition-registry.ts
|
|
2
9
|
var StructureDefinitionRegistry = class {
|
|
3
10
|
profiles = /* @__PURE__ */ new Map();
|
|
@@ -4145,6 +4152,18 @@ var ResourceGoneError = class extends RepositoryError {
|
|
|
4145
4152
|
this.resourceId = id;
|
|
4146
4153
|
}
|
|
4147
4154
|
};
|
|
4155
|
+
var PreconditionFailedError = class extends RepositoryError {
|
|
4156
|
+
name = "PreconditionFailedError";
|
|
4157
|
+
resourceType;
|
|
4158
|
+
matchCount;
|
|
4159
|
+
constructor(resourceType, matchCount) {
|
|
4160
|
+
super(
|
|
4161
|
+
`Conditional operation on ${resourceType} matched ${matchCount} resources (expected 0 or 1)`
|
|
4162
|
+
);
|
|
4163
|
+
this.resourceType = resourceType;
|
|
4164
|
+
this.matchCount = matchCount;
|
|
4165
|
+
}
|
|
4166
|
+
};
|
|
4148
4167
|
var ResourceVersionConflictError = class extends RepositoryError {
|
|
4149
4168
|
name = "ResourceVersionConflictError";
|
|
4150
4169
|
resourceType;
|
|
@@ -5122,6 +5141,15 @@ function buildLookupTableFragmentV2(impl, param, resourceType) {
|
|
|
5122
5141
|
const colRef = `__lookup."${column}"`;
|
|
5123
5142
|
const outerIdRef = `"${resourceType}"."id"`;
|
|
5124
5143
|
if (param.modifier === "exact") {
|
|
5144
|
+
if (table === "HumanName" && column === "name") {
|
|
5145
|
+
const colConds = [];
|
|
5146
|
+
const allVals = [];
|
|
5147
|
+
for (const v of param.values) {
|
|
5148
|
+
colConds.push(`__lookup."family" = ? OR __lookup."given" = ?`);
|
|
5149
|
+
allVals.push(v, v);
|
|
5150
|
+
}
|
|
5151
|
+
return { sql: `EXISTS (SELECT 1 FROM "${table}" __lookup WHERE __lookup."resourceId" = ${outerIdRef} AND (${colConds.join(" OR ")}))`, values: allVals };
|
|
5152
|
+
}
|
|
5125
5153
|
if (param.values.length === 1) {
|
|
5126
5154
|
return { sql: `EXISTS (SELECT 1 FROM "${table}" __lookup WHERE __lookup."resourceId" = ${outerIdRef} AND ${colRef} = ?)`, values: [param.values[0]] };
|
|
5127
5155
|
}
|
|
@@ -6302,9 +6330,41 @@ var FhirStore = class {
|
|
|
6302
6330
|
const selectSQL = buildSelectByIdSQLv2(resourceType);
|
|
6303
6331
|
const current = await this.adapter.queryOne(selectSQL, [id]);
|
|
6304
6332
|
if (!current) {
|
|
6333
|
+
if (options?.upsert) {
|
|
6334
|
+
const created = await this.createResource(resourceType, resource, { assignedId: id });
|
|
6335
|
+
return { resource: created, created: true };
|
|
6336
|
+
}
|
|
6305
6337
|
throw new ResourceNotFoundError(resourceType, id);
|
|
6306
6338
|
}
|
|
6307
6339
|
if (current.deleted === 1) {
|
|
6340
|
+
if (options?.upsert) {
|
|
6341
|
+
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
6342
|
+
const versionId2 = randomUUID3();
|
|
6343
|
+
const persisted2 = {
|
|
6344
|
+
...resource,
|
|
6345
|
+
resourceType,
|
|
6346
|
+
id,
|
|
6347
|
+
meta: { ...resource.meta, versionId: versionId2, lastUpdated: now2 }
|
|
6348
|
+
};
|
|
6349
|
+
const content2 = JSON.stringify(persisted2);
|
|
6350
|
+
const mainRow2 = {
|
|
6351
|
+
id,
|
|
6352
|
+
versionId: versionId2,
|
|
6353
|
+
content: content2,
|
|
6354
|
+
lastUpdated: now2,
|
|
6355
|
+
deleted: 0,
|
|
6356
|
+
_source: persisted2.meta?.source ?? null,
|
|
6357
|
+
_profile: persisted2.meta?.profile ? JSON.stringify(persisted2.meta.profile) : null
|
|
6358
|
+
};
|
|
6359
|
+
const historyRow2 = { id, versionId: versionId2, content: content2, lastUpdated: now2, deleted: 0 };
|
|
6360
|
+
await this.adapter.transaction(async (tx) => {
|
|
6361
|
+
const updateSQL = buildUpdateMainSQLv2(resourceType, mainRow2);
|
|
6362
|
+
await tx.execute(updateSQL.sql, updateSQL.values);
|
|
6363
|
+
const histSQL = buildInsertHistorySQLv2(`${resourceType}_History`, historyRow2);
|
|
6364
|
+
await tx.execute(histSQL.sql, histSQL.values);
|
|
6365
|
+
});
|
|
6366
|
+
return { resource: persisted2, created: true };
|
|
6367
|
+
}
|
|
6308
6368
|
throw new ResourceGoneError(resourceType, id);
|
|
6309
6369
|
}
|
|
6310
6370
|
if (options?.ifMatch) {
|
|
@@ -6355,7 +6415,7 @@ var FhirStore = class {
|
|
|
6355
6415
|
const delRefSQL = buildDeleteReferencesSQLv2(`${resourceType}_References`);
|
|
6356
6416
|
await tx.execute(delRefSQL, [id]);
|
|
6357
6417
|
});
|
|
6358
|
-
return persisted;
|
|
6418
|
+
return { resource: persisted, created: false };
|
|
6359
6419
|
}
|
|
6360
6420
|
// ---------------------------------------------------------------------------
|
|
6361
6421
|
// DELETE (soft)
|
|
@@ -6425,6 +6485,24 @@ var FhirStore = class {
|
|
|
6425
6485
|
}));
|
|
6426
6486
|
}
|
|
6427
6487
|
// ---------------------------------------------------------------------------
|
|
6488
|
+
// TYPE-LEVEL HISTORY (PERS-06)
|
|
6489
|
+
// ---------------------------------------------------------------------------
|
|
6490
|
+
async readTypeHistory(resourceType, options) {
|
|
6491
|
+
const { sql, values } = buildTypeHistorySQLv2(
|
|
6492
|
+
`${resourceType}_History`,
|
|
6493
|
+
options
|
|
6494
|
+
);
|
|
6495
|
+
const rows = await this.adapter.query(sql, values);
|
|
6496
|
+
return rows.map((row) => ({
|
|
6497
|
+
id: row.id,
|
|
6498
|
+
versionId: row.versionId,
|
|
6499
|
+
lastUpdated: row.lastUpdated,
|
|
6500
|
+
deleted: row.deleted === 1,
|
|
6501
|
+
resourceType,
|
|
6502
|
+
resource: row.deleted === 1 ? null : JSON.parse(row.content)
|
|
6503
|
+
}));
|
|
6504
|
+
}
|
|
6505
|
+
// ---------------------------------------------------------------------------
|
|
6428
6506
|
// WRITE REFERENCES (utility for external callers)
|
|
6429
6507
|
// ---------------------------------------------------------------------------
|
|
6430
6508
|
async writeReferences(resourceType, resourceId, refs) {
|
|
@@ -6806,6 +6884,25 @@ var FhirPersistence = class {
|
|
|
6806
6884
|
// CREATE
|
|
6807
6885
|
// ---------------------------------------------------------------------------
|
|
6808
6886
|
async createResource(resourceType, resource, options) {
|
|
6887
|
+
if (options?.ifNoneExist && options.ifNoneExist.length > 0) {
|
|
6888
|
+
const searchSQL = buildSearchSQLv2(
|
|
6889
|
+
{ resourceType, params: options.ifNoneExist, count: 2 },
|
|
6890
|
+
this.registry
|
|
6891
|
+
);
|
|
6892
|
+
const matches = await this.adapter.query(
|
|
6893
|
+
searchSQL.sql,
|
|
6894
|
+
searchSQL.values
|
|
6895
|
+
);
|
|
6896
|
+
if (matches.length > 1) {
|
|
6897
|
+
throw new PreconditionFailedError(resourceType, matches.length);
|
|
6898
|
+
}
|
|
6899
|
+
if (matches.length === 1) {
|
|
6900
|
+
return {
|
|
6901
|
+
resource: JSON.parse(matches[0].content),
|
|
6902
|
+
created: false
|
|
6903
|
+
};
|
|
6904
|
+
}
|
|
6905
|
+
}
|
|
6809
6906
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6810
6907
|
const id = options?.assignedId ?? resource.id ?? randomUUID4();
|
|
6811
6908
|
const versionId = randomUUID4();
|
|
@@ -6845,7 +6942,7 @@ var FhirPersistence = class {
|
|
|
6845
6942
|
const histSQL = buildInsertHistorySQLv2(`${resourceType}_History`, historyRow);
|
|
6846
6943
|
await tx.execute(histSQL.sql, histSQL.values);
|
|
6847
6944
|
});
|
|
6848
|
-
return persisted;
|
|
6945
|
+
return { resource: persisted, created: true };
|
|
6849
6946
|
}
|
|
6850
6947
|
// ---------------------------------------------------------------------------
|
|
6851
6948
|
// READ
|
|
@@ -6872,9 +6969,44 @@ var FhirPersistence = class {
|
|
|
6872
6969
|
const selectSQL = buildSelectByIdSQLv2(resourceType);
|
|
6873
6970
|
const current = await this.adapter.queryOne(selectSQL, [id]);
|
|
6874
6971
|
if (!current) {
|
|
6972
|
+
if (options?.upsert) {
|
|
6973
|
+
const result = await this.createResource(resourceType, resource, { assignedId: id });
|
|
6974
|
+
return { resource: result.resource, created: true };
|
|
6975
|
+
}
|
|
6875
6976
|
throw new ResourceNotFoundError(resourceType, id);
|
|
6876
6977
|
}
|
|
6877
6978
|
if (current.deleted === 1) {
|
|
6979
|
+
if (options?.upsert) {
|
|
6980
|
+
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
6981
|
+
const versionId2 = randomUUID4();
|
|
6982
|
+
const persisted2 = {
|
|
6983
|
+
...resource,
|
|
6984
|
+
resourceType,
|
|
6985
|
+
id,
|
|
6986
|
+
meta: { ...resource.meta, versionId: versionId2, lastUpdated: now2 }
|
|
6987
|
+
};
|
|
6988
|
+
const impls2 = this.getImpls(resourceType);
|
|
6989
|
+
const indexResult2 = await this.pipeline.indexResource(resourceType, persisted2, impls2);
|
|
6990
|
+
const content2 = JSON.stringify(persisted2);
|
|
6991
|
+
const mainRow2 = {
|
|
6992
|
+
id,
|
|
6993
|
+
versionId: versionId2,
|
|
6994
|
+
content: content2,
|
|
6995
|
+
lastUpdated: now2,
|
|
6996
|
+
deleted: 0,
|
|
6997
|
+
_source: persisted2.meta?.source ?? null,
|
|
6998
|
+
_profile: persisted2.meta?.profile ? JSON.stringify(persisted2.meta.profile) : null,
|
|
6999
|
+
...indexResult2.searchColumns
|
|
7000
|
+
};
|
|
7001
|
+
const historyRow2 = { id, versionId: versionId2, content: content2, lastUpdated: now2, deleted: 0 };
|
|
7002
|
+
await this.adapter.transaction(async (tx) => {
|
|
7003
|
+
const updateSQL = buildUpdateMainSQLv2(resourceType, mainRow2);
|
|
7004
|
+
await tx.execute(updateSQL.sql, updateSQL.values);
|
|
7005
|
+
const histSQL = buildInsertHistorySQLv2(`${resourceType}_History`, historyRow2);
|
|
7006
|
+
await tx.execute(histSQL.sql, histSQL.values);
|
|
7007
|
+
});
|
|
7008
|
+
return { resource: persisted2, created: true };
|
|
7009
|
+
}
|
|
6878
7010
|
throw new ResourceGoneError(resourceType, id);
|
|
6879
7011
|
}
|
|
6880
7012
|
if (options?.ifMatch) {
|
|
@@ -6926,7 +7058,7 @@ var FhirPersistence = class {
|
|
|
6926
7058
|
const histSQL = buildInsertHistorySQLv2(`${resourceType}_History`, historyRow);
|
|
6927
7059
|
await tx.execute(histSQL.sql, histSQL.values);
|
|
6928
7060
|
});
|
|
6929
|
-
return persisted;
|
|
7061
|
+
return { resource: persisted, created: false };
|
|
6930
7062
|
}
|
|
6931
7063
|
// ---------------------------------------------------------------------------
|
|
6932
7064
|
// DELETE (soft)
|
|
@@ -6995,6 +7127,24 @@ var FhirPersistence = class {
|
|
|
6995
7127
|
}));
|
|
6996
7128
|
}
|
|
6997
7129
|
// ---------------------------------------------------------------------------
|
|
7130
|
+
// TYPE-LEVEL HISTORY (PERS-06)
|
|
7131
|
+
// ---------------------------------------------------------------------------
|
|
7132
|
+
async readTypeHistory(resourceType, options) {
|
|
7133
|
+
const { sql, values } = buildTypeHistorySQLv2(
|
|
7134
|
+
`${resourceType}_History`,
|
|
7135
|
+
options
|
|
7136
|
+
);
|
|
7137
|
+
const rows = await this.adapter.query(sql, values);
|
|
7138
|
+
return rows.map((row) => ({
|
|
7139
|
+
id: row.id,
|
|
7140
|
+
versionId: row.versionId,
|
|
7141
|
+
lastUpdated: row.lastUpdated,
|
|
7142
|
+
deleted: row.deleted === 1,
|
|
7143
|
+
resourceType,
|
|
7144
|
+
resource: row.deleted === 1 ? null : JSON.parse(row.content)
|
|
7145
|
+
}));
|
|
7146
|
+
}
|
|
7147
|
+
// ---------------------------------------------------------------------------
|
|
6998
7148
|
// SEARCH (streaming)
|
|
6999
7149
|
// ---------------------------------------------------------------------------
|
|
7000
7150
|
/**
|
|
@@ -7045,6 +7195,636 @@ var FhirPersistence = class {
|
|
|
7045
7195
|
}
|
|
7046
7196
|
};
|
|
7047
7197
|
|
|
7198
|
+
// src/store/conditional-service.ts
|
|
7199
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
7200
|
+
var ConditionalService = class {
|
|
7201
|
+
constructor(adapter, registry) {
|
|
7202
|
+
this.adapter = adapter;
|
|
7203
|
+
this.registry = registry;
|
|
7204
|
+
}
|
|
7205
|
+
// ---------------------------------------------------------------------------
|
|
7206
|
+
// conditionalCreate (If-None-Exist)
|
|
7207
|
+
// ---------------------------------------------------------------------------
|
|
7208
|
+
/**
|
|
7209
|
+
* Conditional create: search for existing matches, create only if none found.
|
|
7210
|
+
*
|
|
7211
|
+
* - 0 matches → create new resource
|
|
7212
|
+
* - 1 match → return existing resource (no-op)
|
|
7213
|
+
* - 2+ matches → PreconditionFailedError
|
|
7214
|
+
*
|
|
7215
|
+
* @param resourceType - FHIR resource type
|
|
7216
|
+
* @param resource - The resource to create
|
|
7217
|
+
* @param searchParams - Search criteria (If-None-Exist)
|
|
7218
|
+
*/
|
|
7219
|
+
async conditionalCreate(resourceType, resource, searchParams) {
|
|
7220
|
+
const matches = await this.searchMatches(resourceType, searchParams, 2);
|
|
7221
|
+
if (matches.length > 1) {
|
|
7222
|
+
throw new PreconditionFailedError(resourceType, matches.length);
|
|
7223
|
+
}
|
|
7224
|
+
if (matches.length === 1) {
|
|
7225
|
+
return {
|
|
7226
|
+
outcome: "existing",
|
|
7227
|
+
resource: JSON.parse(matches[0].content)
|
|
7228
|
+
};
|
|
7229
|
+
}
|
|
7230
|
+
const persisted = await this.createInTransaction(resourceType, resource);
|
|
7231
|
+
return { outcome: "created", resource: persisted };
|
|
7232
|
+
}
|
|
7233
|
+
// ---------------------------------------------------------------------------
|
|
7234
|
+
// conditionalUpdate (search-based PUT)
|
|
7235
|
+
// ---------------------------------------------------------------------------
|
|
7236
|
+
/**
|
|
7237
|
+
* Conditional update: search for matches, update if exactly one found.
|
|
7238
|
+
*
|
|
7239
|
+
* - 0 matches → create new resource
|
|
7240
|
+
* - 1 match → update existing resource
|
|
7241
|
+
* - 2+ matches → PreconditionFailedError
|
|
7242
|
+
*
|
|
7243
|
+
* @param resourceType - FHIR resource type
|
|
7244
|
+
* @param resource - The resource to create/update
|
|
7245
|
+
* @param searchParams - Search criteria
|
|
7246
|
+
*/
|
|
7247
|
+
async conditionalUpdate(resourceType, resource, searchParams) {
|
|
7248
|
+
const matches = await this.searchMatches(resourceType, searchParams, 2);
|
|
7249
|
+
if (matches.length > 1) {
|
|
7250
|
+
throw new PreconditionFailedError(resourceType, matches.length);
|
|
7251
|
+
}
|
|
7252
|
+
if (matches.length === 0) {
|
|
7253
|
+
const persisted2 = await this.createInTransaction(resourceType, resource);
|
|
7254
|
+
return { outcome: "created", resource: persisted2 };
|
|
7255
|
+
}
|
|
7256
|
+
const existing = matches[0];
|
|
7257
|
+
const persisted = await this.updateInTransaction(
|
|
7258
|
+
resourceType,
|
|
7259
|
+
resource,
|
|
7260
|
+
existing.id
|
|
7261
|
+
);
|
|
7262
|
+
return { outcome: "updated", resource: persisted };
|
|
7263
|
+
}
|
|
7264
|
+
// ---------------------------------------------------------------------------
|
|
7265
|
+
// conditionalDelete
|
|
7266
|
+
// ---------------------------------------------------------------------------
|
|
7267
|
+
/**
|
|
7268
|
+
* Conditional delete: delete all resources matching the search criteria.
|
|
7269
|
+
*
|
|
7270
|
+
* @param resourceType - FHIR resource type
|
|
7271
|
+
* @param searchParams - Search criteria
|
|
7272
|
+
* @returns Number of resources deleted
|
|
7273
|
+
*/
|
|
7274
|
+
async conditionalDelete(resourceType, searchParams) {
|
|
7275
|
+
const matches = await this.searchMatches(resourceType, searchParams, 1e3);
|
|
7276
|
+
if (matches.length === 0) {
|
|
7277
|
+
return { count: 0 };
|
|
7278
|
+
}
|
|
7279
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7280
|
+
await this.adapter.transaction(async (tx) => {
|
|
7281
|
+
for (const match of matches) {
|
|
7282
|
+
const versionId = randomUUID5();
|
|
7283
|
+
const deleteRow = {
|
|
7284
|
+
id: match.id,
|
|
7285
|
+
versionId,
|
|
7286
|
+
content: match.content,
|
|
7287
|
+
lastUpdated: now,
|
|
7288
|
+
deleted: 1
|
|
7289
|
+
};
|
|
7290
|
+
const historyRow = {
|
|
7291
|
+
id: match.id,
|
|
7292
|
+
versionId,
|
|
7293
|
+
content: match.content,
|
|
7294
|
+
lastUpdated: now,
|
|
7295
|
+
deleted: 1
|
|
7296
|
+
};
|
|
7297
|
+
const updateSQL = buildUpdateMainSQLv2(resourceType, deleteRow);
|
|
7298
|
+
await tx.execute(updateSQL.sql, updateSQL.values);
|
|
7299
|
+
const histSQL = buildInsertHistorySQLv2(`${resourceType}_History`, historyRow);
|
|
7300
|
+
await tx.execute(histSQL.sql, histSQL.values);
|
|
7301
|
+
}
|
|
7302
|
+
});
|
|
7303
|
+
return { count: matches.length };
|
|
7304
|
+
}
|
|
7305
|
+
// ---------------------------------------------------------------------------
|
|
7306
|
+
// Private: Search for matches
|
|
7307
|
+
// ---------------------------------------------------------------------------
|
|
7308
|
+
async searchMatches(resourceType, searchParams, limit) {
|
|
7309
|
+
const searchSQL = buildSearchSQLv2(
|
|
7310
|
+
{
|
|
7311
|
+
resourceType,
|
|
7312
|
+
params: searchParams,
|
|
7313
|
+
count: limit
|
|
7314
|
+
},
|
|
7315
|
+
this.registry
|
|
7316
|
+
);
|
|
7317
|
+
return this.adapter.query(
|
|
7318
|
+
searchSQL.sql,
|
|
7319
|
+
searchSQL.values
|
|
7320
|
+
);
|
|
7321
|
+
}
|
|
7322
|
+
// ---------------------------------------------------------------------------
|
|
7323
|
+
// Private: Create in transaction
|
|
7324
|
+
// ---------------------------------------------------------------------------
|
|
7325
|
+
async createInTransaction(resourceType, resource) {
|
|
7326
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7327
|
+
const id = resource.id ?? randomUUID5();
|
|
7328
|
+
const versionId = randomUUID5();
|
|
7329
|
+
const persisted = {
|
|
7330
|
+
...resource,
|
|
7331
|
+
resourceType,
|
|
7332
|
+
id,
|
|
7333
|
+
meta: {
|
|
7334
|
+
...resource.meta,
|
|
7335
|
+
versionId,
|
|
7336
|
+
lastUpdated: now
|
|
7337
|
+
}
|
|
7338
|
+
};
|
|
7339
|
+
const content = JSON.stringify(persisted);
|
|
7340
|
+
const mainRow = {
|
|
7341
|
+
id,
|
|
7342
|
+
versionId,
|
|
7343
|
+
content,
|
|
7344
|
+
lastUpdated: now,
|
|
7345
|
+
deleted: 0
|
|
7346
|
+
};
|
|
7347
|
+
const historyRow = {
|
|
7348
|
+
id,
|
|
7349
|
+
versionId,
|
|
7350
|
+
content,
|
|
7351
|
+
lastUpdated: now,
|
|
7352
|
+
deleted: 0
|
|
7353
|
+
};
|
|
7354
|
+
await this.adapter.transaction(async (tx) => {
|
|
7355
|
+
const mainSQL = buildInsertMainSQLv2(resourceType, mainRow);
|
|
7356
|
+
await tx.execute(mainSQL.sql, mainSQL.values);
|
|
7357
|
+
const histSQL = buildInsertHistorySQLv2(`${resourceType}_History`, historyRow);
|
|
7358
|
+
await tx.execute(histSQL.sql, histSQL.values);
|
|
7359
|
+
});
|
|
7360
|
+
return persisted;
|
|
7361
|
+
}
|
|
7362
|
+
// ---------------------------------------------------------------------------
|
|
7363
|
+
// Private: Update in transaction
|
|
7364
|
+
// ---------------------------------------------------------------------------
|
|
7365
|
+
async updateInTransaction(resourceType, resource, existingId) {
|
|
7366
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7367
|
+
const versionId = randomUUID5();
|
|
7368
|
+
const persisted = {
|
|
7369
|
+
...resource,
|
|
7370
|
+
resourceType,
|
|
7371
|
+
id: existingId,
|
|
7372
|
+
meta: {
|
|
7373
|
+
...resource.meta,
|
|
7374
|
+
versionId,
|
|
7375
|
+
lastUpdated: now
|
|
7376
|
+
}
|
|
7377
|
+
};
|
|
7378
|
+
const content = JSON.stringify(persisted);
|
|
7379
|
+
const mainRow = {
|
|
7380
|
+
id: existingId,
|
|
7381
|
+
versionId,
|
|
7382
|
+
content,
|
|
7383
|
+
lastUpdated: now,
|
|
7384
|
+
deleted: 0
|
|
7385
|
+
};
|
|
7386
|
+
const historyRow = {
|
|
7387
|
+
id: existingId,
|
|
7388
|
+
versionId,
|
|
7389
|
+
content,
|
|
7390
|
+
lastUpdated: now,
|
|
7391
|
+
deleted: 0
|
|
7392
|
+
};
|
|
7393
|
+
await this.adapter.transaction(async (tx) => {
|
|
7394
|
+
const updateSQL = buildUpdateMainSQLv2(resourceType, mainRow);
|
|
7395
|
+
await tx.execute(updateSQL.sql, updateSQL.values);
|
|
7396
|
+
const histSQL = buildInsertHistorySQLv2(`${resourceType}_History`, historyRow);
|
|
7397
|
+
await tx.execute(histSQL.sql, histSQL.values);
|
|
7398
|
+
});
|
|
7399
|
+
return persisted;
|
|
7400
|
+
}
|
|
7401
|
+
};
|
|
7402
|
+
|
|
7403
|
+
// src/transaction/urn-resolver.ts
|
|
7404
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
7405
|
+
function buildUrnMap(entries) {
|
|
7406
|
+
const map = /* @__PURE__ */ new Map();
|
|
7407
|
+
for (const entry of entries) {
|
|
7408
|
+
if (entry.request?.method === "POST" && entry.fullUrl?.startsWith("urn:uuid:") && entry.resource?.resourceType) {
|
|
7409
|
+
const newId = randomUUID6();
|
|
7410
|
+
map.set(entry.fullUrl, {
|
|
7411
|
+
id: newId,
|
|
7412
|
+
resourceType: entry.resource.resourceType
|
|
7413
|
+
});
|
|
7414
|
+
}
|
|
7415
|
+
}
|
|
7416
|
+
return map;
|
|
7417
|
+
}
|
|
7418
|
+
function deepResolveUrns(resource, urnMap) {
|
|
7419
|
+
if (urnMap.size === 0) return resource;
|
|
7420
|
+
const clone = structuredClone(resource);
|
|
7421
|
+
const stack = [clone];
|
|
7422
|
+
while (stack.length > 0) {
|
|
7423
|
+
const current = stack.pop();
|
|
7424
|
+
if (current === null || current === void 0 || typeof current !== "object") {
|
|
7425
|
+
continue;
|
|
7426
|
+
}
|
|
7427
|
+
if (Array.isArray(current)) {
|
|
7428
|
+
for (const item of current) {
|
|
7429
|
+
if (typeof item === "object" && item !== null) {
|
|
7430
|
+
stack.push(item);
|
|
7431
|
+
}
|
|
7432
|
+
}
|
|
7433
|
+
continue;
|
|
7434
|
+
}
|
|
7435
|
+
const record = current;
|
|
7436
|
+
if (typeof record.reference === "string") {
|
|
7437
|
+
const target = urnMap.get(record.reference);
|
|
7438
|
+
if (target) {
|
|
7439
|
+
record.reference = `${target.resourceType}/${target.id}`;
|
|
7440
|
+
}
|
|
7441
|
+
}
|
|
7442
|
+
for (const key of Object.keys(record)) {
|
|
7443
|
+
const value = record[key];
|
|
7444
|
+
if (typeof value === "object" && value !== null) {
|
|
7445
|
+
stack.push(value);
|
|
7446
|
+
}
|
|
7447
|
+
}
|
|
7448
|
+
}
|
|
7449
|
+
return clone;
|
|
7450
|
+
}
|
|
7451
|
+
|
|
7452
|
+
// src/transaction/bundle-processor.ts
|
|
7453
|
+
function parseRequestUrl(url) {
|
|
7454
|
+
const qIdx = url.indexOf("?");
|
|
7455
|
+
let path = url;
|
|
7456
|
+
let query;
|
|
7457
|
+
if (qIdx !== -1) {
|
|
7458
|
+
path = url.substring(0, qIdx);
|
|
7459
|
+
query = url.substring(qIdx + 1);
|
|
7460
|
+
}
|
|
7461
|
+
const parts = path.split("/");
|
|
7462
|
+
return { resourceType: parts[0], id: parts[1], query };
|
|
7463
|
+
}
|
|
7464
|
+
async function processTransactionV2(store, adapter, bundle) {
|
|
7465
|
+
const entries = bundle.entry ?? [];
|
|
7466
|
+
if (entries.length === 0) {
|
|
7467
|
+
return { resourceType: "Bundle", type: "transaction-response", entry: [] };
|
|
7468
|
+
}
|
|
7469
|
+
const urnMap = buildUrnMap(entries);
|
|
7470
|
+
try {
|
|
7471
|
+
const responseEntries = await adapter.transaction(async (tx) => {
|
|
7472
|
+
const results = [];
|
|
7473
|
+
for (const entry of entries) {
|
|
7474
|
+
const result = await processEntryInTransaction(tx, entry, urnMap);
|
|
7475
|
+
results.push(result);
|
|
7476
|
+
}
|
|
7477
|
+
return results;
|
|
7478
|
+
});
|
|
7479
|
+
return {
|
|
7480
|
+
resourceType: "Bundle",
|
|
7481
|
+
type: "transaction-response",
|
|
7482
|
+
entry: responseEntries
|
|
7483
|
+
};
|
|
7484
|
+
} catch (err) {
|
|
7485
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
7486
|
+
return {
|
|
7487
|
+
resourceType: "Bundle",
|
|
7488
|
+
type: "transaction-response",
|
|
7489
|
+
entry: [{
|
|
7490
|
+
response: {
|
|
7491
|
+
status: "500",
|
|
7492
|
+
outcome: {
|
|
7493
|
+
issue: [{ severity: "error", code: "exception", diagnostics: message }]
|
|
7494
|
+
}
|
|
7495
|
+
}
|
|
7496
|
+
}]
|
|
7497
|
+
};
|
|
7498
|
+
}
|
|
7499
|
+
}
|
|
7500
|
+
async function processBatchV2(store, bundle) {
|
|
7501
|
+
const entries = bundle.entry ?? [];
|
|
7502
|
+
const responseEntries = [];
|
|
7503
|
+
for (const entry of entries) {
|
|
7504
|
+
if (entry.fullUrl?.startsWith("urn:uuid:")) {
|
|
7505
|
+
responseEntries.push({
|
|
7506
|
+
response: {
|
|
7507
|
+
status: "400",
|
|
7508
|
+
outcome: {
|
|
7509
|
+
issue: [{ severity: "error", code: "invalid", diagnostics: "urn:uuid references are not allowed in batch mode" }]
|
|
7510
|
+
}
|
|
7511
|
+
}
|
|
7512
|
+
});
|
|
7513
|
+
continue;
|
|
7514
|
+
}
|
|
7515
|
+
try {
|
|
7516
|
+
const result = await processBatchEntry(store, entry);
|
|
7517
|
+
responseEntries.push(result);
|
|
7518
|
+
} catch (err) {
|
|
7519
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
7520
|
+
const status = errorToStatus(err);
|
|
7521
|
+
responseEntries.push({
|
|
7522
|
+
response: {
|
|
7523
|
+
status,
|
|
7524
|
+
outcome: {
|
|
7525
|
+
issue: [{ severity: "error", code: "exception", diagnostics: message }]
|
|
7526
|
+
}
|
|
7527
|
+
}
|
|
7528
|
+
});
|
|
7529
|
+
}
|
|
7530
|
+
}
|
|
7531
|
+
return {
|
|
7532
|
+
resourceType: "Bundle",
|
|
7533
|
+
type: "batch-response",
|
|
7534
|
+
entry: responseEntries
|
|
7535
|
+
};
|
|
7536
|
+
}
|
|
7537
|
+
async function processEntryInTransaction(tx, entry, urnMap) {
|
|
7538
|
+
if (!entry.request) {
|
|
7539
|
+
throw new Error("Missing request");
|
|
7540
|
+
}
|
|
7541
|
+
const { method, url } = entry.request;
|
|
7542
|
+
const { resourceType, id } = parseRequestUrl(url);
|
|
7543
|
+
switch (method) {
|
|
7544
|
+
case "POST": {
|
|
7545
|
+
if (!entry.resource) {
|
|
7546
|
+
throw new Error("Missing resource for POST");
|
|
7547
|
+
}
|
|
7548
|
+
if (entry.request.ifNoneExist) {
|
|
7549
|
+
const matchResult = await checkIfNoneExistInTx(tx, resourceType, entry.request.ifNoneExist);
|
|
7550
|
+
if (matchResult.status !== "create") {
|
|
7551
|
+
return matchResult.response;
|
|
7552
|
+
}
|
|
7553
|
+
}
|
|
7554
|
+
const resolved = deepResolveUrns(entry.resource, urnMap);
|
|
7555
|
+
let assignedId;
|
|
7556
|
+
if (entry.fullUrl?.startsWith("urn:uuid:")) {
|
|
7557
|
+
const target = urnMap.get(entry.fullUrl);
|
|
7558
|
+
if (target) assignedId = target.id;
|
|
7559
|
+
}
|
|
7560
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7561
|
+
const { randomUUID: randomUUID7 } = __require("node:crypto");
|
|
7562
|
+
const versionId = randomUUID7();
|
|
7563
|
+
const finalId = assignedId ?? resolved.id ?? randomUUID7();
|
|
7564
|
+
const persisted = {
|
|
7565
|
+
...resolved,
|
|
7566
|
+
resourceType,
|
|
7567
|
+
id: finalId,
|
|
7568
|
+
meta: {
|
|
7569
|
+
...resolved.meta,
|
|
7570
|
+
versionId,
|
|
7571
|
+
lastUpdated: now
|
|
7572
|
+
}
|
|
7573
|
+
};
|
|
7574
|
+
const content = JSON.stringify(persisted);
|
|
7575
|
+
await tx.execute(
|
|
7576
|
+
`INSERT INTO "${resourceType}" ("id", "versionId", "content", "lastUpdated", "deleted", "_source", "_profile") VALUES (?, ?, ?, ?, 0, ?, ?)`,
|
|
7577
|
+
[finalId, versionId, content, now, persisted.meta?.source ?? null, persisted.meta?.profile ? JSON.stringify(persisted.meta.profile) : null]
|
|
7578
|
+
);
|
|
7579
|
+
await tx.execute(
|
|
7580
|
+
`INSERT INTO "${resourceType}_History" ("id", "versionId", "content", "lastUpdated", "deleted") VALUES (?, ?, ?, ?, 0)`,
|
|
7581
|
+
[finalId, versionId, content, now]
|
|
7582
|
+
);
|
|
7583
|
+
return {
|
|
7584
|
+
resource: persisted,
|
|
7585
|
+
response: {
|
|
7586
|
+
status: "201",
|
|
7587
|
+
location: `${resourceType}/${finalId}/_history/${versionId}`,
|
|
7588
|
+
etag: `W/"${versionId}"`,
|
|
7589
|
+
lastModified: now
|
|
7590
|
+
}
|
|
7591
|
+
};
|
|
7592
|
+
}
|
|
7593
|
+
case "PUT": {
|
|
7594
|
+
if (!entry.resource) {
|
|
7595
|
+
throw new Error("Missing resource for PUT");
|
|
7596
|
+
}
|
|
7597
|
+
if (!id) {
|
|
7598
|
+
throw new Error("PUT requires resource ID in URL");
|
|
7599
|
+
}
|
|
7600
|
+
const resolved = deepResolveUrns(entry.resource, urnMap);
|
|
7601
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7602
|
+
const { randomUUID: randomUUID7 } = __require("node:crypto");
|
|
7603
|
+
const versionId = randomUUID7();
|
|
7604
|
+
const persisted = {
|
|
7605
|
+
...resolved,
|
|
7606
|
+
resourceType,
|
|
7607
|
+
id,
|
|
7608
|
+
meta: {
|
|
7609
|
+
...resolved.meta,
|
|
7610
|
+
versionId,
|
|
7611
|
+
lastUpdated: now
|
|
7612
|
+
}
|
|
7613
|
+
};
|
|
7614
|
+
const content = JSON.stringify(persisted);
|
|
7615
|
+
const existing = await tx.queryOne(
|
|
7616
|
+
`SELECT "id", "deleted" FROM "${resourceType}" WHERE "id" = ?`,
|
|
7617
|
+
[id]
|
|
7618
|
+
);
|
|
7619
|
+
if (existing) {
|
|
7620
|
+
await tx.execute(
|
|
7621
|
+
`UPDATE "${resourceType}" SET "versionId" = ?, "content" = ?, "lastUpdated" = ?, "deleted" = 0, "_source" = ?, "_profile" = ? WHERE "id" = ?`,
|
|
7622
|
+
[versionId, content, now, persisted.meta?.source ?? null, persisted.meta?.profile ? JSON.stringify(persisted.meta.profile) : null, id]
|
|
7623
|
+
);
|
|
7624
|
+
} else {
|
|
7625
|
+
await tx.execute(
|
|
7626
|
+
`INSERT INTO "${resourceType}" ("id", "versionId", "content", "lastUpdated", "deleted", "_source", "_profile") VALUES (?, ?, ?, ?, 0, ?, ?)`,
|
|
7627
|
+
[id, versionId, content, now, persisted.meta?.source ?? null, persisted.meta?.profile ? JSON.stringify(persisted.meta.profile) : null]
|
|
7628
|
+
);
|
|
7629
|
+
}
|
|
7630
|
+
await tx.execute(
|
|
7631
|
+
`INSERT INTO "${resourceType}_History" ("id", "versionId", "content", "lastUpdated", "deleted") VALUES (?, ?, ?, ?, 0)`,
|
|
7632
|
+
[id, versionId, content, now]
|
|
7633
|
+
);
|
|
7634
|
+
return {
|
|
7635
|
+
resource: persisted,
|
|
7636
|
+
response: {
|
|
7637
|
+
status: existing ? "200" : "201",
|
|
7638
|
+
location: `${resourceType}/${id}/_history/${versionId}`,
|
|
7639
|
+
etag: `W/"${versionId}"`,
|
|
7640
|
+
lastModified: now
|
|
7641
|
+
}
|
|
7642
|
+
};
|
|
7643
|
+
}
|
|
7644
|
+
case "DELETE": {
|
|
7645
|
+
if (!id) {
|
|
7646
|
+
throw new Error("DELETE requires resource ID");
|
|
7647
|
+
}
|
|
7648
|
+
const existing = await tx.queryOne(
|
|
7649
|
+
`SELECT "id", "content", "deleted" FROM "${resourceType}" WHERE "id" = ?`,
|
|
7650
|
+
[id]
|
|
7651
|
+
);
|
|
7652
|
+
if (!existing || existing.deleted === 1) {
|
|
7653
|
+
return { response: { status: "204" } };
|
|
7654
|
+
}
|
|
7655
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7656
|
+
const { randomUUID: randomUUID7 } = __require("node:crypto");
|
|
7657
|
+
const versionId = randomUUID7();
|
|
7658
|
+
await tx.execute(
|
|
7659
|
+
`UPDATE "${resourceType}" SET "versionId" = ?, "lastUpdated" = ?, "deleted" = 1 WHERE "id" = ?`,
|
|
7660
|
+
[versionId, now, id]
|
|
7661
|
+
);
|
|
7662
|
+
await tx.execute(
|
|
7663
|
+
`INSERT INTO "${resourceType}_History" ("id", "versionId", "content", "lastUpdated", "deleted") VALUES (?, ?, ?, ?, 1)`,
|
|
7664
|
+
[id, versionId, existing.content, now]
|
|
7665
|
+
);
|
|
7666
|
+
await tx.execute(
|
|
7667
|
+
`DELETE FROM "${resourceType}_References" WHERE "resourceId" = ?`,
|
|
7668
|
+
[id]
|
|
7669
|
+
);
|
|
7670
|
+
return { response: { status: "204" } };
|
|
7671
|
+
}
|
|
7672
|
+
case "GET": {
|
|
7673
|
+
if (!id) {
|
|
7674
|
+
throw new Error("GET requires resource ID");
|
|
7675
|
+
}
|
|
7676
|
+
const row = await tx.queryOne(
|
|
7677
|
+
`SELECT "content", "deleted" FROM "${resourceType}" WHERE "id" = ?`,
|
|
7678
|
+
[id]
|
|
7679
|
+
);
|
|
7680
|
+
if (!row) {
|
|
7681
|
+
throw new ResourceNotFoundError(resourceType, id);
|
|
7682
|
+
}
|
|
7683
|
+
if (row.deleted === 1) {
|
|
7684
|
+
throw new ResourceGoneError(resourceType, id);
|
|
7685
|
+
}
|
|
7686
|
+
const resource = JSON.parse(row.content);
|
|
7687
|
+
return { resource, response: { status: "200" } };
|
|
7688
|
+
}
|
|
7689
|
+
default:
|
|
7690
|
+
throw new Error(`Unsupported method: ${method}`);
|
|
7691
|
+
}
|
|
7692
|
+
}
|
|
7693
|
+
async function checkIfNoneExistInTx(tx, resourceType, ifNoneExist) {
|
|
7694
|
+
const params = new URLSearchParams(ifNoneExist);
|
|
7695
|
+
const conditions = [];
|
|
7696
|
+
const values = [];
|
|
7697
|
+
for (const [key, value] of params.entries()) {
|
|
7698
|
+
if (key === "_id") {
|
|
7699
|
+
conditions.push('"id" = ?');
|
|
7700
|
+
values.push(value);
|
|
7701
|
+
} else if (key === "identifier") {
|
|
7702
|
+
conditions.push('"content" LIKE ?');
|
|
7703
|
+
values.push(`%${value}%`);
|
|
7704
|
+
} else {
|
|
7705
|
+
conditions.push(`"${key}" = ?`);
|
|
7706
|
+
values.push(value);
|
|
7707
|
+
}
|
|
7708
|
+
}
|
|
7709
|
+
if (conditions.length === 0) {
|
|
7710
|
+
return { status: "create" };
|
|
7711
|
+
}
|
|
7712
|
+
const whereClause = conditions.join(" AND ");
|
|
7713
|
+
const rows = await tx.query(
|
|
7714
|
+
`SELECT "id", "content", "versionId", "lastUpdated" FROM "${resourceType}" WHERE "deleted" = 0 AND ${whereClause}`,
|
|
7715
|
+
values
|
|
7716
|
+
);
|
|
7717
|
+
if (rows.length === 0) {
|
|
7718
|
+
return { status: "create" };
|
|
7719
|
+
}
|
|
7720
|
+
if (rows.length === 1) {
|
|
7721
|
+
const existing = JSON.parse(rows[0].content);
|
|
7722
|
+
return {
|
|
7723
|
+
status: "existing",
|
|
7724
|
+
response: {
|
|
7725
|
+
resource: existing,
|
|
7726
|
+
response: {
|
|
7727
|
+
status: "200",
|
|
7728
|
+
location: `${resourceType}/${rows[0].id}/_history/${rows[0].versionId}`,
|
|
7729
|
+
etag: `W/"${rows[0].versionId}"`,
|
|
7730
|
+
lastModified: rows[0].lastUpdated
|
|
7731
|
+
}
|
|
7732
|
+
}
|
|
7733
|
+
};
|
|
7734
|
+
}
|
|
7735
|
+
return {
|
|
7736
|
+
status: "error",
|
|
7737
|
+
response: {
|
|
7738
|
+
response: {
|
|
7739
|
+
status: "412",
|
|
7740
|
+
outcome: {
|
|
7741
|
+
issue: [{
|
|
7742
|
+
severity: "error",
|
|
7743
|
+
code: "duplicate",
|
|
7744
|
+
diagnostics: `If-None-Exist matched ${rows.length} resources for ${resourceType}`
|
|
7745
|
+
}]
|
|
7746
|
+
}
|
|
7747
|
+
}
|
|
7748
|
+
}
|
|
7749
|
+
};
|
|
7750
|
+
}
|
|
7751
|
+
async function processBatchEntry(store, entry) {
|
|
7752
|
+
if (!entry.request) {
|
|
7753
|
+
return errorResponse("400", "Missing request");
|
|
7754
|
+
}
|
|
7755
|
+
const { method, url } = entry.request;
|
|
7756
|
+
const { resourceType, id } = parseRequestUrl(url);
|
|
7757
|
+
switch (method) {
|
|
7758
|
+
case "POST": {
|
|
7759
|
+
if (!entry.resource) {
|
|
7760
|
+
return errorResponse("400", "Missing resource for POST");
|
|
7761
|
+
}
|
|
7762
|
+
const created = await store.createResource(resourceType, entry.resource);
|
|
7763
|
+
const versionId = created.meta?.versionId ?? "";
|
|
7764
|
+
const lastUpdated = created.meta?.lastUpdated ?? "";
|
|
7765
|
+
return {
|
|
7766
|
+
resource: created,
|
|
7767
|
+
response: {
|
|
7768
|
+
status: "201",
|
|
7769
|
+
location: `${resourceType}/${created.id}/_history/${versionId}`,
|
|
7770
|
+
etag: `W/"${versionId}"`,
|
|
7771
|
+
lastModified: lastUpdated
|
|
7772
|
+
}
|
|
7773
|
+
};
|
|
7774
|
+
}
|
|
7775
|
+
case "PUT": {
|
|
7776
|
+
if (!entry.resource || !id) {
|
|
7777
|
+
return errorResponse("400", "PUT requires resource and ID");
|
|
7778
|
+
}
|
|
7779
|
+
const toUpdate = { ...entry.resource, id };
|
|
7780
|
+
const updateResult = await store.updateResource(resourceType, toUpdate, { upsert: true });
|
|
7781
|
+
const updated = updateResult.resource;
|
|
7782
|
+
const versionId = updated.meta?.versionId ?? "";
|
|
7783
|
+
const lastUpdated = updated.meta?.lastUpdated ?? "";
|
|
7784
|
+
return {
|
|
7785
|
+
resource: updated,
|
|
7786
|
+
response: {
|
|
7787
|
+
status: updateResult.created ? "201" : "200",
|
|
7788
|
+
location: `${resourceType}/${id}/_history/${versionId}`,
|
|
7789
|
+
etag: `W/"${versionId}"`,
|
|
7790
|
+
lastModified: lastUpdated
|
|
7791
|
+
}
|
|
7792
|
+
};
|
|
7793
|
+
}
|
|
7794
|
+
case "DELETE": {
|
|
7795
|
+
if (!id) {
|
|
7796
|
+
return errorResponse("400", "DELETE requires resource ID");
|
|
7797
|
+
}
|
|
7798
|
+
await store.deleteResource(resourceType, id);
|
|
7799
|
+
return { response: { status: "204" } };
|
|
7800
|
+
}
|
|
7801
|
+
case "GET": {
|
|
7802
|
+
if (!id) {
|
|
7803
|
+
return errorResponse("400", "GET requires resource ID");
|
|
7804
|
+
}
|
|
7805
|
+
const resource = await store.readResource(resourceType, id);
|
|
7806
|
+
return { resource, response: { status: "200" } };
|
|
7807
|
+
}
|
|
7808
|
+
default:
|
|
7809
|
+
return errorResponse("400", `Unsupported method: ${method}`);
|
|
7810
|
+
}
|
|
7811
|
+
}
|
|
7812
|
+
function errorResponse(status, message) {
|
|
7813
|
+
return {
|
|
7814
|
+
response: {
|
|
7815
|
+
status,
|
|
7816
|
+
outcome: {
|
|
7817
|
+
issue: [{ severity: "error", code: "processing", diagnostics: message }]
|
|
7818
|
+
}
|
|
7819
|
+
}
|
|
7820
|
+
};
|
|
7821
|
+
}
|
|
7822
|
+
function errorToStatus(err) {
|
|
7823
|
+
if (err instanceof ResourceNotFoundError) return "404";
|
|
7824
|
+
if (err instanceof ResourceGoneError) return "410";
|
|
7825
|
+
return "500";
|
|
7826
|
+
}
|
|
7827
|
+
|
|
7048
7828
|
// src/migration/schema-diff.ts
|
|
7049
7829
|
function compareSchemas(oldSets, newSets) {
|
|
7050
7830
|
const deltas = [];
|
|
@@ -9501,6 +10281,7 @@ var IGImportOrchestrator = class {
|
|
|
9501
10281
|
export {
|
|
9502
10282
|
BetterSqlite3Adapter,
|
|
9503
10283
|
ConceptHierarchyRepo,
|
|
10284
|
+
ConditionalService,
|
|
9504
10285
|
DEFAULT_SEARCH_COUNT,
|
|
9505
10286
|
DELETED_SCHEMA_VERSION,
|
|
9506
10287
|
ElementIndexRepo,
|
|
@@ -9567,10 +10348,12 @@ export {
|
|
|
9567
10348
|
buildTwoPhaseSearchSQLv2,
|
|
9568
10349
|
buildTypeHistorySQLv2,
|
|
9569
10350
|
buildUpdateMainSQLv2,
|
|
10351
|
+
buildUrnMap,
|
|
9570
10352
|
buildWhereClauseV2,
|
|
9571
10353
|
buildWhereFragmentV2,
|
|
9572
10354
|
compareSchemas,
|
|
9573
10355
|
createFhirRuntimeProvider,
|
|
10356
|
+
deepResolveUrns,
|
|
9574
10357
|
executeSearchV2 as executeSearch,
|
|
9575
10358
|
extractPrefix,
|
|
9576
10359
|
extractPropertyPath,
|
|
@@ -9593,6 +10376,8 @@ export {
|
|
|
9593
10376
|
parseSortParam,
|
|
9594
10377
|
planSearch,
|
|
9595
10378
|
prefixToOperator,
|
|
10379
|
+
processBatchV2,
|
|
10380
|
+
processTransactionV2,
|
|
9596
10381
|
reindexAllV2,
|
|
9597
10382
|
reindexResourceTypeV2,
|
|
9598
10383
|
splitSearchValues
|