Releases: iTwin/itwinjs-core
v5.9.2
Release notes
Changes
- Make FormatSpecHandle read current specs on access (backport #9264) [release/5.9.x] (#9265)
- docs: remove duplicate QuantityFormatter migration sample (backport #9270) [release/5.9.x] (#9272)
- [semantic Rebase] Remove unnecessary fields from "$meta" in instance patches and added batching while writing instance patches to temp file (backport #9266) [release/5.9.x] (#9273)
- fix: stop deep-importing core-backend internals in linear-referencing (backport #9271) [release/5.9.x] (#9274)
Full changelog: 5.9.1...5.9.2
v5.9.1
Release notes
Changes
- Security: fix 4 high axios CVEs (GHSA-pmwg-cvhr-8vh7) (backport #9259) [release/5.9.x] (#9260)
- Fixing semantic rebase issues (backport #9215) [release/5.9.x] (#9256)
Full changelog: 5.9.0...5.9.1
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