@itwin/imodel-transformer 1.0.0-dev.13 → 1.0.0-dev.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cjs/IModelExporter.d.ts +1 -2
- package/lib/cjs/IModelExporter.d.ts.map +1 -1
- package/lib/cjs/IModelExporter.js +0 -5
- package/lib/cjs/IModelExporter.js.map +1 -1
- package/lib/cjs/IModelImporter.d.ts +2 -2
- package/lib/cjs/IModelImporter.js +2 -2
- package/lib/cjs/IModelImporter.js.map +1 -1
- package/lib/cjs/IModelTransformer.d.ts +73 -53
- package/lib/cjs/IModelTransformer.d.ts.map +1 -1
- package/lib/cjs/IModelTransformer.js +68 -42
- package/lib/cjs/IModelTransformer.js.map +1 -1
- package/package.json +1 -1
|
@@ -172,7 +172,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
172
172
|
if (this._isProvenanceInitTransform) {
|
|
173
173
|
return "forward";
|
|
174
174
|
}
|
|
175
|
-
if (!this.
|
|
175
|
+
if (!this._options.argsForProcessChanges) {
|
|
176
176
|
return "not-sync";
|
|
177
177
|
}
|
|
178
178
|
try {
|
|
@@ -224,7 +224,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
224
224
|
this._elementsWithExplicitlyTrackedProvenance = new Set();
|
|
225
225
|
/** map of partially committed entities to their partial commit progress */
|
|
226
226
|
this._partiallyCommittedEntities = new EntityMap_1.EntityMap();
|
|
227
|
-
this._isSynchronization = false;
|
|
228
227
|
/**
|
|
229
228
|
* A private variable meant to be set by tests which have an outdated way of setting up transforms. In all synchronizations today we expect to find an ESA in the branch db which describes the master -> branch relationship.
|
|
230
229
|
* The exception to this is the first transform aka the provenance initializing transform which requires that the master imodel and the branch imodel are identical at the time of provenance initialization.
|
|
@@ -278,6 +277,10 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
278
277
|
branchRelationshipDataBehavior: options?.branchRelationshipDataBehavior ?? "reject",
|
|
279
278
|
skipPropagateChangesToRootElements: options?.skipPropagateChangesToRootElements ?? true,
|
|
280
279
|
};
|
|
280
|
+
// check if authorization client is defined
|
|
281
|
+
if (core_backend_1.IModelHost.authorizationClient === undefined) {
|
|
282
|
+
core_bentley_1.Logger.logWarning(loggerCategory, "Authorization client is not set in IModelHost. If the transformer needs an accessToken, then it will fail.");
|
|
283
|
+
}
|
|
281
284
|
this._isProvenanceInitTransform = this._options
|
|
282
285
|
.wasSourceIModelCopiedToTarget
|
|
283
286
|
? true
|
|
@@ -361,9 +364,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
361
364
|
core_bentley_1.Logger.logInfo(loggerCategory, `this._includeSourceProvenance=${this._options.includeSourceProvenance}`);
|
|
362
365
|
core_bentley_1.Logger.logInfo(loggerCategory, `this._cloneUsingBinaryGeometry=${this._options.cloneUsingBinaryGeometry}`);
|
|
363
366
|
core_bentley_1.Logger.logInfo(loggerCategory, `this._wasSourceIModelCopiedToTarget=${this._options.wasSourceIModelCopiedToTarget}`);
|
|
364
|
-
core_bentley_1.Logger.logInfo(loggerCategory,
|
|
365
|
-
// eslint-disable-next-line deprecation/deprecation
|
|
366
|
-
`this._isReverseSynchronization=${this._options.isReverseSynchronization}`);
|
|
367
367
|
core_bentley_1.Logger.logInfo(TransformerLoggerCategory_1.TransformerLoggerCategory.IModelImporter, `this.importer.autoExtendProjectExtents=${JSON.stringify(this.importer.options.autoExtendProjectExtents)}`);
|
|
368
368
|
core_bentley_1.Logger.logInfo(TransformerLoggerCategory_1.TransformerLoggerCategory.IModelImporter, `this.importer.simplifyElementGeometry=${this.importer.options.simplifyElementGeometry}`);
|
|
369
369
|
}
|
|
@@ -606,8 +606,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
606
606
|
let madeChange = false;
|
|
607
607
|
if (this._options.branchRelationshipDataBehavior !== "unsafe-migrate")
|
|
608
608
|
return madeChange;
|
|
609
|
-
const fallbackSyncVersionToUse = this._options.unsafeFallbackSyncVersion ?? "";
|
|
610
|
-
const fallbackReverseSyncVersionToUse = this._options.unsafeFallbackReverseSyncVersion ??
|
|
609
|
+
const fallbackSyncVersionToUse = this._options.argsForProcessChanges?.unsafeFallbackSyncVersion ?? "";
|
|
610
|
+
const fallbackReverseSyncVersionToUse = this._options.argsForProcessChanges?.unsafeFallbackReverseSyncVersion ??
|
|
611
|
+
"";
|
|
611
612
|
if (aspectProps.version === undefined ||
|
|
612
613
|
(aspectProps.version === "" &&
|
|
613
614
|
aspectProps.version !== fallbackSyncVersionToUse)) {
|
|
@@ -867,7 +868,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
867
868
|
}
|
|
868
869
|
/** Returns `true` if *brute force* delete detections should be run.
|
|
869
870
|
* @note This is only called if [[IModelTransformOptions.forceExternalSourceAspectProvenance]] option is true
|
|
870
|
-
* @note Not relevant for
|
|
871
|
+
* @note Not relevant for [[process]] when [[IModelTransformOptions.argsForProcessChanges]] are provided and change history is known.
|
|
871
872
|
*/
|
|
872
873
|
shouldDetectDeletes() {
|
|
873
874
|
nodeAssert(this._syncType !== undefined);
|
|
@@ -877,9 +878,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
877
878
|
* Detect Element deletes using ExternalSourceAspects in the target iModel and a *brute force* comparison against Elements
|
|
878
879
|
* in the source iModel.
|
|
879
880
|
* @deprecated in 1.x. Do not use this. // FIXME<MIKE>: how to better explain this?
|
|
880
|
-
* This method is only called during [[
|
|
881
|
+
* This method is only called during [[process]] when [[IModelTransformOptions.argsForProcessChanges]] is undefined and the option
|
|
881
882
|
* [[IModelTransformOptions.forceExternalSourceAspectProvenance]] is enabled. It is not
|
|
882
|
-
* necessary when
|
|
883
|
+
* necessary when calling [[process]] with [[IModelTransformOptions.argsForProcessChanges]] defined, since changeset information is sufficient.
|
|
883
884
|
* @note you do not need to call this directly unless processing a subset of an iModel.
|
|
884
885
|
* @throws [[IModelError]] If the required provenance information is not available to detect deletes.
|
|
885
886
|
*/
|
|
@@ -1037,7 +1038,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1037
1038
|
}
|
|
1038
1039
|
/** Cause the specified Element and its child Elements (if applicable) to be exported from the source iModel and imported into the target iModel.
|
|
1039
1040
|
* @param sourceElementId Identifies the Element from the source iModel to import.
|
|
1040
|
-
* @note This method is called from [[
|
|
1041
|
+
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1041
1042
|
*/
|
|
1042
1043
|
async processElement(sourceElementId) {
|
|
1043
1044
|
await this.initialize();
|
|
@@ -1048,7 +1049,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1048
1049
|
}
|
|
1049
1050
|
/** Import child elements into the target IModelDb
|
|
1050
1051
|
* @param sourceElementId Import the child elements of this element in the source IModelDb.
|
|
1051
|
-
* @note This method is called from [[
|
|
1052
|
+
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1052
1053
|
*/
|
|
1053
1054
|
async processChildElements(sourceElementId) {
|
|
1054
1055
|
await this.initialize();
|
|
@@ -1333,7 +1334,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1333
1334
|
}
|
|
1334
1335
|
/** Cause the model container, contents, and sub-models to be exported from the source iModel and imported into the target iModel.
|
|
1335
1336
|
* @param sourceModeledElementId Import this [Model]($backend) from the source IModelDb.
|
|
1336
|
-
* @note This method is called from [[
|
|
1337
|
+
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1337
1338
|
*/
|
|
1338
1339
|
async processModel(sourceModeledElementId) {
|
|
1339
1340
|
await this.initialize();
|
|
@@ -1343,7 +1344,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1343
1344
|
* @param sourceModelId Import the contents of this model from the source IModelDb.
|
|
1344
1345
|
* @param targetModelId Import into this model in the target IModelDb. The target model must exist prior to this call.
|
|
1345
1346
|
* @param elementClassFullName Optional classFullName of an element subclass to limit import query against the source model.
|
|
1346
|
-
* @note This method is called from [[
|
|
1347
|
+
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1347
1348
|
*/
|
|
1348
1349
|
async processModelContents(sourceModelId, targetModelId, elementClassFullName = core_backend_1.Element.classFullName) {
|
|
1349
1350
|
await this.initialize();
|
|
@@ -1409,9 +1410,9 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1409
1410
|
* source's changeset has been performed. Also stores all changesets that occurred
|
|
1410
1411
|
* during the transformation as "pending synchronization changeset indices" @see TargetScopeProvenanceJsonProps
|
|
1411
1412
|
*
|
|
1412
|
-
* You generally should not call this function yourself and use [[
|
|
1413
|
+
* You generally should not call this function yourself and use [[process]] with [[IModelTransformOptions.argsForProcessChanges]] provided instead.
|
|
1413
1414
|
* It is public for unsupported use cases of custom synchronization transforms.
|
|
1414
|
-
* @note if
|
|
1415
|
+
* @note if [[IModelTransformOptions.argsForProcessChanges]] are not defined in this transformation, this will fail
|
|
1415
1416
|
* without setting the `force` option to `true`
|
|
1416
1417
|
*/
|
|
1417
1418
|
updateSynchronizationVersion({ force = false } = {}) {
|
|
@@ -1438,7 +1439,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1438
1439
|
core_bentley_1.Logger.logInfo(loggerCategory, `updating sync version from ${this._targetScopeProvenanceProps.version} to ${sourceVersion}`);
|
|
1439
1440
|
this._targetScopeProvenanceProps.version = sourceVersion;
|
|
1440
1441
|
}
|
|
1441
|
-
if (this.
|
|
1442
|
+
if (this._options.argsForProcessChanges ||
|
|
1442
1443
|
(this._startingChangesetIndices && this._isProvenanceInitTransform)) {
|
|
1443
1444
|
nodeAssert(this.targetDb.changeset.index !== undefined &&
|
|
1444
1445
|
this._startingChangesetIndices !== undefined, "updateSynchronizationVersion was called without change history");
|
|
@@ -1515,7 +1516,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1515
1516
|
}
|
|
1516
1517
|
/** Imports all relationships that subclass from the specified base class.
|
|
1517
1518
|
* @param baseRelClassFullName The specified base relationship class.
|
|
1518
|
-
* @note This method is called from [[
|
|
1519
|
+
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1519
1520
|
*/
|
|
1520
1521
|
async processRelationships(baseRelClassFullName) {
|
|
1521
1522
|
await this.initialize();
|
|
@@ -1588,8 +1589,8 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1588
1589
|
}
|
|
1589
1590
|
/** Detect Relationship deletes using ExternalSourceAspects in the target iModel and a *brute force* comparison against relationships in the source iModel.
|
|
1590
1591
|
* @deprecated in 1.x. Don't use this anymore
|
|
1591
|
-
* @see
|
|
1592
|
-
* @note This method is called from [[
|
|
1592
|
+
* @see [[process]] with [[IModelTransformOptions.argsForProcessChanges]] provided.
|
|
1593
|
+
* @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.
|
|
1593
1594
|
* @throws [[IModelError]] If the required provenance information is not available to detect deletes.
|
|
1594
1595
|
*/
|
|
1595
1596
|
async detectRelationshipDeletes() {
|
|
@@ -1771,7 +1772,7 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1771
1772
|
}
|
|
1772
1773
|
}
|
|
1773
1774
|
/** Cause all fonts to be exported from the source iModel and imported into the target iModel.
|
|
1774
|
-
* @note This method is called from [[
|
|
1775
|
+
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1775
1776
|
*/
|
|
1776
1777
|
async processFonts() {
|
|
1777
1778
|
// we do not need to initialize for this since no entities are exported
|
|
@@ -1783,14 +1784,14 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1783
1784
|
this.context.importFont(font.id);
|
|
1784
1785
|
}
|
|
1785
1786
|
/** Cause all CodeSpecs to be exported from the source iModel and imported into the target iModel.
|
|
1786
|
-
* @note This method is called from [[
|
|
1787
|
+
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1787
1788
|
*/
|
|
1788
1789
|
async processCodeSpecs() {
|
|
1789
1790
|
await this.initialize();
|
|
1790
1791
|
return this.exporter.exportCodeSpecs();
|
|
1791
1792
|
}
|
|
1792
1793
|
/** Cause a single CodeSpec to be exported from the source iModel and imported into the target iModel.
|
|
1793
|
-
* @note This method is called from [[
|
|
1794
|
+
* @note This method is called from [[process]], so it only needs to be called directly when processing a subset of an iModel.
|
|
1794
1795
|
*/
|
|
1795
1796
|
async processCodeSpec(codeSpecName) {
|
|
1796
1797
|
await this.initialize();
|
|
@@ -1818,17 +1819,18 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
1818
1819
|
}
|
|
1819
1820
|
/**
|
|
1820
1821
|
* Initialize prerequisites of processing, you must initialize with an [[InitOptions]] if you
|
|
1821
|
-
* are intending to process changes
|
|
1822
|
+
* 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.
|
|
1822
1823
|
* @note Called by all `process*` functions implicitly.
|
|
1823
1824
|
* Overriders must call `super.initialize()` first
|
|
1824
1825
|
*/
|
|
1825
|
-
async initialize(
|
|
1826
|
+
async initialize() {
|
|
1826
1827
|
if (this._initialized)
|
|
1827
1828
|
return;
|
|
1828
|
-
|
|
1829
|
+
this.initScopeProvenance();
|
|
1830
|
+
await this._tryInitChangesetData(this._options.argsForProcessChanges);
|
|
1829
1831
|
await this.context.initialize();
|
|
1830
1832
|
// need exporter initialized to do remapdeletedsourceentities.
|
|
1831
|
-
await this.exporter.initialize(this.getExportInitOpts(
|
|
1833
|
+
await this.exporter.initialize(this.getExportInitOpts(this._options.argsForProcessChanges ?? {}));
|
|
1832
1834
|
// 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).
|
|
1833
1835
|
await this.processChangesets();
|
|
1834
1836
|
this._initialized = true;
|
|
@@ -2037,10 +2039,11 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2037
2039
|
this._csFileProps = [];
|
|
2038
2040
|
return;
|
|
2039
2041
|
}
|
|
2042
|
+
const startChangeset = "startChangeset" in args ? args.startChangeset : undefined;
|
|
2040
2043
|
// NOTE: that we do NOT download the changesummary for the last transformed version, we want
|
|
2041
2044
|
// to ignore those already processed changes
|
|
2042
|
-
const startChangesetIndexOrId =
|
|
2043
|
-
|
|
2045
|
+
const startChangesetIndexOrId = startChangeset?.index ??
|
|
2046
|
+
startChangeset?.id ??
|
|
2044
2047
|
this.synchronizationVersion.index + 1;
|
|
2045
2048
|
const endChangesetId = this.sourceDb.changeset.id;
|
|
2046
2049
|
const [startChangesetIndex, endChangesetIndex] = await Promise.all([startChangesetIndexOrId, endChangesetId].map(async (indexOrId) => typeof indexOrId === "number"
|
|
@@ -2050,11 +2053,11 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2050
2053
|
iModelId: this.sourceDb.iModelId,
|
|
2051
2054
|
// eslint-disable-next-line deprecation/deprecation
|
|
2052
2055
|
changeset: { id: indexOrId },
|
|
2053
|
-
accessToken: args.accessToken,
|
|
2054
2056
|
})
|
|
2055
2057
|
.then((changeset) => changeset.index)));
|
|
2056
2058
|
const missingChangesets = startChangesetIndex > this.synchronizationVersion.index + 1;
|
|
2057
|
-
if (!this._options.
|
|
2059
|
+
if (!this._options.argsForProcessChanges
|
|
2060
|
+
?.ignoreMissingChangesetsInSynchronizations &&
|
|
2058
2061
|
startChangesetIndex !== this.synchronizationVersion.index + 1 &&
|
|
2059
2062
|
this.synchronizationVersion.index !== -1) {
|
|
2060
2063
|
throw Error(`synchronization is ${missingChangesets ? "missing changesets" : ""},` +
|
|
@@ -2089,13 +2092,40 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2089
2092
|
this._sourceChangeDataState =
|
|
2090
2093
|
this._csFileProps.length === 0 ? "no-changes" : "has-changes";
|
|
2091
2094
|
}
|
|
2095
|
+
/**
|
|
2096
|
+
* The behavior of process is influenced by [[IModelTransformOptions.argsForProcessChanges]] being defined or not defined during construction passed of the IModelTransformer.
|
|
2097
|
+
* @section When argsForProcessChanges are defined:
|
|
2098
|
+
*
|
|
2099
|
+
* Export changes from the source iModel and import the transformed entities into the target iModel.
|
|
2100
|
+
* Inserts, updates, and deletes are determined by inspecting the changeset(s).
|
|
2101
|
+
*
|
|
2102
|
+
* Notes:
|
|
2103
|
+
* - the transformer assumes that you saveChanges after processing changes. You should not modify the iModel after processChanges until saveChanges,
|
|
2104
|
+
* failure to do so may result in corrupted
|
|
2105
|
+
* data loss in future branch operations
|
|
2106
|
+
* - if no startChangesetId or startChangeset option is provided as part of the ProcessChangesOptions, the next unsynchronized changeset
|
|
2107
|
+
* will automatically be determined and used
|
|
2108
|
+
* - 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.
|
|
2109
|
+
*
|
|
2110
|
+
* @section When argsForProcessChanges are undefined:
|
|
2111
|
+
*
|
|
2112
|
+
* Export everything from the source iModel and import the transformed entities into the target iModel.
|
|
2113
|
+
*
|
|
2114
|
+
* Notes:
|
|
2115
|
+
* - [[processSchemas]] is not called automatically since the target iModel may want a different collection of schemas.
|
|
2116
|
+
*
|
|
2117
|
+
*/
|
|
2118
|
+
async process() {
|
|
2119
|
+
await this.initialize();
|
|
2120
|
+
this.logSettings();
|
|
2121
|
+
return this._options.argsForProcessChanges !== undefined
|
|
2122
|
+
? this.processChanges(this._options.argsForProcessChanges)
|
|
2123
|
+
: this.processAll();
|
|
2124
|
+
}
|
|
2092
2125
|
/** Export everything from the source iModel and import the transformed entities into the target iModel.
|
|
2093
2126
|
* @note [[processSchemas]] is not called automatically since the target iModel may want a different collection of schemas.
|
|
2094
2127
|
*/
|
|
2095
2128
|
async processAll() {
|
|
2096
|
-
this.logSettings();
|
|
2097
|
-
this.initScopeProvenance();
|
|
2098
|
-
await this.initialize();
|
|
2099
2129
|
await this.exporter.exportCodeSpecs();
|
|
2100
2130
|
await this.exporter.exportFonts();
|
|
2101
2131
|
if (this._options.skipPropagateChangesToRootElements) {
|
|
@@ -2145,10 +2175,6 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2145
2175
|
* @note 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.
|
|
2146
2176
|
*/
|
|
2147
2177
|
async processChanges(options) {
|
|
2148
|
-
this._isSynchronization = true;
|
|
2149
|
-
this.initScopeProvenance();
|
|
2150
|
-
this.logSettings();
|
|
2151
|
-
await this.initialize(options);
|
|
2152
2178
|
// must wait for initialization of synchronization provenance data
|
|
2153
2179
|
await this.exporter.exportChanges(this.getExportInitOpts(options));
|
|
2154
2180
|
await this.processDeferredElements(); // eslint-disable-line deprecation/deprecation
|
|
@@ -2165,17 +2191,17 @@ class IModelTransformer extends IModelExporter_1.IModelExportHandler {
|
|
|
2165
2191
|
* Call [[IModelTransformer.initialize]] for initialization of synchronization provenance data
|
|
2166
2192
|
*/
|
|
2167
2193
|
getExportInitOpts(opts) {
|
|
2168
|
-
if (!this.
|
|
2194
|
+
if (!this._options.argsForProcessChanges)
|
|
2169
2195
|
return {};
|
|
2196
|
+
const startChangeset = "startChangeset" in opts ? opts.startChangeset : undefined;
|
|
2170
2197
|
return {
|
|
2171
2198
|
skipPropagateChangesToRootElements: this._options.skipPropagateChangesToRootElements,
|
|
2172
|
-
accessToken: opts.accessToken,
|
|
2173
2199
|
...(this._csFileProps
|
|
2174
2200
|
? { csFileProps: this._csFileProps }
|
|
2175
2201
|
: this._changesetRanges
|
|
2176
2202
|
? { changesetRanges: this._changesetRanges }
|
|
2177
|
-
:
|
|
2178
|
-
? { startChangeset
|
|
2203
|
+
: startChangeset
|
|
2204
|
+
? { startChangeset }
|
|
2179
2205
|
: {
|
|
2180
2206
|
startChangeset: {
|
|
2181
2207
|
index: this.synchronizationVersion.index + 1,
|