@twin.org/entity 0.10.0 → 0.10.1-next.1
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/dist/es/decorators/propertyDecorator.js +25 -0
- package/dist/es/decorators/propertyDecorator.js.map +1 -1
- package/dist/es/index.js +1 -0
- package/dist/es/index.js.map +1 -1
- package/dist/es/models/IEntitySchemaProperty.js.map +1 -1
- package/dist/es/models/IEntitySchemaPropertyIndex.js +2 -0
- package/dist/es/models/IEntitySchemaPropertyIndex.js.map +1 -0
- package/dist/es/utils/entitySchemaDiffHelper.js +27 -1
- package/dist/es/utils/entitySchemaDiffHelper.js.map +1 -1
- package/dist/es/utils/entitySchemaHelper.js +65 -0
- package/dist/es/utils/entitySchemaHelper.js.map +1 -1
- package/dist/types/decorators/propertyDecorator.d.ts +2 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/models/IEntitySchemaProperty.d.ts +7 -0
- package/dist/types/models/IEntitySchemaPropertyIndex.d.ts +18 -0
- package/dist/types/utils/entitySchemaDiffHelper.d.ts +1 -1
- package/dist/types/utils/entitySchemaHelper.d.ts +17 -1
- package/docs/changelog.md +40 -0
- package/docs/reference/classes/EntitySchemaDiffHelper.md +1 -1
- package/docs/reference/classes/EntitySchemaHelper.md +36 -0
- package/docs/reference/functions/property.md +8 -0
- package/docs/reference/index.md +1 -0
- package/docs/reference/interfaces/IEntitySchemaProperty.md +10 -0
- package/docs/reference/interfaces/IEntitySchemaPropertyIndex.md +27 -0
- package/locales/en.json +8 -0
- package/package.json +3 -3
|
@@ -3,14 +3,39 @@
|
|
|
3
3
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
4
4
|
import "reflect-metadata";
|
|
5
5
|
import "tslib";
|
|
6
|
+
import { GeneralError, Is } from "@twin.org/core";
|
|
7
|
+
import { EntitySchemaPropertyType } from "../models/entitySchemaPropertyType.js";
|
|
6
8
|
import { DecoratorHelper } from "../utils/decoratorHelper.js";
|
|
7
9
|
/**
|
|
8
10
|
* Decorator to produce schema property data for entities.
|
|
9
11
|
* @param options The options for the property.
|
|
10
12
|
* @returns The property decorator.
|
|
13
|
+
* @throws GeneralError if an index group is declared on an object or array property.
|
|
14
|
+
* @throws GeneralError if the same index group name is declared more than once for the property.
|
|
11
15
|
*/
|
|
12
16
|
export function property(options) {
|
|
13
17
|
return (target, propertyKey) => {
|
|
18
|
+
if (Is.arrayValue(options.indexGroup)) {
|
|
19
|
+
if (options.type === EntitySchemaPropertyType.Object ||
|
|
20
|
+
options.type === EntitySchemaPropertyType.Array) {
|
|
21
|
+
throw new GeneralError("propertyDecorator", "indexGroupTypeNotSupported", {
|
|
22
|
+
property: propertyKey,
|
|
23
|
+
type: options.type
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
const seenGroups = new Set();
|
|
27
|
+
for (const propertyIndex of options.indexGroup) {
|
|
28
|
+
if (Is.stringValue(propertyIndex?.name)) {
|
|
29
|
+
if (seenGroups.has(propertyIndex.name)) {
|
|
30
|
+
throw new GeneralError("propertyDecorator", "duplicateIndexGroup", {
|
|
31
|
+
property: propertyKey,
|
|
32
|
+
group: propertyIndex.name
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
seenGroups.add(propertyIndex.name);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
14
39
|
const entitySchema = DecoratorHelper.getSchema(target);
|
|
15
40
|
entitySchema.properties ??= [];
|
|
16
41
|
const idx = entitySchema.properties.findIndex(p => p.property === propertyKey);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"propertyDecorator.js","sourceRoot":"","sources":["../../../src/decorators/propertyDecorator.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,uDAAuD;AACvD,OAAO,kBAAkB,CAAC;AAC1B,OAAO,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"propertyDecorator.js","sourceRoot":"","sources":["../../../src/decorators/propertyDecorator.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,uDAAuD;AACvD,OAAO,kBAAkB,CAAC;AAC1B,OAAO,OAAO,CAAC;AACf,OAAO,EAAE,YAAY,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,wBAAwB,EAAE,MAAM,uCAAuC,CAAC;AAEjF,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAE9D;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAC,OAAgD;IACxE,OAAO,CAAC,MAAW,EAAE,WAAmB,EAAE,EAAE;QAC3C,IAAI,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;YACvC,IACC,OAAO,CAAC,IAAI,KAAK,wBAAwB,CAAC,MAAM;gBAChD,OAAO,CAAC,IAAI,KAAK,wBAAwB,CAAC,KAAK,EAC9C,CAAC;gBACF,MAAM,IAAI,YAAY,CAAC,mBAAmB,EAAE,4BAA4B,EAAE;oBACzE,QAAQ,EAAE,WAAW;oBACrB,IAAI,EAAE,OAAO,CAAC,IAAI;iBAClB,CAAC,CAAC;YACJ,CAAC;YAED,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;YACrC,KAAK,MAAM,aAAa,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;gBAChD,IAAI,EAAE,CAAC,WAAW,CAAC,aAAa,EAAE,IAAI,CAAC,EAAE,CAAC;oBACzC,IAAI,UAAU,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC;wBACxC,MAAM,IAAI,YAAY,CAAC,mBAAmB,EAAE,qBAAqB,EAAE;4BAClE,QAAQ,EAAE,WAAW;4BACrB,KAAK,EAAE,aAAa,CAAC,IAAI;yBACzB,CAAC,CAAC;oBACJ,CAAC;oBACD,UAAU,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;gBACpC,CAAC;YACF,CAAC;QACF,CAAC;QAED,MAAM,YAAY,GAAG,eAAe,CAAC,SAAS,CAAM,MAAM,CAAC,CAAC;QAC5D,YAAY,CAAC,UAAU,KAAK,EAAE,CAAC;QAC/B,MAAM,GAAG,GAAG,YAAY,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,WAAW,CAAC,CAAC;QAC/E,IAAI,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC;YAChB,YAAY,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG;gBAC9B,GAAG,OAAO;gBACV,QAAQ,EAAE,WAAW;aACrB,CAAC;QACH,CAAC;aAAM,CAAC;YACP,YAAY,CAAC,UAAU,CAAC,IAAI,CAAC;gBAC5B,GAAG,OAAO;gBACV,QAAQ,EAAE,WAAW;aACrB,CAAC,CAAC;QACJ,CAAC;QACD,eAAe,CAAC,SAAS,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACjD,CAAC,CAAC;AACH,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\n/* eslint-disable @typescript-eslint/no-explicit-any */\nimport \"reflect-metadata\";\nimport \"tslib\";\nimport { GeneralError, Is } from \"@twin.org/core\";\nimport { EntitySchemaPropertyType } from \"../models/entitySchemaPropertyType.js\";\nimport type { IEntitySchemaProperty } from \"../models/IEntitySchemaProperty.js\";\nimport { DecoratorHelper } from \"../utils/decoratorHelper.js\";\n\n/**\n * Decorator to produce schema property data for entities.\n * @param options The options for the property.\n * @returns The property decorator.\n * @throws GeneralError if an index group is declared on an object or array property.\n * @throws GeneralError if the same index group name is declared more than once for the property.\n */\nexport function property(options: Omit<IEntitySchemaProperty, \"property\">): any {\n\treturn (target: any, propertyKey: string) => {\n\t\tif (Is.arrayValue(options.indexGroup)) {\n\t\t\tif (\n\t\t\t\toptions.type === EntitySchemaPropertyType.Object ||\n\t\t\t\toptions.type === EntitySchemaPropertyType.Array\n\t\t\t) {\n\t\t\t\tthrow new GeneralError(\"propertyDecorator\", \"indexGroupTypeNotSupported\", {\n\t\t\t\t\tproperty: propertyKey,\n\t\t\t\t\ttype: options.type\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst seenGroups = new Set<string>();\n\t\t\tfor (const propertyIndex of options.indexGroup) {\n\t\t\t\tif (Is.stringValue(propertyIndex?.name)) {\n\t\t\t\t\tif (seenGroups.has(propertyIndex.name)) {\n\t\t\t\t\t\tthrow new GeneralError(\"propertyDecorator\", \"duplicateIndexGroup\", {\n\t\t\t\t\t\t\tproperty: propertyKey,\n\t\t\t\t\t\t\tgroup: propertyIndex.name\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tseenGroups.add(propertyIndex.name);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst entitySchema = DecoratorHelper.getSchema<any>(target);\n\t\tentitySchema.properties ??= [];\n\t\tconst idx = entitySchema.properties.findIndex(p => p.property === propertyKey);\n\t\tif (idx !== -1) {\n\t\t\tentitySchema.properties[idx] = {\n\t\t\t\t...options,\n\t\t\t\tproperty: propertyKey\n\t\t\t};\n\t\t} else {\n\t\t\tentitySchema.properties.push({\n\t\t\t\t...options,\n\t\t\t\tproperty: propertyKey\n\t\t\t});\n\t\t}\n\t\tDecoratorHelper.setSchema(target, entitySchema);\n\t};\n}\n"]}
|
package/dist/es/index.js
CHANGED
|
@@ -12,6 +12,7 @@ export * from "./models/IComparatorGroup.js";
|
|
|
12
12
|
export * from "./models/IEntitySchema.js";
|
|
13
13
|
export * from "./models/IEntitySchemaOptions.js";
|
|
14
14
|
export * from "./models/IEntitySchemaProperty.js";
|
|
15
|
+
export * from "./models/IEntitySchemaPropertyIndex.js";
|
|
15
16
|
export * from "./models/IEntitySort.js";
|
|
16
17
|
export * from "./models/logicalOperator.js";
|
|
17
18
|
export * from "./models/sortDirection.js";
|
package/dist/es/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,cAAc,iCAAiC,CAAC;AAChD,cAAc,mCAAmC,CAAC;AAClD,cAAc,oCAAoC,CAAC;AACnD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wCAAwC,CAAC;AACvD,cAAc,sCAAsC,CAAC;AACrD,cAAc,yBAAyB,CAAC;AACxC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kCAAkC,CAAC;AACjD,cAAc,mCAAmC,CAAC;AAClD,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,mCAAmC,CAAC;AAClD,cAAc,yBAAyB,CAAC;AACxC,cAAc,+BAA+B,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nexport * from \"./decorators/entityDecorator.js\";\nexport * from \"./decorators/propertyDecorator.js\";\nexport * from \"./factories/entitySchemaFactory.js\";\nexport * from \"./models/comparisonOperator.js\";\nexport * from \"./models/entityCondition.js\";\nexport * from \"./models/entitySchemaPropertyFormat.js\";\nexport * from \"./models/entitySchemaPropertyType.js\";\nexport * from \"./models/IComparator.js\";\nexport * from \"./models/IComparatorGroup.js\";\nexport * from \"./models/IEntitySchema.js\";\nexport * from \"./models/IEntitySchemaOptions.js\";\nexport * from \"./models/IEntitySchemaProperty.js\";\nexport * from \"./models/IEntitySort.js\";\nexport * from \"./models/logicalOperator.js\";\nexport * from \"./models/sortDirection.js\";\nexport * from \"./utils/decoratorHelper.js\";\nexport * from \"./utils/entityConditions.js\";\nexport * from \"./utils/entitySchemaHelper.js\";\nexport * from \"./utils/entitySchemaDiffHelper.js\";\nexport * from \"./utils/entitySorter.js\";\nexport * from \"./models/IEntitySchemaDiff.js\";\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,cAAc,iCAAiC,CAAC;AAChD,cAAc,mCAAmC,CAAC;AAClD,cAAc,oCAAoC,CAAC;AACnD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wCAAwC,CAAC;AACvD,cAAc,sCAAsC,CAAC;AACrD,cAAc,yBAAyB,CAAC;AACxC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kCAAkC,CAAC;AACjD,cAAc,mCAAmC,CAAC;AAClD,cAAc,wCAAwC,CAAC;AACvD,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,mCAAmC,CAAC;AAClD,cAAc,yBAAyB,CAAC;AACxC,cAAc,+BAA+B,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nexport * from \"./decorators/entityDecorator.js\";\nexport * from \"./decorators/propertyDecorator.js\";\nexport * from \"./factories/entitySchemaFactory.js\";\nexport * from \"./models/comparisonOperator.js\";\nexport * from \"./models/entityCondition.js\";\nexport * from \"./models/entitySchemaPropertyFormat.js\";\nexport * from \"./models/entitySchemaPropertyType.js\";\nexport * from \"./models/IComparator.js\";\nexport * from \"./models/IComparatorGroup.js\";\nexport * from \"./models/IEntitySchema.js\";\nexport * from \"./models/IEntitySchemaOptions.js\";\nexport * from \"./models/IEntitySchemaProperty.js\";\nexport * from \"./models/IEntitySchemaPropertyIndex.js\";\nexport * from \"./models/IEntitySort.js\";\nexport * from \"./models/logicalOperator.js\";\nexport * from \"./models/sortDirection.js\";\nexport * from \"./utils/decoratorHelper.js\";\nexport * from \"./utils/entityConditions.js\";\nexport * from \"./utils/entitySchemaHelper.js\";\nexport * from \"./utils/entitySchemaDiffHelper.js\";\nexport * from \"./utils/entitySorter.js\";\nexport * from \"./models/IEntitySchemaDiff.js\";\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IEntitySchemaProperty.js","sourceRoot":"","sources":["../../../src/models/IEntitySchemaProperty.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { EntitySchemaPropertyFormat } from \"./entitySchemaPropertyFormat.js\";\nimport type { EntitySchemaPropertyType } from \"./entitySchemaPropertyType.js\";\nimport type { SortDirection } from \"./sortDirection.js\";\n\n/**\n * Definition for an entity schema property.\n */\nexport interface IEntitySchemaProperty<T = unknown> {\n\t/**\n\t * The property name from the entity.\n\t */\n\tproperty: keyof T;\n\n\t/**\n\t * The type of the property.\n\t */\n\ttype: EntitySchemaPropertyType;\n\n\t/**\n\t * The format of the property.\n\t */\n\tformat?: EntitySchemaPropertyFormat;\n\n\t/**\n\t * The maximum length of the property value i.e. for text fields.\n\t */\n\tmaxLength?: number;\n\n\t/**\n\t * Is this the primary index property.\n\t */\n\tisPrimary?: boolean;\n\n\t/**\n\t * Is this a secondary index property.\n\t */\n\tisSecondary?: boolean;\n\n\t/**\n\t * Is this property used as the optimistic-lock version token.\n\t * When true, connectors automatically manage the field: the value is\n\t * incremented on every successful write, and a write is rejected with a\n\t * ConflictError when the submitted value does not match the stored value.\n\t * Must be an integer property.\n\t */\n\tisVersion?: boolean;\n\n\t/**\n\t * Default sort direction for this field, leave empty if not sortable.\n\t */\n\tsortDirection?: SortDirection;\n\n\t/**\n\t * Is the property optional.\n\t */\n\toptional?: boolean;\n\n\t/**\n\t * The type of the item (only applies when type is `array`).\n\t */\n\titemType?: EntitySchemaPropertyType;\n\n\t/**\n\t * The type ref of the item (only applies when type is either `array` or `object`).\n\t */\n\titemTypeRef?: string;\n\n\t/**\n\t * Description of the object.\n\t */\n\tdescription?: string;\n\n\t/**\n\t * Examples of the property values.\n\t */\n\texamples?: unknown[];\n\n\t/**\n\t * A default value which can be used in migrations when the property value is not provided.\n\t */\n\tdefaultValue?: unknown;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"IEntitySchemaProperty.js","sourceRoot":"","sources":["../../../src/models/IEntitySchemaProperty.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { EntitySchemaPropertyFormat } from \"./entitySchemaPropertyFormat.js\";\nimport type { EntitySchemaPropertyType } from \"./entitySchemaPropertyType.js\";\nimport type { IEntitySchemaPropertyIndex } from \"./IEntitySchemaPropertyIndex.js\";\nimport type { SortDirection } from \"./sortDirection.js\";\n\n/**\n * Definition for an entity schema property.\n */\nexport interface IEntitySchemaProperty<T = unknown> {\n\t/**\n\t * The property name from the entity.\n\t */\n\tproperty: keyof T;\n\n\t/**\n\t * The type of the property.\n\t */\n\ttype: EntitySchemaPropertyType;\n\n\t/**\n\t * The format of the property.\n\t */\n\tformat?: EntitySchemaPropertyFormat;\n\n\t/**\n\t * The maximum length of the property value i.e. for text fields.\n\t */\n\tmaxLength?: number;\n\n\t/**\n\t * Is this the primary index property.\n\t */\n\tisPrimary?: boolean;\n\n\t/**\n\t * Is this a secondary index property.\n\t */\n\tisSecondary?: boolean;\n\n\t/**\n\t * The composite indexes this property is part of.\n\t * Connectors can use these to build a composite index for each group name,\n\t * combining all the properties which share that name, ordered by their index.\n\t */\n\tindexGroup?: IEntitySchemaPropertyIndex[];\n\n\t/**\n\t * Is this property used as the optimistic-lock version token.\n\t * When true, connectors automatically manage the field: the value is\n\t * incremented on every successful write, and a write is rejected with a\n\t * ConflictError when the submitted value does not match the stored value.\n\t * Must be an integer property.\n\t */\n\tisVersion?: boolean;\n\n\t/**\n\t * Default sort direction for this field, leave empty if not sortable.\n\t */\n\tsortDirection?: SortDirection;\n\n\t/**\n\t * Is the property optional.\n\t */\n\toptional?: boolean;\n\n\t/**\n\t * The type of the item (only applies when type is `array`).\n\t */\n\titemType?: EntitySchemaPropertyType;\n\n\t/**\n\t * The type ref of the item (only applies when type is either `array` or `object`).\n\t */\n\titemTypeRef?: string;\n\n\t/**\n\t * Description of the object.\n\t */\n\tdescription?: string;\n\n\t/**\n\t * Examples of the property values.\n\t */\n\texamples?: unknown[];\n\n\t/**\n\t * A default value which can be used in migrations when the property value is not provided.\n\t */\n\tdefaultValue?: unknown;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IEntitySchemaPropertyIndex.js","sourceRoot":"","sources":["../../../src/models/IEntitySchemaPropertyIndex.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { SortDirection } from \"./sortDirection.js\";\n\n/**\n * Definition of a composite index that a property is part of.\n */\nexport interface IEntitySchemaPropertyIndex {\n\t/**\n\t * The name of the composite index group.\n\t */\n\tname: string;\n\n\t/**\n\t * The sort direction for the property within the index.\n\t */\n\tdirection: SortDirection;\n\n\t/**\n\t * The position of the property within the index, ordered ascending.\n\t */\n\tindex: number;\n}\n"]}
|
|
@@ -12,7 +12,7 @@ export class EntitySchemaDiffHelper {
|
|
|
12
12
|
/**
|
|
13
13
|
* Compare two arrays of entity schema properties and return a structured diff.
|
|
14
14
|
*
|
|
15
|
-
* Properties are matched by their `property` key name. A property is considered modified when any structural field differs: `type`, `format`, `isPrimary`, `isSecondary`, `isVersion`, `sortDirection`, `optional`, `itemType`, or `itemTypeRef`.
|
|
15
|
+
* Properties are matched by their `property` key name. A property is considered modified when any structural field differs: `type`, `format`, `isPrimary`, `isSecondary`, `indexGroup`, `isVersion`, `sortDirection`, `optional`, `itemType`, or `itemTypeRef`.
|
|
16
16
|
* Documentation-only fields (`description`, `examples`) are intentionally excluded from the comparison to avoid spurious diffs.
|
|
17
17
|
*
|
|
18
18
|
* Because a pure name change cannot be detected automatically, callers may supply a `renames` list mapping old names to new names. Renamed properties appear in `modified` (never in `added` or `removed`) even when no other fields changed. Rename lookups take priority over direct same-name matches, which allows swap renames to work correctly and prevents a renamed source from silently disappearing when the target name already existed in the old schema. Self-renames (`from === to`) are ignored and the property is classified normally.
|
|
@@ -131,11 +131,37 @@ export class EntitySchemaDiffHelper {
|
|
|
131
131
|
schema1.maxLength === schema2.maxLength &&
|
|
132
132
|
schema1.isPrimary === schema2.isPrimary &&
|
|
133
133
|
schema1.isSecondary === schema2.isSecondary &&
|
|
134
|
+
EntitySchemaDiffHelper.indexGroupsEqual(schema1.indexGroup, schema2.indexGroup) &&
|
|
134
135
|
schema1.isVersion === schema2.isVersion &&
|
|
135
136
|
schema1.sortDirection === schema2.sortDirection &&
|
|
136
137
|
schema1.optional === schema2.optional &&
|
|
137
138
|
schema1.itemType === schema2.itemType &&
|
|
138
139
|
schema1.itemTypeRef === schema2.itemTypeRef);
|
|
139
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Compare two index group lists, treating them as unordered sets of indexes.
|
|
143
|
+
* The order a property declares its indexes in has no effect on the indexes a connector builds.
|
|
144
|
+
* @param indexGroup1 The first index group list.
|
|
145
|
+
* @param indexGroup2 The second index group list.
|
|
146
|
+
* @returns True if both lists contain the same name, direction and index values.
|
|
147
|
+
* @internal
|
|
148
|
+
*/
|
|
149
|
+
static indexGroupsEqual(indexGroup1, indexGroup2) {
|
|
150
|
+
const groups1 = indexGroup1 ?? [];
|
|
151
|
+
const unmatchedGroups2 = [...(indexGroup2 ?? [])];
|
|
152
|
+
if (groups1.length !== unmatchedGroups2.length) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
for (const group1 of groups1) {
|
|
156
|
+
const matchIndex = unmatchedGroups2.findIndex(group2 => group2?.name === group1?.name &&
|
|
157
|
+
group2?.direction === group1?.direction &&
|
|
158
|
+
group2?.index === group1?.index);
|
|
159
|
+
if (matchIndex === -1) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
unmatchedGroups2.splice(matchIndex, 1);
|
|
163
|
+
}
|
|
164
|
+
return unmatchedGroups2.length === 0;
|
|
165
|
+
}
|
|
140
166
|
}
|
|
141
167
|
//# sourceMappingURL=entitySchemaDiffHelper.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entitySchemaDiffHelper.js","sourceRoot":"","sources":["../../../src/utils/entitySchemaDiffHelper.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAKtD;;GAEG;AACH,MAAM,OAAO,sBAAsB;IAClC;;OAEG;IACI,MAAM,CAAU,UAAU,4BAA4C;IAE7E;;;;;;;;;;;;;;OAcG;IACI,MAAM,CAAC,IAAI,CACjB,aAAyC,EACzC,aAAyC,EACzC,OAAwC;QAExC,MAAM,CAAC,KAAK,CAAC,sBAAsB,CAAC,UAAU,mBAAyB,aAAa,CAAC,CAAC;QACtF,MAAM,CAAC,KAAK,CAAC,sBAAsB,CAAC,UAAU,mBAAyB,aAAa,CAAC,CAAC;QAEtF,MAAM,KAAK,GAA+B,EAAE,CAAC;QAC7C,MAAM,OAAO,GAA+B,EAAE,CAAC;QAC/C,MAAM,QAAQ,GAAwC,EAAE,CAAC;QACzD,MAAM,SAAS,GAAmC,EAAE,CAAC;QAErD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoC,CAAC;QAC3D,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;YAClC,MAAM,OAAO,GAAG,IAAI,CAAC,QAAkB,CAAC;YACxC,IAAI,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;gBACzB,MAAM,IAAI,YAAY,CAAC,sBAAsB,CAAC,UAAU,EAAE,sBAAsB,EAAE;oBACjF,QAAQ,EAAE,OAAO;iBACjB,CAAC,CAAC;YACJ,CAAC;YACD,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAC3B,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoC,CAAC;QAC3D,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;YAClC,MAAM,OAAO,GAAG,IAAI,CAAC,QAAkB,CAAC;YACxC,IAAI,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;gBACzB,MAAM,IAAI,YAAY,CAAC,sBAAsB,CAAC,UAAU,EAAE,sBAAsB,EAAE;oBACjF,QAAQ,EAAE,OAAO;iBACjB,CAAC,CAAC;YACJ,CAAC;YACD,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAC3B,CAAC;QAED,yDAAyD;QACzD,MAAM,YAAY,GAAG,IAAI,GAAG,EAAkB,CAAC;QAC/C,IAAI,OAAO,EAAE,CAAC;YACb,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC9B,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;YAC1C,CAAC;QACF,CAAC;QAED,qFAAqF;QACrF,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAU,CAAC;QAC3C,2FAA2F;QAC3F,MAAM,sBAAsB,GAAG,IAAI,GAAG,EAAU,CAAC;QAEjD,KAAK,MAAM,OAAO,IAAI,aAAa,EAAE,CAAC;YACrC,MAAM,GAAG,GAAG,OAAO,CAAC,QAAkB,CAAC;YAEvC,sEAAsE;YACtE,wDAAwD;YACxD,iFAAiF;YACjF,qFAAqF;YACrF,MAAM,OAAO,GAAG,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACtC,MAAM,aAAa,GAAG,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YAE9E,IACC,OAAO,KAAK,SAAS;gBACrB,OAAO,KAAK,GAAG;gBACf,aAAa,KAAK,SAAS;gBAC3B,CAAC,gBAAgB,CAAC,GAAG,CAAC,OAAO,CAAC,EAC7B,CAAC;gBACF,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;gBACpD,gBAAgB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;gBAC9B,sBAAsB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACjC,CAAC;iBAAM,CAAC;gBACP,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBAChC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;oBAC3B,IAAI,CAAC,sBAAsB,CAAC,qBAAqB,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC;wBACrE,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;oBAC/C,CAAC;yBAAM,CAAC;wBACP,SAAS,CAAC,IAAI,CAAC,OAAuC,CAAC,CAAC;oBACzD,CAAC;gBACF,CAAC;qBAAM,CAAC;oBACP,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBACrB,CAAC;YACF,CAAC;QACF,CAAC;QAED,KAAK,MAAM,OAAO,IAAI,aAAa,EAAE,CAAC;YACrC,MAAM,GAAG,GAAG,OAAO,CAAC,QAAkB,CAAC;YACvC,wFAAwF;YACxF,+DAA+D;YAC/D,2EAA2E;YAC3E,IAAI,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,sBAAsB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBACzF,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACvB,CAAC;QACF,CAAC;QAED,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IAChD,CAAC;IAED;;;;OAIG;IACI,MAAM,CAAC,UAAU,CAAO,IAA6B;QAC3D,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,UAAU,UAAgB,IAAI,CAAC,CAAC;QACrE,MAAM,CAAC,KAAK,CAAC,sBAAsB,CAAC,UAAU,gBAAsB,IAAI,CAAC,KAAK,CAAC,CAAC;QAChF,MAAM,CAAC,KAAK,CAAC,sBAAsB,CAAC,UAAU,kBAAwB,IAAI,CAAC,OAAO,CAAC,CAAC;QACpF,MAAM,CAAC,KAAK,CAAC,sBAAsB,CAAC,UAAU,mBAAyB,IAAI,CAAC,QAAQ,CAAC,CAAC;QACtF,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;IACrF,CAAC;IAED;;;;;;OAMG;IACI,MAAM,CAAC,qBAAqB,CAClC,OAAiC,EACjC,OAAiC;QAEjC,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,UAAU,aAAmB,OAAO,CAAC,CAAC;QAC3E,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,UAAU,aAAmB,OAAO,CAAC,CAAC;QAC3E,OAAO,CACN,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC,IAAI;YAC7B,OAAO,CAAC,MAAM,KAAK,OAAO,CAAC,MAAM;YACjC,OAAO,CAAC,SAAS,KAAK,OAAO,CAAC,SAAS;YACvC,OAAO,CAAC,SAAS,KAAK,OAAO,CAAC,SAAS;YACvC,OAAO,CAAC,WAAW,KAAK,OAAO,CAAC,WAAW;YAC3C,OAAO,CAAC,SAAS,KAAK,OAAO,CAAC,SAAS;YACvC,OAAO,CAAC,aAAa,KAAK,OAAO,CAAC,aAAa;YAC/C,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,QAAQ;YACrC,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,QAAQ;YACrC,OAAO,CAAC,WAAW,KAAK,OAAO,CAAC,WAAW,CAC3C,CAAC;IACH,CAAC","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { GeneralError, Guards } from \"@twin.org/core\";\nimport { nameof } from \"@twin.org/nameof\";\nimport type { IEntitySchemaDiff } from \"../models/IEntitySchemaDiff.js\";\nimport type { IEntitySchemaProperty } from \"../models/IEntitySchemaProperty.js\";\n\n/**\n * Helper class for comparing entity schemas and generating diffs.\n */\nexport class EntitySchemaDiffHelper {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<EntitySchemaDiffHelper>();\n\n\t/**\n\t * Compare two arrays of entity schema properties and return a structured diff.\n\t *\n\t * Properties are matched by their `property` key name. A property is considered modified when any structural field differs: `type`, `format`, `isPrimary`, `isSecondary`, `isVersion`, `sortDirection`, `optional`, `itemType`, or `itemTypeRef`.\n\t * Documentation-only fields (`description`, `examples`) are intentionally excluded from the comparison to avoid spurious diffs.\n\t *\n\t * Because a pure name change cannot be detected automatically, callers may supply a `renames` list mapping old names to new names. Renamed properties appear in `modified` (never in `added` or `removed`) even when no other fields changed. Rename lookups take priority over direct same-name matches, which allows swap renames to work correctly and prevents a renamed source from silently disappearing when the target name already existed in the old schema. Self-renames (`from === to`) are ignored and the property is classified normally.\n\t *\n\t * When `renames` contains duplicate entries: if two entries share the same target, the last definition wins and the first source is treated as removed; if two entries share the same source, the first target wins and the second target is treated as added. Both cases are deterministic but callers should avoid them.\n\t * @param oldProperties The property descriptors from the current (live) schema.\n\t * @param newProperties The property descriptors from the target (new) schema.\n\t * @param renames Optional list of property renames `{ from, to }` where `from` is the old name and `to` is the new name.\n\t * @returns A diff object with `added`, `removed`, `modified`, and `unchanged` arrays, each containing full `IEntitySchemaProperty` descriptors.\n\t * @throws `GeneralError` if either input array contains duplicate property keys.\n\t */\n\tpublic static diff<T, U = T>(\n\t\toldProperties: IEntitySchemaProperty<T>[],\n\t\tnewProperties: IEntitySchemaProperty<U>[],\n\t\trenames?: { from: string; to: string }[]\n\t): IEntitySchemaDiff<T, U> {\n\t\tGuards.array(EntitySchemaDiffHelper.CLASS_NAME, nameof(oldProperties), oldProperties);\n\t\tGuards.array(EntitySchemaDiffHelper.CLASS_NAME, nameof(newProperties), newProperties);\n\n\t\tconst added: IEntitySchemaProperty<U>[] = [];\n\t\tconst removed: IEntitySchemaProperty<T>[] = [];\n\t\tconst modified: IEntitySchemaDiff<T, U>[\"modified\"] = [];\n\t\tconst unchanged: IEntitySchemaProperty<T | U>[] = [];\n\n\t\tconst oldMap = new Map<string, IEntitySchemaProperty<T>>();\n\t\tfor (const prop of oldProperties) {\n\t\t\tconst propKey = prop.property as string;\n\t\t\tif (oldMap.has(propKey)) {\n\t\t\t\tthrow new GeneralError(EntitySchemaDiffHelper.CLASS_NAME, \"duplicateOldProperty\", {\n\t\t\t\t\tproperty: propKey\n\t\t\t\t});\n\t\t\t}\n\t\t\toldMap.set(propKey, prop);\n\t\t}\n\n\t\tconst newMap = new Map<string, IEntitySchemaProperty<U>>();\n\t\tfor (const prop of newProperties) {\n\t\t\tconst propKey = prop.property as string;\n\t\t\tif (newMap.has(propKey)) {\n\t\t\t\tthrow new GeneralError(EntitySchemaDiffHelper.CLASS_NAME, \"duplicateNewProperty\", {\n\t\t\t\t\tproperty: propKey\n\t\t\t\t});\n\t\t\t}\n\t\t\tnewMap.set(propKey, prop);\n\t\t}\n\n\t\t// new-name → old-name, used when iterating newProperties\n\t\tconst renameToFrom = new Map<string, string>();\n\t\tif (renames) {\n\t\t\tfor (const rename of renames) {\n\t\t\t\trenameToFrom.set(rename.to, rename.from);\n\t\t\t}\n\t\t}\n\n\t\t// Old names that were consumed by a rename (prevents double-use of the same source).\n\t\tconst consumedByRename = new Set<string>();\n\t\t// New names matched via rename path (their same-named old prop is not their direct match).\n\t\tconst newKeyMatchedViaRename = new Set<string>();\n\n\t\tfor (const newProp of newProperties) {\n\t\t\tconst key = newProp.property as string;\n\n\t\t\t// Rename lookup takes priority over a direct same-name match so that:\n\t\t\t// - swap renames work (both names exist in old and new)\n\t\t\t// - a renamed source does not vanish when the target name already existed in old\n\t\t\t// Self-renames (fromKey === key) are skipped so the property is classified normally.\n\t\t\tconst fromKey = renameToFrom.get(key);\n\t\t\tconst renamedSource = fromKey !== undefined ? oldMap.get(fromKey) : undefined;\n\n\t\t\tif (\n\t\t\t\tfromKey !== undefined &&\n\t\t\t\tfromKey !== key &&\n\t\t\t\trenamedSource !== undefined &&\n\t\t\t\t!consumedByRename.has(fromKey)\n\t\t\t) {\n\t\t\t\tmodified.push({ from: renamedSource, to: newProp });\n\t\t\t\tconsumedByRename.add(fromKey);\n\t\t\t\tnewKeyMatchedViaRename.add(key);\n\t\t\t} else {\n\t\t\t\tconst oldProp = oldMap.get(key);\n\t\t\t\tif (oldProp !== undefined) {\n\t\t\t\t\tif (!EntitySchemaDiffHelper.schemaPropertiesEqual(oldProp, newProp)) {\n\t\t\t\t\t\tmodified.push({ from: oldProp, to: newProp });\n\t\t\t\t\t} else {\n\t\t\t\t\t\tunchanged.push(newProp as IEntitySchemaProperty<T | U>);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tadded.push(newProp);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor (const oldProp of oldProperties) {\n\t\t\tconst key = oldProp.property as string;\n\t\t\t// Removed when absent from new, or when its same-named new prop was claimed by a rename\n\t\t\t// (meaning this old prop was not the match for that new prop).\n\t\t\t// Exception: skip if this old prop was itself consumed as a rename source.\n\t\t\tif ((!newMap.has(key) || newKeyMatchedViaRename.has(key)) && !consumedByRename.has(key)) {\n\t\t\t\tremoved.push(oldProp);\n\t\t\t}\n\t\t}\n\n\t\treturn { added, removed, modified, unchanged };\n\t}\n\n\t/**\n\t * Returns true when the diff contains at least one added, removed, or modified property.\n\t * @param diff The diff to check.\n\t * @returns True if the diff has any structural changes.\n\t */\n\tpublic static hasChanges<T, U>(diff: IEntitySchemaDiff<T, U>): boolean {\n\t\tGuards.object(EntitySchemaDiffHelper.CLASS_NAME, nameof(diff), diff);\n\t\tGuards.array(EntitySchemaDiffHelper.CLASS_NAME, nameof(diff.added), diff.added);\n\t\tGuards.array(EntitySchemaDiffHelper.CLASS_NAME, nameof(diff.removed), diff.removed);\n\t\tGuards.array(EntitySchemaDiffHelper.CLASS_NAME, nameof(diff.modified), diff.modified);\n\t\treturn diff.added.length > 0 || diff.removed.length > 0 || diff.modified.length > 0;\n\t}\n\n\t/**\n\t * Compare two property descriptors for structural equality.\n\t * The `property` name field and documentation fields (`description`, `examples`) are intentionally excluded - callers match by name before invoking this method.\n\t * @param schema1 The first property descriptor.\n\t * @param schema2 The second property descriptor.\n\t * @returns True if all structural fields are equal.\n\t */\n\tpublic static schemaPropertiesEqual<T, U>(\n\t\tschema1: IEntitySchemaProperty<T>,\n\t\tschema2: IEntitySchemaProperty<U>\n\t): boolean {\n\t\tGuards.object(EntitySchemaDiffHelper.CLASS_NAME, nameof(schema1), schema1);\n\t\tGuards.object(EntitySchemaDiffHelper.CLASS_NAME, nameof(schema2), schema2);\n\t\treturn (\n\t\t\tschema1.type === schema2.type &&\n\t\t\tschema1.format === schema2.format &&\n\t\t\tschema1.maxLength === schema2.maxLength &&\n\t\t\tschema1.isPrimary === schema2.isPrimary &&\n\t\t\tschema1.isSecondary === schema2.isSecondary &&\n\t\t\tschema1.isVersion === schema2.isVersion &&\n\t\t\tschema1.sortDirection === schema2.sortDirection &&\n\t\t\tschema1.optional === schema2.optional &&\n\t\t\tschema1.itemType === schema2.itemType &&\n\t\t\tschema1.itemTypeRef === schema2.itemTypeRef\n\t\t);\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"entitySchemaDiffHelper.js","sourceRoot":"","sources":["../../../src/utils/entitySchemaDiffHelper.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { GeneralError, Guards } from \"@twin.org/core\";\nimport { nameof } from \"@twin.org/nameof\";\nimport type { IEntitySchemaDiff } from \"../models/IEntitySchemaDiff.js\";\nimport type { IEntitySchemaProperty } from \"../models/IEntitySchemaProperty.js\";\nimport type { IEntitySchemaPropertyIndex } from \"../models/IEntitySchemaPropertyIndex.js\";\n\n/**\n * Helper class for comparing entity schemas and generating diffs.\n */\nexport class EntitySchemaDiffHelper {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<EntitySchemaDiffHelper>();\n\n\t/**\n\t * Compare two arrays of entity schema properties and return a structured diff.\n\t *\n\t * Properties are matched by their `property` key name. A property is considered modified when any structural field differs: `type`, `format`, `isPrimary`, `isSecondary`, `indexGroup`, `isVersion`, `sortDirection`, `optional`, `itemType`, or `itemTypeRef`.\n\t * Documentation-only fields (`description`, `examples`) are intentionally excluded from the comparison to avoid spurious diffs.\n\t *\n\t * Because a pure name change cannot be detected automatically, callers may supply a `renames` list mapping old names to new names. Renamed properties appear in `modified` (never in `added` or `removed`) even when no other fields changed. Rename lookups take priority over direct same-name matches, which allows swap renames to work correctly and prevents a renamed source from silently disappearing when the target name already existed in the old schema. Self-renames (`from === to`) are ignored and the property is classified normally.\n\t *\n\t * When `renames` contains duplicate entries: if two entries share the same target, the last definition wins and the first source is treated as removed; if two entries share the same source, the first target wins and the second target is treated as added. Both cases are deterministic but callers should avoid them.\n\t * @param oldProperties The property descriptors from the current (live) schema.\n\t * @param newProperties The property descriptors from the target (new) schema.\n\t * @param renames Optional list of property renames `{ from, to }` where `from` is the old name and `to` is the new name.\n\t * @returns A diff object with `added`, `removed`, `modified`, and `unchanged` arrays, each containing full `IEntitySchemaProperty` descriptors.\n\t * @throws `GeneralError` if either input array contains duplicate property keys.\n\t */\n\tpublic static diff<T, U = T>(\n\t\toldProperties: IEntitySchemaProperty<T>[],\n\t\tnewProperties: IEntitySchemaProperty<U>[],\n\t\trenames?: { from: string; to: string }[]\n\t): IEntitySchemaDiff<T, U> {\n\t\tGuards.array(EntitySchemaDiffHelper.CLASS_NAME, nameof(oldProperties), oldProperties);\n\t\tGuards.array(EntitySchemaDiffHelper.CLASS_NAME, nameof(newProperties), newProperties);\n\n\t\tconst added: IEntitySchemaProperty<U>[] = [];\n\t\tconst removed: IEntitySchemaProperty<T>[] = [];\n\t\tconst modified: IEntitySchemaDiff<T, U>[\"modified\"] = [];\n\t\tconst unchanged: IEntitySchemaProperty<T | U>[] = [];\n\n\t\tconst oldMap = new Map<string, IEntitySchemaProperty<T>>();\n\t\tfor (const prop of oldProperties) {\n\t\t\tconst propKey = prop.property as string;\n\t\t\tif (oldMap.has(propKey)) {\n\t\t\t\tthrow new GeneralError(EntitySchemaDiffHelper.CLASS_NAME, \"duplicateOldProperty\", {\n\t\t\t\t\tproperty: propKey\n\t\t\t\t});\n\t\t\t}\n\t\t\toldMap.set(propKey, prop);\n\t\t}\n\n\t\tconst newMap = new Map<string, IEntitySchemaProperty<U>>();\n\t\tfor (const prop of newProperties) {\n\t\t\tconst propKey = prop.property as string;\n\t\t\tif (newMap.has(propKey)) {\n\t\t\t\tthrow new GeneralError(EntitySchemaDiffHelper.CLASS_NAME, \"duplicateNewProperty\", {\n\t\t\t\t\tproperty: propKey\n\t\t\t\t});\n\t\t\t}\n\t\t\tnewMap.set(propKey, prop);\n\t\t}\n\n\t\t// new-name → old-name, used when iterating newProperties\n\t\tconst renameToFrom = new Map<string, string>();\n\t\tif (renames) {\n\t\t\tfor (const rename of renames) {\n\t\t\t\trenameToFrom.set(rename.to, rename.from);\n\t\t\t}\n\t\t}\n\n\t\t// Old names that were consumed by a rename (prevents double-use of the same source).\n\t\tconst consumedByRename = new Set<string>();\n\t\t// New names matched via rename path (their same-named old prop is not their direct match).\n\t\tconst newKeyMatchedViaRename = new Set<string>();\n\n\t\tfor (const newProp of newProperties) {\n\t\t\tconst key = newProp.property as string;\n\n\t\t\t// Rename lookup takes priority over a direct same-name match so that:\n\t\t\t// - swap renames work (both names exist in old and new)\n\t\t\t// - a renamed source does not vanish when the target name already existed in old\n\t\t\t// Self-renames (fromKey === key) are skipped so the property is classified normally.\n\t\t\tconst fromKey = renameToFrom.get(key);\n\t\t\tconst renamedSource = fromKey !== undefined ? oldMap.get(fromKey) : undefined;\n\n\t\t\tif (\n\t\t\t\tfromKey !== undefined &&\n\t\t\t\tfromKey !== key &&\n\t\t\t\trenamedSource !== undefined &&\n\t\t\t\t!consumedByRename.has(fromKey)\n\t\t\t) {\n\t\t\t\tmodified.push({ from: renamedSource, to: newProp });\n\t\t\t\tconsumedByRename.add(fromKey);\n\t\t\t\tnewKeyMatchedViaRename.add(key);\n\t\t\t} else {\n\t\t\t\tconst oldProp = oldMap.get(key);\n\t\t\t\tif (oldProp !== undefined) {\n\t\t\t\t\tif (!EntitySchemaDiffHelper.schemaPropertiesEqual(oldProp, newProp)) {\n\t\t\t\t\t\tmodified.push({ from: oldProp, to: newProp });\n\t\t\t\t\t} else {\n\t\t\t\t\t\tunchanged.push(newProp as IEntitySchemaProperty<T | U>);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tadded.push(newProp);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor (const oldProp of oldProperties) {\n\t\t\tconst key = oldProp.property as string;\n\t\t\t// Removed when absent from new, or when its same-named new prop was claimed by a rename\n\t\t\t// (meaning this old prop was not the match for that new prop).\n\t\t\t// Exception: skip if this old prop was itself consumed as a rename source.\n\t\t\tif ((!newMap.has(key) || newKeyMatchedViaRename.has(key)) && !consumedByRename.has(key)) {\n\t\t\t\tremoved.push(oldProp);\n\t\t\t}\n\t\t}\n\n\t\treturn { added, removed, modified, unchanged };\n\t}\n\n\t/**\n\t * Returns true when the diff contains at least one added, removed, or modified property.\n\t * @param diff The diff to check.\n\t * @returns True if the diff has any structural changes.\n\t */\n\tpublic static hasChanges<T, U>(diff: IEntitySchemaDiff<T, U>): boolean {\n\t\tGuards.object(EntitySchemaDiffHelper.CLASS_NAME, nameof(diff), diff);\n\t\tGuards.array(EntitySchemaDiffHelper.CLASS_NAME, nameof(diff.added), diff.added);\n\t\tGuards.array(EntitySchemaDiffHelper.CLASS_NAME, nameof(diff.removed), diff.removed);\n\t\tGuards.array(EntitySchemaDiffHelper.CLASS_NAME, nameof(diff.modified), diff.modified);\n\t\treturn diff.added.length > 0 || diff.removed.length > 0 || diff.modified.length > 0;\n\t}\n\n\t/**\n\t * Compare two property descriptors for structural equality.\n\t * The `property` name field and documentation fields (`description`, `examples`) are intentionally excluded - callers match by name before invoking this method.\n\t * @param schema1 The first property descriptor.\n\t * @param schema2 The second property descriptor.\n\t * @returns True if all structural fields are equal.\n\t */\n\tpublic static schemaPropertiesEqual<T, U>(\n\t\tschema1: IEntitySchemaProperty<T>,\n\t\tschema2: IEntitySchemaProperty<U>\n\t): boolean {\n\t\tGuards.object(EntitySchemaDiffHelper.CLASS_NAME, nameof(schema1), schema1);\n\t\tGuards.object(EntitySchemaDiffHelper.CLASS_NAME, nameof(schema2), schema2);\n\t\treturn (\n\t\t\tschema1.type === schema2.type &&\n\t\t\tschema1.format === schema2.format &&\n\t\t\tschema1.maxLength === schema2.maxLength &&\n\t\t\tschema1.isPrimary === schema2.isPrimary &&\n\t\t\tschema1.isSecondary === schema2.isSecondary &&\n\t\t\tEntitySchemaDiffHelper.indexGroupsEqual(schema1.indexGroup, schema2.indexGroup) &&\n\t\t\tschema1.isVersion === schema2.isVersion &&\n\t\t\tschema1.sortDirection === schema2.sortDirection &&\n\t\t\tschema1.optional === schema2.optional &&\n\t\t\tschema1.itemType === schema2.itemType &&\n\t\t\tschema1.itemTypeRef === schema2.itemTypeRef\n\t\t);\n\t}\n\n\t/**\n\t * Compare two index group lists, treating them as unordered sets of indexes.\n\t * The order a property declares its indexes in has no effect on the indexes a connector builds.\n\t * @param indexGroup1 The first index group list.\n\t * @param indexGroup2 The second index group list.\n\t * @returns True if both lists contain the same name, direction and index values.\n\t * @internal\n\t */\n\tprivate static indexGroupsEqual(\n\t\tindexGroup1?: IEntitySchemaPropertyIndex[],\n\t\tindexGroup2?: IEntitySchemaPropertyIndex[]\n\t): boolean {\n\t\tconst groups1 = indexGroup1 ?? [];\n\t\tconst unmatchedGroups2 = [...(indexGroup2 ?? [])];\n\n\t\tif (groups1.length !== unmatchedGroups2.length) {\n\t\t\treturn false;\n\t\t}\n\n\t\tfor (const group1 of groups1) {\n\t\t\tconst matchIndex = unmatchedGroups2.findIndex(\n\t\t\t\tgroup2 =>\n\t\t\t\t\tgroup2?.name === group1?.name &&\n\t\t\t\t\tgroup2?.direction === group1?.direction &&\n\t\t\t\t\tgroup2?.index === group1?.index\n\t\t\t);\n\t\t\tif (matchIndex === -1) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tunmatchedGroups2.splice(matchIndex, 1);\n\t\t}\n\t\treturn unmatchedGroups2.length === 0;\n\t}\n}\n"]}
|
|
@@ -4,6 +4,7 @@ import { GeneralError, Guards, Is } from "@twin.org/core";
|
|
|
4
4
|
import { DecoratorHelper } from "./decoratorHelper.js";
|
|
5
5
|
import { EntitySchemaPropertyFormat } from "../models/entitySchemaPropertyFormat.js";
|
|
6
6
|
import { EntitySchemaPropertyType } from "../models/entitySchemaPropertyType.js";
|
|
7
|
+
import { SortDirection } from "../models/sortDirection.js";
|
|
7
8
|
/**
|
|
8
9
|
* Class to help with entity schema operations.
|
|
9
10
|
*/
|
|
@@ -86,6 +87,70 @@ export class EntitySchemaHelper {
|
|
|
86
87
|
}))
|
|
87
88
|
: undefined;
|
|
88
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Get the composite index groups from the schema.
|
|
92
|
+
* Each property can be part of multiple indexes through its `indexGroup` list, so a property
|
|
93
|
+
* can appear in more than one group. The properties within a group are ordered by the `index`
|
|
94
|
+
* of their index entry, and each is returned with the sort direction it declared for that group.
|
|
95
|
+
* @param entitySchema The entity schema to find the index groups from.
|
|
96
|
+
* @returns The properties and their directions keyed by the group name, empty if there are no groups.
|
|
97
|
+
* @throws GeneralError if an index entry has an invalid direction or index, or if two properties
|
|
98
|
+
* claim the same index within the same group, or if a group contains fewer than two properties.
|
|
99
|
+
*/
|
|
100
|
+
static getIndexGroups(entitySchema) {
|
|
101
|
+
Guards.object(EntitySchemaHelper.CLASS_NAME, "entitySchema", entitySchema);
|
|
102
|
+
const groupEntries = {};
|
|
103
|
+
const groupIndexes = {};
|
|
104
|
+
for (const property of entitySchema.properties ?? []) {
|
|
105
|
+
if (Is.arrayValue(property.indexGroup)) {
|
|
106
|
+
for (const propertyIndex of property.indexGroup) {
|
|
107
|
+
if (Is.stringValue(propertyIndex?.name)) {
|
|
108
|
+
if (!Object.values(SortDirection).includes(propertyIndex.direction)) {
|
|
109
|
+
throw new GeneralError(EntitySchemaHelper.CLASS_NAME, "invalidIndexGroupDirection", {
|
|
110
|
+
group: propertyIndex.name,
|
|
111
|
+
direction: propertyIndex.direction,
|
|
112
|
+
property: property.property
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
if (!Is.integer(propertyIndex.index) || propertyIndex.index < 0) {
|
|
116
|
+
throw new GeneralError(EntitySchemaHelper.CLASS_NAME, "invalidIndexGroupIndex", {
|
|
117
|
+
group: propertyIndex.name,
|
|
118
|
+
index: propertyIndex.index,
|
|
119
|
+
property: property.property
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
groupIndexes[propertyIndex.name] ??= new Set();
|
|
123
|
+
if (groupIndexes[propertyIndex.name].has(propertyIndex.index)) {
|
|
124
|
+
throw new GeneralError(EntitySchemaHelper.CLASS_NAME, "duplicateIndexGroupIndex", {
|
|
125
|
+
group: propertyIndex.name,
|
|
126
|
+
index: propertyIndex.index,
|
|
127
|
+
property: property.property
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
groupIndexes[propertyIndex.name].add(propertyIndex.index);
|
|
131
|
+
groupEntries[propertyIndex.name] ??= [];
|
|
132
|
+
groupEntries[propertyIndex.name].push({
|
|
133
|
+
entry: { property, direction: propertyIndex.direction },
|
|
134
|
+
index: propertyIndex.index
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const indexGroups = {};
|
|
141
|
+
for (const group of Object.keys(groupEntries)) {
|
|
142
|
+
if (groupEntries[group].length < 2) {
|
|
143
|
+
throw new GeneralError(EntitySchemaHelper.CLASS_NAME, "indexGroupMustHaveAtLeastTwoProperties", {
|
|
144
|
+
group,
|
|
145
|
+
count: groupEntries[group].length
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
indexGroups[group] = groupEntries[group]
|
|
149
|
+
.sort((a, b) => a.index - b.index)
|
|
150
|
+
.map(groupEntry => groupEntry.entry);
|
|
151
|
+
}
|
|
152
|
+
return indexGroups;
|
|
153
|
+
}
|
|
89
154
|
/**
|
|
90
155
|
* Build sort properties from the schema and override if necessary.
|
|
91
156
|
* @param entitySchema The entity schema to retrieve the default sort keys.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entitySchemaHelper.js","sourceRoot":"","sources":["../../../src/utils/entitySchemaHelper.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AAE1D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,0BAA0B,EAAE,MAAM,yCAAyC,CAAC;AACrF,OAAO,EAAE,wBAAwB,EAAE,MAAM,uCAAuC,CAAC;AAMjF;;GAEG;AACH,MAAM,OAAO,kBAAkB;IAC9B;;OAEG;IACI,MAAM,CAAU,UAAU,wBAAwC;IAEzE;;OAEG;IACI,MAAM,CAAU,kBAAkB,GAAiC;QACzE,CAAC,0BAA0B,CAAC,IAAI,CAAC,EAAE,EAAE;QACrC,CAAC,0BAA0B,CAAC,IAAI,CAAC,EAAE,EAAE;QACrC,CAAC,0BAA0B,CAAC,IAAI,CAAC,EAAE,EAAE;QACrC,CAAC,0BAA0B,CAAC,QAAQ,CAAC,EAAE,EAAE;QACzC,CAAC,0BAA0B,CAAC,KAAK,CAAC,EAAE,GAAG;QACvC,CAAC,0BAA0B,CAAC,GAAG,CAAC,EAAE,IAAI;KACtC,CAAC;IAEF;;;;OAIG;IACH,8DAA8D;IACvD,MAAM,CAAC,SAAS,CAAc,MAAW;QAC/C,OAAO,eAAe,CAAC,SAAS,CAAI,MAAM,CAAC,CAAC;IAC7C,CAAC;IAED;;;;;;;;OAQG;IACI,MAAM,CAAC,UAAU,CAAC,YAA2B;QACnD,MAAM,CAAC,MAAM,CAAgB,kBAAkB,CAAC,UAAU,kBAAwB,YAAY,CAAC,CAAC;QAEhG,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,YAAY,EAAE,OAAO,CAAC,EAAE,CAAC;YACtC,MAAM,CAAC,OAAO,CACb,kBAAkB,CAAC,UAAU,0BAE7B,YAAY,EAAE,OAAO,CACrB,CAAC;YACF,IAAI,YAAY,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC;gBAC9B,MAAM,IAAI,YAAY,CACrB,kBAAkB,CAAC,UAAU,EAC7B,qCAAqC,EACrC;oBACC,OAAO,EAAE,YAAY,CAAC,OAAO;iBAC7B,CACD,CAAC;YACH,CAAC;QACF,CAAC;QAED,OAAO,YAAY,CAAC,OAAO,IAAI,CAAC,CAAC;IAClC,CAAC;IAED;;;;;OAKG;IACI,MAAM,CAAC,aAAa,CAAI,YAA8B;QAC5D,MAAM,CAAC,MAAM,CACZ,kBAAkB,CAAC,UAAU,kBAE7B,YAAY,CACZ,CAAC;QAEF,MAAM,WAAW,GAAG,CAAC,YAAY,CAAC,UAAU,IAAI,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QAC9E,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC9B,MAAM,IAAI,YAAY,CAAC,kBAAkB,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;QACtE,CAAC;QACD,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,YAAY,CAAC,kBAAkB,CAAC,UAAU,EAAE,mBAAmB,CAAC,CAAC;QAC5E,CAAC;QACD,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC;IACvB,CAAC;IAED;;;;OAIG;IACI,MAAM,CAAC,iBAAiB,CAAI,YAA8B;QAChE,MAAM,CAAC,MAAM,CACZ,kBAAkB,CAAC,UAAU,kBAE7B,YAAY,CACZ,CAAC;QAEF,MAAM,UAAU,GAAG,CAAC,YAAY,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC;QAE/F,OAAO,UAAU,CAAC,MAAM,GAAG,CAAC;YAC3B,CAAC,CAAC,UAAU,CAAC,GAAG,CACd,CAAC,CAAC,EAAE,CACH,CAAC;gBACA,QAAQ,EAAE,CAAC,CAAC,QAAQ;gBACpB,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,aAAa,EAAE,CAAC,CAAC,aAAa;aAC9B,CAAmB,CACrB;YACF,CAAC,CAAC,SAAS,CAAC;IACd,CAAC;IAED;;;;;OAKG;IACI,MAAM,CAAC,mBAAmB,CAChC,YAA8B,EAC9B,gBAGG;QAEH,MAAM,CAAC,MAAM,CAAC,kBAAkB,CAAC,UAAU,kBAAwB,YAAY,CAAC,CAAC;QAEjF,IAAI,aAA2C,CAAC;QAEhD,IAAI,EAAE,CAAC,UAAU,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACrC,aAAa,GAAG,EAAE,CAAC;YAEnB,KAAK,MAAM,OAAO,IAAI,gBAAgB,EAAE,CAAC;gBACxC,MAAM,QAAQ,GAAG,CAAC,YAAY,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;gBAC5F,IAAI,QAAQ,EAAE,CAAC;oBACd,aAAa,CAAC,IAAI,CAAC;wBAClB,QAAQ,EAAE,OAAO,CAAC,QAAQ;wBAC1B,aAAa,EAAE,OAAO,CAAC,aAAa;wBACpC,IAAI,EAAE,QAAQ,CAAC,IAAI;qBACnB,CAAC,CAAC;gBACJ,CAAC;YACF,CAAC;QACF,CAAC;aAAM,CAAC;YACP,aAAa,GAAG,kBAAkB,CAAC,iBAAiB,CAAC,YAAY,CAAC,CAAC;QACpE,CAAC;QAED,OAAO,aAAa,CAAC;IACtB,CAAC;IAED;;;;;OAKG;IACI,MAAM,CAAC,cAAc,CAAI,MAAS,EAAE,YAA8B;QACxE,MAAM,CAAC,MAAM,CAAC,kBAAkB,CAAC,UAAU,YAAkB,MAAM,CAAC,CAAC;QACrE,MAAM,CAAC,MAAM,CACZ,kBAAkB,CAAC,UAAU,kBAE7B,YAAY,CACZ,CAAC;QAEF,MAAM,UAAU,GAAG,YAAY,CAAC,UAAU,IAAI,EAAE,CAAC;QACjD,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC;YACvD,MAAM,IAAI,YAAY,CAAC,kBAAkB,CAAC,UAAU,EAAE,yBAAyB,CAAC,CAAC;QAClF,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAEpC,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;YAC/B,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,QAAkB,CAAC,CAAC;YACrD,IAAI,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC;gBAChB,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YACxB,CAAC;YAED,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACpC,MAAM,SAAS,GAAG,OAAO,KAAK,CAAC;YAE/B,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;gBACrB,4EAA4E;gBAC5E,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;oBACpB,MAAM,IAAI,YAAY,CAAC,kBAAkB,CAAC,UAAU,EAAE,iBAAiB,EAAE;wBACxE,QAAQ,EAAE,IAAI,CAAC,QAAQ;wBACvB,IAAI,EAAE,IAAI,CAAC,IAAI;qBACf,CAAC,CAAC;gBACJ,CAAC;YACF,CAAC;iBAAM,IAAI,IAAI,CAAC,IAAI,KAAK,wBAAwB,CAAC,OAAO,IAAI,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;gBAChF,gFAAgF;YACjF,CAAC;iBAAM,IACN,IAAI,CAAC,IAAI,KAAK,wBAAwB,CAAC,MAAM;gBAC7C,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC;oBAChB,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC;oBACf,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC;oBAChB,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC;oBAChB,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;oBACjB,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EACf,CAAC;gBACF,yGAAyG;YAC1G,CAAC;iBAAM,IAAI,IAAI,CAAC,IAAI,KAAK,wBAAwB,CAAC,KAAK,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC5E,4EAA4E;YAC7E,CAAC;iBAAM,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBACpC,gDAAgD;gBAChD,MAAM,IAAI,YAAY,CAAC,kBAAkB,CAAC,UAAU,EAAE,uBAAuB,EAAE;oBAC9E,KAAK;oBACL,QAAQ,EAAE,IAAI,CAAC,QAAQ;oBACvB,IAAI,EAAE,IAAI,CAAC,IAAI;iBACf,CAAC,CAAC;YACJ,CAAC;YAED,IAAI,IAAI,CAAC,IAAI,KAAK,wBAAwB,CAAC,MAAM,IAAI,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;gBACvE,8EAA8E;gBAC9E,MAAM,SAAS,GACd,IAAI,CAAC,SAAS;oBACd,CAAC,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC;wBAC3B,CAAC,CAAC,kBAAkB,CAAC,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC;wBACpD,CAAC,CAAC,SAAS,CAAC,CAAC;gBAEf,IAAI,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,SAAS,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,SAAS,EAAE,CAAC;oBACvE,oEAAoE;oBACpE,MAAM,IAAI,YAAY,CAAC,kBAAkB,CAAC,UAAU,EAAE,mBAAmB,EAAE;wBAC1E,QAAQ,EAAE,IAAI,CAAC,QAAQ;wBACvB,SAAS;wBACT,MAAM,EAAE,KAAK,CAAC,MAAM;qBACpB,CAAC,CAAC;gBACJ,CAAC;YACF,CAAC;QACF,CAAC;QAED,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,0DAA0D;YAC1D,MAAM,IAAI,YAAY,CAAC,kBAAkB,CAAC,UAAU,EAAE,mBAAmB,EAAE;gBAC1E,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;aACxB,CAAC,CAAC;QACJ,CAAC;IACF,CAAC;IAED;;;;;;OAMG;IACI,MAAM,CAAC,mBAAmB,CAAI,MAAwB;QAC5D,MAAM,CAAC,MAAM,CAAC,kBAAkB,CAAC,UAAU,YAAkB,MAAM,CAAC,CAAC;QAErE,MAAM,iBAAiB,GAAG,CAAC,MAAM,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC;QACtF,IAAI,iBAAiB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAClC,MAAM,IAAI,YAAY,CAAC,kBAAkB,CAAC,UAAU,EAAE,2BAA2B,CAAC,CAAC;QACpF,CAAC;QACD,IAAI,iBAAiB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACpC,IAAI,iBAAiB,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,wBAAwB,CAAC,OAAO,EAAE,CAAC;gBACpE,MAAM,IAAI,YAAY,CAAC,kBAAkB,CAAC,UAAU,EAAE,8BAA8B,EAAE;oBACrF,QAAQ,EAAE,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;oBAC/C,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC,IAAI;iBAC/B,CAAC,CAAC;YACJ,CAAC;YACD,OAAO,iBAAiB,CAAC,CAAC,CAAC,CAAC,QAA8B,CAAC;QAC5D,CAAC;QACD,OAAO,SAAS,CAAC;IAClB,CAAC","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { GeneralError, Guards, Is } from \"@twin.org/core\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { DecoratorHelper } from \"./decoratorHelper.js\";\nimport { EntitySchemaPropertyFormat } from \"../models/entitySchemaPropertyFormat.js\";\nimport { EntitySchemaPropertyType } from \"../models/entitySchemaPropertyType.js\";\nimport type { IEntitySchema } from \"../models/IEntitySchema.js\";\nimport type { IEntitySchemaProperty } from \"../models/IEntitySchemaProperty.js\";\nimport type { IEntitySort } from \"../models/IEntitySort.js\";\nimport type { SortDirection } from \"../models/sortDirection.js\";\n\n/**\n * Class to help with entity schema operations.\n */\nexport class EntitySchemaHelper {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<EntitySchemaHelper>();\n\n\t/**\n\t * The default maximum lengths for string properties, keyed by their format.\n\t */\n\tpublic static readonly FORMAT_MAX_LENGTHS: { [format: string]: number } = {\n\t\t[EntitySchemaPropertyFormat.Uuid]: 36,\n\t\t[EntitySchemaPropertyFormat.Date]: 64,\n\t\t[EntitySchemaPropertyFormat.Time]: 64,\n\t\t[EntitySchemaPropertyFormat.DateTime]: 64,\n\t\t[EntitySchemaPropertyFormat.Email]: 254,\n\t\t[EntitySchemaPropertyFormat.Uri]: 2048\n\t};\n\n\t/**\n\t * Get the schema for the specified object.\n\t * @param target The object to get the schema data for.\n\t * @returns The schema for the object if it can be found.\n\t */\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tpublic static getSchema<T = unknown>(target: any): IEntitySchema<T> {\n\t\treturn DecoratorHelper.getSchema<T>(target);\n\t}\n\n\t/**\n\t * Get the version of the entity schema, defaulting to 0 when absent.\n\t * This is the single source of truth for the \"absent version = v0\" convention.\n\t * When a version is present it must be a non-negative integer >= 0.\n\t * @param entitySchema The entity schema to read the version from.\n\t * @returns The declared version, or 0 if no version was set.\n\t * @throws GuardError if entitySchema is undefined or version is not an integer.\n\t * @throws GeneralError if version is present but less than 0.\n\t */\n\tpublic static getVersion(entitySchema: IEntitySchema): number {\n\t\tGuards.object<IEntitySchema>(EntitySchemaHelper.CLASS_NAME, nameof(entitySchema), entitySchema);\n\n\t\tif (!Is.empty(entitySchema?.version)) {\n\t\t\tGuards.integer(\n\t\t\t\tEntitySchemaHelper.CLASS_NAME,\n\t\t\t\tnameof(entitySchema?.version),\n\t\t\t\tentitySchema?.version\n\t\t\t);\n\t\t\tif (entitySchema.version < 0) {\n\t\t\t\tthrow new GeneralError(\n\t\t\t\t\tEntitySchemaHelper.CLASS_NAME,\n\t\t\t\t\t\"versionMustBeGreaterThanOrEqualZero\",\n\t\t\t\t\t{\n\t\t\t\t\t\tversion: entitySchema.version\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn entitySchema.version ?? 0;\n\t}\n\n\t/**\n\t * Get the primary key from the entity schema.\n\t * @param entitySchema The entity schema to find the primary key from.\n\t * @returns The key if only one was found.\n\t * @throws If no primary key was found, or more than one.\n\t */\n\tpublic static getPrimaryKey<T>(entitySchema: IEntitySchema<T>): IEntitySchemaProperty<T> {\n\t\tGuards.object<IEntitySchema<T>>(\n\t\t\tEntitySchemaHelper.CLASS_NAME,\n\t\t\tnameof(entitySchema),\n\t\t\tentitySchema\n\t\t);\n\n\t\tconst primaryKeys = (entitySchema.properties ?? [])?.filter(p => p.isPrimary);\n\t\tif (primaryKeys.length === 0) {\n\t\t\tthrow new GeneralError(EntitySchemaHelper.CLASS_NAME, \"noIsPrimary\");\n\t\t}\n\t\tif (primaryKeys.length > 1) {\n\t\t\tthrow new GeneralError(EntitySchemaHelper.CLASS_NAME, \"multipleIsPrimary\");\n\t\t}\n\t\treturn primaryKeys[0];\n\t}\n\n\t/**\n\t * Get the sort properties from the schema.\n\t * @param entitySchema The entity schema to find the primary key from.\n\t * @returns The sort keys from the schema or undefined if there are none.\n\t */\n\tpublic static getSortProperties<T>(entitySchema: IEntitySchema<T>): IEntitySort<T>[] | undefined {\n\t\tGuards.object<IEntitySchema<T>>(\n\t\t\tEntitySchemaHelper.CLASS_NAME,\n\t\t\tnameof(entitySchema),\n\t\t\tentitySchema\n\t\t);\n\n\t\tconst sortFields = (entitySchema.properties ?? []).filter(p => !Is.undefined(p.sortDirection));\n\n\t\treturn sortFields.length > 0\n\t\t\t? sortFields.map(\n\t\t\t\t\tp =>\n\t\t\t\t\t\t({\n\t\t\t\t\t\t\tproperty: p.property,\n\t\t\t\t\t\t\ttype: p.type,\n\t\t\t\t\t\t\tsortDirection: p.sortDirection\n\t\t\t\t\t\t}) as IEntitySort<T>\n\t\t\t\t)\n\t\t\t: undefined;\n\t}\n\n\t/**\n\t * Build sort properties from the schema and override if necessary.\n\t * @param entitySchema The entity schema to retrieve the default sort keys.\n\t * @param overrideSortKeys The override sort keys.\n\t * @returns The finalised sort keys.\n\t */\n\tpublic static buildSortProperties<T>(\n\t\tentitySchema: IEntitySchema<T>,\n\t\toverrideSortKeys?: {\n\t\t\tproperty: keyof T;\n\t\t\tsortDirection: SortDirection;\n\t\t}[]\n\t): IEntitySort<T>[] | undefined {\n\t\tGuards.object(EntitySchemaHelper.CLASS_NAME, nameof(entitySchema), entitySchema);\n\n\t\tlet finalSortKeys: IEntitySort<T>[] | undefined;\n\n\t\tif (Is.arrayValue(overrideSortKeys)) {\n\t\t\tfinalSortKeys = [];\n\n\t\t\tfor (const sortKey of overrideSortKeys) {\n\t\t\t\tconst property = (entitySchema.properties ?? []).find(p => p.property === sortKey.property);\n\t\t\t\tif (property) {\n\t\t\t\t\tfinalSortKeys.push({\n\t\t\t\t\t\tproperty: sortKey.property,\n\t\t\t\t\t\tsortDirection: sortKey.sortDirection,\n\t\t\t\t\t\ttype: property.type\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tfinalSortKeys = EntitySchemaHelper.getSortProperties(entitySchema);\n\t\t}\n\n\t\treturn finalSortKeys;\n\t}\n\n\t/**\n\t * Validate the entity against the schema.\n\t * @param entity The entity to validate.\n\t * @param entitySchema The schema to validate against.\n\t * @throws If the entity is invalid, or a string value exceeds its maxLength.\n\t */\n\tpublic static validateEntity<T>(entity: T, entitySchema: IEntitySchema<T>): void {\n\t\tGuards.object(EntitySchemaHelper.CLASS_NAME, nameof(entity), entity);\n\t\tGuards.object<IEntitySchema<T>>(\n\t\t\tEntitySchemaHelper.CLASS_NAME,\n\t\t\tnameof(entitySchema),\n\t\t\tentitySchema\n\t\t);\n\n\t\tconst properties = entitySchema.properties ?? [];\n\t\tif (properties.length === 0 && Is.objectValue(entity)) {\n\t\t\tthrow new GeneralError(EntitySchemaHelper.CLASS_NAME, \"invalidEntityProperties\");\n\t\t}\n\n\t\tconst allKeys = Object.keys(entity);\n\n\t\tfor (const prop of properties) {\n\t\t\tconst idx = allKeys.indexOf(prop.property as string);\n\t\t\tif (idx !== -1) {\n\t\t\t\tallKeys.splice(idx, 1);\n\t\t\t}\n\n\t\t\tconst value = entity[prop.property];\n\t\t\tconst valueType = typeof value;\n\n\t\t\tif (Is.empty(value)) {\n\t\t\t\t// If the value is empty but the property is not optional, then it's invalid\n\t\t\t\tif (!prop.optional) {\n\t\t\t\t\tthrow new GeneralError(EntitySchemaHelper.CLASS_NAME, \"invalidOptional\", {\n\t\t\t\t\t\tproperty: prop.property,\n\t\t\t\t\t\ttype: prop.type\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} else if (prop.type === EntitySchemaPropertyType.Integer && Is.integer(value)) {\n\t\t\t\t// If the schema expects an integer and the value is an integer, then it's valid\n\t\t\t} else if (\n\t\t\t\tprop.type === EntitySchemaPropertyType.Object &&\n\t\t\t\t(Is.object(value) ||\n\t\t\t\t\tIs.array(value) ||\n\t\t\t\t\tIs.string(value) ||\n\t\t\t\t\tIs.number(value) ||\n\t\t\t\t\tIs.boolean(value) ||\n\t\t\t\t\tIs.null(value))\n\t\t\t) {\n\t\t\t\t// If the schema expects an object and the value is anything that can be JSON serialised, then it's valid\n\t\t\t} else if (prop.type === EntitySchemaPropertyType.Array && Is.array(value)) {\n\t\t\t\t// If the schema expects an array and the value is an array, then it's valid\n\t\t\t} else if (prop.type !== valueType) {\n\t\t\t\t// The schema type does not match the value type\n\t\t\t\tthrow new GeneralError(EntitySchemaHelper.CLASS_NAME, \"invalidEntityProperty\", {\n\t\t\t\t\tvalue,\n\t\t\t\t\tproperty: prop.property,\n\t\t\t\t\ttype: prop.type\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (prop.type === EntitySchemaPropertyType.String && Is.string(value)) {\n\t\t\t\t// Formats with a known length default to it when no explicit maxLength is set\n\t\t\t\tconst maxLength =\n\t\t\t\t\tprop.maxLength ??\n\t\t\t\t\t(Is.stringValue(prop.format)\n\t\t\t\t\t\t? EntitySchemaHelper.FORMAT_MAX_LENGTHS[prop.format]\n\t\t\t\t\t\t: undefined);\n\n\t\t\t\tif (Is.number(maxLength) && maxLength > 0 && value.length > maxLength) {\n\t\t\t\t\t// The value is longer than the maximum length defined in the schema\n\t\t\t\t\tthrow new GeneralError(EntitySchemaHelper.CLASS_NAME, \"maxLengthExceeded\", {\n\t\t\t\t\t\tproperty: prop.property,\n\t\t\t\t\t\tmaxLength,\n\t\t\t\t\t\tlength: value.length\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (allKeys.length > 0) {\n\t\t\t// There are keys in the entity that are not in the schema\n\t\t\tthrow new GeneralError(EntitySchemaHelper.CLASS_NAME, \"invalidEntityKeys\", {\n\t\t\t\tkeys: allKeys.join(\", \")\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Find the property in the schema that is marked as the optimistic-lock version token.\n\t * @param schema The entity schema to search.\n\t * @returns The name of the version property, or undefined if none is declared.\n\t * @throws GeneralError if more than one property has isVersion set.\n\t * @throws GeneralError if the version property type is not integer.\n\t */\n\tpublic static findVersionProperty<T>(schema: IEntitySchema<T>): string | undefined {\n\t\tGuards.object(EntitySchemaHelper.CLASS_NAME, nameof(schema), schema);\n\n\t\tconst versionProperties = (schema.properties ?? []).filter(p => p.isVersion === true);\n\t\tif (versionProperties.length > 1) {\n\t\t\tthrow new GeneralError(EntitySchemaHelper.CLASS_NAME, \"multipleVersionProperties\");\n\t\t}\n\t\tif (versionProperties.length === 1) {\n\t\t\tif (versionProperties[0].type !== EntitySchemaPropertyType.Integer) {\n\t\t\t\tthrow new GeneralError(EntitySchemaHelper.CLASS_NAME, \"versionPropertyMustBeInteger\", {\n\t\t\t\t\tproperty: String(versionProperties[0].property),\n\t\t\t\t\ttype: versionProperties[0].type\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn versionProperties[0].property as string | undefined;\n\t\t}\n\t\treturn undefined;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"entitySchemaHelper.js","sourceRoot":"","sources":["../../../src/utils/entitySchemaHelper.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { GeneralError, Guards, Is } from \"@twin.org/core\";\nimport { nameof } from \"@twin.org/nameof\";\nimport { DecoratorHelper } from \"./decoratorHelper.js\";\nimport { EntitySchemaPropertyFormat } from \"../models/entitySchemaPropertyFormat.js\";\nimport { EntitySchemaPropertyType } from \"../models/entitySchemaPropertyType.js\";\nimport type { IEntitySchema } from \"../models/IEntitySchema.js\";\nimport type { IEntitySchemaProperty } from \"../models/IEntitySchemaProperty.js\";\nimport type { IEntitySort } from \"../models/IEntitySort.js\";\nimport { SortDirection } from \"../models/sortDirection.js\";\n\n/**\n * Class to help with entity schema operations.\n */\nexport class EntitySchemaHelper {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<EntitySchemaHelper>();\n\n\t/**\n\t * The default maximum lengths for string properties, keyed by their format.\n\t */\n\tpublic static readonly FORMAT_MAX_LENGTHS: { [format: string]: number } = {\n\t\t[EntitySchemaPropertyFormat.Uuid]: 36,\n\t\t[EntitySchemaPropertyFormat.Date]: 64,\n\t\t[EntitySchemaPropertyFormat.Time]: 64,\n\t\t[EntitySchemaPropertyFormat.DateTime]: 64,\n\t\t[EntitySchemaPropertyFormat.Email]: 254,\n\t\t[EntitySchemaPropertyFormat.Uri]: 2048\n\t};\n\n\t/**\n\t * Get the schema for the specified object.\n\t * @param target The object to get the schema data for.\n\t * @returns The schema for the object if it can be found.\n\t */\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tpublic static getSchema<T = unknown>(target: any): IEntitySchema<T> {\n\t\treturn DecoratorHelper.getSchema<T>(target);\n\t}\n\n\t/**\n\t * Get the version of the entity schema, defaulting to 0 when absent.\n\t * This is the single source of truth for the \"absent version = v0\" convention.\n\t * When a version is present it must be a non-negative integer >= 0.\n\t * @param entitySchema The entity schema to read the version from.\n\t * @returns The declared version, or 0 if no version was set.\n\t * @throws GuardError if entitySchema is undefined or version is not an integer.\n\t * @throws GeneralError if version is present but less than 0.\n\t */\n\tpublic static getVersion(entitySchema: IEntitySchema): number {\n\t\tGuards.object<IEntitySchema>(EntitySchemaHelper.CLASS_NAME, nameof(entitySchema), entitySchema);\n\n\t\tif (!Is.empty(entitySchema?.version)) {\n\t\t\tGuards.integer(\n\t\t\t\tEntitySchemaHelper.CLASS_NAME,\n\t\t\t\tnameof(entitySchema?.version),\n\t\t\t\tentitySchema?.version\n\t\t\t);\n\t\t\tif (entitySchema.version < 0) {\n\t\t\t\tthrow new GeneralError(\n\t\t\t\t\tEntitySchemaHelper.CLASS_NAME,\n\t\t\t\t\t\"versionMustBeGreaterThanOrEqualZero\",\n\t\t\t\t\t{\n\t\t\t\t\t\tversion: entitySchema.version\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn entitySchema.version ?? 0;\n\t}\n\n\t/**\n\t * Get the primary key from the entity schema.\n\t * @param entitySchema The entity schema to find the primary key from.\n\t * @returns The key if only one was found.\n\t * @throws If no primary key was found, or more than one.\n\t */\n\tpublic static getPrimaryKey<T>(entitySchema: IEntitySchema<T>): IEntitySchemaProperty<T> {\n\t\tGuards.object<IEntitySchema<T>>(\n\t\t\tEntitySchemaHelper.CLASS_NAME,\n\t\t\tnameof(entitySchema),\n\t\t\tentitySchema\n\t\t);\n\n\t\tconst primaryKeys = (entitySchema.properties ?? [])?.filter(p => p.isPrimary);\n\t\tif (primaryKeys.length === 0) {\n\t\t\tthrow new GeneralError(EntitySchemaHelper.CLASS_NAME, \"noIsPrimary\");\n\t\t}\n\t\tif (primaryKeys.length > 1) {\n\t\t\tthrow new GeneralError(EntitySchemaHelper.CLASS_NAME, \"multipleIsPrimary\");\n\t\t}\n\t\treturn primaryKeys[0];\n\t}\n\n\t/**\n\t * Get the sort properties from the schema.\n\t * @param entitySchema The entity schema to find the primary key from.\n\t * @returns The sort keys from the schema or undefined if there are none.\n\t */\n\tpublic static getSortProperties<T>(entitySchema: IEntitySchema<T>): IEntitySort<T>[] | undefined {\n\t\tGuards.object<IEntitySchema<T>>(\n\t\t\tEntitySchemaHelper.CLASS_NAME,\n\t\t\tnameof(entitySchema),\n\t\t\tentitySchema\n\t\t);\n\n\t\tconst sortFields = (entitySchema.properties ?? []).filter(p => !Is.undefined(p.sortDirection));\n\n\t\treturn sortFields.length > 0\n\t\t\t? sortFields.map(\n\t\t\t\t\tp =>\n\t\t\t\t\t\t({\n\t\t\t\t\t\t\tproperty: p.property,\n\t\t\t\t\t\t\ttype: p.type,\n\t\t\t\t\t\t\tsortDirection: p.sortDirection\n\t\t\t\t\t\t}) as IEntitySort<T>\n\t\t\t\t)\n\t\t\t: undefined;\n\t}\n\n\t/**\n\t * Get the composite index groups from the schema.\n\t * Each property can be part of multiple indexes through its `indexGroup` list, so a property\n\t * can appear in more than one group. The properties within a group are ordered by the `index`\n\t * of their index entry, and each is returned with the sort direction it declared for that group.\n\t * @param entitySchema The entity schema to find the index groups from.\n\t * @returns The properties and their directions keyed by the group name, empty if there are no groups.\n\t * @throws GeneralError if an index entry has an invalid direction or index, or if two properties\n\t * claim the same index within the same group, or if a group contains fewer than two properties.\n\t */\n\tpublic static getIndexGroups<T>(entitySchema: IEntitySchema<T>): {\n\t\t[group: string]: { property: IEntitySchemaProperty<T>; direction: SortDirection }[];\n\t} {\n\t\tGuards.object<IEntitySchema<T>>(\n\t\t\tEntitySchemaHelper.CLASS_NAME,\n\t\t\tnameof(entitySchema),\n\t\t\tentitySchema\n\t\t);\n\n\t\tconst groupEntries: {\n\t\t\t[group: string]: {\n\t\t\t\tentry: { property: IEntitySchemaProperty<T>; direction: SortDirection };\n\t\t\t\tindex: number;\n\t\t\t}[];\n\t\t} = {};\n\t\tconst groupIndexes: { [group: string]: Set<number> } = {};\n\n\t\tfor (const property of entitySchema.properties ?? []) {\n\t\t\tif (Is.arrayValue(property.indexGroup)) {\n\t\t\t\tfor (const propertyIndex of property.indexGroup) {\n\t\t\t\t\tif (Is.stringValue(propertyIndex?.name)) {\n\t\t\t\t\t\tif (!Object.values(SortDirection).includes(propertyIndex.direction)) {\n\t\t\t\t\t\t\tthrow new GeneralError(EntitySchemaHelper.CLASS_NAME, \"invalidIndexGroupDirection\", {\n\t\t\t\t\t\t\t\tgroup: propertyIndex.name,\n\t\t\t\t\t\t\t\tdirection: propertyIndex.direction,\n\t\t\t\t\t\t\t\tproperty: property.property\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!Is.integer(propertyIndex.index) || propertyIndex.index < 0) {\n\t\t\t\t\t\t\tthrow new GeneralError(EntitySchemaHelper.CLASS_NAME, \"invalidIndexGroupIndex\", {\n\t\t\t\t\t\t\t\tgroup: propertyIndex.name,\n\t\t\t\t\t\t\t\tindex: propertyIndex.index,\n\t\t\t\t\t\t\t\tproperty: property.property\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tgroupIndexes[propertyIndex.name] ??= new Set();\n\t\t\t\t\t\tif (groupIndexes[propertyIndex.name].has(propertyIndex.index)) {\n\t\t\t\t\t\t\tthrow new GeneralError(EntitySchemaHelper.CLASS_NAME, \"duplicateIndexGroupIndex\", {\n\t\t\t\t\t\t\t\tgroup: propertyIndex.name,\n\t\t\t\t\t\t\t\tindex: propertyIndex.index,\n\t\t\t\t\t\t\t\tproperty: property.property\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tgroupIndexes[propertyIndex.name].add(propertyIndex.index);\n\n\t\t\t\t\t\tgroupEntries[propertyIndex.name] ??= [];\n\t\t\t\t\t\tgroupEntries[propertyIndex.name].push({\n\t\t\t\t\t\t\tentry: { property, direction: propertyIndex.direction },\n\t\t\t\t\t\t\tindex: propertyIndex.index\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst indexGroups: {\n\t\t\t[group: string]: { property: IEntitySchemaProperty<T>; direction: SortDirection }[];\n\t\t} = {};\n\n\t\tfor (const group of Object.keys(groupEntries)) {\n\t\t\tif (groupEntries[group].length < 2) {\n\t\t\t\tthrow new GeneralError(\n\t\t\t\t\tEntitySchemaHelper.CLASS_NAME,\n\t\t\t\t\t\"indexGroupMustHaveAtLeastTwoProperties\",\n\t\t\t\t\t{\n\t\t\t\t\t\tgroup,\n\t\t\t\t\t\tcount: groupEntries[group].length\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tindexGroups[group] = groupEntries[group]\n\t\t\t\t.sort((a, b) => a.index - b.index)\n\t\t\t\t.map(groupEntry => groupEntry.entry);\n\t\t}\n\n\t\treturn indexGroups;\n\t}\n\n\t/**\n\t * Build sort properties from the schema and override if necessary.\n\t * @param entitySchema The entity schema to retrieve the default sort keys.\n\t * @param overrideSortKeys The override sort keys.\n\t * @returns The finalised sort keys.\n\t */\n\tpublic static buildSortProperties<T>(\n\t\tentitySchema: IEntitySchema<T>,\n\t\toverrideSortKeys?: {\n\t\t\tproperty: keyof T;\n\t\t\tsortDirection: SortDirection;\n\t\t}[]\n\t): IEntitySort<T>[] | undefined {\n\t\tGuards.object(EntitySchemaHelper.CLASS_NAME, nameof(entitySchema), entitySchema);\n\n\t\tlet finalSortKeys: IEntitySort<T>[] | undefined;\n\n\t\tif (Is.arrayValue(overrideSortKeys)) {\n\t\t\tfinalSortKeys = [];\n\n\t\t\tfor (const sortKey of overrideSortKeys) {\n\t\t\t\tconst property = (entitySchema.properties ?? []).find(p => p.property === sortKey.property);\n\t\t\t\tif (property) {\n\t\t\t\t\tfinalSortKeys.push({\n\t\t\t\t\t\tproperty: sortKey.property,\n\t\t\t\t\t\tsortDirection: sortKey.sortDirection,\n\t\t\t\t\t\ttype: property.type\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tfinalSortKeys = EntitySchemaHelper.getSortProperties(entitySchema);\n\t\t}\n\n\t\treturn finalSortKeys;\n\t}\n\n\t/**\n\t * Validate the entity against the schema.\n\t * @param entity The entity to validate.\n\t * @param entitySchema The schema to validate against.\n\t * @throws If the entity is invalid, or a string value exceeds its maxLength.\n\t */\n\tpublic static validateEntity<T>(entity: T, entitySchema: IEntitySchema<T>): void {\n\t\tGuards.object(EntitySchemaHelper.CLASS_NAME, nameof(entity), entity);\n\t\tGuards.object<IEntitySchema<T>>(\n\t\t\tEntitySchemaHelper.CLASS_NAME,\n\t\t\tnameof(entitySchema),\n\t\t\tentitySchema\n\t\t);\n\n\t\tconst properties = entitySchema.properties ?? [];\n\t\tif (properties.length === 0 && Is.objectValue(entity)) {\n\t\t\tthrow new GeneralError(EntitySchemaHelper.CLASS_NAME, \"invalidEntityProperties\");\n\t\t}\n\n\t\tconst allKeys = Object.keys(entity);\n\n\t\tfor (const prop of properties) {\n\t\t\tconst idx = allKeys.indexOf(prop.property as string);\n\t\t\tif (idx !== -1) {\n\t\t\t\tallKeys.splice(idx, 1);\n\t\t\t}\n\n\t\t\tconst value = entity[prop.property];\n\t\t\tconst valueType = typeof value;\n\n\t\t\tif (Is.empty(value)) {\n\t\t\t\t// If the value is empty but the property is not optional, then it's invalid\n\t\t\t\tif (!prop.optional) {\n\t\t\t\t\tthrow new GeneralError(EntitySchemaHelper.CLASS_NAME, \"invalidOptional\", {\n\t\t\t\t\t\tproperty: prop.property,\n\t\t\t\t\t\ttype: prop.type\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} else if (prop.type === EntitySchemaPropertyType.Integer && Is.integer(value)) {\n\t\t\t\t// If the schema expects an integer and the value is an integer, then it's valid\n\t\t\t} else if (\n\t\t\t\tprop.type === EntitySchemaPropertyType.Object &&\n\t\t\t\t(Is.object(value) ||\n\t\t\t\t\tIs.array(value) ||\n\t\t\t\t\tIs.string(value) ||\n\t\t\t\t\tIs.number(value) ||\n\t\t\t\t\tIs.boolean(value) ||\n\t\t\t\t\tIs.null(value))\n\t\t\t) {\n\t\t\t\t// If the schema expects an object and the value is anything that can be JSON serialised, then it's valid\n\t\t\t} else if (prop.type === EntitySchemaPropertyType.Array && Is.array(value)) {\n\t\t\t\t// If the schema expects an array and the value is an array, then it's valid\n\t\t\t} else if (prop.type !== valueType) {\n\t\t\t\t// The schema type does not match the value type\n\t\t\t\tthrow new GeneralError(EntitySchemaHelper.CLASS_NAME, \"invalidEntityProperty\", {\n\t\t\t\t\tvalue,\n\t\t\t\t\tproperty: prop.property,\n\t\t\t\t\ttype: prop.type\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (prop.type === EntitySchemaPropertyType.String && Is.string(value)) {\n\t\t\t\t// Formats with a known length default to it when no explicit maxLength is set\n\t\t\t\tconst maxLength =\n\t\t\t\t\tprop.maxLength ??\n\t\t\t\t\t(Is.stringValue(prop.format)\n\t\t\t\t\t\t? EntitySchemaHelper.FORMAT_MAX_LENGTHS[prop.format]\n\t\t\t\t\t\t: undefined);\n\n\t\t\t\tif (Is.number(maxLength) && maxLength > 0 && value.length > maxLength) {\n\t\t\t\t\t// The value is longer than the maximum length defined in the schema\n\t\t\t\t\tthrow new GeneralError(EntitySchemaHelper.CLASS_NAME, \"maxLengthExceeded\", {\n\t\t\t\t\t\tproperty: prop.property,\n\t\t\t\t\t\tmaxLength,\n\t\t\t\t\t\tlength: value.length\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (allKeys.length > 0) {\n\t\t\t// There are keys in the entity that are not in the schema\n\t\t\tthrow new GeneralError(EntitySchemaHelper.CLASS_NAME, \"invalidEntityKeys\", {\n\t\t\t\tkeys: allKeys.join(\", \")\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Find the property in the schema that is marked as the optimistic-lock version token.\n\t * @param schema The entity schema to search.\n\t * @returns The name of the version property, or undefined if none is declared.\n\t * @throws GeneralError if more than one property has isVersion set.\n\t * @throws GeneralError if the version property type is not integer.\n\t */\n\tpublic static findVersionProperty<T>(schema: IEntitySchema<T>): string | undefined {\n\t\tGuards.object(EntitySchemaHelper.CLASS_NAME, nameof(schema), schema);\n\n\t\tconst versionProperties = (schema.properties ?? []).filter(p => p.isVersion === true);\n\t\tif (versionProperties.length > 1) {\n\t\t\tthrow new GeneralError(EntitySchemaHelper.CLASS_NAME, \"multipleVersionProperties\");\n\t\t}\n\t\tif (versionProperties.length === 1) {\n\t\t\tif (versionProperties[0].type !== EntitySchemaPropertyType.Integer) {\n\t\t\t\tthrow new GeneralError(EntitySchemaHelper.CLASS_NAME, \"versionPropertyMustBeInteger\", {\n\t\t\t\t\tproperty: String(versionProperties[0].property),\n\t\t\t\t\ttype: versionProperties[0].type\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn versionProperties[0].property as string | undefined;\n\t\t}\n\t\treturn undefined;\n\t}\n}\n"]}
|
|
@@ -5,5 +5,7 @@ import type { IEntitySchemaProperty } from "../models/IEntitySchemaProperty.js";
|
|
|
5
5
|
* Decorator to produce schema property data for entities.
|
|
6
6
|
* @param options The options for the property.
|
|
7
7
|
* @returns The property decorator.
|
|
8
|
+
* @throws GeneralError if an index group is declared on an object or array property.
|
|
9
|
+
* @throws GeneralError if the same index group name is declared more than once for the property.
|
|
8
10
|
*/
|
|
9
11
|
export declare function property(options: Omit<IEntitySchemaProperty, "property">): any;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export * from "./models/IComparatorGroup.js";
|
|
|
10
10
|
export * from "./models/IEntitySchema.js";
|
|
11
11
|
export * from "./models/IEntitySchemaOptions.js";
|
|
12
12
|
export * from "./models/IEntitySchemaProperty.js";
|
|
13
|
+
export * from "./models/IEntitySchemaPropertyIndex.js";
|
|
13
14
|
export * from "./models/IEntitySort.js";
|
|
14
15
|
export * from "./models/logicalOperator.js";
|
|
15
16
|
export * from "./models/sortDirection.js";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { EntitySchemaPropertyFormat } from "./entitySchemaPropertyFormat.js";
|
|
2
2
|
import type { EntitySchemaPropertyType } from "./entitySchemaPropertyType.js";
|
|
3
|
+
import type { IEntitySchemaPropertyIndex } from "./IEntitySchemaPropertyIndex.js";
|
|
3
4
|
import type { SortDirection } from "./sortDirection.js";
|
|
4
5
|
/**
|
|
5
6
|
* Definition for an entity schema property.
|
|
@@ -29,6 +30,12 @@ export interface IEntitySchemaProperty<T = unknown> {
|
|
|
29
30
|
* Is this a secondary index property.
|
|
30
31
|
*/
|
|
31
32
|
isSecondary?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* The composite indexes this property is part of.
|
|
35
|
+
* Connectors can use these to build a composite index for each group name,
|
|
36
|
+
* combining all the properties which share that name, ordered by their index.
|
|
37
|
+
*/
|
|
38
|
+
indexGroup?: IEntitySchemaPropertyIndex[];
|
|
32
39
|
/**
|
|
33
40
|
* Is this property used as the optimistic-lock version token.
|
|
34
41
|
* When true, connectors automatically manage the field: the value is
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SortDirection } from "./sortDirection.js";
|
|
2
|
+
/**
|
|
3
|
+
* Definition of a composite index that a property is part of.
|
|
4
|
+
*/
|
|
5
|
+
export interface IEntitySchemaPropertyIndex {
|
|
6
|
+
/**
|
|
7
|
+
* The name of the composite index group.
|
|
8
|
+
*/
|
|
9
|
+
name: string;
|
|
10
|
+
/**
|
|
11
|
+
* The sort direction for the property within the index.
|
|
12
|
+
*/
|
|
13
|
+
direction: SortDirection;
|
|
14
|
+
/**
|
|
15
|
+
* The position of the property within the index, ordered ascending.
|
|
16
|
+
*/
|
|
17
|
+
index: number;
|
|
18
|
+
}
|
|
@@ -11,7 +11,7 @@ export declare class EntitySchemaDiffHelper {
|
|
|
11
11
|
/**
|
|
12
12
|
* Compare two arrays of entity schema properties and return a structured diff.
|
|
13
13
|
*
|
|
14
|
-
* Properties are matched by their `property` key name. A property is considered modified when any structural field differs: `type`, `format`, `isPrimary`, `isSecondary`, `isVersion`, `sortDirection`, `optional`, `itemType`, or `itemTypeRef`.
|
|
14
|
+
* Properties are matched by their `property` key name. A property is considered modified when any structural field differs: `type`, `format`, `isPrimary`, `isSecondary`, `indexGroup`, `isVersion`, `sortDirection`, `optional`, `itemType`, or `itemTypeRef`.
|
|
15
15
|
* Documentation-only fields (`description`, `examples`) are intentionally excluded from the comparison to avoid spurious diffs.
|
|
16
16
|
*
|
|
17
17
|
* Because a pure name change cannot be detected automatically, callers may supply a `renames` list mapping old names to new names. Renamed properties appear in `modified` (never in `added` or `removed`) even when no other fields changed. Rename lookups take priority over direct same-name matches, which allows swap renames to work correctly and prevents a renamed source from silently disappearing when the target name already existed in the old schema. Self-renames (`from === to`) are ignored and the property is classified normally.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { IEntitySchema } from "../models/IEntitySchema.js";
|
|
2
2
|
import type { IEntitySchemaProperty } from "../models/IEntitySchemaProperty.js";
|
|
3
3
|
import type { IEntitySort } from "../models/IEntitySort.js";
|
|
4
|
-
import
|
|
4
|
+
import { SortDirection } from "../models/sortDirection.js";
|
|
5
5
|
/**
|
|
6
6
|
* Class to help with entity schema operations.
|
|
7
7
|
*/
|
|
@@ -45,6 +45,22 @@ export declare class EntitySchemaHelper {
|
|
|
45
45
|
* @returns The sort keys from the schema or undefined if there are none.
|
|
46
46
|
*/
|
|
47
47
|
static getSortProperties<T>(entitySchema: IEntitySchema<T>): IEntitySort<T>[] | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Get the composite index groups from the schema.
|
|
50
|
+
* Each property can be part of multiple indexes through its `indexGroup` list, so a property
|
|
51
|
+
* can appear in more than one group. The properties within a group are ordered by the `index`
|
|
52
|
+
* of their index entry, and each is returned with the sort direction it declared for that group.
|
|
53
|
+
* @param entitySchema The entity schema to find the index groups from.
|
|
54
|
+
* @returns The properties and their directions keyed by the group name, empty if there are no groups.
|
|
55
|
+
* @throws GeneralError if an index entry has an invalid direction or index, or if two properties
|
|
56
|
+
* claim the same index within the same group, or if a group contains fewer than two properties.
|
|
57
|
+
*/
|
|
58
|
+
static getIndexGroups<T>(entitySchema: IEntitySchema<T>): {
|
|
59
|
+
[group: string]: {
|
|
60
|
+
property: IEntitySchemaProperty<T>;
|
|
61
|
+
direction: SortDirection;
|
|
62
|
+
}[];
|
|
63
|
+
};
|
|
48
64
|
/**
|
|
49
65
|
* Build sort properties from the schema and override if necessary.
|
|
50
66
|
* @param entitySchema The entity schema to retrieve the default sort keys.
|
package/docs/changelog.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.10.1-next.1](https://github.com/iotaledger/twin-framework/compare/entity-v0.10.1-next.0...entity-v0.10.1-next.1) (2026-09-17)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add IEntitySchemaDiff and entitySchemaDiff utility ([#282](https://github.com/iotaledger/twin-framework/issues/282)) ([9d63e94](https://github.com/iotaledger/twin-framework/commit/9d63e94021ee2ffc138004ee68cf53d08a6b17f9))
|
|
9
|
+
* add support for null in EntityConditions.compare ([922c4ba](https://github.com/iotaledger/twin-framework/commit/922c4ba8af578b4e7eaaf21b3c37a9d788941487))
|
|
10
|
+
* add version field to IEntitySchema and EntitySchemaHelper.getVersion for migration support ([#346](https://github.com/iotaledger/twin-framework/issues/346)) ([e74557e](https://github.com/iotaledger/twin-framework/commit/e74557e4ccbda5b9971f4cfcd0852ba5957cead0))
|
|
11
|
+
* composite indexes ([b506d95](https://github.com/iotaledger/twin-framework/commit/b506d957a5eff6ff6012be773e98af36e750be4a))
|
|
12
|
+
* composite indexes ([#509](https://github.com/iotaledger/twin-framework/issues/509)) ([44b3327](https://github.com/iotaledger/twin-framework/commit/44b33270e6c8dabcc81cc4aa8d27a69255a5e7d4))
|
|
13
|
+
* entity max string lengths ([#497](https://github.com/iotaledger/twin-framework/issues/497)) ([7fc91e2](https://github.com/iotaledger/twin-framework/commit/7fc91e2a49b9f1e2113bfe10983e083298ef2539))
|
|
14
|
+
* entity schema decorators default value ([33397c2](https://github.com/iotaledger/twin-framework/commit/33397c2e24978a91257371a4c63ce7f6a7125d0c))
|
|
15
|
+
* entity schema diff updates ([#294](https://github.com/iotaledger/twin-framework/issues/294)) ([7a7a94d](https://github.com/iotaledger/twin-framework/commit/7a7a94d14ea5e785dd68fd6de1c5a84941721d28))
|
|
16
|
+
* linting and dependency update ([676b4e9](https://github.com/iotaledger/twin-framework/commit/676b4e9d9bce158065200bbf875bb31da81d166d))
|
|
17
|
+
* optimistic locking version property for schemas ([#443](https://github.com/iotaledger/twin-framework/issues/443)) ([1b90987](https://github.com/iotaledger/twin-framework/commit/1b909875dc915a0ec133d8424d013ae7e4ddfb48))
|
|
18
|
+
* support for object comparisons in entity conditions ([edae91d](https://github.com/iotaledger/twin-framework/commit/edae91d3205524080188a35e0ab04da036fa4f39))
|
|
19
|
+
* typescript 6 update ([1d10f31](https://github.com/iotaledger/twin-framework/commit/1d10f31e6516ec622773f45e88af82fe749b384a))
|
|
20
|
+
* update dependencies ([4da77ab](https://github.com/iotaledger/twin-framework/commit/4da77ab30f499e52825ac5a76f51436ceb59c26e))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Bug Fixes
|
|
24
|
+
|
|
25
|
+
* allow vacuous truth to match SQL semantics ([5bbc5e5](https://github.com/iotaledger/twin-framework/commit/5bbc5e53069f90bd39485a74a284eda9e5864e66))
|
|
26
|
+
* coerce and includes ([#429](https://github.com/iotaledger/twin-framework/issues/429)) ([98c9132](https://github.com/iotaledger/twin-framework/commit/98c9132316c0393ede9d6e634bbae9e5ece2d6b9))
|
|
27
|
+
* ensure __decorate is defined for decorators ([103a563](https://github.com/iotaledger/twin-framework/commit/103a563ce01ebdef6240d2e590e7b026e8692684))
|
|
28
|
+
* update copyright year ([#260](https://github.com/iotaledger/twin-framework/issues/260)) ([c4ad930](https://github.com/iotaledger/twin-framework/commit/c4ad930fcc84ba6b5447a8074574329870b4c3f5))
|
|
29
|
+
* vacuous entity condition ([#385](https://github.com/iotaledger/twin-framework/issues/385)) ([e1082bf](https://github.com/iotaledger/twin-framework/commit/e1082bff4a66ac270efc9c6654d974c19f889d2d))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
### Dependencies
|
|
33
|
+
|
|
34
|
+
* The following workspace dependencies were updated
|
|
35
|
+
* dependencies
|
|
36
|
+
* @twin.org/core bumped from 0.10.1-next.0 to 0.10.1-next.1
|
|
37
|
+
* @twin.org/nameof bumped from 0.10.1-next.0 to 0.10.1-next.1
|
|
38
|
+
* devDependencies
|
|
39
|
+
* @twin.org/nameof-transformer bumped from 0.10.1-next.0 to 0.10.1-next.1
|
|
40
|
+
* @twin.org/nameof-vitest-plugin bumped from 0.10.1-next.0 to 0.10.1-next.1
|
|
41
|
+
* @twin.org/validate-locales bumped from 0.10.1-next.0 to 0.10.1-next.1
|
|
42
|
+
|
|
3
43
|
## [0.10.0](https://github.com/iotaledger/twin-framework/compare/entity-v0.10.0...entity-v0.10.0) (2026-09-16)
|
|
4
44
|
|
|
5
45
|
|
|
@@ -28,7 +28,7 @@ Runtime name for the class.
|
|
|
28
28
|
|
|
29
29
|
Compare two arrays of entity schema properties and return a structured diff.
|
|
30
30
|
|
|
31
|
-
Properties are matched by their `property` key name. A property is considered modified when any structural field differs: `type`, `format`, `isPrimary`, `isSecondary`, `isVersion`, `sortDirection`, `optional`, `itemType`, or `itemTypeRef`.
|
|
31
|
+
Properties are matched by their `property` key name. A property is considered modified when any structural field differs: `type`, `format`, `isPrimary`, `isSecondary`, `indexGroup`, `isVersion`, `sortDirection`, `optional`, `itemType`, or `itemTypeRef`.
|
|
32
32
|
Documentation-only fields (`description`, `examples`) are intentionally excluded from the comparison to avoid spurious diffs.
|
|
33
33
|
|
|
34
34
|
Because a pure name change cannot be detected automatically, callers may supply a `renames` list mapping old names to new names. Renamed properties appear in `modified` (never in `added` or `removed`) even when no other fields changed. Rename lookups take priority over direct same-name matches, which allows swap renames to work correctly and prevents a renamed source from silently disappearing when the target name already existed in the old schema. Self-renames (`from === to`) are ignored and the property is classified normally.
|
|
@@ -154,6 +154,42 @@ The sort keys from the schema or undefined if there are none.
|
|
|
154
154
|
|
|
155
155
|
***
|
|
156
156
|
|
|
157
|
+
### getIndexGroups() {#getindexgroups}
|
|
158
|
+
|
|
159
|
+
> `static` **getIndexGroups**\<`T`\>(`entitySchema`): `object`
|
|
160
|
+
|
|
161
|
+
Get the composite index groups from the schema.
|
|
162
|
+
Each property can be part of multiple indexes through its `indexGroup` list, so a property
|
|
163
|
+
can appear in more than one group. The properties within a group are ordered by the `index`
|
|
164
|
+
of their index entry, and each is returned with the sort direction it declared for that group.
|
|
165
|
+
|
|
166
|
+
#### Type Parameters
|
|
167
|
+
|
|
168
|
+
##### T
|
|
169
|
+
|
|
170
|
+
`T`
|
|
171
|
+
|
|
172
|
+
#### Parameters
|
|
173
|
+
|
|
174
|
+
##### entitySchema
|
|
175
|
+
|
|
176
|
+
[`IEntitySchema`](../interfaces/IEntitySchema.md)\<`T`\>
|
|
177
|
+
|
|
178
|
+
The entity schema to find the index groups from.
|
|
179
|
+
|
|
180
|
+
#### Returns
|
|
181
|
+
|
|
182
|
+
`object`
|
|
183
|
+
|
|
184
|
+
The properties and their directions keyed by the group name, empty if there are no groups.
|
|
185
|
+
|
|
186
|
+
#### Throws
|
|
187
|
+
|
|
188
|
+
GeneralError if an index entry has an invalid direction or index, or if two properties
|
|
189
|
+
claim the same index within the same group, or if a group contains fewer than two properties.
|
|
190
|
+
|
|
191
|
+
***
|
|
192
|
+
|
|
157
193
|
### buildSortProperties() {#buildsortproperties}
|
|
158
194
|
|
|
159
195
|
> `static` **buildSortProperties**\<`T`\>(`entitySchema`, `overrideSortKeys?`): [`IEntitySort`](../interfaces/IEntitySort.md)\<`T`\>[] \| `undefined`
|
|
@@ -17,3 +17,11 @@ The options for the property.
|
|
|
17
17
|
`any`
|
|
18
18
|
|
|
19
19
|
The property decorator.
|
|
20
|
+
|
|
21
|
+
## Throws
|
|
22
|
+
|
|
23
|
+
GeneralError if an index group is declared on an object or array property.
|
|
24
|
+
|
|
25
|
+
## Throws
|
|
26
|
+
|
|
27
|
+
GeneralError if the same index group name is declared more than once for the property.
|
package/docs/reference/index.md
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
- [IEntitySchemaDiff](interfaces/IEntitySchemaDiff.md)
|
|
17
17
|
- [IEntitySchemaOptions](interfaces/IEntitySchemaOptions.md)
|
|
18
18
|
- [IEntitySchemaProperty](interfaces/IEntitySchemaProperty.md)
|
|
19
|
+
- [IEntitySchemaPropertyIndex](interfaces/IEntitySchemaPropertyIndex.md)
|
|
19
20
|
- [IEntitySort](interfaces/IEntitySort.md)
|
|
20
21
|
|
|
21
22
|
## Type Aliases
|
|
@@ -58,6 +58,16 @@ Is this a secondary index property.
|
|
|
58
58
|
|
|
59
59
|
***
|
|
60
60
|
|
|
61
|
+
### indexGroup? {#indexgroup}
|
|
62
|
+
|
|
63
|
+
> `optional` **indexGroup?**: [`IEntitySchemaPropertyIndex`](IEntitySchemaPropertyIndex.md)[]
|
|
64
|
+
|
|
65
|
+
The composite indexes this property is part of.
|
|
66
|
+
Connectors can use these to build a composite index for each group name,
|
|
67
|
+
combining all the properties which share that name, ordered by their index.
|
|
68
|
+
|
|
69
|
+
***
|
|
70
|
+
|
|
61
71
|
### isVersion? {#isversion}
|
|
62
72
|
|
|
63
73
|
> `optional` **isVersion?**: `boolean`
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Interface: IEntitySchemaPropertyIndex
|
|
2
|
+
|
|
3
|
+
Definition of a composite index that a property is part of.
|
|
4
|
+
|
|
5
|
+
## Properties
|
|
6
|
+
|
|
7
|
+
### name {#name}
|
|
8
|
+
|
|
9
|
+
> **name**: `string`
|
|
10
|
+
|
|
11
|
+
The name of the composite index group.
|
|
12
|
+
|
|
13
|
+
***
|
|
14
|
+
|
|
15
|
+
### direction {#direction}
|
|
16
|
+
|
|
17
|
+
> **direction**: [`SortDirection`](../type-aliases/SortDirection.md)
|
|
18
|
+
|
|
19
|
+
The sort direction for the property within the index.
|
|
20
|
+
|
|
21
|
+
***
|
|
22
|
+
|
|
23
|
+
### index {#index}
|
|
24
|
+
|
|
25
|
+
> **index**: `number`
|
|
26
|
+
|
|
27
|
+
The position of the property within the index, ordered ascending.
|
package/locales/en.json
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
"duplicateOldProperty": "The oldProperties array contains a duplicate property key \"{property}\"",
|
|
5
5
|
"duplicateNewProperty": "The newProperties array contains a duplicate property key \"{property}\""
|
|
6
6
|
},
|
|
7
|
+
"propertyDecorator": {
|
|
8
|
+
"indexGroupTypeNotSupported": "The property \"{property}\" of type \"{type}\" cannot be part of an index group",
|
|
9
|
+
"duplicateIndexGroup": "The property \"{property}\" declares the index group \"{group}\" more than once"
|
|
10
|
+
},
|
|
7
11
|
"entitySchemaHelper": {
|
|
8
12
|
"noIsPrimary": "Property \"entitySchema.properties\" must contain a value with isPrimary set",
|
|
9
13
|
"multipleIsPrimary": "Property \"entitySchema.properties\" contains more than one property with isPrimary set",
|
|
@@ -14,6 +18,10 @@
|
|
|
14
18
|
"maxLengthExceeded": "The entity property \"{property}\" has a length of {length} which exceeds the maximum length of {maxLength}",
|
|
15
19
|
"versionMustBeGreaterThanOrEqualZero": "Property \"entitySchema.version\" must be an integer >= 0, but got {version}",
|
|
16
20
|
"multipleVersionProperties": "The schema has more than one property with isVersion set, only one is allowed",
|
|
21
|
+
"invalidIndexGroupDirection": "The index group \"{group}\" on the property \"{property}\" has the direction \"{direction}\" which is not a valid sort direction",
|
|
22
|
+
"invalidIndexGroupIndex": "The index group \"{group}\" on the property \"{property}\" has the index {index} which must be an integer >= 0",
|
|
23
|
+
"duplicateIndexGroupIndex": "The index group \"{group}\" has more than one property using the index {index}, found again on the property \"{property}\"",
|
|
24
|
+
"indexGroupMustHaveAtLeastTwoProperties": "The index group \"{group}\" must contain at least two properties, but contains {count}",
|
|
17
25
|
"versionPropertyMustBeInteger": "The version property \"{property}\" has type \"{type}\" but must be of type integer"
|
|
18
26
|
}
|
|
19
27
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twin.org/entity",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.1-next.1",
|
|
4
4
|
"description": "Helpers for defining and working with entities",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
"node": ">=24.0.0"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@twin.org/core": "
|
|
18
|
-
"@twin.org/nameof": "
|
|
17
|
+
"@twin.org/core": "0.10.1-next.1",
|
|
18
|
+
"@twin.org/nameof": "0.10.1-next.1",
|
|
19
19
|
"reflect-metadata": "0.2.2",
|
|
20
20
|
"tslib": "2.8.1"
|
|
21
21
|
},
|