@itwin/core-backend 5.14.3 → 5.14.5

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/CHANGELOG.md CHANGED
@@ -1,6 +1,16 @@
1
1
  # Change Log - @itwin/core-backend
2
2
 
3
- This log was last generated on Mon, 12 Oct 2026 17:37:54 GMT and should not be manually modified.
3
+ This log was last generated on Tue, 27 Oct 2026 12:19:48 GMT and should not be manually modified.
4
+
5
+ ## 5.14.5
6
+ Tue, 27 Oct 2026 12:19:48 GMT
7
+
8
+ _Version update only_
9
+
10
+ ## 5.14.4
11
+ Fri, 23 Oct 2026 19:58:24 GMT
12
+
13
+ _Version update only_
4
14
 
5
15
  ## 5.14.3
6
16
  Mon, 12 Oct 2026 17:37:54 GMT
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=SchemaImportOrphanColumns.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SchemaImportOrphanColumns.test.d.ts","sourceRoot":"","sources":["../../../../src/test/hubaccess/SchemaImportOrphanColumns.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,142 @@
1
+ /*---------------------------------------------------------------------------------------------
2
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
3
+ * See LICENSE.md in the project root for license information.
4
+ *--------------------------------------------------------------------------------------------*/
5
+ import { DbResult } from "@itwin/core-bentley";
6
+ import * as chai from "chai";
7
+ import * as chaiAsPromised from "chai-as-promised";
8
+ import { HubWrappers, KnownTestLocations } from "..";
9
+ import { HubMock } from "../../internal/HubMock";
10
+ chai.use(chaiAsPromised);
11
+ /**
12
+ * Repro for Bug 2115705: "DbTable 'bis_ElementMultiAspect' has N non-virtual columns, but the physical table has M columns."
13
+ *
14
+ * No semantic rebase, schema sync or dynamic schema involved. A schema adds an ElementMultiAspect subclass wide enough to
15
+ * ALTER TABLE ADD shared columns to bis_ElementMultiAspect. discardChanges() then reverses the EcSchema txn (ec_* rows)
16
+ * but skips the Ddl txn (TxnManager::ApplyTxnChanges returns early for TxnType::Ddl), so the physical columns remain
17
+ * with no ec_Column rows. The next regular schema import used to fail the strict column-count check; it must now succeed.
18
+ *
19
+ * The dynamic-schema tests show that deleting properties/classes (allowed for dynamic schemas) is not a cause.
20
+ */
21
+ describe("Schema import discard leaves orphan physical columns (Bug 2115705)", function () {
22
+ this.timeout(90000);
23
+ const tableName = "bis_ElementMultiAspect";
24
+ const extraColumns = 6;
25
+ let briefcase;
26
+ before(() => HubMock.startup("SchemaImportOrphanColumns", KnownTestLocations.outputDir));
27
+ after(() => HubMock.shutdown());
28
+ afterEach(() => {
29
+ briefcase?.close();
30
+ briefcase = undefined;
31
+ });
32
+ const physicalColumns = (db) => db.withSqliteStatement(`PRAGMA table_info([${tableName}])`, (stmt) => {
33
+ const names = [];
34
+ while (stmt.step() === DbResult.BE_SQLITE_ROW)
35
+ names.push(stmt.getValue(1).getString());
36
+ return names;
37
+ });
38
+ const mappedNonVirtualColumnCount = (db) => db.withSqliteStatement(`SELECT COUNT(*) FROM ec_Column c JOIN ec_Table t ON t.Id = c.TableId WHERE t.Name = '${tableName}' AND c.IsVirtual = 0`, (stmt) => {
39
+ chai.expect(stmt.step()).to.equal(DbResult.BE_SQLITE_ROW);
40
+ return stmt.getValue(0).getInteger();
41
+ });
42
+ const schemaWithWideMultiAspect = (propCount) => `<?xml version="1.0" encoding="UTF-8"?>
43
+ <ECSchema schemaName="WideAspect" alias="wa" version="01.00.00" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2">
44
+ <ECSchemaReference name="BisCore" version="01.00.00" alias="bis"/>
45
+ <ECEntityClass typeName="WideMultiAspect">
46
+ <BaseClass>bis:ElementMultiAspect</BaseClass>
47
+ ${Array.from({ length: propCount }, (_, i) => `<ECProperty propertyName="P${i + 1}" typeName="string"/>`).join("\n ")}
48
+ </ECEntityClass>
49
+ </ECSchema>`;
50
+ const unrelatedSchema = `<?xml version="1.0" encoding="UTF-8"?>
51
+ <ECSchema schemaName="Unrelated" alias="unr" version="01.00.00" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2">
52
+ <ECSchemaReference name="BisCore" version="01.00.00" alias="bis"/>
53
+ <ECEntityClass typeName="Thing">
54
+ <BaseClass>bis:GraphicalElement2d</BaseClass>
55
+ <ECProperty propertyName="Name" typeName="string"/>
56
+ </ECEntityClass>
57
+ </ECSchema>`;
58
+ const dynamicSchema = (version, propCount) => `<?xml version="1.0" encoding="UTF-8"?>
59
+ <ECSchema schemaName="DynTest" alias="dyn" version="${version}" xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2">
60
+ <ECSchemaReference name="BisCore" version="01.00.00" alias="bis"/>
61
+ <ECSchemaReference name="CoreCustomAttributes" version="01.00.03" alias="CoreCA"/>
62
+ <ECCustomAttributes>
63
+ <DynamicSchema xmlns="CoreCustomAttributes.01.00.03"/>
64
+ </ECCustomAttributes>
65
+ ${propCount === undefined ? "" : `<ECEntityClass typeName="WideMultiAspect">
66
+ <BaseClass>bis:ElementMultiAspect</BaseClass>
67
+ ${Array.from({ length: propCount }, (_, i) => `<ECProperty propertyName="P${i + 1}" typeName="string"/>`).join("\n ")}
68
+ </ECEntityClass>`}
69
+ </ECSchema>`;
70
+ it("deleting dynamic-schema properties/classes keeps ec_Column and physical columns in sync", async () => {
71
+ const accessToken = "user1";
72
+ const iModelId = await HubMock.createNewIModel({ accessToken, iTwinId: HubMock.iTwinId, iModelName: "DynDelete" });
73
+ briefcase = await HubWrappers.downloadAndOpenBriefcase({ accessToken, iTwinId: HubMock.iTwinId, iModelId });
74
+ const db = briefcase;
75
+ const assertInSync = (step) => chai.expect(physicalColumns(db).length).to.equal(mappedNonVirtualColumnCount(db), `out of sync after ${step}`);
76
+ const sharedColumnCount = physicalColumns(db).filter((name) => /^ps\d+$/i.test(name)).length;
77
+ const wide = sharedColumnCount + extraColumns;
78
+ await db.importSchemaStrings([dynamicSchema("01.00.00", wide)]);
79
+ assertInSync("wide import");
80
+ await db.importSchemaStrings([dynamicSchema("02.00.00", 1)]); // delete P2..Pn
81
+ assertInSync("property deletion");
82
+ await db.importSchemaStrings([dynamicSchema("03.00.00", undefined)]); // delete the class
83
+ assertInSync("class deletion");
84
+ await db.importSchemaStrings([unrelatedSchema]); // strict validation passes
85
+ assertInSync("unrelated import");
86
+ });
87
+ it("dynamic-schema deletions pushed and pulled keep the pulling briefcase in sync", async () => {
88
+ const iModelId = await HubMock.createNewIModel({ accessToken: "user1", iTwinId: HubMock.iTwinId, iModelName: "DynDeletePull" });
89
+ const writer = await HubWrappers.downloadAndOpenBriefcase({ accessToken: "user1", iTwinId: HubMock.iTwinId, iModelId });
90
+ const reader = await HubWrappers.downloadAndOpenBriefcase({ accessToken: "user2", iTwinId: HubMock.iTwinId, iModelId });
91
+ try {
92
+ const assertInSync = (db, step) => chai.expect(physicalColumns(db).length).to.equal(mappedNonVirtualColumnCount(db), `${db === writer ? "writer" : "reader"} out of sync after ${step}`);
93
+ const wide = physicalColumns(writer).filter((name) => /^ps\d+$/i.test(name)).length + extraColumns;
94
+ const steps = [
95
+ ["wide import", dynamicSchema("01.00.00", wide)],
96
+ ["property deletion", dynamicSchema("02.00.00", 1)],
97
+ ["class deletion", dynamicSchema("03.00.00", undefined)],
98
+ ];
99
+ for (const [step, xml] of steps) {
100
+ await writer.importSchemaStrings([xml]);
101
+ await writer.pushChanges({ description: step });
102
+ assertInSync(writer, step);
103
+ await reader.pullChanges();
104
+ assertInSync(reader, step);
105
+ }
106
+ // Strict validation on the pulling briefcase passes.
107
+ await reader.importSchemaStrings([unrelatedSchema]);
108
+ assertInSync(reader, "unrelated import");
109
+ }
110
+ finally {
111
+ writer.close();
112
+ reader.close();
113
+ }
114
+ });
115
+ it("schema import succeeds after discarding a schema import that added columns", async () => {
116
+ const accessToken = "user1";
117
+ const iModelId = await HubMock.createNewIModel({ accessToken, iTwinId: HubMock.iTwinId, iModelName: "DiscardOrphan" });
118
+ briefcase = await HubWrappers.downloadAndOpenBriefcase({ accessToken, iTwinId: HubMock.iTwinId, iModelId });
119
+ const baselineColumns = physicalColumns(briefcase);
120
+ chai.expect(baselineColumns.length).to.equal(mappedNonVirtualColumnCount(briefcase), "baseline must be consistent");
121
+ // Plain (non-dynamic) schema whose aspect needs more shared columns than the table has -> ALTER TABLE ADD COLUMN.
122
+ const sharedColumnCount = baselineColumns.filter((name) => /^ps\d+$/i.test(name)).length;
123
+ await briefcase.importSchemaStrings([schemaWithWideMultiAspect(sharedColumnCount + extraColumns)]);
124
+ chai.expect(physicalColumns(briefcase).length).to.equal(baselineColumns.length + extraColumns, "import should add physical columns");
125
+ chai.expect(mappedNonVirtualColumnCount(briefcase)).to.equal(baselineColumns.length + extraColumns);
126
+ // Abandon the un-pushed schema import: ec_* is reverted, the DDL is not.
127
+ await briefcase.discardChanges();
128
+ const schemaCount = briefcase.withSqliteStatement("SELECT COUNT(*) FROM ec_Schema WHERE Name = 'WideAspect'", (stmt) => {
129
+ stmt.step();
130
+ return stmt.getValue(0).getInteger();
131
+ });
132
+ chai.expect(schemaCount).to.equal(0, "schema should be gone after discardChanges");
133
+ const mapped = mappedNonVirtualColumnCount(briefcase);
134
+ const physical = physicalColumns(briefcase).length;
135
+ chai.expect(mapped).to.equal(baselineColumns.length);
136
+ chai.expect(physical - mapped).to.equal(extraColumns, `orphan columns left in ${tableName}`);
137
+ // A subsequent regular schema import must tolerate the unused shared columns left behind.
138
+ await briefcase.importSchemaStrings([unrelatedSchema]);
139
+ chai.expect(physicalColumns(briefcase).length).to.equal(physical);
140
+ });
141
+ });
142
+ //# sourceMappingURL=SchemaImportOrphanColumns.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SchemaImportOrphanColumns.test.js","sourceRoot":"","sources":["../../../../src/test/hubaccess/SchemaImportOrphanColumns.test.ts"],"names":[],"mappings":"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","sourcesContent":["/*---------------------------------------------------------------------------------------------\r\n* Copyright (c) Bentley Systems, Incorporated. All rights reserved.\r\n* See LICENSE.md in the project root for license information.\r\n*--------------------------------------------------------------------------------------------*/\r\nimport { DbResult } from \"@itwin/core-bentley\";\r\nimport * as chai from \"chai\";\r\nimport * as chaiAsPromised from \"chai-as-promised\";\r\nimport { Suite } from \"mocha\";\r\nimport { HubWrappers, KnownTestLocations } from \"..\";\r\nimport { BriefcaseDb } from \"../../core-backend\";\r\nimport { HubMock } from \"../../internal/HubMock\";\r\n\r\nchai.use(chaiAsPromised);\r\n\r\n/**\r\n * Repro for Bug 2115705: \"DbTable 'bis_ElementMultiAspect' has N non-virtual columns, but the physical table has M columns.\"\r\n *\r\n * No semantic rebase, schema sync or dynamic schema involved. A schema adds an ElementMultiAspect subclass wide enough to\r\n * ALTER TABLE ADD shared columns to bis_ElementMultiAspect. discardChanges() then reverses the EcSchema txn (ec_* rows)\r\n * but skips the Ddl txn (TxnManager::ApplyTxnChanges returns early for TxnType::Ddl), so the physical columns remain\r\n * with no ec_Column rows. The next regular schema import used to fail the strict column-count check; it must now succeed.\r\n *\r\n * The dynamic-schema tests show that deleting properties/classes (allowed for dynamic schemas) is not a cause.\r\n */\r\ndescribe(\"Schema import discard leaves orphan physical columns (Bug 2115705)\", function (this: Suite) {\r\n this.timeout(90000);\r\n const tableName = \"bis_ElementMultiAspect\";\r\n const extraColumns = 6;\r\n let briefcase: BriefcaseDb | undefined;\r\n\r\n before(() => HubMock.startup(\"SchemaImportOrphanColumns\", KnownTestLocations.outputDir));\r\n after(() => HubMock.shutdown());\r\n afterEach(() => {\r\n briefcase?.close();\r\n briefcase = undefined;\r\n });\r\n\r\n const physicalColumns = (db: BriefcaseDb): string[] =>\r\n db.withSqliteStatement(`PRAGMA table_info([${tableName}])`, (stmt) => {\r\n const names: string[] = [];\r\n while (stmt.step() === DbResult.BE_SQLITE_ROW)\r\n names.push(stmt.getValue(1).getString());\r\n return names;\r\n });\r\n\r\n const mappedNonVirtualColumnCount = (db: BriefcaseDb): number =>\r\n db.withSqliteStatement(\r\n `SELECT COUNT(*) FROM ec_Column c JOIN ec_Table t ON t.Id = c.TableId WHERE t.Name = '${tableName}' AND c.IsVirtual = 0`,\r\n (stmt) => {\r\n chai.expect(stmt.step()).to.equal(DbResult.BE_SQLITE_ROW);\r\n return stmt.getValue(0).getInteger();\r\n });\r\n\r\n const schemaWithWideMultiAspect = (propCount: number) => `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n <ECSchema schemaName=\"WideAspect\" alias=\"wa\" version=\"01.00.00\" xmlns=\"http://www.bentley.com/schemas/Bentley.ECXML.3.2\">\r\n <ECSchemaReference name=\"BisCore\" version=\"01.00.00\" alias=\"bis\"/>\r\n <ECEntityClass typeName=\"WideMultiAspect\">\r\n <BaseClass>bis:ElementMultiAspect</BaseClass>\r\n ${Array.from({ length: propCount }, (_, i) => `<ECProperty propertyName=\"P${i + 1}\" typeName=\"string\"/>`).join(\"\\n \")}\r\n </ECEntityClass>\r\n </ECSchema>`;\r\n\r\n const unrelatedSchema = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n <ECSchema schemaName=\"Unrelated\" alias=\"unr\" version=\"01.00.00\" xmlns=\"http://www.bentley.com/schemas/Bentley.ECXML.3.2\">\r\n <ECSchemaReference name=\"BisCore\" version=\"01.00.00\" alias=\"bis\"/>\r\n <ECEntityClass typeName=\"Thing\">\r\n <BaseClass>bis:GraphicalElement2d</BaseClass>\r\n <ECProperty propertyName=\"Name\" typeName=\"string\"/>\r\n </ECEntityClass>\r\n </ECSchema>`;\r\n\r\n const dynamicSchema = (version: string, propCount: number | undefined) => `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n <ECSchema schemaName=\"DynTest\" alias=\"dyn\" version=\"${version}\" xmlns=\"http://www.bentley.com/schemas/Bentley.ECXML.3.2\">\r\n <ECSchemaReference name=\"BisCore\" version=\"01.00.00\" alias=\"bis\"/>\r\n <ECSchemaReference name=\"CoreCustomAttributes\" version=\"01.00.03\" alias=\"CoreCA\"/>\r\n <ECCustomAttributes>\r\n <DynamicSchema xmlns=\"CoreCustomAttributes.01.00.03\"/>\r\n </ECCustomAttributes>\r\n ${propCount === undefined ? \"\" : `<ECEntityClass typeName=\"WideMultiAspect\">\r\n <BaseClass>bis:ElementMultiAspect</BaseClass>\r\n ${Array.from({ length: propCount }, (_, i) => `<ECProperty propertyName=\"P${i + 1}\" typeName=\"string\"/>`).join(\"\\n \")}\r\n </ECEntityClass>`}\r\n </ECSchema>`;\r\n\r\n it(\"deleting dynamic-schema properties/classes keeps ec_Column and physical columns in sync\", async () => {\r\n const accessToken = \"user1\";\r\n const iModelId = await HubMock.createNewIModel({ accessToken, iTwinId: HubMock.iTwinId, iModelName: \"DynDelete\" });\r\n briefcase = await HubWrappers.downloadAndOpenBriefcase({ accessToken, iTwinId: HubMock.iTwinId, iModelId });\r\n const db = briefcase;\r\n const assertInSync = (step: string) =>\r\n chai.expect(physicalColumns(db).length).to.equal(mappedNonVirtualColumnCount(db), `out of sync after ${step}`);\r\n\r\n const sharedColumnCount = physicalColumns(db).filter((name) => /^ps\\d+$/i.test(name)).length;\r\n const wide = sharedColumnCount + extraColumns;\r\n\r\n await db.importSchemaStrings([dynamicSchema(\"01.00.00\", wide)]);\r\n assertInSync(\"wide import\");\r\n await db.importSchemaStrings([dynamicSchema(\"02.00.00\", 1)]); // delete P2..Pn\r\n assertInSync(\"property deletion\");\r\n await db.importSchemaStrings([dynamicSchema(\"03.00.00\", undefined)]); // delete the class\r\n assertInSync(\"class deletion\");\r\n await db.importSchemaStrings([unrelatedSchema]); // strict validation passes\r\n assertInSync(\"unrelated import\");\r\n });\r\n\r\n it(\"dynamic-schema deletions pushed and pulled keep the pulling briefcase in sync\", async () => {\r\n const iModelId = await HubMock.createNewIModel({ accessToken: \"user1\", iTwinId: HubMock.iTwinId, iModelName: \"DynDeletePull\" });\r\n const writer = await HubWrappers.downloadAndOpenBriefcase({ accessToken: \"user1\", iTwinId: HubMock.iTwinId, iModelId });\r\n const reader = await HubWrappers.downloadAndOpenBriefcase({ accessToken: \"user2\", iTwinId: HubMock.iTwinId, iModelId });\r\n try {\r\n const assertInSync = (db: BriefcaseDb, step: string) =>\r\n chai.expect(physicalColumns(db).length).to.equal(mappedNonVirtualColumnCount(db), `${db === writer ? \"writer\" : \"reader\"} out of sync after ${step}`);\r\n\r\n const wide = physicalColumns(writer).filter((name) => /^ps\\d+$/i.test(name)).length + extraColumns;\r\n const steps: Array<[string, string]> = [\r\n [\"wide import\", dynamicSchema(\"01.00.00\", wide)],\r\n [\"property deletion\", dynamicSchema(\"02.00.00\", 1)],\r\n [\"class deletion\", dynamicSchema(\"03.00.00\", undefined)],\r\n ];\r\n for (const [step, xml] of steps) {\r\n await writer.importSchemaStrings([xml]);\r\n await writer.pushChanges({ description: step });\r\n assertInSync(writer, step);\r\n await reader.pullChanges();\r\n assertInSync(reader, step);\r\n }\r\n\r\n // Strict validation on the pulling briefcase passes.\r\n await reader.importSchemaStrings([unrelatedSchema]);\r\n assertInSync(reader, \"unrelated import\");\r\n } finally {\r\n writer.close();\r\n reader.close();\r\n }\r\n });\r\n\r\n it(\"schema import succeeds after discarding a schema import that added columns\", async () => {\r\n const accessToken = \"user1\";\r\n const iModelId = await HubMock.createNewIModel({ accessToken, iTwinId: HubMock.iTwinId, iModelName: \"DiscardOrphan\" });\r\n briefcase = await HubWrappers.downloadAndOpenBriefcase({ accessToken, iTwinId: HubMock.iTwinId, iModelId });\r\n\r\n const baselineColumns = physicalColumns(briefcase);\r\n chai.expect(baselineColumns.length).to.equal(mappedNonVirtualColumnCount(briefcase), \"baseline must be consistent\");\r\n\r\n // Plain (non-dynamic) schema whose aspect needs more shared columns than the table has -> ALTER TABLE ADD COLUMN.\r\n const sharedColumnCount = baselineColumns.filter((name) => /^ps\\d+$/i.test(name)).length;\r\n await briefcase.importSchemaStrings([schemaWithWideMultiAspect(sharedColumnCount + extraColumns)]);\r\n chai.expect(physicalColumns(briefcase).length).to.equal(baselineColumns.length + extraColumns, \"import should add physical columns\");\r\n chai.expect(mappedNonVirtualColumnCount(briefcase)).to.equal(baselineColumns.length + extraColumns);\r\n\r\n // Abandon the un-pushed schema import: ec_* is reverted, the DDL is not.\r\n await briefcase.discardChanges();\r\n const schemaCount = briefcase.withSqliteStatement(\"SELECT COUNT(*) FROM ec_Schema WHERE Name = 'WideAspect'\", (stmt) => {\r\n stmt.step();\r\n return stmt.getValue(0).getInteger();\r\n });\r\n chai.expect(schemaCount).to.equal(0, \"schema should be gone after discardChanges\");\r\n\r\n const mapped = mappedNonVirtualColumnCount(briefcase);\r\n const physical = physicalColumns(briefcase).length;\r\n chai.expect(mapped).to.equal(baselineColumns.length);\r\n chai.expect(physical - mapped).to.equal(extraColumns, `orphan columns left in ${tableName}`);\r\n\r\n // A subsequent regular schema import must tolerate the unused shared columns left behind.\r\n await briefcase.importSchemaStrings([unrelatedSchema]);\r\n chai.expect(physicalColumns(briefcase).length).to.equal(physical);\r\n });\r\n});\r\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itwin/core-backend",
3
- "version": "5.14.3",
3
+ "version": "5.14.5",
4
4
  "description": "iTwin.js backend components",
5
5
  "main": "lib/cjs/core-backend.js",
6
6
  "module": "lib/esm/core-backend.js",
@@ -27,11 +27,11 @@
27
27
  },
28
28
  "peerDependencies": {
29
29
  "@opentelemetry/api": "^1.0.4",
30
- "@itwin/core-bentley": "5.14.3",
31
- "@itwin/core-common": "5.14.3",
32
- "@itwin/core-geometry": "5.14.3",
33
- "@itwin/core-quantity": "5.14.3",
34
- "@itwin/ecschema-metadata": "5.14.3"
30
+ "@itwin/core-bentley": "5.14.5",
31
+ "@itwin/core-quantity": "5.14.5",
32
+ "@itwin/core-common": "5.14.5",
33
+ "@itwin/ecschema-metadata": "5.14.5",
34
+ "@itwin/core-geometry": "5.14.5"
35
35
  },
36
36
  "peerDependenciesMeta": {
37
37
  "@opentelemetry/api": {
@@ -57,7 +57,7 @@
57
57
  "@types/sinon": "^17.0.2",
58
58
  "@types/touch": "^3.1.2",
59
59
  "@types/ws": "^7.0.0",
60
- "@xmldom/xmldom": "~0.8.13",
60
+ "@xmldom/xmldom": "~0.8.15",
61
61
  "chai": "^4.3.10",
62
62
  "chai-as-promised": "^7.1.1",
63
63
  "cpx2": "^8.0.0",
@@ -71,19 +71,19 @@
71
71
  "typescript": "~5.6.2",
72
72
  "marked": "^14.1.3",
73
73
  "sql-formatter": "^15.4.6",
74
- "vite": "^6.4.3",
75
- "@itwin/core-geometry": "5.14.3",
76
- "@itwin/core-quantity": "5.14.3",
77
- "@itwin/build-tools": "5.14.3",
78
- "@itwin/core-bentley": "5.14.3",
79
- "@itwin/ecschema-metadata": "5.14.3",
80
- "internal-tools": "3.0.0-dev.69",
81
- "@itwin/ecsql-common": "5.14.3",
82
- "@itwin/core-common": "5.14.3",
83
- "@itwin/ecschema-locaters": "5.14.3"
74
+ "vite": "^6.4.4",
75
+ "@itwin/core-geometry": "5.14.5",
76
+ "@itwin/core-bentley": "5.14.5",
77
+ "@itwin/build-tools": "5.14.5",
78
+ "@itwin/ecschema-locaters": "5.14.5",
79
+ "@itwin/core-common": "5.14.5",
80
+ "@itwin/core-quantity": "5.14.5",
81
+ "@itwin/ecsql-common": "5.14.5",
82
+ "@itwin/ecschema-metadata": "5.14.5",
83
+ "internal-tools": "3.0.0-dev.69"
84
84
  },
85
85
  "dependencies": {
86
- "@bentley/imodeljs-native": "5.14.48",
86
+ "@bentley/imodeljs-native": "5.14.55",
87
87
  "@itwin/object-storage-azure": "^3.0.4",
88
88
  "@azure/storage-blob": "^12.28.0",
89
89
  "form-data": "^4.0.6",