@itwin/presentation-core-interop 2.0.0-alpha.3 → 2.0.0-alpha.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,5 +1,177 @@
1
1
  # @itwin/presentation-core-interop
2
2
 
3
+ ## 2.0.0-alpha.5
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies:
8
+ - @itwin/presentation-shared@2.0.0-alpha.14
9
+
10
+ ## 2.0.0-alpha.4
11
+
12
+ ### Major Changes
13
+
14
+ - [#1394](https://github.com/iTwin/presentation/pull/1394): Changed `createECSchemaProvider` to take an object exposing the iModel's `getSchemaView` and `createQueryReader` functions instead of a `SchemaContext`. An `IModelDb` or `IModelConnection` satisfies this shape directly, so you can now pass the iModel itself; you can also pass any object that provides just those two functions.
15
+
16
+ Typical migration:
17
+
18
+ ```ts
19
+ const iModel: IModelDb | IModelConnection = ...;
20
+
21
+ // previously:
22
+ const schemaProvider = createECSchemaProvider(iModel.schemaContext);
23
+
24
+ // now (pass the iModel directly):
25
+ const schemaProvider = createECSchemaProvider(iModel);
26
+
27
+ // or provide only the required functions:
28
+ const schemaProvider = createECSchemaProvider({
29
+ getSchemaView: iModel.getSchemaView.bind(iModel),
30
+ createQueryReader: iModel.createQueryReader.bind(iModel),
31
+ });
32
+ ```
33
+
34
+ - [#1531](https://github.com/iTwin/presentation/pull/1531): `EC.RelationshipConstraintMultiplicity`: Changed `upperLimit` type from `number` to `number | "unbounded"`.
35
+
36
+ Previously, `createECSchemaProvider` from `@itwin/presentation-core-interop` reported an unbounded (`*`) upper limit as `0`, which silently made the natural `upperLimit > 1` check treat many-valued relationships as single-valued. The limit is now an explicit `"unbounded"` value that consumers must handle:
37
+
38
+ ```ts
39
+ const isManyValued =
40
+ constraint.multiplicity.upperLimit === "unbounded" ||
41
+ constraint.multiplicity.upperLimit > 1;
42
+ ```
43
+
44
+ - [#1494](https://github.com/iTwin/presentation/pull/1494): `createValueFormatter`: Changed to take a `formatsProvider`, a `unitsProvider` and the iModel instead of a `SchemaContext`.
45
+
46
+ `SchemaContext` is inefficient on iModels with large domain schemas, so the function no longer depends on it. Sourcing formats from a `FormatsProvider` also lets the consuming application register its own formatting overrides (per organization, per iModel, per user, etc.), achieving cohesive formatting across the whole application - something a bare `SchemaContext` couldn't provide.
47
+
48
+ Additionally, when a kind of quantity can’t be resolved (e.g. missing schema/KoQ or an unsupported persistence unit name), `createValueFormatter` now falls back to the `baseFormatter` instead of throwing.
49
+
50
+ Migration: the `schemaContext` prop is removed, provide `formatsProvider`, `unitsProvider` and `imodel` instead:
51
+
52
+ ```ts
53
+ // previously:
54
+ const formatter = createValueFormatter({
55
+ schemaContext: imodel.schemaContext,
56
+ unitSystem: "metric",
57
+ });
58
+
59
+ // now, on the frontend:
60
+ const formatter = createValueFormatter({
61
+ formatsProvider: IModelApp.formatsProvider,
62
+ unitsProvider: IModelApp.quantityFormatter,
63
+ imodel,
64
+ unitSystem: "metric",
65
+ });
66
+ ```
67
+
68
+ On the backend, where there's no `IModelApp`, construct equivalent providers from the iModel's `SchemaContext`, e.g. `formatsProvider: new SchemaFormatsProvider(schemaContext)` and `unitsProvider: new SchemaUnitProvider(schemaContext)` from `@itwin/ecschema-metadata`, ideally caching them per iModel.
69
+
70
+ - [#1394](https://github.com/iTwin/presentation/pull/1394): `EC` namespace interfaces in `@itwin/presentation-shared` no longer use `Promise` wrappers — once a schema is loaded via the still-async `ECSchemaProvider.getSchema`, all further navigation (`baseClass`, `is()`, `getProperty()`, `getProperties()`, `kindOfQuantity`, `relationshipClass`, `enumeration`, `abstractConstraint`) is synchronous.
71
+
72
+ Additional changes:
73
+
74
+ - The `EC.Class.getDerivedClasses()` method was replaced with `getDerivedClassNames(props?: { onlyDirect?: boolean })`. `ECSchemaProvider` can be used to load the derived classes by name, if needed.
75
+ - The `getCustomAttributes()` method has been removed from `EC.Schema`, `EC.Class`, and `EC.Property` and replaced with an `isHidden: boolean` property. `EC.CustomAttributeSet` and `EC.CustomAttribute` types have been removed.
76
+ - Added an `EC.Class.getOwnProperties()` method that returns only the properties defined on the class itself, without inherited properties.
77
+ - Added an `EC.EntityClass.getMixins()` method that returns all mixins applied to the entity class.
78
+ - Added an optional `EC.Property.category` attribute.
79
+ - Added a required `EC.RelationshipConstraint.constraintClasses` attribute.
80
+ - Added missing optional `description` attributes to `EC.Schema` and `EC.Property`.
81
+
82
+ ### Minor Changes
83
+
84
+ - [#1493](https://github.com/iTwin/presentation/pull/1493): Expose access to enumerations, kind-of-quantities and property categories through `EC.Schema`, and extend `EC.KindOfQuantity` with `relativeError` and `persistenceUnit` attributes. Schemas returned by `createECSchemaProvider` now implement the new getters.
85
+
86
+ - `EC.Schema` now requires `getEnumeration`, `getKindOfQuantity` and `getPropertyCategory` methods (mirroring the existing `getClass`). Consumers that only use `EC.Schema` are unaffected, but custom implementations of the interface must add these getters:
87
+
88
+ ```ts
89
+ const schema: EC.Schema = {
90
+ name,
91
+ version,
92
+ isHidden,
93
+ getClass: (className) => classes.get(className),
94
+ // added:
95
+ getEnumeration: (enumName) => enumerations.get(enumName),
96
+ getKindOfQuantity: (koqName) => kindOfQuantities.get(koqName),
97
+ getPropertyCategory: (categoryName) => categories.get(categoryName),
98
+ };
99
+ ```
100
+
101
+ - `EC.KindOfQuantity` now requires `relativeError` (`number`) and `persistenceUnit` (`string`) attributes. Custom implementations must provide them.
102
+
103
+ - `createECSchemaProvider`: The `EC.Schema` returned by the provider now implements the new getters, giving access to enumerations, kind-of-quantities and property categories in addition to classes. Existing code keeps working without changes and can now read these additional schema items:
104
+
105
+ ```ts
106
+ const schemaProvider = createECSchemaProvider(imodel);
107
+ const schema = await schemaProvider.getSchema("BisCore");
108
+ const enumeration = schema?.getEnumeration("MySchema.MyEnum");
109
+ const koq = schema?.getKindOfQuantity("MySchema.MyKoq");
110
+ const category = schema?.getPropertyCategory("MySchema.MyCategory");
111
+ ```
112
+
113
+ - [#1491](https://github.com/iTwin/presentation/pull/1491): `ECSchemaProvider`: Added a `classDerivesFrom` method for checking whether one ECClass is the same as, or derives from, another. `createECSchemaProvider` (in `@itwin/presentation-core-interop`) implements it using the class hierarchy information it already loads, so the answer is returned synchronously once the hierarchy has been loaded and no additional round-trips to the iModel are needed.
114
+
115
+ Also, `ECClassHierarchyInspector` and `createCachingECClassHierarchyInspector` have been deprecated. Because `ECSchemaProvider` now exposes `classDerivesFrom` directly, a separate class hierarchy inspector is no longer needed when setting up iModel access:
116
+
117
+ ```ts
118
+ // Before:
119
+ import { createCachingECClassHierarchyInspector } from "@itwin/presentation-shared";
120
+ import {
121
+ createECSchemaProvider,
122
+ createECSqlQueryExecutor,
123
+ } from "@itwin/presentation-core-interop";
124
+
125
+ const schemaProvider = createECSchemaProvider(imodel);
126
+ const imodelAccess = {
127
+ ...schemaProvider,
128
+ ...createCachingECClassHierarchyInspector({ schemaProvider }),
129
+ ...createECSqlQueryExecutor(imodel),
130
+ };
131
+
132
+ // After:
133
+ import {
134
+ createECSchemaProvider,
135
+ createECSqlQueryExecutor,
136
+ } from "@itwin/presentation-core-interop";
137
+
138
+ const imodelAccess = {
139
+ ...createECSchemaProvider(imodel), // now also provides `classDerivesFrom`
140
+ ...createECSqlQueryExecutor(imodel),
141
+ };
142
+ ```
143
+
144
+ **Breaking changes:**
145
+
146
+ - `ECSchemaProvider` now requires a `classDerivesFrom` method. Objects created via `createECSchemaProvider` get it automatically, so most consumers don't need to change anything. Only custom, hand-written `ECSchemaProvider` implementations need to add the method.
147
+
148
+ - Renamed the `classHierarchyInspector` prop to `imodelAccess` on `createClassBasedInstanceLabelSelectClauseFactory`, `createBisInstanceLabelSelectClauseFactory` (both in `@itwin/presentation-shared`) and `createPredicateBasedHierarchyDefinition` (in `@itwin/presentation-hierarchies`). The prop's type is unchanged, so the value passed to it doesn't need to change - only the prop name:
149
+
150
+ ```ts
151
+ // Before:
152
+ const classHierarchyInspector = createCachingECClassHierarchyInspector({
153
+ schemaProvider: createECSchemaProvider(imodel),
154
+ });
155
+ createPredicateBasedHierarchyDefinition({
156
+ classHierarchyInspector,
157
+ hierarchy,
158
+ });
159
+
160
+ // After:
161
+ const imodelAccess = createECSchemaProvider(imodel);
162
+ createPredicateBasedHierarchyDefinition({ imodelAccess, hierarchy });
163
+ ```
164
+
165
+ ### Patch Changes
166
+
167
+ - [#1495](https://github.com/iTwin/presentation/pull/1495): Fix `createECSchemaProvider` to set `EC.Property.class` to the class that declares or contributes the property (the base class for an inherited property, or the mixin for a mixin-contributed property), instead of the class the property was queried or enumerated through.
168
+ - [#1511](https://github.com/iTwin/presentation/pull/1511): Fixed a performance regression that made property grouping of large hierarchies dramatically slower.
169
+
170
+ `createECSchemaProvider` now caches resolved schemas and the `EC.Class` objects built from them, so repeated `getSchema`/`getClass` calls and `EC.Property.class` accesses no longer trigger a new native schema view request or rebuild the class each time. Cached schemas are reused until the underlying schema view becomes outdated, at which point they are refreshed on next access. In addition, property grouping now resolves each properties class once instead of once per grouped node.
171
+
172
+ - Updated dependencies:
173
+ - @itwin/presentation-shared@2.0.0-alpha.13
174
+
3
175
  ## 2.0.0-alpha.3
4
176
 
5
177
  ### Major Changes
package/README.md CHANGED
@@ -56,7 +56,7 @@ for await (const row of executor.createQueryReader({ ecsql: MY_QUERY })) {
56
56
 
57
57
  ### `createECSchemaProvider`
58
58
 
59
- Maps an instance of `itwinjs-core` [SchemaContext](https://www.itwinjs.org/reference/ecschema-metadata/context/schemacontext/) class to an instance of `ECSchemaProvider`, used in `@itwin/presentation-hierarchies` and `@itwin/unified-selection` packages.
59
+ Maps an instance of `itwinjs-core` iModel (either [IModelConnection](https://www.itwinjs.org/reference/core-frontend/imodelconnection/imodelconnection/) or [IModelDb](https://www.itwinjs.org/reference/core-backend/imodels/imodeldb/)) to an instance of `ECSchemaProvider`, used in `@itwin/presentation-hierarchies`, `@itwin/unified-selection` and other packages.
60
60
 
61
61
  Example:
62
62
 
@@ -68,7 +68,7 @@ import { IModelConnection } from "@itwin/core-frontend";
68
68
  import { createECSchemaProvider } from "@itwin/presentation-core-interop";
69
69
 
70
70
  const imodel: IModelConnection = getIModelConnection();
71
- const schemaProvider = createECSchemaProvider(imodel.schemaContext);
71
+ const schemaProvider = createECSchemaProvider(imodel);
72
72
  // the created schema provider may be used in `@itwin/presentation-hierarchies` or `@itwin/unified-selection` packages
73
73
  ```
74
74
 
@@ -76,7 +76,7 @@ const schemaProvider = createECSchemaProvider(imodel.schemaContext);
76
76
 
77
77
  ### `createValueFormatter`
78
78
 
79
- Creates an instance of `IPrimitiveValueFormatter` that knows how to format primitive property values using their units' information. That information is retrieved from an iModel through `itwinjs-core` [SchemaContext](https://www.itwinjs.org/reference/ecschema-metadata/context/schemacontext/).
79
+ Creates an instance of `IPrimitiveValueFormatter` that knows how to format primitive property values using their units' information. The formats and units used come from a `FormatsProvider` and `UnitsProvider` registered by the end product (e.g. `IModelApp.formatsProvider` / `IModelApp.quantityFormatter` on the frontend), while the kind of quantity's persistence unit is resolved from the iModel itself.
80
80
 
81
81
  Example:
82
82
 
@@ -84,12 +84,22 @@ Example:
84
84
  <!-- BEGIN EXTRACTION -->
85
85
 
86
86
  ```ts
87
- import { SchemaContext } from "@itwin/ecschema-metadata";
87
+ import { IModelApp } from "@itwin/core-frontend";
88
88
  import { createValueFormatter } from "@itwin/presentation-core-interop";
89
89
 
90
- const schemaContext: SchemaContext = getIModelConnection().schemaContext;
91
- const metricFormatter = createValueFormatter({ schemaContext, unitSystem: "metric" });
92
- const imperialFormatter = createValueFormatter({ schemaContext, unitSystem: "imperial" });
90
+ const imodel = getIModelConnection();
91
+ const metricFormatter = createValueFormatter({
92
+ formatsProvider: IModelApp.formatsProvider,
93
+ unitsProvider: IModelApp.quantityFormatter,
94
+ imodel,
95
+ unitSystem: "metric",
96
+ });
97
+ const imperialFormatter = createValueFormatter({
98
+ formatsProvider: IModelApp.formatsProvider,
99
+ unitsProvider: IModelApp.quantityFormatter,
100
+ imodel,
101
+ unitSystem: "imperial",
102
+ });
93
103
 
94
104
  // Define the raw value to be formatted
95
105
  const value = 1.234;
@@ -1,25 +1,45 @@
1
- import type { UnitSystemKey } from "@itwin/core-quantity";
2
- import type { SchemaContext } from "@itwin/ecschema-metadata";
1
+ import type { FormatsProvider, UnitsProvider, UnitSystemKey } from "@itwin/core-quantity";
2
+ import type { SchemaView } from "@itwin/ecschema-metadata";
3
3
  import type { IPrimitiveValueFormatter } from "@itwin/presentation-shared";
4
+ /**
5
+ * Subset of `SchemaView` used to look up a kind of quantity's persistence unit.
6
+ * @public
7
+ */
8
+ type PersistenceUnitSchemaView = Pick<SchemaView, "findKindOfQuantity" | "getSchemaByAlias">;
4
9
  /**
5
10
  * Props for `createValueFormatter` function.
6
11
  * @public
7
12
  */
8
13
  interface CreateValueFormatterProps {
9
14
  /**
10
- * An instance of [SchemaContext](https://www.itwinjs.org/reference/ecschema-metadata/context/schemacontext/) for
11
- * getting units information. Generally, retrieved directly from `IModelDb` or `IModelConnection` using the `schemaContext` accessor.
15
+ * Supplies `FormatProps` for a property's kind of quantity, by its full name. On the frontend, `IModelApp.formatsProvider`
16
+ * satisfies this. `getFormat` returning `undefined` means no format is registered for the kind of quantity - `baseFormatter`
17
+ * is used in that case.
18
+ */
19
+ formatsProvider: Pick<FormatsProvider, "getFormat">;
20
+ /**
21
+ * Resolves units and is used to build format specs. On the frontend, `IModelApp.quantityFormatter` implements this
22
+ * interface directly.
23
+ */
24
+ unitsProvider: UnitsProvider;
25
+ /**
26
+ * Supplies [SchemaView](https://www.itwinjs.org/reference/ecschema-metadata/context/schemaview/) instances used to look up
27
+ * a kind of quantity's persistence unit. `IModelDb` and `IModelConnection` satisfy this shape directly.
12
28
  */
13
- schemaContext: SchemaContext;
29
+ imodel: {
30
+ getSchemaView(props?: {
31
+ schemas?: string[];
32
+ }): Promise<PersistenceUnitSchemaView>;
33
+ };
14
34
  /**
15
- * An optional unit system to use for formatting property values. If not provided, default presentation units are used. If a property
16
- * doesn't have a default presentation unit, then persistence unit is used. Finally, if a property doesn't have a unit assigned at all,
17
- * `baseFormatter` is used to format the property value.
35
+ * An optional unit system override, forwarded to `formatsProvider.getFormat`. If not provided, `formatsProvider` formats
36
+ * using whatever unit system it's configured with.
18
37
  */
19
38
  unitSystem?: UnitSystemKey;
20
39
  /**
21
- * Base primitive value formatter for cases when a property doesn't have any units' information. Defaults to the result of `createDefaultValueFormatter`
22
- * from `@itwin/presentation-shared` package.
40
+ * Base primitive value formatter used whenever a property's value can't be formatted using unit information - e.g. the
41
+ * property doesn't have a kind of quantity, no format is registered for it, or its kind of quantity doesn't specify a usable
42
+ * persistence unit. Defaults to the result of `createDefaultValueFormatter` from `@itwin/presentation-shared` package.
23
43
  */
24
44
  baseFormatter?: IPrimitiveValueFormatter;
25
45
  }
@@ -30,14 +50,23 @@ interface CreateValueFormatterProps {
30
50
  * Usage example:
31
51
  *
32
52
  * ```ts
33
- * import { IModelConnection } from "@itwin/core-frontend";
53
+ * import { IModelApp, IModelConnection } from "@itwin/core-frontend";
34
54
  * import { createValueFormatter } from "@itwin/presentation-core-interop";
35
55
  *
36
56
  * const imodel: IModelConnection = getIModel();
37
- * const formatter = createValueFormatter({ schemaContext: imodel.schemaContext, unitSystem: "metric" });
57
+ * const formatter = createValueFormatter({
58
+ * formatsProvider: IModelApp.formatsProvider,
59
+ * unitsProvider: IModelApp.quantityFormatter,
60
+ * imodel,
61
+ * unitSystem: "metric",
62
+ * });
38
63
  * const formattedValue = await formatter({ type: "Double", value: 1.234, koqName: "MySchema.LengthKindOfQuantity" });
39
64
  * ```
40
65
  *
66
+ * On the backend, where there's no `IModelApp`, construct equivalent `formatsProvider` / `unitsProvider` instances
67
+ * from the active iModel using `SchemaFormatsProvider` and `SchemaUnitProvider` from `@itwin/ecschema-metadata`,
68
+ * ideally caching them per iModel.
69
+ *
41
70
  * @public
42
71
  */
43
72
  export declare function createValueFormatter(props: CreateValueFormatterProps): IPrimitiveValueFormatter;
@@ -1 +1 @@
1
- {"version":3,"file":"Formatting.d.ts","sourceRoot":"","sources":["../../src/core-interop/Formatting.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAA8B,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACtF,OAAO,KAAK,EAA0C,aAAa,EAAQ,MAAM,0BAA0B,CAAC;AAC5G,OAAO,KAAK,EAAE,wBAAwB,EAAuB,MAAM,4BAA4B,CAAC;AAEhG;;;GAGG;AACH,UAAU,yBAAyB;IACjC;;;OAGG;IACH,aAAa,EAAE,aAAa,CAAC;IAE7B;;;;OAIG;IACH,UAAU,CAAC,EAAE,aAAa,CAAC;IAE3B;;;OAGG;IACH,aAAa,CAAC,EAAE,wBAAwB,CAAC;CAC1C;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,yBAAyB,GAAG,wBAAwB,CAe/F"}
1
+ {"version":3,"file":"Formatting.d.ts","sourceRoot":"","sources":["../../src/core-interop/Formatting.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,KAAK,EAAE,wBAAwB,EAAuB,MAAM,4BAA4B,CAAC;AAEhG;;;GAGG;AACH,KAAK,yBAAyB,GAAG,IAAI,CAAC,UAAU,EAAE,oBAAoB,GAAG,kBAAkB,CAAC,CAAC;AAE7F;;;GAGG;AACH,UAAU,yBAAyB;IACjC;;;;OAIG;IACH,eAAe,EAAE,IAAI,CAAC,eAAe,EAAE,WAAW,CAAC,CAAC;IAEpD;;;OAGG;IACH,aAAa,EAAE,aAAa,CAAC;IAE7B;;;OAGG;IACH,MAAM,EAAE;QAAE,aAAa,CAAC,KAAK,CAAC,EAAE;YAAE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;SAAE,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAA;KAAE,CAAC;IAE9F;;;OAGG;IACH,UAAU,CAAC,EAAE,aAAa,CAAC;IAE3B;;;;OAIG;IACH,aAAa,CAAC,EAAE,wBAAwB,CAAC;CAC1C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,yBAAyB,GAAG,wBAAwB,CAoB/F"}
@@ -3,8 +3,8 @@
3
3
  * See LICENSE.md in the project root for license terms and full copyright notice.
4
4
  *--------------------------------------------------------------------------------------------*/
5
5
  import { FormatterSpec, Format as QuantityFormat } from "@itwin/core-quantity";
6
- import { KindOfQuantity, OverrideFormat, SchemaKey, SchemaMatchType, SchemaUnitProvider, } from "@itwin/ecschema-metadata";
7
6
  import { createDefaultValueFormatter, parseFullClassName } from "@itwin/presentation-shared";
7
+ import { createBatchedSchemaViewGetter } from "./Metadata.js";
8
8
  /**
9
9
  * Creates an instance of `IPrimitiveValueFormatter` that knows how to format values of properties with assigned kind of quantity. In
10
10
  * case the property does not have an assigned kind of quantity, the base formatter is used.
@@ -12,25 +12,39 @@ import { createDefaultValueFormatter, parseFullClassName } from "@itwin/presenta
12
12
  * Usage example:
13
13
  *
14
14
  * ```ts
15
- * import { IModelConnection } from "@itwin/core-frontend";
15
+ * import { IModelApp, IModelConnection } from "@itwin/core-frontend";
16
16
  * import { createValueFormatter } from "@itwin/presentation-core-interop";
17
17
  *
18
18
  * const imodel: IModelConnection = getIModel();
19
- * const formatter = createValueFormatter({ schemaContext: imodel.schemaContext, unitSystem: "metric" });
19
+ * const formatter = createValueFormatter({
20
+ * formatsProvider: IModelApp.formatsProvider,
21
+ * unitsProvider: IModelApp.quantityFormatter,
22
+ * imodel,
23
+ * unitSystem: "metric",
24
+ * });
20
25
  * const formattedValue = await formatter({ type: "Double", value: 1.234, koqName: "MySchema.LengthKindOfQuantity" });
21
26
  * ```
22
27
  *
28
+ * On the backend, where there's no `IModelApp`, construct equivalent `formatsProvider` / `unitsProvider` instances
29
+ * from the active iModel using `SchemaFormatsProvider` and `SchemaUnitProvider` from `@itwin/ecschema-metadata`,
30
+ * ideally caching them per iModel.
31
+ *
23
32
  * @public
24
33
  */
25
34
  export function createValueFormatter(props) {
26
- const { schemaContext, unitSystem } = props;
35
+ const { formatsProvider, unitsProvider, imodel, unitSystem } = props;
27
36
  /* v8 ignore next -- @preserve */
28
37
  const baseFormatter = props.baseFormatter ?? createDefaultValueFormatter();
29
- const unitsProvider = new SchemaUnitProvider(schemaContext);
38
+ const getSchemaView = createBatchedSchemaViewGetter(imodel);
30
39
  return async function (value) {
31
40
  if (value.type === "Double" && !!value.koqName) {
32
- const koq = await getKindOfQuantity(schemaContext, value.koqName);
33
- const spec = await getFormatterSpec(unitsProvider, koq, unitSystem);
41
+ const spec = await getFormatterSpec({
42
+ formatsProvider,
43
+ unitsProvider,
44
+ getSchemaView,
45
+ koqName: value.koqName,
46
+ unitSystem,
47
+ });
34
48
  if (spec) {
35
49
  return spec.applyFormatting(value.value);
36
50
  }
@@ -38,96 +52,44 @@ export function createValueFormatter(props) {
38
52
  return baseFormatter(value);
39
53
  };
40
54
  }
41
- async function getKindOfQuantity(schemas, fullName) {
42
- const { schemaName, className: koqName } = parseFullClassName(fullName);
43
- const schema = await schemas.getSchema(new SchemaKey(schemaName), SchemaMatchType.Latest);
44
- if (!schema) {
45
- throw new Error(`Invalid schema "${schemaName}" specified in KoQ full name "${fullName}"`);
46
- }
47
- const koq = await schema.getItem(koqName, KindOfQuantity);
48
- if (!koq) {
49
- throw new Error(`Invalid kind of quantity "${koqName}" specified in KoQ full name "${fullName}" - it does not exist in schema "${schemaName}"`);
55
+ async function getFormatterSpec(props) {
56
+ const { formatsProvider, unitsProvider, getSchemaView, koqName, unitSystem } = props;
57
+ const formatProps = await formatsProvider.getFormat(koqName, unitSystem);
58
+ if (!formatProps) {
59
+ return undefined;
50
60
  }
51
- return koq;
52
- }
53
- async function getFormatterSpec(unitsProvider, koq, unitSystem) {
54
- const formattingProps = await getFormattingProps(koq, unitSystem);
55
- if (!formattingProps) {
61
+ const persistenceUnitName = await getPersistenceUnitName(getSchemaView, koqName);
62
+ if (!persistenceUnitName) {
56
63
  return undefined;
57
64
  }
58
- const { formatProps, persistenceUnitName } = formattingProps;
59
65
  const persistenceUnit = await unitsProvider.findUnitByName(persistenceUnitName);
60
66
  const format = await QuantityFormat.createFromJSON("", unitsProvider, formatProps);
61
67
  return FormatterSpec.create("", format, unitsProvider, persistenceUnit);
62
68
  }
63
- async function getFormattingProps(koq, unitSystem) {
64
- const persistenceUnit = await koq.persistenceUnit;
65
- if (!persistenceUnit) {
66
- return undefined;
67
- }
68
- const formatProps = await getKoqFormatProps(koq, persistenceUnit, unitSystem);
69
- if (!formatProps) {
69
+ /**
70
+ * `Units`/`Formats` are excluded from `SchemaView` by design (see class docs), so `SchemaView.getSchemaByAlias` can never
71
+ * resolve them. Schemas almost universally reference them using these exact aliases, so fall back to them when
72
+ * `getSchemaByAlias` can't help.
73
+ */
74
+ const WELL_KNOWN_SCHEMA_ALIASES = { u: "Units", f: "Formats" };
75
+ async function getPersistenceUnitName(getSchemaView, koqName) {
76
+ const { schemaName } = parseFullClassName(koqName);
77
+ const schemaView = await getSchemaView(schemaName);
78
+ const koq = schemaView.findKindOfQuantity(koqName);
79
+ if (!koq) {
70
80
  return undefined;
71
81
  }
72
- return { formatProps, persistenceUnitName: persistenceUnit.fullName };
73
- }
74
- async function getKoqFormatProps(koq, persistenceUnit, unitSystem) {
75
- const unitSystems = getUnitSystemGroupNames(unitSystem);
76
- // use one of KOQ presentation format that matches requested unit system
77
- const presentationFormat = await getKoqPresentationFormat(koq, unitSystems);
78
- if (presentationFormat) {
79
- return getFormatProps(presentationFormat);
80
- }
81
- // use persistence unit format if it matches requested unit system and matching presentation format was not found
82
- const persistenceUnitSystem = await persistenceUnit.unitSystem;
83
- if (persistenceUnitSystem && unitSystems.includes(persistenceUnitSystem.name.toUpperCase())) {
84
- return getPersistenceUnitFormatProps(persistenceUnit);
85
- }
86
- // use default presentation format if persistence unit does not match requested unit system
87
- if (koq.defaultPresentationFormat) {
88
- return getFormatProps(koq.defaultPresentationFormat);
89
- }
90
- return undefined;
91
- }
92
- async function getKoqPresentationFormat(koq, unitSystems) {
93
- const presentationFormats = koq.presentationFormats;
94
- for (const system of unitSystems) {
95
- for (const format of presentationFormats) {
96
- const units = format instanceof OverrideFormat ? format.units : (await format).units;
97
- const lazyUnit = units && units[0][0];
98
- const currentUnitSystem = lazyUnit && (await lazyUnit).unitSystem;
99
- if (currentUnitSystem && currentUnitSystem.name.toUpperCase() === system) {
100
- return format;
101
- }
102
- }
82
+ try {
83
+ // Despite `persistenceUnit`'s doc comment, it's returned alias-qualified (e.g. "u:M"), not schema-name-qualified.
84
+ // Legacy ECDb profiles (pre EC3.2 Units/Formats migration) return it in a format this doesn't understand at all.
85
+ const { schemaName: aliasOrSchemaName, className: unitName } = parseFullClassName(koq.persistenceUnit);
86
+ const resolvedSchemaName = schemaView.getSchemaByAlias(aliasOrSchemaName)?.name ??
87
+ WELL_KNOWN_SCHEMA_ALIASES[aliasOrSchemaName.toLowerCase()] ??
88
+ aliasOrSchemaName;
89
+ return `${resolvedSchemaName}.${unitName}`;
103
90
  }
104
- return undefined;
105
- }
106
- async function getFormatProps(format) {
107
- return format instanceof OverrideFormat ? format.getFormatProps() : (await format).toJSON();
108
- }
109
- function getPersistenceUnitFormatProps(persistenceUnit) {
110
- // Same as Format "DefaultRealU" in Formats ecschema
111
- return {
112
- formatTraits: ["keepSingleZero", "keepDecimalPoint", "showUnitLabel"],
113
- precision: 6,
114
- type: "Decimal",
115
- uomSeparator: " ",
116
- decimalSeparator: ".",
117
- composite: { units: [{ name: persistenceUnit.fullName, label: persistenceUnit.label }] },
118
- };
119
- }
120
- function getUnitSystemGroupNames(unitSystem) {
121
- switch (unitSystem) {
122
- case "imperial":
123
- return ["IMPERIAL", "USCUSTOM", "INTERNATIONAL", "FINANCE"];
124
- case "metric":
125
- return ["SI", "METRIC", "INTERNATIONAL", "FINANCE"];
126
- case "usCustomary":
127
- return ["USCUSTOM", "INTERNATIONAL", "FINANCE"];
128
- case "usSurvey":
129
- return ["USSURVEY", "USCUSTOM", "INTERNATIONAL", "FINANCE"];
91
+ catch {
92
+ return undefined;
130
93
  }
131
- return [];
132
94
  }
133
95
  //# sourceMappingURL=Formatting.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Formatting.js","sourceRoot":"","sources":["../../src/core-interop/Formatting.ts"],"names":[],"mappings":"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","sourcesContent":["/*---------------------------------------------------------------------------------------------\n * Copyright (c) Bentley Systems, Incorporated. All rights reserved.\n * See LICENSE.md in the project root for license terms and full copyright notice.\n *--------------------------------------------------------------------------------------------*/\n\nimport { FormatterSpec, Format as QuantityFormat } from \"@itwin/core-quantity\";\nimport {\n KindOfQuantity,\n OverrideFormat,\n SchemaKey,\n SchemaMatchType,\n SchemaUnitProvider,\n} from \"@itwin/ecschema-metadata\";\nimport { createDefaultValueFormatter, parseFullClassName } from \"@itwin/presentation-shared\";\n\nimport type { FormatProps, UnitsProvider, UnitSystemKey } from \"@itwin/core-quantity\";\nimport type { Format, InvertedUnit, LazyLoadedFormat, SchemaContext, Unit } from \"@itwin/ecschema-metadata\";\nimport type { IPrimitiveValueFormatter, TypedPrimitiveValue } from \"@itwin/presentation-shared\";\n\n/**\n * Props for `createValueFormatter` function.\n * @public\n */\ninterface CreateValueFormatterProps {\n /**\n * An instance of [SchemaContext](https://www.itwinjs.org/reference/ecschema-metadata/context/schemacontext/) for\n * getting units information. Generally, retrieved directly from `IModelDb` or `IModelConnection` using the `schemaContext` accessor.\n */\n schemaContext: SchemaContext;\n\n /**\n * An optional unit system to use for formatting property values. If not provided, default presentation units are used. If a property\n * doesn't have a default presentation unit, then persistence unit is used. Finally, if a property doesn't have a unit assigned at all,\n * `baseFormatter` is used to format the property value.\n */\n unitSystem?: UnitSystemKey;\n\n /**\n * Base primitive value formatter for cases when a property doesn't have any units' information. Defaults to the result of `createDefaultValueFormatter`\n * from `@itwin/presentation-shared` package.\n */\n baseFormatter?: IPrimitiveValueFormatter;\n}\n\n/**\n * Creates an instance of `IPrimitiveValueFormatter` that knows how to format values of properties with assigned kind of quantity. In\n * case the property does not have an assigned kind of quantity, the base formatter is used.\n *\n * Usage example:\n *\n * ```ts\n * import { IModelConnection } from \"@itwin/core-frontend\";\n * import { createValueFormatter } from \"@itwin/presentation-core-interop\";\n *\n * const imodel: IModelConnection = getIModel();\n * const formatter = createValueFormatter({ schemaContext: imodel.schemaContext, unitSystem: \"metric\" });\n * const formattedValue = await formatter({ type: \"Double\", value: 1.234, koqName: \"MySchema.LengthKindOfQuantity\" });\n * ```\n *\n * @public\n */\nexport function createValueFormatter(props: CreateValueFormatterProps): IPrimitiveValueFormatter {\n const { schemaContext, unitSystem } = props;\n /* v8 ignore next -- @preserve */\n const baseFormatter = props.baseFormatter ?? createDefaultValueFormatter();\n const unitsProvider = new SchemaUnitProvider(schemaContext);\n return async function (value: TypedPrimitiveValue): Promise<string> {\n if (value.type === \"Double\" && !!value.koqName) {\n const koq = await getKindOfQuantity(schemaContext, value.koqName);\n const spec = await getFormatterSpec(unitsProvider, koq, unitSystem);\n if (spec) {\n return spec.applyFormatting(value.value);\n }\n }\n return baseFormatter(value);\n };\n}\n\nasync function getKindOfQuantity(schemas: SchemaContext, fullName: string) {\n const { schemaName, className: koqName } = parseFullClassName(fullName);\n const schema = await schemas.getSchema(new SchemaKey(schemaName), SchemaMatchType.Latest);\n if (!schema) {\n throw new Error(`Invalid schema \"${schemaName}\" specified in KoQ full name \"${fullName}\"`);\n }\n const koq = await schema.getItem(koqName, KindOfQuantity);\n if (!koq) {\n throw new Error(\n `Invalid kind of quantity \"${koqName}\" specified in KoQ full name \"${fullName}\" - it does not exist in schema \"${schemaName}\"`,\n );\n }\n return koq;\n}\n\nasync function getFormatterSpec(unitsProvider: UnitsProvider, koq: KindOfQuantity, unitSystem?: UnitSystemKey) {\n const formattingProps = await getFormattingProps(koq, unitSystem);\n if (!formattingProps) {\n return undefined;\n }\n const { formatProps, persistenceUnitName } = formattingProps;\n const persistenceUnit = await unitsProvider.findUnitByName(persistenceUnitName);\n const format = await QuantityFormat.createFromJSON(\"\", unitsProvider, formatProps);\n return FormatterSpec.create(\"\", format, unitsProvider, persistenceUnit);\n}\n\ninterface FormattingProps {\n formatProps: FormatProps;\n persistenceUnitName: string;\n}\n\nasync function getFormattingProps(\n koq: KindOfQuantity,\n unitSystem?: UnitSystemKey,\n): Promise<FormattingProps | undefined> {\n const persistenceUnit = await koq.persistenceUnit;\n if (!persistenceUnit) {\n return undefined;\n }\n const formatProps = await getKoqFormatProps(koq, persistenceUnit, unitSystem);\n if (!formatProps) {\n return undefined;\n }\n return { formatProps, persistenceUnitName: persistenceUnit.fullName };\n}\n\nasync function getKoqFormatProps(\n koq: KindOfQuantity,\n persistenceUnit: Unit | InvertedUnit,\n unitSystem?: UnitSystemKey,\n) {\n const unitSystems = getUnitSystemGroupNames(unitSystem);\n // use one of KOQ presentation format that matches requested unit system\n const presentationFormat = await getKoqPresentationFormat(koq, unitSystems);\n if (presentationFormat) {\n return getFormatProps(presentationFormat);\n }\n\n // use persistence unit format if it matches requested unit system and matching presentation format was not found\n const persistenceUnitSystem = await persistenceUnit.unitSystem;\n if (persistenceUnitSystem && unitSystems.includes(persistenceUnitSystem.name.toUpperCase())) {\n return getPersistenceUnitFormatProps(persistenceUnit);\n }\n\n // use default presentation format if persistence unit does not match requested unit system\n if (koq.defaultPresentationFormat) {\n return getFormatProps(koq.defaultPresentationFormat);\n }\n\n return undefined;\n}\n\nasync function getKoqPresentationFormat(koq: KindOfQuantity, unitSystems: string[]) {\n const presentationFormats = koq.presentationFormats;\n for (const system of unitSystems) {\n for (const format of presentationFormats) {\n const units = format instanceof OverrideFormat ? format.units : (await format).units;\n const lazyUnit = units && units[0][0];\n const currentUnitSystem = lazyUnit && (await lazyUnit).unitSystem;\n if (currentUnitSystem && currentUnitSystem.name.toUpperCase() === system) {\n return format;\n }\n }\n }\n return undefined;\n}\n\nasync function getFormatProps(format: LazyLoadedFormat | OverrideFormat | Format): Promise<FormatProps> {\n return format instanceof OverrideFormat ? format.getFormatProps() : (await format).toJSON();\n}\n\nfunction getPersistenceUnitFormatProps(persistenceUnit: Unit | InvertedUnit): FormatProps {\n // Same as Format \"DefaultRealU\" in Formats ecschema\n return {\n formatTraits: [\"keepSingleZero\", \"keepDecimalPoint\", \"showUnitLabel\"],\n precision: 6,\n type: \"Decimal\",\n uomSeparator: \" \",\n decimalSeparator: \".\",\n composite: { units: [{ name: persistenceUnit.fullName, label: persistenceUnit.label }] },\n };\n}\n\nfunction getUnitSystemGroupNames(unitSystem?: UnitSystemKey) {\n switch (unitSystem) {\n case \"imperial\":\n return [\"IMPERIAL\", \"USCUSTOM\", \"INTERNATIONAL\", \"FINANCE\"];\n case \"metric\":\n return [\"SI\", \"METRIC\", \"INTERNATIONAL\", \"FINANCE\"];\n case \"usCustomary\":\n return [\"USCUSTOM\", \"INTERNATIONAL\", \"FINANCE\"];\n case \"usSurvey\":\n return [\"USSURVEY\", \"USCUSTOM\", \"INTERNATIONAL\", \"FINANCE\"];\n }\n return [];\n}\n"]}
1
+ {"version":3,"file":"Formatting.js","sourceRoot":"","sources":["../../src/core-interop/Formatting.ts"],"names":[],"mappings":"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","sourcesContent":["/*---------------------------------------------------------------------------------------------\n * Copyright (c) Bentley Systems, Incorporated. All rights reserved.\n * See LICENSE.md in the project root for license terms and full copyright notice.\n *--------------------------------------------------------------------------------------------*/\n\nimport { FormatterSpec, Format as QuantityFormat } from \"@itwin/core-quantity\";\nimport { createDefaultValueFormatter, parseFullClassName } from \"@itwin/presentation-shared\";\nimport { createBatchedSchemaViewGetter } from \"./Metadata.js\";\n\nimport type { FormatsProvider, UnitsProvider, UnitSystemKey } from \"@itwin/core-quantity\";\nimport type { SchemaView } from \"@itwin/ecschema-metadata\";\nimport type { IPrimitiveValueFormatter, TypedPrimitiveValue } from \"@itwin/presentation-shared\";\n\n/**\n * Subset of `SchemaView` used to look up a kind of quantity's persistence unit.\n * @public\n */\ntype PersistenceUnitSchemaView = Pick<SchemaView, \"findKindOfQuantity\" | \"getSchemaByAlias\">;\n\n/**\n * Props for `createValueFormatter` function.\n * @public\n */\ninterface CreateValueFormatterProps {\n /**\n * Supplies `FormatProps` for a property's kind of quantity, by its full name. On the frontend, `IModelApp.formatsProvider`\n * satisfies this. `getFormat` returning `undefined` means no format is registered for the kind of quantity - `baseFormatter`\n * is used in that case.\n */\n formatsProvider: Pick<FormatsProvider, \"getFormat\">;\n\n /**\n * Resolves units and is used to build format specs. On the frontend, `IModelApp.quantityFormatter` implements this\n * interface directly.\n */\n unitsProvider: UnitsProvider;\n\n /**\n * Supplies [SchemaView](https://www.itwinjs.org/reference/ecschema-metadata/context/schemaview/) instances used to look up\n * a kind of quantity's persistence unit. `IModelDb` and `IModelConnection` satisfy this shape directly.\n */\n imodel: { getSchemaView(props?: { schemas?: string[] }): Promise<PersistenceUnitSchemaView> };\n\n /**\n * An optional unit system override, forwarded to `formatsProvider.getFormat`. If not provided, `formatsProvider` formats\n * using whatever unit system it's configured with.\n */\n unitSystem?: UnitSystemKey;\n\n /**\n * Base primitive value formatter used whenever a property's value can't be formatted using unit information - e.g. the\n * property doesn't have a kind of quantity, no format is registered for it, or its kind of quantity doesn't specify a usable\n * persistence unit. Defaults to the result of `createDefaultValueFormatter` from `@itwin/presentation-shared` package.\n */\n baseFormatter?: IPrimitiveValueFormatter;\n}\n\n/**\n * Creates an instance of `IPrimitiveValueFormatter` that knows how to format values of properties with assigned kind of quantity. In\n * case the property does not have an assigned kind of quantity, the base formatter is used.\n *\n * Usage example:\n *\n * ```ts\n * import { IModelApp, IModelConnection } from \"@itwin/core-frontend\";\n * import { createValueFormatter } from \"@itwin/presentation-core-interop\";\n *\n * const imodel: IModelConnection = getIModel();\n * const formatter = createValueFormatter({\n * formatsProvider: IModelApp.formatsProvider,\n * unitsProvider: IModelApp.quantityFormatter,\n * imodel,\n * unitSystem: \"metric\",\n * });\n * const formattedValue = await formatter({ type: \"Double\", value: 1.234, koqName: \"MySchema.LengthKindOfQuantity\" });\n * ```\n *\n * On the backend, where there's no `IModelApp`, construct equivalent `formatsProvider` / `unitsProvider` instances\n * from the active iModel using `SchemaFormatsProvider` and `SchemaUnitProvider` from `@itwin/ecschema-metadata`,\n * ideally caching them per iModel.\n *\n * @public\n */\nexport function createValueFormatter(props: CreateValueFormatterProps): IPrimitiveValueFormatter {\n const { formatsProvider, unitsProvider, imodel, unitSystem } = props;\n /* v8 ignore next -- @preserve */\n const baseFormatter = props.baseFormatter ?? createDefaultValueFormatter();\n const getSchemaView = createBatchedSchemaViewGetter(imodel);\n return async function (value: TypedPrimitiveValue): Promise<string> {\n if (value.type === \"Double\" && !!value.koqName) {\n const spec = await getFormatterSpec({\n formatsProvider,\n unitsProvider,\n getSchemaView,\n koqName: value.koqName,\n unitSystem,\n });\n if (spec) {\n return spec.applyFormatting(value.value);\n }\n }\n return baseFormatter(value);\n };\n}\n\nasync function getFormatterSpec(props: {\n formatsProvider: Pick<FormatsProvider, \"getFormat\">;\n unitsProvider: UnitsProvider;\n getSchemaView: (schemaName: string) => Promise<PersistenceUnitSchemaView>;\n koqName: string;\n unitSystem?: UnitSystemKey;\n}): Promise<FormatterSpec | undefined> {\n const { formatsProvider, unitsProvider, getSchemaView, koqName, unitSystem } = props;\n const formatProps = await formatsProvider.getFormat(koqName, unitSystem);\n if (!formatProps) {\n return undefined;\n }\n const persistenceUnitName = await getPersistenceUnitName(getSchemaView, koqName);\n if (!persistenceUnitName) {\n return undefined;\n }\n const persistenceUnit = await unitsProvider.findUnitByName(persistenceUnitName);\n const format = await QuantityFormat.createFromJSON(\"\", unitsProvider, formatProps);\n return FormatterSpec.create(\"\", format, unitsProvider, persistenceUnit);\n}\n\n/**\n * `Units`/`Formats` are excluded from `SchemaView` by design (see class docs), so `SchemaView.getSchemaByAlias` can never\n * resolve them. Schemas almost universally reference them using these exact aliases, so fall back to them when\n * `getSchemaByAlias` can't help.\n */\nconst WELL_KNOWN_SCHEMA_ALIASES: Partial<Record<string, string>> = { u: \"Units\", f: \"Formats\" };\n\nasync function getPersistenceUnitName(\n getSchemaView: (schemaName: string) => Promise<PersistenceUnitSchemaView>,\n koqName: string,\n): Promise<string | undefined> {\n const { schemaName } = parseFullClassName(koqName);\n const schemaView = await getSchemaView(schemaName);\n const koq = schemaView.findKindOfQuantity(koqName);\n if (!koq) {\n return undefined;\n }\n try {\n // Despite `persistenceUnit`'s doc comment, it's returned alias-qualified (e.g. \"u:M\"), not schema-name-qualified.\n // Legacy ECDb profiles (pre EC3.2 Units/Formats migration) return it in a format this doesn't understand at all.\n const { schemaName: aliasOrSchemaName, className: unitName } = parseFullClassName(koq.persistenceUnit);\n const resolvedSchemaName =\n schemaView.getSchemaByAlias(aliasOrSchemaName)?.name ??\n WELL_KNOWN_SCHEMA_ALIASES[aliasOrSchemaName.toLowerCase()] ??\n aliasOrSchemaName;\n return `${resolvedSchemaName}.${unitName}`;\n } catch {\n return undefined;\n }\n}\n"]}