@itwin/core-backend 5.10.0-dev.18 → 5.10.0-dev.20

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.
Files changed (95) hide show
  1. package/lib/cjs/BriefcaseManager.d.ts +8 -1
  2. package/lib/cjs/BriefcaseManager.d.ts.map +1 -1
  3. package/lib/cjs/BriefcaseManager.js.map +1 -1
  4. package/lib/cjs/ChangesetReader.d.ts +2 -2
  5. package/lib/cjs/ChangesetReader.js +2 -2
  6. package/lib/cjs/ChangesetReader.js.map +1 -1
  7. package/lib/cjs/ClassRegistry.d.ts +3 -3
  8. package/lib/cjs/ClassRegistry.js +3 -3
  9. package/lib/cjs/ClassRegistry.js.map +1 -1
  10. package/lib/cjs/Element.d.ts +6 -3
  11. package/lib/cjs/Element.d.ts.map +1 -1
  12. package/lib/cjs/Element.js +6 -3
  13. package/lib/cjs/Element.js.map +1 -1
  14. package/lib/cjs/Entity.d.ts +13 -5
  15. package/lib/cjs/Entity.d.ts.map +1 -1
  16. package/lib/cjs/Entity.js +13 -5
  17. package/lib/cjs/Entity.js.map +1 -1
  18. package/lib/cjs/IModelDb.d.ts +79 -17
  19. package/lib/cjs/IModelDb.d.ts.map +1 -1
  20. package/lib/cjs/IModelDb.js +181 -22
  21. package/lib/cjs/IModelDb.js.map +1 -1
  22. package/lib/cjs/Relationship.d.ts +3 -1
  23. package/lib/cjs/Relationship.d.ts.map +1 -1
  24. package/lib/cjs/Relationship.js +3 -1
  25. package/lib/cjs/Relationship.js.map +1 -1
  26. package/lib/cjs/internal/ElementLRUCache.d.ts.map +1 -1
  27. package/lib/cjs/internal/ElementLRUCache.js +23 -4
  28. package/lib/cjs/internal/ElementLRUCache.js.map +1 -1
  29. package/lib/cjs/internal/workspace/SettingsSchemasImpl.d.ts.map +1 -1
  30. package/lib/cjs/internal/workspace/SettingsSchemasImpl.js +54 -9
  31. package/lib/cjs/internal/workspace/SettingsSchemasImpl.js.map +1 -1
  32. package/lib/cjs/workspace/SettingsSchemas.d.ts +15 -1
  33. package/lib/cjs/workspace/SettingsSchemas.d.ts.map +1 -1
  34. package/lib/cjs/workspace/SettingsSchemas.js.map +1 -1
  35. package/lib/esm/BriefcaseManager.d.ts +8 -1
  36. package/lib/esm/BriefcaseManager.d.ts.map +1 -1
  37. package/lib/esm/BriefcaseManager.js.map +1 -1
  38. package/lib/esm/ChangesetReader.d.ts +2 -2
  39. package/lib/esm/ChangesetReader.js +2 -2
  40. package/lib/esm/ChangesetReader.js.map +1 -1
  41. package/lib/esm/ClassRegistry.d.ts +3 -3
  42. package/lib/esm/ClassRegistry.js +3 -3
  43. package/lib/esm/ClassRegistry.js.map +1 -1
  44. package/lib/esm/Element.d.ts +6 -3
  45. package/lib/esm/Element.d.ts.map +1 -1
  46. package/lib/esm/Element.js +6 -3
  47. package/lib/esm/Element.js.map +1 -1
  48. package/lib/esm/Entity.d.ts +13 -5
  49. package/lib/esm/Entity.d.ts.map +1 -1
  50. package/lib/esm/Entity.js +13 -5
  51. package/lib/esm/Entity.js.map +1 -1
  52. package/lib/esm/IModelDb.d.ts +79 -17
  53. package/lib/esm/IModelDb.d.ts.map +1 -1
  54. package/lib/esm/IModelDb.js +182 -23
  55. package/lib/esm/IModelDb.js.map +1 -1
  56. package/lib/esm/Relationship.d.ts +3 -1
  57. package/lib/esm/Relationship.d.ts.map +1 -1
  58. package/lib/esm/Relationship.js +3 -1
  59. package/lib/esm/Relationship.js.map +1 -1
  60. package/lib/esm/internal/ElementLRUCache.d.ts.map +1 -1
  61. package/lib/esm/internal/ElementLRUCache.js +23 -4
  62. package/lib/esm/internal/ElementLRUCache.js.map +1 -1
  63. package/lib/esm/internal/workspace/SettingsSchemasImpl.d.ts.map +1 -1
  64. package/lib/esm/internal/workspace/SettingsSchemasImpl.js +54 -9
  65. package/lib/esm/internal/workspace/SettingsSchemasImpl.js.map +1 -1
  66. package/lib/esm/test/ElementLRUCache.test.js +60 -0
  67. package/lib/esm/test/ElementLRUCache.test.js.map +1 -1
  68. package/lib/esm/test/imodel/IModel.test.js +31 -0
  69. package/lib/esm/test/imodel/IModel.test.js.map +1 -1
  70. package/lib/esm/test/schema/ClassRegistry.test.js +3 -0
  71. package/lib/esm/test/schema/ClassRegistry.test.js.map +1 -1
  72. package/lib/esm/test/schema/IModelSchemaContext.test.js +2 -0
  73. package/lib/esm/test/schema/IModelSchemaContext.test.js.map +1 -1
  74. package/lib/esm/test/schema/SchemaViewHidden.test.d.ts +2 -0
  75. package/lib/esm/test/schema/SchemaViewHidden.test.d.ts.map +1 -0
  76. package/lib/esm/test/schema/SchemaViewHidden.test.js +275 -0
  77. package/lib/esm/test/schema/SchemaViewHidden.test.js.map +1 -0
  78. package/lib/esm/test/schema/SchemaViewKoQ.test.d.ts +2 -0
  79. package/lib/esm/test/schema/SchemaViewKoQ.test.d.ts.map +1 -0
  80. package/lib/esm/test/schema/SchemaViewKoQ.test.js +175 -0
  81. package/lib/esm/test/schema/SchemaViewKoQ.test.js.map +1 -0
  82. package/lib/esm/test/schema/SchemaViewLifecycle.test.d.ts +2 -0
  83. package/lib/esm/test/schema/SchemaViewLifecycle.test.d.ts.map +1 -0
  84. package/lib/esm/test/schema/SchemaViewLifecycle.test.js +141 -0
  85. package/lib/esm/test/schema/SchemaViewLifecycle.test.js.map +1 -0
  86. package/lib/esm/test/schema/SchemaViewValidation.test.d.ts +2 -0
  87. package/lib/esm/test/schema/SchemaViewValidation.test.d.ts.map +1 -0
  88. package/lib/esm/test/schema/SchemaViewValidation.test.js +475 -0
  89. package/lib/esm/test/schema/SchemaViewValidation.test.js.map +1 -0
  90. package/lib/esm/test/standalone/SettingsSchemas.test.js +382 -0
  91. package/lib/esm/test/standalone/SettingsSchemas.test.js.map +1 -1
  92. package/lib/esm/workspace/SettingsSchemas.d.ts +15 -1
  93. package/lib/esm/workspace/SettingsSchemas.d.ts.map +1 -1
  94. package/lib/esm/workspace/SettingsSchemas.js.map +1 -1
  95. package/package.json +14 -14
@@ -46,7 +46,7 @@ import { IModelNative } from "./internal/NativePlatform";
46
46
  import { createNoOpLockControl } from "./internal/NoLocks";
47
47
  import { createIModelDbFonts } from "./internal/IModelDbFontsImpl";
48
48
  import { _activeTxn, _cache, _close, _hubAccess, _implicitTxn, _instanceKeyCache, _nativeDb, _releaseAllLocks, _resetIModelDb } from "./internal/Symbols";
49
- import { ECVersion, SchemaContext, SchemaJsonLocater } from "@itwin/ecschema-metadata";
49
+ import { ECVersion, SchemaContext, SchemaJsonLocater, SchemaView } from "@itwin/ecschema-metadata";
50
50
  import { SchemaMap } from "./Schema";
51
51
  import { ElementLRUCache, InstanceKeyLRUCache } from "./internal/ElementLRUCache";
52
52
  import { IModelIncrementalSchemaLocater } from "./IModelIncrementalSchemaLocater";
@@ -162,6 +162,7 @@ export class IModelDb extends IModel {
162
162
  _jsClassMap;
163
163
  _schemaMap;
164
164
  _schemaContext;
165
+ _schemasPromise;
165
166
  /** @deprecated in 5.0.0 - will not be removed until after 2026-06-13. Use [[fonts]]. */
166
167
  _fontMap; // eslint-disable-line @typescript-eslint/no-deprecated
167
168
  _fonts = createIModelDbFonts(this);
@@ -794,6 +795,11 @@ export class IModelDb extends IModel {
794
795
  this._jsClassMap = undefined;
795
796
  this._schemaMap = undefined;
796
797
  this._schemaContext = undefined;
798
+ if (this._schemasPromise) {
799
+ const old = this._schemasPromise;
800
+ this._schemasPromise = undefined;
801
+ old.then((view) => view.markOutdated()).catch(() => { });
802
+ }
797
803
  this[_nativeDb].clearECDbCache();
798
804
  }
799
805
  this.elements[_cache].clear();
@@ -1234,7 +1240,7 @@ export class IModelDb extends IModel {
1234
1240
  }
1235
1241
  /** The registry of entity metadata for this iModel.
1236
1242
  * @internal
1237
- * @deprecated in 5.0 - will not be removed until after 2026-06-13. Please use `schemaContext` from the `iModel` instead.
1243
+ * @deprecated in 5.0 - will not be removed until after 2026-06-13. Use `getSchemaView()` from the `iModel` instead.
1238
1244
  *
1239
1245
  * @example
1240
1246
  * ```typescript
@@ -1242,7 +1248,8 @@ export class IModelDb extends IModel {
1242
1248
  * const classMetaData: EntityMetaData | undefined = iModel.classMetaDataRegistry.find("SchemaName:ClassName");
1243
1249
  *
1244
1250
  * // Replacement:
1245
- * const metaData: EntityClass | undefined = imodel.schemaContext.getSchemaItemSync("SchemaName.ClassName", EntityClass);
1251
+ * const view = await imodel.getSchemaView();
1252
+ * const cls = view.findClass("SchemaName:ClassName");
1246
1253
  * ```
1247
1254
  */
1248
1255
  // eslint-disable-next-line @typescript-eslint/no-deprecated
@@ -1269,7 +1276,11 @@ export class IModelDb extends IModel {
1269
1276
  return this._schemaMap;
1270
1277
  }
1271
1278
  /**
1272
- * Gets the context that allows accessing the metadata (ecschema-metadata package) of this iModel
1279
+ * Gets the context that allows accessing the metadata (`@itwin/ecschema-metadata` package) of this iModel.
1280
+ *
1281
+ * For runtime read-only access - class/property iteration, IS-A checks, navigating relationships, KOQ lookups -
1282
+ * prefer [[getSchemaView]]. `schemaContext` remains the right choice when you need schema authoring
1283
+ * (via `@itwin/ecschema-editing`), custom-attribute deserialization, or the full ecschema-metadata object graph.
1273
1284
  * @public @preview
1274
1285
  */
1275
1286
  get schemaContext() {
@@ -1283,6 +1294,55 @@ export class IModelDb extends IModel {
1283
1294
  }
1284
1295
  return this._schemaContext;
1285
1296
  }
1297
+ /** Get the schema view for this iModel. The view is built lazily on
1298
+ * first call by fetching compact binary schema data via `PRAGMA schema_view` through
1299
+ * the ConcurrentQuery thread pool. Subsequent calls return the cached view. Multiple
1300
+ * concurrent callers share a single in-flight build.
1301
+ *
1302
+ * The returned `SchemaView` is a lightweight, read-only, synchronous API for
1303
+ * navigating schema metadata - classes, properties, relationships, enumerations, etc.
1304
+ * It is the recommended default for runtime read-only metadata access and is significantly
1305
+ * faster and lower-memory than [[schemaContext]]. Use [[schemaContext]] for schema authoring,
1306
+ * custom-attribute deserialization, or anywhere you need the full ecschema-metadata object graph.
1307
+ * @beta
1308
+ */
1309
+ async getSchemaView() {
1310
+ if (this._schemasPromise) {
1311
+ const ctx = await this._schemasPromise;
1312
+ if (!ctx.isOutdated)
1313
+ return ctx;
1314
+ }
1315
+ // Capture the in-flight promise locally so the rejection handler only clears
1316
+ // `_schemasPromise` if it still points at this build. A concurrent invalidation +
1317
+ // re-fetch could otherwise replace the field before our hydrate fails, and a naive
1318
+ // `_schemasPromise = undefined` would clobber that newer reference.
1319
+ const inflight = this._hydrateSchemas();
1320
+ this._schemasPromise = inflight;
1321
+ inflight.catch(() => {
1322
+ if (this._schemasPromise === inflight)
1323
+ this._schemasPromise = undefined;
1324
+ });
1325
+ return inflight;
1326
+ }
1327
+ async _hydrateSchemas() {
1328
+ // PRAGMA returns exactly one row with format, formatVersion, data (binary), schemaToken.
1329
+ // Important: only call reader.next() once - do NOT use `for await` on PRAGMA results.
1330
+ // ConcurrentQuery wraps regular ECSQL in LIMIT/OFFSET for pagination but skips this for
1331
+ // PRAGMAs. If the serialized result exceeds the memory threshold, the response is marked
1332
+ // "Partial", and a `for await` loop would re-issue the same PRAGMA forever since PRAGMAs
1333
+ // don't support OFFSET-based pagination.
1334
+ // This implementation uses the non-pinned version of the pragma other than frontend - because backend
1335
+ // is always strictly coupled with the native code.
1336
+ const reader = this.createQueryReader("PRAGMA schema_view");
1337
+ const result = await reader.next();
1338
+ if (result.done)
1339
+ throw new IModelError(DbResult.BE_SQLITE_ERROR, "PRAGMA schema_view returned no rows");
1340
+ const data = result.value.data;
1341
+ const token = result.value.schemaToken;
1342
+ if (data === undefined || data === null)
1343
+ throw new IModelError(DbResult.BE_SQLITE_ERROR, "PRAGMA schema_view returned null data column");
1344
+ return SchemaView.fromBinary(data, token ?? "");
1345
+ }
1286
1346
  /** Get the linkTableRelationships for this IModel */
1287
1347
  get relationships() {
1288
1348
  return this._relationships || (this._relationships = new Relationships(this));
@@ -1357,7 +1417,7 @@ export class IModelDb extends IModel {
1357
1417
  }
1358
1418
  /** Get metadata for a class. This method will load the metadata from the iModel into the cache as a side-effect, if necessary.
1359
1419
  * @throws [[IModelError]] if the metadata cannot be found nor loaded.
1360
- * @deprecated in 5.0 - will not be removed until after 2026-06-13. Please use `getSchemaItem` from `SchemaContext` class instead.
1420
+ * @deprecated in 5.0 - will not be removed until after 2026-06-13. Use `getSchemaView()` on the iModel and call `view.findClass(...)` instead.
1361
1421
  *
1362
1422
  * @example
1363
1423
  * * ```typescript
@@ -1365,7 +1425,8 @@ export class IModelDb extends IModel {
1365
1425
  * const metaData: EntityMetaData = imodel.getMetaData("SchemaName:ClassName");
1366
1426
  *
1367
1427
  * // Replacement:
1368
- * const metaData: EntityClass | undefined = imodel.schemaContext.getSchemaItemSync("SchemaName", "ClassName", EntityClass);
1428
+ * const view = await imodel.getSchemaView();
1429
+ * const cls = view.findClass("SchemaName:ClassName");
1369
1430
  * ```
1370
1431
  */
1371
1432
  // eslint-disable-next-line @typescript-eslint/no-deprecated
@@ -1383,7 +1444,7 @@ export class IModelDb extends IModel {
1383
1444
  return metadata;
1384
1445
  }
1385
1446
  /** Identical to [[getMetaData]], except it returns `undefined` instead of throwing an error if the metadata cannot be found nor loaded.
1386
- * @deprecated in 5.0 - will not be removed until after 2026-06-13. Please use `getSchemaItem` from `SchemaContext` class instead.
1447
+ * @deprecated in 5.0 - will not be removed until after 2026-06-13. Use `getSchemaView()` on the iModel and call `view.findClass(...)` instead.
1387
1448
  *
1388
1449
  * @example
1389
1450
  * * ```typescript
@@ -1391,7 +1452,8 @@ export class IModelDb extends IModel {
1391
1452
  * const metaData: EntityMetaData | undefined = imodel.tryGetMetaData("SchemaName:ClassName");
1392
1453
  *
1393
1454
  * // Replacement:
1394
- * const metaData: EntityClass | undefined = imodel.schemaContext.getSchemaItemSync("SchemaName.ClassName", EntityClass);
1455
+ * const view = await imodel.getSchemaView();
1456
+ * const cls = view.findClass("SchemaName:ClassName");
1395
1457
  * ```
1396
1458
  */
1397
1459
  // eslint-disable-next-line @typescript-eslint/no-deprecated
@@ -1411,7 +1473,7 @@ export class IModelDb extends IModel {
1411
1473
  * @param func The callback to be invoked on each property
1412
1474
  * @param includeCustom If true (default), include custom-handled properties in the iteration. Otherwise, skip custom-handled properties.
1413
1475
  * @note Custom-handled properties are core properties that have behavior enforced by C++ handlers.
1414
- * @deprecated in 5.0 - will not be removed until after 2026-06-13. Please use `forEachProperty` instead.
1476
+ * @deprecated in 5.0 - will not be removed until after 2026-06-13. Use `getSchemaView()` on the iModel and iterate `view.findClass(classFullName)?.getProperties()` instead.
1415
1477
  *
1416
1478
  * @example
1417
1479
  * ```typescript
@@ -1421,9 +1483,10 @@ export class IModelDb extends IModel {
1421
1483
  * }, false);
1422
1484
  *
1423
1485
  * // Replacement:
1424
- * await IModelDb.forEachProperty(imodel, "TestDomain.TestDomainClass", true, (propName: string, property: Property) => {
1425
- * console.log(`Property name: ${propName}, Property type: ${property.propertyType}`);
1426
- * }, false);
1486
+ * const view = await imodel.getSchemaView();
1487
+ * for (const property of view.findClass("BisCore:Element")?.getProperties() ?? []) {
1488
+ * console.log(`Property name: ${property.name}, Kind: ${property.kind}`);
1489
+ * }
1427
1490
  * ```
1428
1491
  */
1429
1492
  // eslint-disable-next-line @typescript-eslint/no-deprecated
@@ -1437,7 +1500,7 @@ export class IModelDb extends IModel {
1437
1500
  * @param func The callback to be invoked on each property
1438
1501
  * @param includeCustom If true (default), include custom-handled properties in the iteration. Otherwise, skip custom-handled properties.
1439
1502
  * @note Custom-handled properties are core properties that have behavior enforced by C++ handlers.
1440
- * @deprecated in 5.0 - will not be removed until after 2026-06-13. Use `forEachProperty` from `SchemaContext` class instead.
1503
+ * @deprecated in 5.0 - will not be removed until after 2026-06-13. Use `getSchemaView()` on the iModel and iterate `view.findClass(classFullName)?.getProperties()` instead.
1441
1504
  *
1442
1505
  * @example
1443
1506
  * ```typescript
@@ -1447,9 +1510,10 @@ export class IModelDb extends IModel {
1447
1510
  * });
1448
1511
  *
1449
1512
  * // Replacement:
1450
- * imodel.schemaContext.forEachProperty("BisCore:Element", true, (propName: string, property: Property) => {
1451
- * console.log(`Property name: ${propName}, Property type: ${property.propertyType}`);
1452
- * });
1513
+ * const view = await imodel.getSchemaView();
1514
+ * for (const property of view.findClass("BisCore:Element")?.getProperties() ?? []) {
1515
+ * console.log(`Property name: ${property.name}, Kind: ${property.kind}`);
1516
+ * }
1453
1517
  * ```
1454
1518
  */
1455
1519
  // eslint-disable-next-line @typescript-eslint/no-deprecated
@@ -3335,7 +3399,63 @@ export class BriefcaseDb extends IModelDb {
3335
3399
  async getAllChangesetHealthData() {
3336
3400
  return this[_nativeDb].getAllChangesetHealthData();
3337
3401
  }
3338
- /** Revert timeline changes and then push resulting changeset */
3402
+ /**
3403
+ * Whether file-based transactions are enabled for this briefcase.
3404
+ *
3405
+ * When enabled, transaction data is stored in separate temporary `.txn` files rather than in the
3406
+ * briefcase's internal transaction table. This avoids SQLite blob size limits and reduces memory
3407
+ * pressure for very large changesets, at the cost of additional disk I/O.
3408
+ * @see [[enableFileBasedTxns]] to enable, [[disableFileBasedTxns]] to disable.
3409
+ * @internal
3410
+ */
3411
+ get isFileBasedTxnsEnabled() {
3412
+ return this[_nativeDb].queryLocalValue("fileBasedTxns") === "1";
3413
+ }
3414
+ /**
3415
+ * Enable file-based transactions for this briefcase.
3416
+ * @throws IModelError with [[ChangeSetStatus.HasUncommittedChanges]] if there are unsaved changes.
3417
+ * @throws IModelError with [[ChangeSetStatus.HasLocalChanges]] if there are pending transactions.
3418
+ * @internal
3419
+ */
3420
+ enableFileBasedTxns() {
3421
+ this._setFileBasedTxnsSetting(true);
3422
+ }
3423
+ /**
3424
+ * Disable file-based transactions for this briefcase, reverting to the default storage mode
3425
+ * (transactions stored within the briefcase's internal transaction table).
3426
+ * @throws IModelError with [[ChangeSetStatus.HasUncommittedChanges]] if there are unsaved changes.
3427
+ * @throws IModelError with [[ChangeSetStatus.HasLocalChanges]] if there are pending transactions.
3428
+ * @internal
3429
+ */
3430
+ disableFileBasedTxns() {
3431
+ this._setFileBasedTxnsSetting(false);
3432
+ }
3433
+ _setFileBasedTxnsSetting(enabled) {
3434
+ if (this.isFileBasedTxnsEnabled === enabled)
3435
+ return;
3436
+ const nativeDb = this[_nativeDb];
3437
+ if (nativeDb.hasUnsavedChanges())
3438
+ throw new IModelError(ChangeSetStatus.HasUncommittedChanges, "Cannot change file-based transactions setting while there are unsaved changes");
3439
+ if (nativeDb.hasPendingTxns())
3440
+ throw new IModelError(ChangeSetStatus.HasLocalChanges, "Cannot change file-based transactions setting while there are pending transactions");
3441
+ if (enabled)
3442
+ nativeDb.saveLocalValue("fileBasedTxns", "1");
3443
+ else
3444
+ nativeDb.deleteLocalValue("fileBasedTxns");
3445
+ }
3446
+ /**
3447
+ * Revert timeline changes and push the resulting changeset.
3448
+ *
3449
+ * Pulls the latest changes, acquires the schema lock, reverts the inclusive range of
3450
+ * changesets `[toIndex..current]`, and pushes the revert as a new changeset. On failure,
3451
+ * follow the behavior specified by `arg.inCaseOfFailure`, which may discard local changes,
3452
+ * retain local changes, or delete the briefcase.
3453
+ *
3454
+ * @param arg - Arguments specifying the target changeset index, push options, access token, and failure handling behavior.
3455
+ * @throws IModelError with [[ChangeSetStatus.ApplyError]] if `toIndex` is not specified.
3456
+ * @throws IModelError with [[ChangeSetStatus.HasUncommittedChanges]] if there are unsaved changes.
3457
+ * @throws IModelError with [[ChangeSetStatus.HasLocalChanges]] if there are pending transactions.
3458
+ */
3339
3459
  async revertAndPushChanges(arg) {
3340
3460
  const nativeDb = this[_nativeDb];
3341
3461
  if (arg.toIndex === undefined) {
@@ -3367,11 +3487,14 @@ export class BriefcaseDb extends IModelDb {
3367
3487
  if (nativeDb.schemaSyncEnabled()) {
3368
3488
  arg.skipSchemaChanges = true;
3369
3489
  }
3490
+ // The native side enables file-based txns during revert. Restore the original setting afterward.
3491
+ const wasFileBasedTxnsEnabled = this.isFileBasedTxnsEnabled;
3492
+ const preRevertIndex = this.changeset.index;
3370
3493
  try {
3371
3494
  await BriefcaseManager.revertTimelineChanges(this, arg);
3372
- this[_nativeDb].saveChanges("Revert changes");
3495
+ nativeDb.saveChanges("Revert changes");
3373
3496
  if (!arg.description) {
3374
- arg.description = `Reverted changes from ${this.changeset.index} to ${arg.toIndex}${arg.skipSchemaChanges ? " (schema changes skipped)" : ""}`;
3497
+ arg.description = `Reverted changes from ${preRevertIndex} to ${arg.toIndex}${arg.skipSchemaChanges ? " (schema changes skipped)" : ""}`;
3375
3498
  }
3376
3499
  const pushArgs = {
3377
3500
  description: arg.description,
@@ -3386,13 +3509,49 @@ export class BriefcaseDb extends IModelDb {
3386
3509
  this.clearCaches();
3387
3510
  }
3388
3511
  catch (err) {
3389
- if (!arg.retainLocks) {
3390
- await this.locks.releaseAllLocks();
3391
- throw err;
3512
+ const failureAction = arg.inCaseOfFailure ?? "revert";
3513
+ try {
3514
+ switch (failureAction) {
3515
+ case "revert":
3516
+ // Restore the briefcase to its pre-revert state: save any unsaved changes into txns,
3517
+ // reverse all txns, then delete them.
3518
+ nativeDb.saveChanges();
3519
+ if (nativeDb.hasPendingTxns())
3520
+ nativeDb.reverseAll();
3521
+ nativeDb.deleteAllTxns();
3522
+ break;
3523
+ case "delete":
3524
+ // Clear local changes first so lock release can succeed.
3525
+ nativeDb.abandonChanges();
3526
+ nativeDb.deleteAllTxns();
3527
+ if (!arg.retainLocks)
3528
+ await this.locks.releaseAllLocks();
3529
+ const filePath = this.pathName;
3530
+ this.close();
3531
+ await BriefcaseManager.deleteBriefcaseFiles(filePath, arg.accessToken);
3532
+ break;
3533
+ case "retain":
3534
+ // Keep local changes as-is for caller inspection/recovery.
3535
+ nativeDb.saveChanges();
3536
+ break;
3537
+ }
3538
+ }
3539
+ catch (cleanupErr) {
3540
+ Logger.logError(loggerCategory, `Failed to clean up after revert error (action=${failureAction}): ${String(cleanupErr)}`);
3541
+ }
3542
+ if (!arg.retainLocks && this.isOpen) {
3543
+ try {
3544
+ await this.locks.releaseAllLocks();
3545
+ }
3546
+ catch (lockErr) {
3547
+ Logger.logError(loggerCategory, `Failed to release locks after revert failure (action=${failureAction}): ${String(lockErr)}`);
3548
+ }
3392
3549
  }
3550
+ throw err;
3393
3551
  }
3394
3552
  finally {
3395
- this[_nativeDb].abandonChanges();
3553
+ if (this.isOpen && !wasFileBasedTxnsEnabled && !nativeDb.hasPendingTxns() && !nativeDb.hasUnsavedChanges())
3554
+ this.disableFileBasedTxns();
3396
3555
  }
3397
3556
  }
3398
3557
  /**