Skip to content

Releases: iTwin/itwinjs-core

v5.9.2

08 May 21:18

Choose a tag to compare

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

05 May 18:24

Choose a tag to compare

Release notes

Changes

Full changelog: 5.9.0...5.9.1

v5.9.0

04 May 17:13

Choose a tag to compare

5.9.0 Change Notes

@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) removal

This 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, 0x32

Output 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, withQueryReader provides synchronous row-by-row access to query results
  • Consistent API across databases: The same interface is available on both ECDb and IModelDb instances
  • 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 DefinitionElement usage checks inline — no need to call deleteDefinitionElements separately.
  • 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 is externally 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...

Read more

v5.8.4

23 Apr 18:48

Choose a tag to compare

Release notes

Changes

  • @bentley/imodeljs-native 5.8.31

Full changelog: 5.8.3...5.8.4

v5.8.3

23 Apr 15:42

Choose a tag to compare

Release notes

Changes

Full changelog: 5.8.2...5.8.3

v5.8.2

16 Apr 15:06

Choose a tag to compare

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

10 Apr 13:45

Choose a tag to compare

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 under NestedContentField (backport #9188) [release/5.8.x] (#9189)

Full changelog: 5.8.0...5.8.1

v5.8.0

02 Apr 19:56

Choose a tag to compare

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, withQueryReader provides synchronous row-by-row access to query results
  • Consistent API across databases: The same interface is available on both ECDb and IModelDb instances
  • 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() and getSettings() for accessing settings stored in a dedicated database
  • EditableSettingsDb: Write interface with updateSetting(), removeSetting(), and updateSettings() 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 containerId and 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 EditableSettingsCloudContainer cannot accidentally add or retrieve WorkspaceDbs 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

24 Mar 15:38

Choose a tag to compare

Release notes

Changes

  • Security Fix: Remove unnecessary fast-xml-parser override (backport #9095) [release/5.7.x] (#9096)
  • Revert default engineering length metric formatting to meters (backport #9120) [release/5.7.x] (#9121)

Full changelog: 5.7.2...5.7.3

v5.7.2

12 Mar 14:52

Choose a tag to compare

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