diff --git a/packages/test-app/src/Transformer.ts b/packages/test-app/src/Transformer.ts index 3c906e62..8ebe66c5 100644 --- a/packages/test-app/src/Transformer.ts +++ b/packages/test-app/src/Transformer.ts @@ -93,14 +93,14 @@ export class Transformer extends IModelTransformer { } const transformer = new Transformer(sourceDb, targetDb, { ...options, - isSynchronization: true, + argsForProcessChanges: { + accessToken, + startChangeset: { id: sourceStartChangesetId }, + }, }); await transformer.processSchemas(); await transformer.saveChanges("processSchemas"); - await transformer.process({ - accessToken, - startChangeset: { id: sourceStartChangesetId }, - }); + await transformer.process(); await transformer.saveChanges("processChanges"); if (options?.deleteUnusedGeometryParts) { transformer.deleteUnusedGeometryParts(); diff --git a/packages/transformer/src/IModelTransformer.ts b/packages/transformer/src/IModelTransformer.ts index f8f7e299..ef210849 100644 --- a/packages/transformer/src/IModelTransformer.ts +++ b/packages/transformer/src/IModelTransformer.ts @@ -135,7 +135,7 @@ export interface IModelTransformOptions { targetScopeElementId?: Id64String; /** Set to `true` if IModelTransformer should not record its provenance. - * Provenance tracks a target element back to its corresponding source element and is essential for [[IModelTransformer.process]] to work properly when [[IModelTransformOptions.isSynchronization]] is set to true. + * Provenance tracks a target element back to its corresponding source element and is essential for [[IModelTransformer.process]] to work properly when [[IModelTransformOptions.argsForProcessChanges]] are provided. * Turning off IModelTransformer provenance is really only relevant for producing snapshots or another one time transformations. * @note See the [[includeSourceProvenance]] option for determining whether existing source provenance is cloned into the target. * @note The default is `false` which means that new IModelTransformer provenance will be recorded. @@ -158,7 +158,7 @@ export interface IModelTransformOptions { /** Flag that indicates that the current source and target iModels are now synchronizing in the reverse direction from a prior synchronization. * The most common example is to first synchronize master to branch, make changes to the branch, and then reverse directions to synchronize from branch to master. * This means that the provenance on the (current) source is used instead. - * @note This also means that [[IModelTransformer.process]] can only detect deletes when [[IModelTransformOptions.isSynchronization]] is set to true. + * @note This also means that [[IModelTransformer.process]] can only detect deletes when [[IModelTransformOptions.argsForProcessChanges]] are provided. * @deprecated in 1.x this option is ignored and the transformer now detects synchronization direction using the target scope element */ isReverseSynchronization?: boolean; @@ -232,7 +232,7 @@ export interface IModelTransformOptions { noDetachChangeCache?: boolean; /** - * Do not check that process (with [[IModelTransformOptions.isSynchronization]] set to true) is called from the next changeset index. + * Do not check that process (with [[IModelTransformOptions.argsForProcessChanges]] provided) is called from the next changeset index. * This is an unsafe option (e.g. it can cause data loss in future branch operations) * and you should not use it. * @default false @@ -276,7 +276,7 @@ export interface IModelTransformOptions { * Whether or not this transformation is a synchronization. This influences the behavior of @see [[IModelTransformer.process]]. * @default false */ - isSynchronization?: boolean; + argsForProcessChanges?: ProcessChangesOptions; } /** @@ -379,7 +379,7 @@ export interface InitOptions { } /** - * Arguments for [[IModelTransformer.process]] when [[IModelTransformOptions.isSynchronization]] is set to true. + * Arguments used during [[IModelTransformer.process]] if provided in [[IModelTransformOptions.argsForProcessChanges]]. */ export type ProcessChangesOptions = ExportChangesOptions & { /** how to call saveChanges on the target. Must call targetDb.saveChanges, should not edit the iModel */ @@ -557,7 +557,7 @@ export class IModelTransformer extends IModelExportHandler { if (this._isProvenanceInitTransform) { return "forward"; } - if (!this._options.isSynchronization) { + if (!this._options.argsForProcessChanges) { return "not-sync"; } try { @@ -642,7 +642,7 @@ export class IModelTransformer extends IModelExportHandler { skipPropagateChangesToRootElements: options?.skipPropagateChangesToRootElements ?? true, }; - this._options.isSynchronization = this._options.isSynchronization ?? false; + this._isProvenanceInitTransform = this._options .wasSourceIModelCopiedToTarget ? true @@ -1514,7 +1514,7 @@ export class IModelTransformer extends IModelExportHandler { /** Returns `true` if *brute force* delete detections should be run. * @note This is only called if [[IModelTransformOptions.forceExternalSourceAspectProvenance]] option is true - * @note Not relevant for [[process]] when [[IModelTransformOptions.isSynchronization]] is set to true when change history is known. + * @note Not relevant for [[process]] when [[IModelTransformOptions.argsForProcessChanges]] are provided when change history is known. */ protected shouldDetectDeletes(): boolean { nodeAssert(this._syncType !== undefined); @@ -1528,7 +1528,7 @@ export class IModelTransformer extends IModelExportHandler { * @deprecated in 1.x. Do not use this. // FIXME: how to better explain this? * This method is only called during [[processAll]] when the option * [[IModelTransformOptions.forceExternalSourceAspectProvenance]] is enabled. It is not - * necessary when calling [[process]] with [[IModelTransformOptions.isSynchronization]] set to true, since changeset information is sufficient. + * necessary when calling [[process]] with [[IModelTransformOptions.argsForProcessChanges]] set to true, since changeset information is sufficient. * @note you do not need to call this directly unless processing a subset of an iModel. * @throws [[IModelError]] If the required provenance information is not available to detect deletes. */ @@ -2250,9 +2250,9 @@ export class IModelTransformer extends IModelExportHandler { * source's changeset has been performed. Also stores all changesets that occurred * during the transformation as "pending synchronization changeset indices" @see TargetScopeProvenanceJsonProps * - * You generally should not call this function yourself and use [[process]] with [[IModelTransformOptions.isSynchronization]] set to true instead. + * You generally should not call this function yourself and use [[process]] with [[IModelTransformOptions.argsForProcessChanges]] provided instead. * It is public for unsupported use cases of custom synchronization transforms. - * @note if [[IModelTransformOptions.isSynchronization]] is not set to true in this transformation, this will fail + * @note if [[IModelTransformOptions.argsForProcessChanges]] are not defined in this transformation, this will fail * without setting the `force` option to `true` */ public updateSynchronizationVersion({ force = false } = {}) { @@ -2290,7 +2290,7 @@ export class IModelTransformer extends IModelExportHandler { } if ( - this._options.isSynchronization || + this._options.argsForProcessChanges || (this._startingChangesetIndices && this._isProvenanceInitTransform) ) { nodeAssert( @@ -2530,8 +2530,8 @@ export class IModelTransformer extends IModelExportHandler { /** Detect Relationship deletes using ExternalSourceAspects in the target iModel and a *brute force* comparison against relationships in the source iModel. * @deprecated in 1.x. Don't use this anymore - * @see [[process]] with [[IModelTransformOptions.isSynchronization]] is set to true. - * @note This method is called from [[process]] when [[IModelTransformOptions.isSynchronization]] is set to false, so it only needs to be called directly when processing a subset of an iModel. + * @see [[process]] with [[IModelTransformOptions.argsForProcessChanges]] provided. + * @note This method is called from [[process]] when [[IModelTransformOptions.argsForProcessChanges]] are undefined, so it only needs to be called directly when processing a subset of an iModel. * @throws [[IModelError]] If the required provenance information is not available to detect deletes. */ public async detectRelationshipDeletes(): Promise { @@ -2866,23 +2866,19 @@ export class IModelTransformer extends IModelExportHandler { * are intending to process changes. Callers may wish to explicitly call initialize if they need to execute code after initialize but before [[process]] is called. * @note Called by all `process*` functions implicitly. * Overriders must call `super.initialize()` first - * @throws if [[IModelTransformOptions.isSynchronization]] is true and [[InitOptions]] is not provided. */ - public async initialize(args?: InitOptions): Promise { + public async initialize(): Promise { if (this._initialized) return; - if (args === undefined && this._options.isSynchronization) { - throw new Error( - "Must provide InitOptions when isSynchronization is set to true." - ); - } this.initScopeProvenance(); - await this._tryInitChangesetData(args); + await this._tryInitChangesetData(this._options.argsForProcessChanges); await this.context.initialize(); // need exporter initialized to do remapdeletedsourceentities. - await this.exporter.initialize(this.getExportInitOpts(args ?? {})); + await this.exporter.initialize( + this.getExportInitOpts(this._options.argsForProcessChanges ?? {}) + ); // Exporter must be initialized prior to processing changesets in order to properly handle entity recreations (an entity delete followed by an insert of that same entity). await this.processChangesets(); @@ -3239,8 +3235,8 @@ export class IModelTransformer extends IModelExportHandler { } /** - * The behavior of process is influenced by the value of [[IModelTransformOptions.isSynchronization]] passed to the constructor of the IModelTransformer. - * @section When isSynchronization is true: + * The behavior of process is influenced by [[IModelTransformOptions.argsForProcessChanges]] being defined or not defined during construction passed of the IModelTransformer. + * @section When argsForProcessChanges are defined: * * Export changes from the source iModel and import the transformed entities into the target iModel. * Inserts, updates, and deletes are determined by inspecting the changeset(s). @@ -3253,44 +3249,26 @@ export class IModelTransformer extends IModelExportHandler { * will automatically be determined and used * - To form a range of versions to process, set `startChangesetId` for the start (inclusive) of the desired range and open the source iModel as of the end (inclusive) of the desired range. * - * @section When isSynchronization is false: + * @section When argsForProcessChanges are undefined: * * Export everything from the source iModel and import the transformed entities into the target iModel. * * Notes: * - [[processSchemas]] is not called automatically since the target iModel may want a different collection of schemas. * - * @throws if [[IModelTransformOptions.isSynchronization]] is true and [[ProcessChangesOptions]] is not provided. - * @throws if [[IModelTransformOptions.isSynchronization]] is false / undefined and [[ProcessChangesOptions]] is provided. */ - public async process(options?: ProcessChangesOptions): Promise { - this.validateOptionsPassedToProcess(options); + public async process(): Promise { if (!this._initialized) { - await this.initialize(options); + await this.initialize(); } this.logSettings(); - // validateOptionsPassedToProcess throws if options is undefined and isSynchronization is true, so we can confidently assert options is defined. - return this._options.isSynchronization - ? this.processChanges(options!) + return this._options.argsForProcessChanges !== undefined + ? this.processChanges(this._options.argsForProcessChanges) : this.processAll(); } - private validateOptionsPassedToProcess(options?: ProcessChangesOptions) { - if (options === undefined && this._options.isSynchronization) { - throw new Error( - "ProcessChangesOptions must be provided when isSynchronization is set to true during construction of IModelTransformer." - ); - } - if (options && !this._options.isSynchronization) { - throw new Error( - "ProcessChangesOptions should not be provided when isSynchronization is set to false or not provided during construction of IModelTransformer." - ); - } - return; - } - /** Export everything from the source iModel and import the transformed entities into the target iModel. * @note [[processSchemas]] is not called automatically since the target iModel may want a different collection of schemas. */ @@ -3384,7 +3362,7 @@ export class IModelTransformer extends IModelExportHandler { * Call [[IModelTransformer.initialize]] for initialization of synchronization provenance data */ private getExportInitOpts(opts: InitOptions): ExporterInitOptions { - if (!this._options.isSynchronization) return {}; + if (!this._options.argsForProcessChanges) return {}; return { skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements, diff --git a/packages/transformer/src/test/IModelTransformerUtils.ts b/packages/transformer/src/test/IModelTransformerUtils.ts index 1806b153..6ea68ca0 100644 --- a/packages/transformer/src/test/IModelTransformerUtils.ts +++ b/packages/transformer/src/test/IModelTransformerUtils.ts @@ -94,6 +94,7 @@ import { IModelImporter, IModelImportOptions } from "../IModelImporter"; import { IModelTransformer, IModelTransformOptions, + ProcessChangesOptions, RelationshipPropsForDelete, } from "../IModelTransformer"; import { KnownTestLocations } from "./TestUtils/KnownTestLocations"; @@ -1540,10 +1541,10 @@ export class PhysicalModelConsolidator extends IModelTransformer { sourceDb: IModelDb, targetDb: IModelDb, targetModelId: Id64String, - isSynchronization?: boolean + argsForProcessChanges?: ProcessChangesOptions ) { super(sourceDb, targetDb, { - isSynchronization: isSynchronization ? true : false, + argsForProcessChanges, }); this._targetModelId = targetModelId; this.importer.doNotUpdateElementIds.add(targetModelId); diff --git a/packages/transformer/src/test/TestUtils/TimelineTestUtil.ts b/packages/transformer/src/test/TestUtils/TimelineTestUtil.ts index aa7863c6..57d654bf 100644 --- a/packages/transformer/src/test/TestUtils/TimelineTestUtil.ts +++ b/packages/transformer/src/test/TestUtils/TimelineTestUtil.ts @@ -515,18 +515,14 @@ export async function runTimeline( const syncer = new IModelTransformer(source.db, target.db, { ...transformerOpts, isReverseSynchronization: !isForwardSync, - isSynchronization: true, + argsForProcessChanges: { + accessToken, + startChangeset: startIndex ? { index: startIndex } : undefined, + }, }); initTransformer?.(syncer); try { - await syncer.initialize({ - accessToken, - startChangeset: startIndex ? { index: startIndex } : undefined, - }); - await syncer.process({ - accessToken, - startChangeset: startIndex ? { index: startIndex } : undefined, - }); + await syncer.process(); expect( expectThrow === false || expectThrow === undefined, "expectThrow was set to true and transformer succeeded." diff --git a/packages/transformer/src/test/standalone/IModelTransformerHub.test.ts b/packages/transformer/src/test/standalone/IModelTransformerHub.test.ts index 73f439be..91a9bdac 100644 --- a/packages/transformer/src/test/standalone/IModelTransformerHub.test.ts +++ b/packages/transformer/src/test/standalone/IModelTransformerHub.test.ts @@ -78,6 +78,7 @@ import { IModelExporter, IModelImporter, IModelTransformer, + ProcessChangesOptions, TransformerLoggerCategory, } from "../../transformer"; import { @@ -258,13 +259,13 @@ describe("IModelTransformerHub", () => { assert.equal(sourceDbChanges.relationship.deleteIds.size, 0); const transformer = new TestIModelTransformer(sourceDb, targetDb, { - isSynchronization: true, + argsForProcessChanges: { + accessToken, + startChangeset: { id: sourceDb.changeset.id }, + }, }); transformer["_allowNoScopingESA"] = true; - await transformer.process({ - accessToken, - startChangeset: { id: sourceDb.changeset.id }, - }); + await transformer.process(); transformer.dispose(); targetDb.saveChanges(); await targetDb.pushChanges({ accessToken, description: "Import #1" }); @@ -338,9 +339,9 @@ describe("IModelTransformerHub", () => { const transformer = new TestIModelTransformer( sourceDb, targetImporter, - { isSynchronization: true } + { argsForProcessChanges: { accessToken } } ); - await transformer.process({ accessToken }); + await transformer.process(); assert.equal(targetImporter.numModelsInserted, 0); assert.equal(targetImporter.numModelsUpdated, 0); assert.equal(targetImporter.numElementsInserted, 0); @@ -421,9 +422,9 @@ describe("IModelTransformerHub", () => { assert.equal(sourceDbChanges.aspect.deleteIds.size, 0); const transformer = new TestIModelTransformer(sourceDb, targetDb, { - isSynchronization: true, + argsForProcessChanges: { accessToken }, }); - await transformer.process({ accessToken }); + await transformer.process(); transformer.dispose(); targetDb.saveChanges(); await targetDb.pushChanges({ accessToken, description: "Import #2" }); @@ -673,12 +674,12 @@ describe("IModelTransformerHub", () => { sourceDb, targetDb, targetModelId, - true + { + accessToken, + startChangeset: sourceDb.changeset, + } ); - await transformer.process({ - accessToken, - startChangeset: sourceDb.changeset, - }); + await transformer.process(); transformer.dispose(); const sql = `SELECT ECInstanceId, Model.Id FROM ${PhysicalObject.classFullName}`; @@ -1589,9 +1590,11 @@ describe("IModelTransformerHub", () => { iModelId: master.id, asOf: IModelVersion.first().toJSON(), }); - const makeReplayTransformer = (isSynchronization: boolean) => { + const makeReplayTransformer = ( + argsForProcessChanges?: ProcessChangesOptions + ) => { const result = new IModelTransformer(sourceDb, replayedDb, { - isSynchronization, + argsForProcessChanges, }); // this replay strategy pretends that deleted elements never existed for (const elementId of masterDeletedElementIds) { @@ -1601,21 +1604,21 @@ describe("IModelTransformerHub", () => { }; // NOTE: this test knows that there were no schema changes, so does not call `processSchemas` - const replayInitTransformer = makeReplayTransformer(false); + const replayInitTransformer = makeReplayTransformer(); await replayInitTransformer.process(); // process any elements that were part of the "seed" replayInitTransformer.dispose(); await saveAndPushChanges(replayedDb, "changes from source seed"); for (const masterDbChangeset of masterDbChangesets) { - const replayTransformer = makeReplayTransformer(true); await sourceDb.pullChanges({ accessToken, toIndex: masterDbChangeset.index, }); - await replayTransformer.process({ + const replayTransformer = makeReplayTransformer({ accessToken, startChangeset: sourceDb.changeset, }); + await replayTransformer.process(); await saveAndPushChanges( replayedDb, masterDbChangeset.description ?? "" @@ -1802,9 +1805,9 @@ describe("IModelTransformerHub", () => { const synchronizer = new IModelTransformerInjected( sourceDb, new IModelImporterInjected(targetDb), - { isSynchronization: true } + { argsForProcessChanges: { accessToken } } ); - await synchronizer.process({ accessToken }); + await synchronizer.process(); expect(didExportModelSelector).to.be.true; expect(didImportModelSelector).to.be.true; synchronizer.dispose(); @@ -1904,12 +1907,12 @@ describe("IModelTransformerHub", () => { }); transformer = new IModelTransformer(sourceDb, targetDb, { - isSynchronization: true, - }); - await transformer.process({ - accessToken, - startChangeset: { id: sourceDb.changeset.id }, + argsForProcessChanges: { + accessToken, + startChangeset: { id: sourceDb.changeset.id }, + }, }); + await transformer.process(); const elementCodeValueMap = new Map(); targetDb.withStatement( @@ -2062,9 +2065,9 @@ describe("IModelTransformerHub", () => { // Reverse Sync to add a pendingsyncchangesetindex transformer = new IModelTransformer(targetDb, sourceDb, { - isSynchronization: true, + argsForProcessChanges: { accessToken }, }); - await transformer.process({ accessToken }); + await transformer.process(); let scopingEsa = transformer["_targetScopeProvenanceProps"]; expect( scopingEsa?.jsonProperties.pendingSyncChangesetIndices.length @@ -2090,9 +2093,9 @@ describe("IModelTransformerHub", () => { // Forward Sync. We expect 4 is still there because we didnt process it (as a result of our sourceDb not being at the tip) transformer = new IModelTransformer(sourceDbNotAtTip, targetDb, { - isSynchronization: true, + argsForProcessChanges: { accessToken }, }); - await transformer.process({ accessToken }); + await transformer.process(); scopingEsa = transformer["_targetScopeProvenanceProps"]; expect( scopingEsa?.jsonProperties.pendingSyncChangesetIndices @@ -2218,10 +2221,10 @@ describe("IModelTransformerHub", () => { // running reverse synchronization transformer = new IModelTransformer(targetDb, sourceDb, { isReverseSynchronization: true, - isSynchronization: true, + argsForProcessChanges: { accessToken }, }); - await transformer.process({ accessToken }); + await transformer.process(); transformer.dispose(); expect(count(sourceDb, PhysicalObject.classFullName)).to.equal(7); @@ -2519,9 +2522,9 @@ describe("IModelTransformerHub", () => { const synchronizer = new IModelTransformer(branchDb, masterDb, { // NOTE: not using a targetScopeElementId because this test deals with temporary dbs, but that is a bad practice, use one isReverseSynchronization: true, - isSynchronization: true, + argsForProcessChanges: { accessToken }, }); - await synchronizer.process({ accessToken }); + await synchronizer.process(); branchDb.saveChanges(); await branchDb.pushChanges({ accessToken, description: "synchronize" }); synchronizer.dispose(); @@ -2607,13 +2610,13 @@ describe("IModelTransformerHub", () => { const syncer = new IModelTransformer(branchAt2, master.db, { isReverseSynchronization: true, - isSynchronization: true, + argsForProcessChanges: { + accessToken, + startChangeset: branchAt2Changeset, + }, }); const queryChangeset = sinon.spy(HubMock, "queryChangeset"); - await syncer.process({ - accessToken, - startChangeset: branchAt2Changeset, - }); + await syncer.process(); expect( queryChangeset.alwaysCalledWith({ accessToken, @@ -2704,9 +2707,9 @@ describe("IModelTransformerHub", () => { transformer = new IModelTransformer(targetDb, sourceDb, { isReverseSynchronization: true, - isSynchronization: true, + argsForProcessChanges: { startChangeset: targetDb.changeset }, }); - await transformer.process({ startChangeset: targetDb.changeset }); + await transformer.process(); sourceDb.saveChanges(); await sourceDb.pushChanges({ description: "change processing transformation", @@ -2856,9 +2859,9 @@ describe("IModelTransformerHub", () => { }); transformer = new IModelTransformer(sourceDb, targetDb, { - isSynchronization: true, + argsForProcessChanges: { startChangeset: sourceDb.changeset }, }); - await transformer.process({ startChangeset: sourceDb.changeset }); + await transformer.process(); targetDb.saveChanges(); await targetDb.pushChanges({ description: "change processing transformation", @@ -2920,9 +2923,9 @@ describe("IModelTransformerHub", () => { }); transformer = new IModelTransformer(sourceDb, targetDb, { - isSynchronization: true, + argsForProcessChanges: { startChangeset }, }); - await transformer.process({ startChangeset }); + await transformer.process(); targetDb.saveChanges(); await targetDb.pushChanges({ description: "transformation" }); @@ -3042,9 +3045,9 @@ describe("IModelTransformerHub", () => { }); transformer = new IModelTransformer(sourceDb, targetDb, { - isSynchronization: true, + argsForProcessChanges: { startChangeset: sourceDb.changeset }, }); - await transformer.process({ startChangeset: sourceDb.changeset }); + await transformer.process(); targetDb.saveChanges(); await targetDb.pushChanges({ description: "change processing transformation", @@ -3131,12 +3134,12 @@ describe("IModelTransformerHub", () => { ); const transformer = new IModelTransformer(exporter, targetDb, { includeSourceProvenance: true, - isSynchronization: true, + argsForProcessChanges: { accessToken }, }); transformer["_allowNoScopingESA"] = true; // run first transformation - await transformer.process({ accessToken }); + await transformer.process(); await saveAndPushChanges(targetDb, "First transformation"); const addedAspectProps: ExternalSourceAspectProps = { @@ -3153,10 +3156,14 @@ describe("IModelTransformerHub", () => { await saveAndPushChanges(sourceDb, "Update source"); - await transformer.process({ - accessToken, - startChangeset: sourceDb.changeset, + const transformer2 = new IModelTransformer(exporter, targetDb, { + includeSourceProvenance: true, + argsForProcessChanges: { + accessToken, + startChangeset: sourceDb.changeset, + }, }); + await transformer2.process(); await saveAndPushChanges(targetDb, "Second transformation"); const targetElementIds = targetDb.queryEntityIds({