@itwin/core-backend 4.2.0-dev.3 → 4.2.0-dev.30

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 (48) hide show
  1. package/CHANGELOG.md +78 -1
  2. package/lib/cjs/BlobContainerService.d.ts +6 -2
  3. package/lib/cjs/BlobContainerService.d.ts.map +1 -1
  4. package/lib/cjs/BlobContainerService.js.map +1 -1
  5. package/lib/cjs/BriefcaseManager.d.ts.map +1 -1
  6. package/lib/cjs/BriefcaseManager.js +5 -1
  7. package/lib/cjs/BriefcaseManager.js.map +1 -1
  8. package/lib/cjs/CheckpointManager.d.ts.map +1 -1
  9. package/lib/cjs/CheckpointManager.js +6 -3
  10. package/lib/cjs/CheckpointManager.js.map +1 -1
  11. package/lib/cjs/CloudSqlite.d.ts +79 -34
  12. package/lib/cjs/CloudSqlite.d.ts.map +1 -1
  13. package/lib/cjs/CloudSqlite.js +11 -9
  14. package/lib/cjs/CloudSqlite.js.map +1 -1
  15. package/lib/cjs/IModelDb.d.ts +40 -7
  16. package/lib/cjs/IModelDb.d.ts.map +1 -1
  17. package/lib/cjs/IModelDb.js +196 -45
  18. package/lib/cjs/IModelDb.js.map +1 -1
  19. package/lib/cjs/SQLiteDb.js +1 -1
  20. package/lib/cjs/SQLiteDb.js.map +1 -1
  21. package/lib/cjs/SchemaSync.d.ts +41 -0
  22. package/lib/cjs/SchemaSync.d.ts.map +1 -0
  23. package/lib/cjs/SchemaSync.js +77 -0
  24. package/lib/cjs/SchemaSync.js.map +1 -0
  25. package/lib/cjs/SchemaUtils.d.ts.map +1 -1
  26. package/lib/cjs/SchemaUtils.js +3 -3
  27. package/lib/cjs/SchemaUtils.js.map +1 -1
  28. package/lib/cjs/TileStorage.d.ts +16 -5
  29. package/lib/cjs/TileStorage.d.ts.map +1 -1
  30. package/lib/cjs/TileStorage.js +34 -3
  31. package/lib/cjs/TileStorage.js.map +1 -1
  32. package/lib/cjs/TxnManager.d.ts +41 -0
  33. package/lib/cjs/TxnManager.d.ts.map +1 -1
  34. package/lib/cjs/TxnManager.js +38 -0
  35. package/lib/cjs/TxnManager.js.map +1 -1
  36. package/lib/cjs/ViewStore.js +1 -1
  37. package/lib/cjs/ViewStore.js.map +1 -1
  38. package/lib/cjs/core-backend.d.ts +1 -0
  39. package/lib/cjs/core-backend.d.ts.map +1 -1
  40. package/lib/cjs/core-backend.js +1 -0
  41. package/lib/cjs/core-backend.js.map +1 -1
  42. package/lib/cjs/rpc-impl/RpcBriefcaseUtility.js +2 -2
  43. package/lib/cjs/rpc-impl/RpcBriefcaseUtility.js.map +1 -1
  44. package/lib/cjs/workspace/Workspace.d.ts +2 -4
  45. package/lib/cjs/workspace/Workspace.d.ts.map +1 -1
  46. package/lib/cjs/workspace/Workspace.js +4 -6
  47. package/lib/cjs/workspace/Workspace.js.map +1 -1
  48. package/package.json +17 -14
@@ -8,8 +8,9 @@
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.StandaloneDb = exports.SnapshotDb = exports.BriefcaseDb = exports.IModelDb = exports.BriefcaseLocalValue = void 0;
11
- const path_1 = require("path");
12
11
  const fs = require("fs");
12
+ const path_1 = require("path");
13
+ const touch = require("touch");
13
14
  const core_bentley_1 = require("@itwin/core-bentley");
14
15
  const core_common_1 = require("@itwin/core-common");
15
16
  const core_geometry_1 = require("@itwin/core-geometry");
@@ -24,7 +25,6 @@ const CodeSpecs_1 = require("./CodeSpecs");
24
25
  const ConcurrentQuery_1 = require("./ConcurrentQuery");
25
26
  const ECSqlStatement_1 = require("./ECSqlStatement");
26
27
  const Element_1 = require("./Element");
27
- const ElementAspect_1 = require("./ElementAspect");
28
28
  const ElementGraphics_1 = require("./ElementGraphics");
29
29
  const Entity_1 = require("./Entity");
30
30
  const GeoCoordConfig_1 = require("./GeoCoordConfig");
@@ -33,6 +33,7 @@ const IModelJsFs_1 = require("./IModelJsFs");
33
33
  const IpcHost_1 = require("./IpcHost");
34
34
  const Model_1 = require("./Model");
35
35
  const Relationship_1 = require("./Relationship");
36
+ const SchemaSync_1 = require("./SchemaSync");
36
37
  const ServerBasedLocks_1 = require("./ServerBasedLocks");
37
38
  const SqliteStatement_1 = require("./SqliteStatement");
38
39
  const TxnManager_1 = require("./TxnManager");
@@ -123,6 +124,10 @@ class IModelDb extends core_common_1.IModel {
123
124
  this.changeset = this.nativeDb.getCurrentChangeset();
124
125
  this.onChangesetApplied.raiseEvent();
125
126
  }
127
+ /** @internal */
128
+ restartDefaultTxn() {
129
+ this.nativeDb.restartDefaultTxn();
130
+ }
126
131
  get fontMap() {
127
132
  return this._fontMap ?? (this._fontMap = new core_common_1.FontMap(this.nativeDb.readFontMap()));
128
133
  }
@@ -150,12 +155,18 @@ class IModelDb extends core_common_1.IModel {
150
155
  (0, core_bentley_1.assert)(undefined !== super.iModelId);
151
156
  return super.iModelId;
152
157
  } // GuidString | undefined for the IModel superclass, but required for all IModelDb subclasses
153
- /** @internal*/
154
- get nativeDb() { return this._nativeDb; } // eslint-disable-line @typescript-eslint/no-non-null-assertion
155
158
  /** Get the full path fileName of this iModelDb
156
159
  * @note this member is only valid while the iModel is opened.
157
160
  */
158
161
  get pathName() { return this.nativeDb.getFilePath(); }
162
+ /** Get the full path to this iModel's "watch file".
163
+ * A read-only briefcase opened with `watchForChanges: true` creates this file next to the briefcase file on open, if it doesn't already exist.
164
+ * A writable briefcase "touches" this file if it exists whenever it commits changes to the briefcase.
165
+ * The read-only briefcase can use a file watcher to react when the writable briefcase makes changes to the briefcase.
166
+ * This is more reliable than watching the sqlite WAL file.
167
+ * @internal
168
+ */
169
+ get watchFilePathName() { return `${this.pathName}-watch`; }
159
170
  /** @internal */
160
171
  constructor(args) {
161
172
  super({ ...args, iTwinId: args.nativeDb.getITwinId(), iModelId: args.nativeDb.getIModelId() });
@@ -175,7 +186,7 @@ class IModelDb extends core_common_1.IModel {
175
186
  this.onChangesetApplied = new core_bentley_1.BeEvent();
176
187
  /** Event called when the iModel is about to be closed. */
177
188
  this.onBeforeClose = new core_bentley_1.BeEvent();
178
- this._nativeDb = args.nativeDb;
189
+ this.nativeDb = args.nativeDb;
179
190
  this.nativeDb.setIModelDb(this);
180
191
  this.loadSettingDictionaries();
181
192
  GeoCoordConfig_1.GeoCoordConfig.loadForImodel(this.workspace.settings); // load gcs data specified by iModel's settings dictionaries, must be done before calling initializeIModelDb
@@ -205,10 +216,9 @@ class IModelDb extends core_common_1.IModel {
205
216
  this._codeService?.close();
206
217
  this._codeService = undefined;
207
218
  this.nativeDb.closeIModel();
208
- this._nativeDb = undefined; // the underlying nativeDb has been freed by closeIModel
209
219
  }
210
220
  /** @internal */
211
- async refreshContainerSas(_userAccessToken) { }
221
+ async refreshContainer(_userAccessToken) { }
212
222
  /**
213
223
  * Called by derived classes before closing the connection
214
224
  * @internal
@@ -256,7 +266,7 @@ class IModelDb extends core_common_1.IModel {
256
266
  /** Return `true` if the underlying nativeDb is open and valid.
257
267
  * @internal
258
268
  */
259
- get isOpen() { return undefined !== this.nativeDb; }
269
+ get isOpen() { return this.nativeDb.isOpen(); }
260
270
  /** Get the briefcase Id of this iModel */
261
271
  getBriefcaseId() { return this.isOpen ? this.nativeDb.getBriefcaseId() : core_common_1.BriefcaseIdValue.Illegal; }
262
272
  /**
@@ -332,9 +342,8 @@ class IModelDb extends core_common_1.IModel {
332
342
  * @public
333
343
  * */
334
344
  createQueryReader(ecsql, params, config) {
335
- if (!this._nativeDb || !this._nativeDb.isOpen()) {
345
+ if (!this.nativeDb.isOpen())
336
346
  throw new core_common_1.IModelError(core_bentley_1.DbResult.BE_SQLITE_ERROR, "db not open");
337
- }
338
347
  const executor = {
339
348
  execute: async (request) => {
340
349
  return ConcurrentQuery_1.ConcurrentQuery.executeQueryRequest(this.nativeDb, request);
@@ -537,6 +546,7 @@ class IModelDb extends core_common_1.IModel {
537
546
  clearCaches() {
538
547
  this._statementCache.clear();
539
548
  this._sqliteStatementCache.clear();
549
+ this._classMetaDataRegistry = undefined;
540
550
  }
541
551
  /** Update the project extents for this iModel.
542
552
  * <p><em>Example:</em>
@@ -626,16 +636,34 @@ class IModelDb extends core_common_1.IModel {
626
636
  * @see querySchemaVersion
627
637
  */
628
638
  async importSchemas(schemaFileNames, options) {
629
- if (this.nativeDb.getITwinId() !== core_bentley_1.Guid.empty) // if this iModel is associated with an iTwin, importing schema requires the schema lock
630
- await this.acquireSchemaLock();
639
+ if (schemaFileNames.length === 0)
640
+ return;
631
641
  const maybeCustomNativeContext = options?.ecSchemaXmlContext?.nativeContext;
632
- const nativeImportOptions = {
633
- schemaLockHeld: true,
634
- ecSchemaXmlContext: maybeCustomNativeContext,
635
- };
636
- const stat = this.nativeDb.importSchemas(schemaFileNames, nativeImportOptions);
637
- if (core_bentley_1.DbResult.BE_SQLITE_OK !== stat) {
638
- throw new core_common_1.IModelError(stat, "Error importing schema");
642
+ if (this.nativeDb.schemaSyncEnabled()) {
643
+ await SchemaSync_1.SchemaSync.withLockedAccess(this, { openMode: core_bentley_1.OpenMode.Readonly, operationName: "schema sync" }, async (syncAccess) => {
644
+ const schemaSyncDbUri = syncAccess.getUri();
645
+ this.saveChanges();
646
+ let stat = this.nativeDb.importSchemas(schemaFileNames, { schemaLockHeld: false, ecSchemaXmlContext: maybeCustomNativeContext, schemaSyncDbUri });
647
+ if (core_bentley_1.DbResult.BE_SQLITE_ERROR_SchemaLockFailed === stat) {
648
+ this.abandonChanges();
649
+ if (this.nativeDb.getITwinId() !== core_bentley_1.Guid.empty)
650
+ await this.acquireSchemaLock();
651
+ stat = this.nativeDb.importSchemas(schemaFileNames, { schemaLockHeld: true, ecSchemaXmlContext: maybeCustomNativeContext, schemaSyncDbUri });
652
+ }
653
+ if (core_bentley_1.DbResult.BE_SQLITE_OK !== stat)
654
+ throw new core_common_1.IModelError(stat, "Error importing schema");
655
+ });
656
+ }
657
+ else {
658
+ const nativeImportOptions = {
659
+ schemaLockHeld: true,
660
+ ecSchemaXmlContext: maybeCustomNativeContext,
661
+ };
662
+ if (this.nativeDb.getITwinId() !== core_bentley_1.Guid.empty) // if this iModel is associated with an iTwin, importing schema requires the schema lock
663
+ await this.acquireSchemaLock();
664
+ const stat = this.nativeDb.importSchemas(schemaFileNames, nativeImportOptions);
665
+ if (core_bentley_1.DbResult.BE_SQLITE_OK !== stat)
666
+ throw new core_common_1.IModelError(stat, "Error importing schema");
639
667
  }
640
668
  this.clearCaches();
641
669
  }
@@ -649,11 +677,30 @@ class IModelDb extends core_common_1.IModel {
649
677
  * @alpha
650
678
  */
651
679
  async importSchemaStrings(serializedXmlSchemas) {
652
- if (this.iTwinId && this.iTwinId !== core_bentley_1.Guid.empty) // if this iModel is associated with an iTwin, importing schema requires the schema lock
653
- await this.acquireSchemaLock();
654
- const stat = this.nativeDb.importXmlSchemas(serializedXmlSchemas, { schemaLockHeld: true });
655
- if (core_bentley_1.DbResult.BE_SQLITE_OK !== stat)
656
- throw new core_common_1.IModelError(stat, "Error importing schema");
680
+ if (serializedXmlSchemas.length === 0)
681
+ return;
682
+ if (this.nativeDb.schemaSyncEnabled()) {
683
+ await SchemaSync_1.SchemaSync.withLockedAccess(this, { openMode: core_bentley_1.OpenMode.Readonly, operationName: "schemaSync" }, async (syncAccess) => {
684
+ const schemaSyncDbUri = syncAccess.getUri();
685
+ this.saveChanges();
686
+ let stat = this.nativeDb.importXmlSchemas(serializedXmlSchemas, { schemaLockHeld: false, schemaSyncDbUri });
687
+ if (core_bentley_1.DbResult.BE_SQLITE_ERROR_SchemaLockFailed === stat) {
688
+ this.abandonChanges();
689
+ if (this.nativeDb.getITwinId() !== core_bentley_1.Guid.empty)
690
+ await this.acquireSchemaLock();
691
+ stat = this.nativeDb.importXmlSchemas(serializedXmlSchemas, { schemaLockHeld: true, schemaSyncDbUri });
692
+ }
693
+ if (core_bentley_1.DbResult.BE_SQLITE_OK !== stat)
694
+ throw new core_common_1.IModelError(stat, "Error importing schema");
695
+ });
696
+ }
697
+ else {
698
+ if (this.iTwinId && this.iTwinId !== core_bentley_1.Guid.empty) // if this iModel is associated with an iTwin, importing schema requires the schema lock
699
+ await this.acquireSchemaLock();
700
+ const stat = this.nativeDb.importXmlSchemas(serializedXmlSchemas, { schemaLockHeld: true });
701
+ if (core_bentley_1.DbResult.BE_SQLITE_OK !== stat)
702
+ throw new core_common_1.IModelError(stat, "Error importing schema");
703
+ }
657
704
  this.clearCaches();
658
705
  }
659
706
  /** Find an opened instance of any subclass of IModelDb, by filename
@@ -1094,6 +1141,16 @@ class IModelDb extends core_common_1.IModel {
1094
1141
  }
1095
1142
  });
1096
1143
  }
1144
+ /**
1145
+ * Controls how [Code]($common)s are copied from this iModel into another iModel, to work around problems with iModels created by older connectors. The [imodel-transformer](https://github.com/iTwin/imodel-transformer) sets this appropriately on your behalf - you should never need to set or interrogate this property yourself.
1146
+ * @public
1147
+ */
1148
+ get codeValueBehavior() {
1149
+ return this.nativeDb.getCodeValueBehavior();
1150
+ }
1151
+ set codeValueBehavior(newBehavior) {
1152
+ this.nativeDb.setCodeValueBehavior(newBehavior);
1153
+ }
1097
1154
  }
1098
1155
  /** Keep track of open imodels to support `tryFind` for RPC purposes */
1099
1156
  IModelDb._openDbs = new Map();
@@ -1692,18 +1749,77 @@ exports.IModelDb = IModelDb;
1692
1749
  }
1693
1750
  return this._queryAspect(aspectInstanceId, aspectClassFullName);
1694
1751
  }
1752
+ runInstanceQuery(sql, elementId, excludedClassFullNames) {
1753
+ return this._iModel.withPreparedStatement(sql, (statement) => {
1754
+ statement.bindId("elementId", elementId);
1755
+ const aspects = [];
1756
+ while (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) {
1757
+ const row = {};
1758
+ const parsedRow = JSON.parse(statement.getValue(0).getString());
1759
+ // eslint-disable-next-line guard-for-in
1760
+ for (const key in parsedRow) {
1761
+ const jsName = core_common_1.ECJsNames.toJsName(key[0].toUpperCase() + key.substring(1));
1762
+ Object.defineProperty(row, jsName, { enumerable: true, configurable: true, writable: true, value: parsedRow[key] });
1763
+ }
1764
+ const aspectProps = row;
1765
+ aspectProps.classFullName = aspectProps.className.replace(".", ":"); // add in property required by EntityProps
1766
+ aspectProps.className = undefined; // clear property from SELECT $ that we don't want in the final instance
1767
+ if ((undefined === excludedClassFullNames) || !excludedClassFullNames.has(aspectProps.classFullName))
1768
+ aspects.push(this._iModel.constructEntity(aspectProps));
1769
+ }
1770
+ return aspects;
1771
+ });
1772
+ }
1695
1773
  /** Get the ElementAspect instances that are owned by the specified element.
1696
1774
  * @param elementId Get ElementAspects associated with this Element
1697
1775
  * @param aspectClassFullName Optionally filter ElementAspects polymorphically by this class name
1776
+ * @param excludedClassFullNames Optional filter to exclude aspects from classes in the given set.
1698
1777
  * @throws [[IModelError]]
1699
1778
  */
1700
- getAspects(elementId, aspectClassFullName) {
1701
- if (undefined === aspectClassFullName) {
1702
- const uniqueAspects = this._queryAspects(elementId, ElementAspect_1.ElementUniqueAspect.classFullName);
1703
- const multiAspects = this._queryAspects(elementId, ElementAspect_1.ElementMultiAspect.classFullName);
1704
- return uniqueAspects.concat(multiAspects);
1779
+ getAspects(elementId, aspectClassFullName, excludedClassFullNames) {
1780
+ if (aspectClassFullName === undefined) {
1781
+ const allAspects = this.runInstanceQuery(`SELECT $ FROM (
1782
+ SELECT ECInstanceId, ECClassId FROM Bis.ElementMultiAspect WHERE Element.Id = :elementId
1783
+ UNION ALL
1784
+ SELECT ECInstanceId, ECClassId FROM Bis.ElementUniqueAspect WHERE Element.Id = :elementId) OPTIONS USE_JS_PROP_NAMES DO_NOT_TRUNCATE_BLOB`, elementId, excludedClassFullNames);
1785
+ if (allAspects.length === 0)
1786
+ core_bentley_1.Logger.logError(BackendLoggerCategory_1.BackendLoggerCategory.ECDb, `No aspects found for class ${aspectClassFullName} and element ${elementId}`);
1787
+ return allAspects;
1705
1788
  }
1706
- const aspects = this._queryAspects(elementId, aspectClassFullName);
1789
+ // Check if class is abstract
1790
+ const fullClassName = aspectClassFullName.split(":");
1791
+ const val = this._iModel.nativeDb.getECClassMetaData(fullClassName[0], fullClassName[1]);
1792
+ if (val.result !== undefined) {
1793
+ const metaData = new core_common_1.EntityMetaData(JSON.parse(val.result));
1794
+ if (metaData.modifier !== "Abstract") // Class is not abstract, use normal query to retrieve aspects
1795
+ return this._queryAspects(elementId, aspectClassFullName, excludedClassFullNames);
1796
+ }
1797
+ // If class specified is abstract, get the list of all classes derived from it
1798
+ let classIdList = IModelDb.Elements.classMap.get(aspectClassFullName);
1799
+ if (classIdList === undefined) {
1800
+ const classIds = [];
1801
+ this._iModel.withPreparedStatement(`select SourceECInstanceId from meta.ClassHasAllBaseClasses where TargetECInstanceId = (select ECInstanceId from meta.ECClassDef where Name='${fullClassName[1]}'
1802
+ and Schema.Id = (select ECInstanceId from meta.ECSchemaDef where Name='${fullClassName[0]}')) and SourceECInstanceId != TargetECInstanceId`, (statement) => {
1803
+ while (statement.step() === core_bentley_1.DbResult.BE_SQLITE_ROW)
1804
+ classIds.push(statement.getValue(0).getId());
1805
+ });
1806
+ if (classIds.length > 0) {
1807
+ classIdList = classIds.join(",");
1808
+ IModelDb.Elements.classMap.set(aspectClassFullName, classIdList);
1809
+ }
1810
+ }
1811
+ if (classIdList === undefined) {
1812
+ core_bentley_1.Logger.logError(BackendLoggerCategory_1.BackendLoggerCategory.ECDb, `No aspects found for the class ${aspectClassFullName}`);
1813
+ return [];
1814
+ }
1815
+ // Execute an instance query to retrieve all aspects from all the derived classes
1816
+ const aspects = this.runInstanceQuery(`SELECT $ FROM (
1817
+ SELECT ECInstanceId, ECClassId FROM Bis.ElementMultiAspect WHERE Element.Id = :elementId AND ECClassId IN (${classIdList})
1818
+ UNION ALL
1819
+ SELECT ECInstanceId, ECClassId FROM Bis.ElementUniqueAspect WHERE Element.Id = :elementId AND ECClassId IN (${classIdList})
1820
+ ) OPTIONS USE_JS_PROP_NAMES DO_NOT_TRUNCATE_BLOB`, elementId, excludedClassFullNames);
1821
+ if (aspects.length === 0)
1822
+ core_bentley_1.Logger.logError(BackendLoggerCategory_1.BackendLoggerCategory.ECDb, `No aspects found for class ${aspectClassFullName} and element ${elementId}`);
1707
1823
  return aspects;
1708
1824
  }
1709
1825
  /** Insert a new ElementAspect into the iModel.
@@ -1749,6 +1865,7 @@ exports.IModelDb = IModelDb;
1749
1865
  });
1750
1866
  }
1751
1867
  }
1868
+ Elements.classMap = new Map();
1752
1869
  IModelDb.Elements = Elements;
1753
1870
  /** The collection of views in an [[IModelDb]].
1754
1871
  * @public
@@ -1928,8 +2045,10 @@ exports.IModelDb = IModelDb;
1928
2045
  this._iModel.nativeDb.saveFileProperty(viewArg, JSON.stringify(props), thumbnail.image);
1929
2046
  return 0;
1930
2047
  }
1931
- /** Set the default view property the iModel
2048
+ /** Set the default view property the iModel.
1932
2049
  * @param viewId The Id of the ViewDefinition to use as the default
2050
+ * @deprecated in 4.2.x. Avoid setting this property - it is not practical for one single view to serve the needs of the many applications
2051
+ * that might wish to view the contents of the iModel.
1933
2052
  */
1934
2053
  setDefaultViewId(viewId) {
1935
2054
  const spec = { namespace: "dgn_View", name: "DefaultView" };
@@ -2110,12 +2229,20 @@ class BriefcaseDb extends IModelDb {
2110
2229
  const openMode = (args.readonly || args.watchForChanges) ? core_bentley_1.OpenMode.Readonly : core_bentley_1.OpenMode.ReadWrite;
2111
2230
  const nativeDb = this.openDgnDb(file, openMode, undefined, args);
2112
2231
  const briefcaseDb = new BriefcaseDb({ nativeDb, key: file.key ?? core_bentley_1.Guid.createValue(), openMode, briefcaseId: nativeDb.getBriefcaseId() });
2113
- // If they asked to watch for changes, set an fs.watch on the "-wal" file (only it is modified while we hold this connection.)
2232
+ // If they asked to watch for changes, set an fs.watch on the "-watch" file (only it is modified while we hold this connection.)
2114
2233
  // Whenever there are changes, restart our defaultTxn. That loads the changes from the other connection and sends
2115
2234
  // notifications as if they happened on this connection. Note: the watcher is called only when the backend event loop cycles.
2116
2235
  if (args.watchForChanges && undefined === args.container) {
2117
- const watcher = fs.watch(`${file.path}-wal`, { persistent: false }, () => nativeDb.restartDefaultTxn());
2118
- briefcaseDb.onBeforeClose.addOnce(() => watcher.close()); // Stop the watcher when we close this connection.
2236
+ // Must touch the file synchronously - cannot watch a file until it exists.
2237
+ touch.sync(briefcaseDb.watchFilePathName);
2238
+ // Restart default txn to trigger events when watch file is changed by some other process.
2239
+ const watcher = fs.watch(briefcaseDb.watchFilePathName, { persistent: false }, () => {
2240
+ nativeDb.restartDefaultTxn();
2241
+ });
2242
+ // Stop the watcher when we close this connection.
2243
+ briefcaseDb.onBeforeClose.addOnce(() => {
2244
+ watcher.close();
2245
+ });
2119
2246
  }
2120
2247
  if (openMode === core_bentley_1.OpenMode.ReadWrite && CodeService_1.CodeService.createForIModel) {
2121
2248
  try {
@@ -2130,23 +2257,39 @@ class BriefcaseDb extends IModelDb {
2130
2257
  this.onOpened.raiseEvent(briefcaseDb, args);
2131
2258
  return briefcaseDb;
2132
2259
  }
2133
- closeAndReopen(openMode) {
2260
+ /** If the briefcase is read-only, reopen the native briefcase for writing.
2261
+ * Execute the supplied function.
2262
+ * If the briefcase was read-only, reopen the native briefcase as read-only.
2263
+ * @note this._openMode is not changed from its initial value.
2264
+ * @internal Exported strictly for tests.
2265
+ */
2266
+ async executeWritable(func) {
2134
2267
  const fileName = this.pathName;
2268
+ try {
2269
+ if (this.isReadonly)
2270
+ this.closeAndReopen(core_bentley_1.OpenMode.ReadWrite, fileName);
2271
+ await func();
2272
+ }
2273
+ finally {
2274
+ if (this.isReadonly)
2275
+ this.closeAndReopen(core_bentley_1.OpenMode.Readonly, fileName);
2276
+ }
2277
+ }
2278
+ closeAndReopen(openMode, fileName) {
2279
+ // Unclosed statements will produce BUSY error when attempting to close.
2280
+ this.clearCaches();
2281
+ // The following resets the native db's pointer to this JavaScript object.
2135
2282
  this.nativeDb.closeIModel();
2136
2283
  this.nativeDb.openIModel(fileName, openMode);
2284
+ // Restore the native db's pointer to this JavaScript object.
2285
+ this.nativeDb.setIModelDb(this);
2137
2286
  }
2138
2287
  /** Pull and apply changesets from iModelHub */
2139
2288
  async pullChanges(arg) {
2140
- if (this.isReadonly) // we allow pulling changes into a briefcase that is readonly - close and reopen it writeable
2141
- this.closeAndReopen(core_bentley_1.OpenMode.ReadWrite);
2142
- try {
2289
+ await this.executeWritable(async () => {
2143
2290
  await BriefcaseManager_1.BriefcaseManager.pullAndApplyChangesets(this, arg ?? {});
2144
2291
  this.initializeIModelDb();
2145
- }
2146
- finally {
2147
- if (this.isReadonly) // if the briefcase was opened readonly - close and reopen it readonly
2148
- this.closeAndReopen(core_bentley_1.OpenMode.Readonly);
2149
- }
2292
+ });
2150
2293
  IpcHost_1.IpcHost.notifyTxns(this, "notifyPulledChanges", this.changeset);
2151
2294
  }
2152
2295
  /** Push changes to iModelHub. */
@@ -2354,18 +2497,26 @@ class SnapshotDb extends IModelDb {
2354
2497
  snapshot.close();
2355
2498
  throw err;
2356
2499
  }
2500
+ // unref timer, so it doesn't prevent a process from shutting down.
2501
+ snapshot._restartDefaultTxnTimer = setTimeout(() => {
2502
+ snapshot.restartDefaultTxn();
2503
+ }, (10 * 60) * 1000).unref(); // 10 * 60 is 10 minutes in seconds, then converted to milliseconds (* 1000);
2357
2504
  snapshot._refreshSas = new RefreshV2CheckpointSas(container.accessToken, checkpoint.reattachSafetySeconds);
2358
2505
  return snapshot;
2359
2506
  }
2360
- /** Used to refresh the container sasToken using the current user's accessToken
2507
+ /** Used to refresh the container sasToken using the current user's accessToken.
2508
+ * Also restarts the timer which causes the default txn to be restarted on db if the timer activates.
2361
2509
  * @internal
2362
2510
  */
2363
- async refreshContainerSas(userAccessToken) {
2511
+ async refreshContainer(userAccessToken) {
2512
+ this._restartDefaultTxnTimer?.refresh();
2364
2513
  return this._refreshSas?.refreshSas(userAccessToken, this);
2365
2514
  }
2366
2515
  /** @internal */
2367
2516
  beforeClose() {
2368
2517
  super.beforeClose();
2518
+ if (this._restartDefaultTxnTimer)
2519
+ clearTimeout(this._restartDefaultTxnTimer);
2369
2520
  if (this._createClassViewsOnClose) { // check for flag set during create
2370
2521
  if (core_bentley_1.BentleyStatus.SUCCESS !== this.nativeDb.createClassViewsInDb()) {
2371
2522
  throw new core_common_1.IModelError(core_bentley_1.IModelStatus.SQLiteError, "Error creating class views");