Releases: iTwin/itwinjs-core
v5.9.0
5.9.0 Change Notes
- 5.9.0 Change Notes
- @itwin/core-bentley
- @itwin/core-backend
- @itwin/core-frontend
- Backend
- Quantity Formatting
@itwin/core-bentley
BeUnorderedEvent
BeUnorderedEvent<T> and BeUnorderedUiEvent<T> are new Set-backed event classes where listeners can safely add or remove themselves during event emission. Useful for patterns where many independent subscribers need to react to the same event without worrying about concurrent modification.
Closure-only unsubscription: Unlike BeEvent, BeUnorderedEvent does not expose has() or removeListener(). The only way to unsubscribe is to call the closure returned by addListener() or addOnce():
const remove = myEvent.addListener((args) => { /* ... */ });
// later:
remove(); // O(1) removalThis is intentional — it avoids a class of bugs where removeListener() silently fails to match due to inline closures or binding mismatches (e.g. event.addListener(() => this.onFoo()) followed by event.removeListener(() => this.onFoo()) removes nothing because the two arrow functions are different objects). Capturing the returned closure is a reliable unsubscription pattern and enables O(1) removal via Set.delete.
@itwin/core-backend
IdSet virtual table performance improvements
The IdSet virtual table now uses a sorted vector internally, enabling O(log n) point lookups (id = ?) and efficient single-pass IN filtering instead of full scans.
-- Point lookup — binary search, O(log n)
SELECT id FROM IdSet('[1,2,3,4,5]') WHERE id = 3
-- returns: 0x3
-- IN filter — single Filter call for all values
SELECT id FROM IdSet('[1,2,3,4,5,6,7,8,9,10]') WHERE id IN (3, 5, 7)
-- returns: 0x3, 0x5, 0x7
-- Sorted deduplication — unsorted input with duplicates
SELECT id FROM IdSet('[50,10,30,20,40,10]')
-- returns: 0xa, 0x14, 0x1e, 0x28, 0x32Output is always returned in ascending ID order and deduplicated, regardless of input order. This is a behavioral change — previously, output order was unspecified.
WithQueryReader API
A new withQueryReader method has been added to both ECDb and IModelDb, providing true row-by-row behavior for ECSQL queries with synchronous execution. This API introduces a new ECSqlSyncReader through the ECSqlRowExecutor and supports configuration via SynchronousQueryOptions.
Key Features:
- True row-by-row streaming: Unlike the existing async reader APIs,
withQueryReaderprovides synchronous row-by-row access to query results - Consistent API across databases: The same interface is available on both
ECDbandIModelDbinstances - Configurable behavior: Support for various query options through
SynchronousQueryOptions
Usage Examples:
// ECDb usage
db.withQueryReader("SELECT ECInstanceId, UserLabel FROM bis.Element LIMIT 100", (reader) => {
while (reader.step()) {
const row = reader.current;
console.log(`ID: ${row.id}, Label: ${row.userLabel}`);
}
});
// IModelDb usage with options
iModelDb.withQueryReader(
"SELECT ECInstanceId, CodeValue FROM bis.Element",
(reader) => {
while (reader.step()) {
const row = reader.current;
processElement(row);
}
}
);Migration from deprecated APIs:
This API serves as the recommended replacement for synchronous query scenarios previously handled by the deprecated ECSqlStatement for read-only operations:
// Before - using deprecated ECSqlStatement
db.withPreparedStatement(query, (stmt) => {
while (stmt.step() === DbResult.BE_SQLITE_ROW) {
const row = stmt.getRow();
processRow(row);
}
});
// Now - using withQueryReader
db.withQueryReader(query, (reader) => {
while (reader.step()) {
const row = reader.current;
processRow(row);
}
});CRS unit metadata and filtering
getAvailableCoordinateReferenceSystems now returns the linear unit used by each available coordinate reference system in its unit property. The same API also accepts an optional unit filter, letting applications narrow the returned CRS list by unit name without applying client-side filtering after the fact.
Unit filtering is case-insensitive. Use the new getAvailableCRSUnits helper to retrieve the canonical unit names recognized by the backend.
const units = getAvailableCRSUnits();
const usFootSystems = await getAvailableCoordinateReferenceSystems({
includeWorld: true,
unit: "ussurveyfoot",
});
for (const crs of usFootSystems) {
console.log(`${crs.name}: ${crs.unit}`);
}Bulk element deletion with `deleteElements`
EditTxn.deleteElements is a @beta API that efficiently deletes many elements in a single native operation when removing trees of elements, partitions, or mixes of ordinary and definition elements.
It is intended as the preferred replacement for EditTxn.deleteElement.
What it does that deleteElement does not:
- Automatically cascades into the full parent-child subtree of every requested element — you only need to pass root IDs.
- Cascades into sub-models: deleting a partition element also removes the entire sub-model and all elements inside it.
- Handles intra-set constraint violations without failing.
- Handles
DefinitionElementusage checks inline — no need to calldeleteDefinitionElementsseparately. - Returns a structured BulkDeleteElementsResult describing which IDs could not be deleted due to constraint violations (e.g. code-scope dependencies held by elements outside the delete set), rather than throwing.
- Better performance when deleting elements in bulk.
Return type — BulkDeleteElementsResult
The return value is a BulkDeleteElementsResult with three fields:
| Field | Description |
|---|---|
status |
BulkDeleteElementsStatus: Success, PartialSuccess, or DeletionFailed |
failedIds |
Id64Set of element IDs that could not be deleted |
sqlDeleteStatus |
Raw DbResult from the underlying SQL DELETE statement |
Basic usage:
const result: BulkDeleteElementsResult = txn.deleteElements([idA, idB, idC]);
if (result.status === BulkDeleteElementsStatus.Success) {
// All elements were deleted.
} else {
// Some or all elements could not be deleted — inspect result.failedIds.
}Performance option — skipFKConstraintValidations
Pass { skipFKConstraintValidations: true } via BulkDeleteElementsArgs to skip the pre-deletion On Delete No-Action foreign-key constraint validation pass. This can significantly improve throughput for very large deletions where you know the supplied IDs are self-consistent and free of external FK dependencies:
// Only safe when you are certain no element in the batch is referenced from outside the batch.
const result = txn.deleteElements(ids, { skipFKConstraintValidations: true });If any element in the batch
isexternally referenced the SQL DELETE will fail entirely (DeletionFailed) and all changes will need to be rolled back.
Bulk element deletion lifecycle callbacks
To avoid firing one notification per element (which dominated runtime for lar...
v5.8.4
v5.8.3
Release notes
Changes
- Security: fix critical CVE GHSA-xq3m-2v4x-88gg (protobufjs) (backport #9209) [release/5.8.x] (#9210)
- Security: fix 4 high @xmldom/xmldom CVEs (GHSA-2v35-w6hq-6mfw) (backport #9219) [release/5.8.x] (#9221)
- Fix race conditions interacting with Cloud SQLite jobs (backport #9207) [release/5.8.x] (#9225)
- @bentley/imodeljs-native 5.8.30
Full changelog: 5.8.2...5.8.3
v5.8.2
Release notes
Changes
- fix: IpcHandler error serialization not culling unserializable properties (backport #9197) [release/5.8.x] (#9203)
Full changelog: 5.8.1...5.8.2
v5.8.1
Release notes
Changes
- Security: fix high CVE GHSA-p9ff-h696-f583 (vite arbitrary file read) (backport #9174) [release/5.8.x] (#9178)
- Security: fix high/critical CVEs (GHSA-3p68-rc4w-qgx5 and 14 others) (backport #9184) [release/5.8.x] (#9185)
- Presentation: Fix content traverser (
createContentTraverser) creating invalid fields hierarchy when array properties are nested underNestedContentField(backport #9188) [release/5.8.x] (#9189)
Full changelog: 5.8.0...5.8.1
v5.8.0
5.8.0 Change Notes
@itwin/core-backend
WithQueryReader API
A new withQueryReader method has been added to both ECDb and IModelDb, providing true row-by-row behavior for ECSQL queries with synchronous execution. This API introduces a new ECSqlSyncReader through the ECSqlRowExecutor and supports configuration via SynchronousQueryOptions.
Key Features:
- True row-by-row streaming: Unlike the existing async reader APIs,
withQueryReaderprovides synchronous row-by-row access to query results - Consistent API across databases: The same interface is available on both
ECDbandIModelDbinstances - Configurable behavior: Support for various query options through
SynchronousQueryOptions
Usage Examples:
// ECDb usage
db.withQueryReader("SELECT ECInstanceId, UserLabel FROM bis.Element LIMIT 100", (reader) => {
while (reader.step()) {
const row = reader.current;
console.log(`ID: ${row.id}, Label: ${row.userLabel}`);
}
});
// IModelDb usage with options
iModelDb.withQueryReader(
"SELECT ECInstanceId, CodeValue FROM bis.Element",
(reader) => {
while (reader.step()) {
const row = reader.current;
processElement(row);
}
}
);Migration from deprecated APIs:
This API serves as the recommended replacement for synchronous query scenarios previously handled by the deprecated ECSqlStatement for read-only operations:
// Before - using deprecated ECSqlStatement
db.withPreparedStatement(query, (stmt) => {
while (stmt.step() === DbResult.BE_SQLITE_ROW) {
const row = stmt.getRow();
processRow(row);
}
});
// Now - using withQueryReader
db.withQueryReader(query, (reader) => {
while (reader.step()) {
const row = reader.current;
processRow(row);
}
});Dedicated SettingsDb for workspace settings
A new SettingsDb type has been added to the workspace system, providing a dedicated database for storing JSON settings as key-value pairs, separate from general-purpose WorkspaceDb resource storage.
Why SettingsDb?
Previously, settings and binary resources (fonts, textures, templates) were stored together in WorkspaceDb containers. This coupling created issues:
- Lookup: Finding which containers hold settings required opening each one
- Granularity: Settings updates required republishing entire containers with large binary resources
- Separation of concerns: Settings (JSON key-value) and resources (binary blobs) have different access patterns
New APIs
- SettingsDb: Read-only interface with
getSetting()andgetSettings()for accessing settings stored in a dedicated database - EditableSettingsDb: Write interface with
updateSetting(),removeSetting(), andupdateSettings()for modifying settings within a SettingsDb - SettingsEditor: Write interface for creating and managing SettingsDb containers
- Workspace.getSettingsDb: Method to open a SettingsDb from a previously-loaded container by its
containerIdand desired priority
Usage examples
Creating a local SettingsDb
[[include:SettingsDb.createLocal]]
See SettingsDb for full documentation.
Container type convention
SettingsDb containers use containerType: "settings" in their cloud metadata, enabling them to be discovered independently of any iModel.
Container separation and lock isolation
Settings containers are deliberately separate from workspace containers. Both extend the new CloudSqliteContainer base interface, but EditableSettingsCloudContainer does not extend WorkspaceContainer. This means:
- Independent write locks: Editing settings does not lock out workspace resource editors, and vice versa.
- Clean API surface: Settings containers do not inherit workspace-db read/write methods (
getWorkspaceDb,addWorkspaceDb, etc.), exposing only settings-specific operations. - Type safety: Code that receives an
EditableSettingsCloudContainercannot accidentally add or retrieveWorkspaceDbs from it.
Display
Fixes
- Fixed reality data geometry not being reprojected correctly when the reality data is in a different CRS than the iModel.
Electron 41 support
In addition to already supported Electron versions, iTwin.js now supports Electron 41.
Quantity Formatting
Reverted default metric engineering length in QuantityFormatter
The default metric engineering length format introduced in iTwin.js 5.7.0 has been reverted. Applications using QuantityFormatter with QuantityType.LengthEngineering will once again display metric engineering lengths in meters with 4 decimal places (e.g. 1000 m) rather than millimeters.
v5.7.3
v5.7.2
Release notes
Changes
- 5.6.3 Changelogs
- Bugfix/default civilunits length koq (backport #9071) [release/5.7.x] (#9072)
- Fix reality data not being reprojected correctly when its CRS is different than iModel (backport #9059) [release/5.7.x] (#9075)
- Security: fix high-severity CVE-2026-30951 (GHSA-6457-6jrx-69cr) in sequelize (backport #9080) [release/5.7.x] (#9081)
Full changelog: 5.7.1...5.7.2
v5.7.1
Release notes
Changes
- sparse wmts datasource fixes (backport #9024) [release/5.7.x] (#9053)
- @bentley/imodeljs-native 5.7.11
- @bentley/imodeljs-native 5.7.12
- @bentley/imodeljs-native 5.7.13
Full changelog: 5.7.0...5.7.1
v5.7.0
deltaDoc: true
version: '5.7.0'
5.7.0 Change Notes
- 5.7.0 Change Notes
Semantic rebase (beta)
A new useSemanticRebase option has been added to IModelHostConfiguration. When enabled, pullChanges can intelligently merge local and incoming changes that involve schema modifications — a scenario where traditional binary changeset merging produces incorrect results.
Instead of requiring an exclusive lock for schema changes, semantic rebase captures local changes as high-level representations (schema XML and instance patches), applies incoming changesets, then re-applies local changes against the updated schema. This allows schema changes to use shared locks, reducing lock contention.
Limitations:
- Incompatible schema changes on both sides may cause the rebase to be rejected. To minimize risk, push schema changes promptly and separately from data changes.
- Cannot be used alongside Schema Sync.
- Profile upgrades still require exclusive locks.
Electron 40 support
In addition to already supported Electron versions, iTwin.js now supports Electron 40.
Note: with Electron 40, Chromium no longer uses SwiftShader as an automatic fallback for WebGL. This may cause issues when Electron is run in an environment without a supported GPU. For more information: Using Chromium with SwiftShader.
Display
Batch category display changes
Viewport.changeCategoryDisplay now accepts an optional batchNotify parameter. When set to true, a single notification event is raised after all categories have been added or removed, rather than one event per category. This significantly improves performance when changing the visibility of a large number of categories at once.
// Before: each category triggers a separate event, causing poor performance for large sets
viewport.changeCategoryDisplay(categoryIds, true);
// After: a single batch event is raised after all categories are updated
viewport.changeCategoryDisplay(categoryIds, true, undefined, true);The default behavior (batchNotify = false) is unchanged, preserving backward compatibility.
Additionally, ObservableSet now provides addAll and deleteAll methods for batch mutations, along with corresponding onBatchAdded and onBatchDeleted events. CategorySelectorState exposes these via addCategoriesBatched and dropCategoriesBatched.
Quantity Formatting
Updated default engineering lengths in QuantityFormatter
For applications and tools using QuantityFormatter and QuantityType APIs, the default engineering length formatting, retrieved via QuantityType.LengthEngineering has been updated. Metric engineering lengths now use millimeters with 3 decimal places; imperial engineering lengths use feet with 2 decimal places.
Fix `Quantity.convertTo()` return type to reflect actual behavior
The Quantity.convertTo() method has always returned a valid Quantity object since its initial implementation. However, its TypeScript signature incorrectly indicated it could return undefined with the type Quantity | undefined. This has been corrected to return Quantity.
Quantity code that was defensively checking for undefined or using non-null assertions (!) can now be simplified. TypeScript will no longer warn about possible undefined values when calling this method.
Presentation
Reducing the number of properties that are loaded with content
The Descriptor class, which describes the content to be loaded, now has a fieldsSelector property that allows specifying which fields should be included or excluded in the content. This is useful for cases when only a subset of fields is needed, which can reduce the amount of data that needs to be loaded and processed.
Similarly, the backend's PresentationManager.getElementProperties method now accepts an optional fieldsSelector parameter, which allows clients to specify which properties should be included or excluded in the response.
Reducing the number of fields that are loaded with content can improve performance, especially for large datasets, by minimizing the amount of data that needs to be transferred and processed.
API Deprecations
`logException()`
| Deprecated | Replacement |
|---|---|
logException() |
Use logError() instead |
The @itwin/core-bentley logging method for logging exceptions logException() was redundant with logError() and it was often unclear as to when either should be used. logException() ended up just calling logError() but allowed for passing in error objects instead of a message.
A new logError() overload is now provided to maintain this functionality, logging errors passed in the same way as logException() but also providing the option to provide LoggingMetaData in addition to the error, which logException() did not support.
logException() will not be removed until the alloted deprecation window has passed, as per our breaking changes policy.