@itwin/core-backend 5.5.1 → 5.6.0-dev.10

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 (38) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/lib/cjs/ChannelControl.d.ts +29 -0
  3. package/lib/cjs/ChannelControl.d.ts.map +1 -1
  4. package/lib/cjs/ChannelControl.js.map +1 -1
  5. package/lib/cjs/IModelDb.d.ts +148 -7
  6. package/lib/cjs/IModelDb.d.ts.map +1 -1
  7. package/lib/cjs/IModelDb.js +159 -67
  8. package/lib/cjs/IModelDb.js.map +1 -1
  9. package/lib/cjs/SqliteChangesetReader.d.ts +5 -0
  10. package/lib/cjs/SqliteChangesetReader.d.ts.map +1 -1
  11. package/lib/cjs/SqliteChangesetReader.js +7 -0
  12. package/lib/cjs/SqliteChangesetReader.js.map +1 -1
  13. package/lib/cjs/internal/ChannelAdmin.d.ts +2 -1
  14. package/lib/cjs/internal/ChannelAdmin.d.ts.map +1 -1
  15. package/lib/cjs/internal/ChannelAdmin.js +23 -0
  16. package/lib/cjs/internal/ChannelAdmin.js.map +1 -1
  17. package/lib/esm/ChannelControl.d.ts +29 -0
  18. package/lib/esm/ChannelControl.d.ts.map +1 -1
  19. package/lib/esm/ChannelControl.js.map +1 -1
  20. package/lib/esm/IModelDb.d.ts +148 -7
  21. package/lib/esm/IModelDb.d.ts.map +1 -1
  22. package/lib/esm/IModelDb.js +158 -66
  23. package/lib/esm/IModelDb.js.map +1 -1
  24. package/lib/esm/SqliteChangesetReader.d.ts +5 -0
  25. package/lib/esm/SqliteChangesetReader.d.ts.map +1 -1
  26. package/lib/esm/SqliteChangesetReader.js +7 -0
  27. package/lib/esm/SqliteChangesetReader.js.map +1 -1
  28. package/lib/esm/internal/ChannelAdmin.d.ts +2 -1
  29. package/lib/esm/internal/ChannelAdmin.d.ts.map +1 -1
  30. package/lib/esm/internal/ChannelAdmin.js +23 -0
  31. package/lib/esm/internal/ChannelAdmin.js.map +1 -1
  32. package/lib/esm/test/hubaccess/Rebase.test.js +98 -1
  33. package/lib/esm/test/hubaccess/Rebase.test.js.map +1 -1
  34. package/lib/esm/test/schema/SchemaImportCallbacks.test.d.ts +2 -0
  35. package/lib/esm/test/schema/SchemaImportCallbacks.test.d.ts.map +1 -0
  36. package/lib/esm/test/schema/SchemaImportCallbacks.test.js +916 -0
  37. package/lib/esm/test/schema/SchemaImportCallbacks.test.js.map +1 -0
  38. package/package.json +13 -13
@@ -7,7 +7,7 @@
7
7
  * @module iModels
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.StandaloneDb = exports.SnapshotDb = exports.BriefcaseDb = exports.IModelDb = exports.BriefcaseLocalValue = void 0;
10
+ exports.StandaloneDb = exports.SnapshotDb = exports.BriefcaseDb = exports.IModelDb = exports.DataTransformationStrategy = exports.BriefcaseLocalValue = void 0;
11
11
  const fs = require("fs");
12
12
  const path_1 = require("path");
13
13
  const touch = require("touch");
@@ -84,6 +84,24 @@ class IModelSettings extends SettingsImpl_1.SettingsImpl {
84
84
  yield* IModelHost_1.IModelHost.appWorkspace.settings.getSettingEntries(name);
85
85
  }
86
86
  }
87
+ /**
88
+ * Strategy for transforming data during schema import.
89
+ * @beta
90
+ */
91
+ var DataTransformationStrategy;
92
+ (function (DataTransformationStrategy) {
93
+ /** No data transformation will be performed after schema import. */
94
+ DataTransformationStrategy["None"] = "None";
95
+ /** Data transformation will be performed using a temporary snapshot created before schema import.
96
+ * Useful for complex transformations requiring full read access to complete pre-import state for lazy conversion.
97
+ * Note: Creates a complete copy of the briefcase file, which may be large.
98
+ */
99
+ DataTransformationStrategy["Snapshot"] = "Snapshot";
100
+ /** Data transformation will be performed using in-memory cached data created before schema import.
101
+ * Useful for lightweight transformations involving limited data.
102
+ */
103
+ DataTransformationStrategy["InMemory"] = "InMemory";
104
+ })(DataTransformationStrategy || (exports.DataTransformationStrategy = DataTransformationStrategy = {}));
87
105
  /** An iModel database file. The database file can either be a briefcase or a snapshot.
88
106
  * @see [Accessing iModels]($docs/learning/backend/AccessingIModels.md)
89
107
  * @see [About IModelDb]($docs/learning/backend/IModelDb.md)
@@ -262,11 +280,15 @@ class IModelDb extends core_common_1.IModel {
262
280
  this.clearCaches();
263
281
  this[Symbols_1._nativeDb].detachDb(alias);
264
282
  }
265
- /** Close this IModel, if it is currently open, and save changes if it was opened in ReadWrite mode. */
266
- close() {
283
+ /** Close this IModel, if it is currently open, and save changes if it was opened in ReadWrite mode.
284
+ * @param options Options for closing the iModel.
285
+ */
286
+ close(options) {
267
287
  if (!this.isOpen)
268
288
  return; // don't continue if already closed
269
289
  this.beforeClose();
290
+ if (options?.optimize)
291
+ this.optimize();
270
292
  IModelDb._openDbs.delete(this._fileKey);
271
293
  this._workspace?.close();
272
294
  this.locks[Symbols_1._close]();
@@ -277,6 +299,40 @@ class IModelDb extends core_common_1.IModel {
277
299
  this.saveChanges();
278
300
  this[Symbols_1._nativeDb].closeFile();
279
301
  }
302
+ /** Optimize this iModel by vacuuming, and analyzing.
303
+ *
304
+ * @note This operation requires exclusive access to the database and may take some time on large files.
305
+ * @beta
306
+ */
307
+ optimize() {
308
+ // Vacuum to reclaim space and defragment
309
+ this.vacuum();
310
+ // Analyze to update statistics for query optimizer
311
+ this.analyze();
312
+ }
313
+ /**
314
+ * Vacuum the model to reclaim space and defragment.
315
+ * @throws [[IModelError]] if the iModel is not open or is read-only.
316
+ * @beta
317
+ */
318
+ vacuum() {
319
+ if (!this.isOpen || this.isReadonly)
320
+ throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "IModel is not open or is read-only");
321
+ this[Symbols_1._nativeDb].clearECDbCache();
322
+ this[Symbols_1._nativeDb].vacuum();
323
+ }
324
+ /**
325
+ * Update SQLite query optimizer statistics for this iModel.
326
+ * This helps SQLite choose better query plans.
327
+ *
328
+ * @throws [[IModelError]] if the iModel is not open or is read-only.
329
+ * @beta
330
+ */
331
+ analyze() {
332
+ if (!this.isOpen || this.isReadonly)
333
+ throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "IModel is not open or is read-only");
334
+ this[Symbols_1._nativeDb].analyze();
335
+ }
280
336
  /** @internal */
281
337
  async refreshContainerForRpc(_userAccessToken) { }
282
338
  /** Event called when the iModel is about to be closed. */
@@ -718,21 +774,68 @@ class IModelDb extends core_common_1.IModel {
718
774
  this.clearCaches();
719
775
  }
720
776
  }
721
- /** Import an ECSchema. On success, the schema definition is stored in the iModel.
722
- * This method is asynchronous (must be awaited) because, in the case where this IModelDb is a briefcase, this method first obtains the schema lock from the iModel server.
723
- * You must import a schema into an iModel before you can insert instances of the classes in that schema. See [[Element]]
724
- * @param schemaFileName array of Full paths to ECSchema.xml files to be imported.
725
- * @param {SchemaImportOptions} options - options during schema import.
726
- * @throws [[IModelError]] if the schema lock cannot be obtained or there is a problem importing the schema.
727
- * @note Changes are saved if importSchemas is successful and abandoned if not successful.
728
- * - You can use NativeLoggerCategory to turn on the native logs. You can also control [what exactly is logged by the loggers](https://www.itwinjs.org/learning/common/logging/#controlling-what-is-logged).
729
- * - See [Schema Versioning]($docs/bis/guide/schema-evolution/schema-versioning-and-generations.md) for more information on acceptable changes to schemas.
730
- * @note This method should not be called from {TxnManager.withIndirectTxnModeAsync} or {RebaseHandler.recompute}.
731
- * @see querySchemaVersion
777
+ /** Helper to clean up snapshot resources safely
778
+ * @internal
732
779
  */
733
- async importSchemas(schemaFileNames, options) {
734
- if (schemaFileNames.length === 0)
735
- return;
780
+ cleanupSnapshot(resources) {
781
+ if (resources.snapshot) {
782
+ const pathName = resources.snapshot.pathName;
783
+ resources.snapshot.close();
784
+ if (pathName && IModelJsFs_1.IModelJsFs.existsSync(pathName)) {
785
+ IModelJsFs_1.IModelJsFs.removeSync(pathName);
786
+ }
787
+ }
788
+ }
789
+ async preSchemaImportCallback(callback, context) {
790
+ const callbackResources = {
791
+ transformStrategy: DataTransformationStrategy.None,
792
+ };
793
+ try {
794
+ if (callback?.preSchemaImportCallback) {
795
+ const callbackResult = await callback.preSchemaImportCallback(context);
796
+ callbackResources.transformStrategy = callbackResult.transformStrategy;
797
+ if (callbackResult.transformStrategy === DataTransformationStrategy.Snapshot) {
798
+ // Create temporary snapshot file
799
+ const snapshotDb = SnapshotDb.createFrom(this, `${this.pathName}.snapshot-${Date.now()}`);
800
+ callbackResources.snapshot = snapshotDb;
801
+ }
802
+ else if (callbackResult.transformStrategy === DataTransformationStrategy.InMemory) {
803
+ if (callbackResult.cachedData === undefined) {
804
+ throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "InMemory transform strategy requires cachedData to be provided.");
805
+ }
806
+ callbackResources.cachedData = callbackResult.cachedData;
807
+ }
808
+ }
809
+ }
810
+ catch (callbackError) {
811
+ this.abandonChanges();
812
+ this.cleanupSnapshot(callbackResources);
813
+ throw new core_common_1.IModelError(callbackError.errorNumber ?? core_bentley_1.IModelStatus.BadRequest, `Failed to execute preSchemaImportCallback: ${callbackError.message}`);
814
+ }
815
+ return callbackResources;
816
+ }
817
+ async postSchemaImportCallback(callback, context) {
818
+ if (context.resources.transformStrategy === DataTransformationStrategy.Snapshot && (context.resources.snapshot === undefined || !IModelJsFs_1.IModelJsFs.existsSync(context.resources.snapshot.pathName))) {
819
+ throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "Snapshot transform strategy requires a snapshot to be created");
820
+ }
821
+ if (context.resources.transformStrategy === DataTransformationStrategy.InMemory && context.resources.cachedData === undefined) {
822
+ throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "InMemory transform strategy requires cachedData to be provided.");
823
+ }
824
+ try {
825
+ if (callback?.postSchemaImportCallback)
826
+ await callback.postSchemaImportCallback(context);
827
+ }
828
+ catch (callbackError) {
829
+ this.abandonChanges();
830
+ throw new core_common_1.IModelError(callbackError.errorNumber ?? core_bentley_1.IModelStatus.BadRequest, `Failed to execute postSchemaImportCallback: ${callbackError.message}`);
831
+ }
832
+ finally {
833
+ // Always clean up snapshot, whether success or error
834
+ this.cleanupSnapshot(context.resources);
835
+ }
836
+ }
837
+ /** Shared implementation for importing schemas from file or string. */
838
+ async importSchemasInternal(schemas, options, nativeImportOp) {
736
839
  if (this instanceof BriefcaseDb) {
737
840
  if (this.txns.rebaser.isRebasing) {
738
841
  throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "Cannot import schemas while rebasing");
@@ -741,13 +844,25 @@ class IModelDb extends core_common_1.IModel {
741
844
  throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "Cannot import schemas while in an indirect change scope");
742
845
  }
743
846
  }
847
+ if (options?.channelUpgrade) {
848
+ try {
849
+ await this.channels.upgradeChannel(options.channelUpgrade, this, options.data);
850
+ }
851
+ catch (error) {
852
+ this.abandonChanges();
853
+ throw error;
854
+ }
855
+ }
856
+ let preSchemaImportCallbackResult = { transformStrategy: DataTransformationStrategy.None };
857
+ if (options?.schemaImportCallbacks?.preSchemaImportCallback)
858
+ preSchemaImportCallbackResult = await this.preSchemaImportCallback(options.schemaImportCallbacks, { iModel: this, data: options.data, schemaData: schemas });
744
859
  const maybeCustomNativeContext = options?.ecSchemaXmlContext?.nativeContext;
745
860
  if (this[Symbols_1._nativeDb].schemaSyncEnabled()) {
746
861
  await SchemaSync_1.SchemaSync.withLockedAccess(this, { openMode: core_bentley_1.OpenMode.Readonly, operationName: "schema sync" }, async (syncAccess) => {
747
862
  const schemaSyncDbUri = syncAccess.getUri();
748
863
  this.saveChanges();
749
864
  try {
750
- this[Symbols_1._nativeDb].importSchemas(schemaFileNames, { schemaLockHeld: false, ecSchemaXmlContext: maybeCustomNativeContext, schemaSyncDbUri });
865
+ nativeImportOp(schemas, { schemaLockHeld: false, ecSchemaXmlContext: maybeCustomNativeContext, schemaSyncDbUri });
751
866
  }
752
867
  catch (outerErr) {
753
868
  if (core_bentley_1.DbResult.BE_SQLITE_ERROR_DataTransformRequired === outerErr.errorNumber) {
@@ -755,7 +870,7 @@ class IModelDb extends core_common_1.IModel {
755
870
  if (this[Symbols_1._nativeDb].getITwinId() !== core_bentley_1.Guid.empty)
756
871
  await this.acquireSchemaLock();
757
872
  try {
758
- this[Symbols_1._nativeDb].importSchemas(schemaFileNames, { schemaLockHeld: true, ecSchemaXmlContext: maybeCustomNativeContext, schemaSyncDbUri });
873
+ nativeImportOp(schemas, { schemaLockHeld: true, ecSchemaXmlContext: maybeCustomNativeContext, schemaSyncDbUri });
759
874
  }
760
875
  catch (innerErr) {
761
876
  throw new core_common_1.IModelError(innerErr.errorNumber, innerErr.message);
@@ -775,71 +890,48 @@ class IModelDb extends core_common_1.IModel {
775
890
  if (this[Symbols_1._nativeDb].getITwinId() !== core_bentley_1.Guid.empty) // if this iModel is associated with an iTwin, importing schema requires the schema lock
776
891
  await this.acquireSchemaLock();
777
892
  try {
778
- this[Symbols_1._nativeDb].importSchemas(schemaFileNames, nativeImportOptions);
893
+ nativeImportOp(schemas, nativeImportOptions);
779
894
  }
780
895
  catch (err) {
781
896
  throw new core_common_1.IModelError(err.errorNumber, err.message);
782
897
  }
783
898
  }
784
899
  this.clearCaches();
900
+ if (options?.schemaImportCallbacks?.postSchemaImportCallback)
901
+ await this.postSchemaImportCallback(options.schemaImportCallbacks, { iModel: this, resources: preSchemaImportCallbackResult, data: options.data });
902
+ }
903
+ /** Import an ECSchema. On success, the schema definition is stored in the iModel.
904
+ * This method is asynchronous (must be awaited) because, in the case where this IModelDb is a briefcase, this method first obtains the schema lock from the iModel server.
905
+ * You must import a schema into an iModel before you can insert instances of the classes in that schema. See [[Element]]
906
+ * @param schemaFileName array of Full paths to ECSchema.xml files to be imported.
907
+ * @param {SchemaImportOptions} options - options during schema import.
908
+ * @throws [[IModelError]] if the schema lock cannot be obtained or there is a problem importing the schema.
909
+ * @note Changes are saved if importSchemas is successful and abandoned if not successful.
910
+ * - You can use NativeLoggerCategory to turn on the native logs. You can also control [what exactly is logged by the loggers](https://www.itwinjs.org/learning/common/logging/#controlling-what-is-logged).
911
+ * - See [Schema Versioning]($docs/bis/guide/schema-evolution/schema-versioning-and-generations.md) for more information on acceptable changes to schemas.
912
+ * @note This method should not be called from {TxnManager.withIndirectTxnModeAsync} or {RebaseHandler.recompute}.
913
+ * @see querySchemaVersion
914
+ */
915
+ async importSchemas(schemaFileNames, options) {
916
+ if (schemaFileNames.length === 0)
917
+ return;
918
+ await this.importSchemasInternal(schemaFileNames, options, (schemas, importOptions) => this[Symbols_1._nativeDb].importSchemas(schemas, importOptions));
785
919
  }
786
920
  /** Import ECSchema(s) serialized to XML. On success, the schema definition is stored in the iModel.
787
921
  * This method is asynchronous (must be awaited) because, in the case where this IModelDb is a briefcase, this method first obtains the schema lock from the iModel server.
788
922
  * You must import a schema into an iModel before you can insert instances of the classes in that schema. See [[Element]]
789
923
  * @param serializedXmlSchemas The xml string(s) created from a serialized ECSchema.
924
+ * @param {SchemaImportOptions} options - options during schema import.
790
925
  * @throws [[IModelError]] if the schema lock cannot be obtained or there is a problem importing the schema.
791
926
  * @note Changes are saved if importSchemaStrings is successful and abandoned if not successful.
792
927
  * @note This method should not be called from {TxnManager.withIndirectTxnModeAsync} or {RebaseHandler.recompute}.
793
928
  * @see querySchemaVersion
794
929
  * @alpha
795
930
  */
796
- async importSchemaStrings(serializedXmlSchemas) {
931
+ async importSchemaStrings(serializedXmlSchemas, options) {
797
932
  if (serializedXmlSchemas.length === 0)
798
933
  return;
799
- if (this instanceof BriefcaseDb) {
800
- if (this.txns.rebaser.isRebasing) {
801
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "Cannot import schemas while rebasing");
802
- }
803
- if (this.txns.isIndirectChanges) {
804
- throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "Cannot import schemas while in an indirect change scope");
805
- }
806
- }
807
- if (this[Symbols_1._nativeDb].schemaSyncEnabled()) {
808
- await SchemaSync_1.SchemaSync.withLockedAccess(this, { openMode: core_bentley_1.OpenMode.Readonly, operationName: "schemaSync" }, async (syncAccess) => {
809
- const schemaSyncDbUri = syncAccess.getUri();
810
- this.saveChanges();
811
- try {
812
- this[Symbols_1._nativeDb].importXmlSchemas(serializedXmlSchemas, { schemaLockHeld: false, schemaSyncDbUri });
813
- }
814
- catch (outerErr) {
815
- if (core_bentley_1.DbResult.BE_SQLITE_ERROR_DataTransformRequired === outerErr.errorNumber) {
816
- this.abandonChanges();
817
- if (this[Symbols_1._nativeDb].getITwinId() !== core_bentley_1.Guid.empty)
818
- await this.acquireSchemaLock();
819
- try {
820
- this[Symbols_1._nativeDb].importXmlSchemas(serializedXmlSchemas, { schemaLockHeld: true, schemaSyncDbUri });
821
- }
822
- catch (innerErr) {
823
- throw new core_common_1.IModelError(innerErr.errorNumber, innerErr.message);
824
- }
825
- }
826
- else {
827
- throw new core_common_1.IModelError(outerErr.errorNumber, outerErr.message);
828
- }
829
- }
830
- });
831
- }
832
- else {
833
- if (this.iTwinId && this.iTwinId !== core_bentley_1.Guid.empty) // if this iModel is associated with an iTwin, importing schema requires the schema lock
834
- await this.acquireSchemaLock();
835
- try {
836
- this[Symbols_1._nativeDb].importXmlSchemas(serializedXmlSchemas, { schemaLockHeld: true });
837
- }
838
- catch (err) {
839
- throw new core_common_1.IModelError(err.errorNumber, err.message);
840
- }
841
- }
842
- this.clearCaches();
934
+ await this.importSchemasInternal(serializedXmlSchemas, options, (schemas, importOptions) => this[Symbols_1._nativeDb].importXmlSchemas(schemas, importOptions));
843
935
  }
844
936
  /** Find an opened instance of any subclass of IModelDb, by filename
845
937
  * @note this method returns an IModelDb if the filename is open for *any* subclass of IModelDb
@@ -3209,11 +3301,11 @@ class BriefcaseDb extends IModelDb {
3209
3301
  });
3210
3302
  this.txns._onChangesPushed(this.changeset);
3211
3303
  }
3212
- close() {
3304
+ close(options) {
3213
3305
  if (this.isBriefcase && this.isOpen && !this.isReadonly && this.txns.rebaser.inProgress()) {
3214
3306
  this.abandonChanges();
3215
3307
  }
3216
- super.close();
3308
+ super.close(options);
3217
3309
  this.onClosed.raiseEvent();
3218
3310
  }
3219
3311
  }