@twin.org/entity 0.10.0 → 0.10.1-next.10

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.
Files changed (34) hide show
  1. package/dist/es/decorators/propertyDecorator.js +25 -0
  2. package/dist/es/decorators/propertyDecorator.js.map +1 -1
  3. package/dist/es/index.js +1 -0
  4. package/dist/es/index.js.map +1 -1
  5. package/dist/es/models/IEntitySchemaProperty.js.map +1 -1
  6. package/dist/es/models/IEntitySchemaPropertyIndex.js +2 -0
  7. package/dist/es/models/IEntitySchemaPropertyIndex.js.map +1 -0
  8. package/dist/es/models/comparisonOperator.js +6 -1
  9. package/dist/es/models/comparisonOperator.js.map +1 -1
  10. package/dist/es/utils/entityConditions.js +122 -125
  11. package/dist/es/utils/entityConditions.js.map +1 -1
  12. package/dist/es/utils/entitySchemaDiffHelper.js +27 -1
  13. package/dist/es/utils/entitySchemaDiffHelper.js.map +1 -1
  14. package/dist/es/utils/entitySchemaHelper.js +65 -0
  15. package/dist/es/utils/entitySchemaHelper.js.map +1 -1
  16. package/dist/es/utils/entitySorter.js +2 -2
  17. package/dist/es/utils/entitySorter.js.map +1 -1
  18. package/dist/types/decorators/propertyDecorator.d.ts +2 -0
  19. package/dist/types/index.d.ts +1 -0
  20. package/dist/types/models/IEntitySchemaProperty.d.ts +7 -0
  21. package/dist/types/models/IEntitySchemaPropertyIndex.d.ts +18 -0
  22. package/dist/types/models/comparisonOperator.d.ts +6 -1
  23. package/dist/types/utils/entitySchemaDiffHelper.d.ts +1 -1
  24. package/dist/types/utils/entitySchemaHelper.d.ts +17 -1
  25. package/docs/changelog.md +263 -0
  26. package/docs/reference/classes/EntitySchemaDiffHelper.md +1 -1
  27. package/docs/reference/classes/EntitySchemaHelper.md +36 -0
  28. package/docs/reference/functions/property.md +8 -0
  29. package/docs/reference/index.md +1 -0
  30. package/docs/reference/interfaces/IEntitySchemaProperty.md +10 -0
  31. package/docs/reference/interfaces/IEntitySchemaPropertyIndex.md +27 -0
  32. package/docs/reference/variables/ComparisonOperator.md +8 -1
  33. package/locales/en.json +8 -0
  34. package/package.json +3 -3
@@ -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":"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;AAIjF,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAE3D;;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;;;;;;;;;OASG;IACI,MAAM,CAAC,cAAc,CAAI,YAA8B;QAG7D,MAAM,CAAC,MAAM,CACZ,kBAAkB,CAAC,UAAU,kBAE7B,YAAY,CACZ,CAAC;QAEF,MAAM,YAAY,GAKd,EAAE,CAAC;QACP,MAAM,YAAY,GAAqC,EAAE,CAAC;QAE1D,KAAK,MAAM,QAAQ,IAAI,YAAY,CAAC,UAAU,IAAI,EAAE,EAAE,CAAC;YACtD,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;gBACxC,KAAK,MAAM,aAAa,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC;oBACjD,IAAI,EAAE,CAAC,WAAW,CAAC,aAAa,EAAE,IAAI,CAAC,EAAE,CAAC;wBACzC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,SAAS,CAAC,EAAE,CAAC;4BACrE,MAAM,IAAI,YAAY,CAAC,kBAAkB,CAAC,UAAU,EAAE,4BAA4B,EAAE;gCACnF,KAAK,EAAE,aAAa,CAAC,IAAI;gCACzB,SAAS,EAAE,aAAa,CAAC,SAAS;gCAClC,QAAQ,EAAE,QAAQ,CAAC,QAAQ;6BAC3B,CAAC,CAAC;wBACJ,CAAC;wBAED,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,aAAa,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC;4BACjE,MAAM,IAAI,YAAY,CAAC,kBAAkB,CAAC,UAAU,EAAE,wBAAwB,EAAE;gCAC/E,KAAK,EAAE,aAAa,CAAC,IAAI;gCACzB,KAAK,EAAE,aAAa,CAAC,KAAK;gCAC1B,QAAQ,EAAE,QAAQ,CAAC,QAAQ;6BAC3B,CAAC,CAAC;wBACJ,CAAC;wBAED,YAAY,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,IAAI,GAAG,EAAE,CAAC;wBAC/C,IAAI,YAAY,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;4BAC/D,MAAM,IAAI,YAAY,CAAC,kBAAkB,CAAC,UAAU,EAAE,0BAA0B,EAAE;gCACjF,KAAK,EAAE,aAAa,CAAC,IAAI;gCACzB,KAAK,EAAE,aAAa,CAAC,KAAK;gCAC1B,QAAQ,EAAE,QAAQ,CAAC,QAAQ;6BAC3B,CAAC,CAAC;wBACJ,CAAC;wBACD,YAAY,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;wBAE1D,YAAY,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;wBACxC,YAAY,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC;4BACrC,KAAK,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,aAAa,CAAC,SAAS,EAAE;4BACvD,KAAK,EAAE,aAAa,CAAC,KAAK;yBAC1B,CAAC,CAAC;oBACJ,CAAC;gBACF,CAAC;YACF,CAAC;QACF,CAAC;QAED,MAAM,WAAW,GAEb,EAAE,CAAC;QAEP,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;YAC/C,IAAI,YAAY,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACpC,MAAM,IAAI,YAAY,CACrB,kBAAkB,CAAC,UAAU,EAC7B,wCAAwC,EACxC;oBACC,KAAK;oBACL,KAAK,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC,MAAM;iBACjC,CACD,CAAC;YACH,CAAC;YAED,WAAW,CAAC,KAAK,CAAC,GAAG,YAAY,CAAC,KAAK,CAAC;iBACtC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;iBACjC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QACvC,CAAC;QAED,OAAO,WAAW,CAAC;IACpB,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 { 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"]}
@@ -50,10 +50,10 @@ export class EntitySorter {
50
50
  res = 0;
51
51
  }
52
52
  else if (b1) {
53
- res = -1;
53
+ res = 1;
54
54
  }
55
55
  else {
56
- res = 1;
56
+ res = -1;
57
57
  }
58
58
  }
59
59
  else if (type === "string") {
@@ -1 +1 @@
1
- {"version":3,"file":"entitySorter.js","sourceRoot":"","sources":["../../../src/utils/entitySorter.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AAGpC,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAE3D;;GAEG;AACH,MAAM,OAAO,YAAY;IACxB;;;;;OAKG;IACI,MAAM,CAAC,IAAI,CAAI,QAAa,EAAE,aAAgC;QACpE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC;YACzD,OAAO,QAAQ,CAAC;QACjB,CAAC;QAED,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YAC7B,KAAK,MAAM,YAAY,IAAI,aAAa,EAAE,CAAC;gBAC1C,MAAM,aAAa,GAAG,YAAY,CAAC,OAAO,CACzC,CAAC,EACD,CAAC,EACD,YAAY,CAAC,QAAQ,EACrB,YAAY,CAAC,IAAI,EACjB,YAAY,CAAC,aAAa,CAC1B,CAAC;gBACF,IAAI,aAAa,KAAK,CAAC,EAAE,CAAC;oBACzB,OAAO,aAAa,CAAC;gBACtB,CAAC;YACF,CAAC;YACD,OAAO,CAAC,CAAC;QACV,CAAC,CAAC,CAAC;IACJ,CAAC;IAED;;;;;;;;OAQG;IACI,MAAM,CAAC,OAAO,CACpB,OAAU,EACV,OAAU,EACV,IAAa,EACb,IAA8B,EAC9B,YAA2B,aAAa,CAAC,SAAS;QAElD,IAAI,GAAG,GAAG,CAAC,CAAC;QACZ,MAAM,QAAQ,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAC1C,MAAM,QAAQ,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAE1C,IAAI,QAAQ,IAAI,QAAQ,EAAE,CAAC;YAC1B,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;gBAC7C,GAAG,GAAI,OAAO,CAAC,IAAI,CAAuB,GAAI,OAAO,CAAC,IAAI,CAAuB,CAAC;YACnF,CAAC;iBAAM,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;gBAC/B,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAuB,CAAC;gBAC/C,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAuB,CAAC;gBAC/C,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;oBACf,GAAG,GAAG,CAAC,CAAC;gBACT,CAAC;qBAAM,IAAI,EAAE,EAAE,CAAC;oBACf,GAAG,GAAG,CAAC,CAAC,CAAC;gBACV,CAAC;qBAAM,CAAC;oBACP,GAAG,GAAG,CAAC,CAAC;gBACT,CAAC;YACF,CAAC;iBAAM,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC9B,GAAG,GAAI,OAAO,CAAC,IAAI,CAAuB,CAAC,aAAa,CACvD,OAAO,CAAC,IAAI,CAAsB,CAClC,CAAC;YACH,CAAC;QACF,CAAC;aAAM,IAAI,QAAQ,EAAE,CAAC;YACrB,GAAG,GAAG,CAAC,CAAC,CAAC;QACV,CAAC;aAAM,IAAI,QAAQ,EAAE,CAAC;YACrB,GAAG,GAAG,CAAC,CAAC;QACT,CAAC;QAED,OAAO,SAAS,KAAK,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IAC/D,CAAC;CACD","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { Is } from \"@twin.org/core\";\nimport type { EntitySchemaPropertyType } from \"../models/entitySchemaPropertyType.js\";\nimport type { IEntitySort } from \"../models/IEntitySort.js\";\nimport { SortDirection } from \"../models/sortDirection.js\";\n\n/**\n * Class to perform sort operations on entities.\n */\nexport class EntitySorter {\n\t/**\n\t * Sort a list of entities using multiple keys and direction.\n\t * @param entities The list of entities.\n\t * @param entitySorters The sort keys to use.\n\t * @returns The sorted list.\n\t */\n\tpublic static sort<T>(entities: T[], entitySorters?: IEntitySort<T>[]): T[] {\n\t\tif (!Is.arrayValue(entities) || Is.empty(entitySorters)) {\n\t\t\treturn entities;\n\t\t}\n\n\t\treturn entities.sort((a, b) => {\n\t\t\tfor (const entitySorter of entitySorters) {\n\t\t\t\tconst compareResult = EntitySorter.compare(\n\t\t\t\t\ta,\n\t\t\t\t\tb,\n\t\t\t\t\tentitySorter.property,\n\t\t\t\t\tentitySorter.type,\n\t\t\t\t\tentitySorter.sortDirection\n\t\t\t\t);\n\t\t\t\tif (compareResult !== 0) {\n\t\t\t\t\treturn compareResult;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn 0;\n\t\t});\n\t}\n\n\t/**\n\t * Compare two properties.\n\t * @param entity1 The first entity.\n\t * @param entity2 The second entity.\n\t * @param prop The property to compare.\n\t * @param type The type of the property.\n\t * @param direction The direction of the sort.\n\t * @returns The result of the comparison.\n\t */\n\tpublic static compare<T>(\n\t\tentity1: T,\n\t\tentity2: T,\n\t\tprop: keyof T,\n\t\ttype: EntitySchemaPropertyType,\n\t\tdirection: SortDirection = SortDirection.Ascending\n\t): number {\n\t\tlet res = 0;\n\t\tconst hasProp1 = !Is.empty(entity1[prop]);\n\t\tconst hasProp2 = !Is.empty(entity2[prop]);\n\n\t\tif (hasProp1 && hasProp2) {\n\t\t\tif (type === \"number\" || type === \"integer\") {\n\t\t\t\tres = (entity1[prop] as unknown as number) - (entity2[prop] as unknown as number);\n\t\t\t} else if (type === \"boolean\") {\n\t\t\t\tconst b1 = entity1[prop] as unknown as boolean;\n\t\t\t\tconst b2 = entity2[prop] as unknown as boolean;\n\t\t\t\tif (b1 === b2) {\n\t\t\t\t\tres = 0;\n\t\t\t\t} else if (b1) {\n\t\t\t\t\tres = -1;\n\t\t\t\t} else {\n\t\t\t\t\tres = 1;\n\t\t\t\t}\n\t\t\t} else if (type === \"string\") {\n\t\t\t\tres = (entity1[prop] as unknown as string).localeCompare(\n\t\t\t\t\tentity2[prop] as unknown as string\n\t\t\t\t);\n\t\t\t}\n\t\t} else if (hasProp1) {\n\t\t\tres = -1;\n\t\t} else if (hasProp2) {\n\t\t\tres = 1;\n\t\t}\n\n\t\treturn direction === SortDirection.Ascending ? res : res * -1;\n\t}\n}\n"]}
1
+ {"version":3,"file":"entitySorter.js","sourceRoot":"","sources":["../../../src/utils/entitySorter.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AAGpC,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAE3D;;GAEG;AACH,MAAM,OAAO,YAAY;IACxB;;;;;OAKG;IACI,MAAM,CAAC,IAAI,CAAI,QAAa,EAAE,aAAgC;QACpE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC;YACzD,OAAO,QAAQ,CAAC;QACjB,CAAC;QAED,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YAC7B,KAAK,MAAM,YAAY,IAAI,aAAa,EAAE,CAAC;gBAC1C,MAAM,aAAa,GAAG,YAAY,CAAC,OAAO,CACzC,CAAC,EACD,CAAC,EACD,YAAY,CAAC,QAAQ,EACrB,YAAY,CAAC,IAAI,EACjB,YAAY,CAAC,aAAa,CAC1B,CAAC;gBACF,IAAI,aAAa,KAAK,CAAC,EAAE,CAAC;oBACzB,OAAO,aAAa,CAAC;gBACtB,CAAC;YACF,CAAC;YACD,OAAO,CAAC,CAAC;QACV,CAAC,CAAC,CAAC;IACJ,CAAC;IAED;;;;;;;;OAQG;IACI,MAAM,CAAC,OAAO,CACpB,OAAU,EACV,OAAU,EACV,IAAa,EACb,IAA8B,EAC9B,YAA2B,aAAa,CAAC,SAAS;QAElD,IAAI,GAAG,GAAG,CAAC,CAAC;QACZ,MAAM,QAAQ,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAC1C,MAAM,QAAQ,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAE1C,IAAI,QAAQ,IAAI,QAAQ,EAAE,CAAC;YAC1B,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;gBAC7C,GAAG,GAAI,OAAO,CAAC,IAAI,CAAuB,GAAI,OAAO,CAAC,IAAI,CAAuB,CAAC;YACnF,CAAC;iBAAM,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;gBAC/B,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAuB,CAAC;gBAC/C,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAuB,CAAC;gBAC/C,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;oBACf,GAAG,GAAG,CAAC,CAAC;gBACT,CAAC;qBAAM,IAAI,EAAE,EAAE,CAAC;oBACf,GAAG,GAAG,CAAC,CAAC;gBACT,CAAC;qBAAM,CAAC;oBACP,GAAG,GAAG,CAAC,CAAC,CAAC;gBACV,CAAC;YACF,CAAC;iBAAM,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC9B,GAAG,GAAI,OAAO,CAAC,IAAI,CAAuB,CAAC,aAAa,CACvD,OAAO,CAAC,IAAI,CAAsB,CAClC,CAAC;YACH,CAAC;QACF,CAAC;aAAM,IAAI,QAAQ,EAAE,CAAC;YACrB,GAAG,GAAG,CAAC,CAAC,CAAC;QACV,CAAC;aAAM,IAAI,QAAQ,EAAE,CAAC;YACrB,GAAG,GAAG,CAAC,CAAC;QACT,CAAC;QAED,OAAO,SAAS,KAAK,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IAC/D,CAAC;CACD","sourcesContent":["// Copyright 2024 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { Is } from \"@twin.org/core\";\nimport type { EntitySchemaPropertyType } from \"../models/entitySchemaPropertyType.js\";\nimport type { IEntitySort } from \"../models/IEntitySort.js\";\nimport { SortDirection } from \"../models/sortDirection.js\";\n\n/**\n * Class to perform sort operations on entities.\n */\nexport class EntitySorter {\n\t/**\n\t * Sort a list of entities using multiple keys and direction.\n\t * @param entities The list of entities.\n\t * @param entitySorters The sort keys to use.\n\t * @returns The sorted list.\n\t */\n\tpublic static sort<T>(entities: T[], entitySorters?: IEntitySort<T>[]): T[] {\n\t\tif (!Is.arrayValue(entities) || Is.empty(entitySorters)) {\n\t\t\treturn entities;\n\t\t}\n\n\t\treturn entities.sort((a, b) => {\n\t\t\tfor (const entitySorter of entitySorters) {\n\t\t\t\tconst compareResult = EntitySorter.compare(\n\t\t\t\t\ta,\n\t\t\t\t\tb,\n\t\t\t\t\tentitySorter.property,\n\t\t\t\t\tentitySorter.type,\n\t\t\t\t\tentitySorter.sortDirection\n\t\t\t\t);\n\t\t\t\tif (compareResult !== 0) {\n\t\t\t\t\treturn compareResult;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn 0;\n\t\t});\n\t}\n\n\t/**\n\t * Compare two properties.\n\t * @param entity1 The first entity.\n\t * @param entity2 The second entity.\n\t * @param prop The property to compare.\n\t * @param type The type of the property.\n\t * @param direction The direction of the sort.\n\t * @returns The result of the comparison.\n\t */\n\tpublic static compare<T>(\n\t\tentity1: T,\n\t\tentity2: T,\n\t\tprop: keyof T,\n\t\ttype: EntitySchemaPropertyType,\n\t\tdirection: SortDirection = SortDirection.Ascending\n\t): number {\n\t\tlet res = 0;\n\t\tconst hasProp1 = !Is.empty(entity1[prop]);\n\t\tconst hasProp2 = !Is.empty(entity2[prop]);\n\n\t\tif (hasProp1 && hasProp2) {\n\t\t\tif (type === \"number\" || type === \"integer\") {\n\t\t\t\tres = (entity1[prop] as unknown as number) - (entity2[prop] as unknown as number);\n\t\t\t} else if (type === \"boolean\") {\n\t\t\t\tconst b1 = entity1[prop] as unknown as boolean;\n\t\t\t\tconst b2 = entity2[prop] as unknown as boolean;\n\t\t\t\tif (b1 === b2) {\n\t\t\t\t\tres = 0;\n\t\t\t\t} else if (b1) {\n\t\t\t\t\tres = 1;\n\t\t\t\t} else {\n\t\t\t\t\tres = -1;\n\t\t\t\t}\n\t\t\t} else if (type === \"string\") {\n\t\t\t\tres = (entity1[prop] as unknown as string).localeCompare(\n\t\t\t\t\tentity2[prop] as unknown as string\n\t\t\t\t);\n\t\t\t}\n\t\t} else if (hasProp1) {\n\t\t\tres = -1;\n\t\t} else if (hasProp2) {\n\t\t\tres = 1;\n\t\t}\n\n\t\treturn direction === SortDirection.Ascending ? res : res * -1;\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;
@@ -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
+ }
@@ -28,7 +28,7 @@ export declare const ComparisonOperator: {
28
28
  readonly LessThanOrEqual: "less-than-or-equal";
29
29
  /**
30
30
  * Includes.
31
- * A string in a substring.
31
+ * A string in a substring, this is a scan and cannot use an index.
32
32
  * A set contains an element.
33
33
  * A list contains an element.
34
34
  */
@@ -40,6 +40,11 @@ export declare const ComparisonOperator: {
40
40
  * A list does not contain an element.
41
41
  */
42
42
  readonly NotIncludes: "not-includes";
43
+ /**
44
+ * Starts With.
45
+ * A string begins with a prefix, the anchored match can use an index.
46
+ */
47
+ readonly StartsWith: "starts-with";
43
48
  /**
44
49
  * In.
45
50
  * A element is in a set.
@@ -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 type { SortDirection } from "../models/sortDirection.js";
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,268 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.10.1-next.10](https://github.com/iotaledger/twin-framework/compare/entity-v0.10.1-next.9...entity-v0.10.1-next.10) (2026-09-22)
4
+
5
+
6
+ ### Miscellaneous Chores
7
+
8
+ * **entity:** Synchronize repo versions
9
+
10
+
11
+ ### Dependencies
12
+
13
+ * The following workspace dependencies were updated
14
+ * dependencies
15
+ * @twin.org/core bumped from 0.10.1-next.9 to 0.10.1-next.10
16
+ * @twin.org/nameof bumped from 0.10.1-next.9 to 0.10.1-next.10
17
+ * devDependencies
18
+ * @twin.org/nameof-transformer bumped from 0.10.1-next.9 to 0.10.1-next.10
19
+ * @twin.org/nameof-vitest-plugin bumped from 0.10.1-next.9 to 0.10.1-next.10
20
+ * @twin.org/validate-locales bumped from 0.10.1-next.9 to 0.10.1-next.10
21
+
22
+ ## [0.10.1-next.9](https://github.com/iotaledger/twin-framework/compare/entity-v0.10.1-next.8...entity-v0.10.1-next.9) (2026-09-21)
23
+
24
+
25
+ ### Features
26
+
27
+ * add IEntitySchemaDiff and entitySchemaDiff utility ([#282](https://github.com/iotaledger/twin-framework/issues/282)) ([9d63e94](https://github.com/iotaledger/twin-framework/commit/9d63e94021ee2ffc138004ee68cf53d08a6b17f9))
28
+ * add StartsWith comparison operator ([#517](https://github.com/iotaledger/twin-framework/issues/517)) ([5c39b5b](https://github.com/iotaledger/twin-framework/commit/5c39b5b77bb5d59199e6dad2000d1806e7f4035d))
29
+ * add support for null in EntityConditions.compare ([922c4ba](https://github.com/iotaledger/twin-framework/commit/922c4ba8af578b4e7eaaf21b3c37a9d788941487))
30
+ * 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))
31
+ * composite indexes ([b506d95](https://github.com/iotaledger/twin-framework/commit/b506d957a5eff6ff6012be773e98af36e750be4a))
32
+ * composite indexes ([#509](https://github.com/iotaledger/twin-framework/issues/509)) ([44b3327](https://github.com/iotaledger/twin-framework/commit/44b33270e6c8dabcc81cc4aa8d27a69255a5e7d4))
33
+ * dev tools ([#500](https://github.com/iotaledger/twin-framework/issues/500)) ([0879e55](https://github.com/iotaledger/twin-framework/commit/0879e558c71dca5e992d14914c6bde560a191df2))
34
+ * entity conditions boolean in ([#530](https://github.com/iotaledger/twin-framework/issues/530)) ([50cc09a](https://github.com/iotaledger/twin-framework/commit/50cc09aa7b32e0dd7936ed0c2f074b0b166bcbf5))
35
+ * entity max string lengths ([#497](https://github.com/iotaledger/twin-framework/issues/497)) ([7fc91e2](https://github.com/iotaledger/twin-framework/commit/7fc91e2a49b9f1e2113bfe10983e083298ef2539))
36
+ * entity schema decorators default value ([33397c2](https://github.com/iotaledger/twin-framework/commit/33397c2e24978a91257371a4c63ce7f6a7125d0c))
37
+ * entity schema diff updates ([#294](https://github.com/iotaledger/twin-framework/issues/294)) ([7a7a94d](https://github.com/iotaledger/twin-framework/commit/7a7a94d14ea5e785dd68fd6de1c5a84941721d28))
38
+ * linting and dependency update ([676b4e9](https://github.com/iotaledger/twin-framework/commit/676b4e9d9bce158065200bbf875bb31da81d166d))
39
+ * native enhancements ([#527](https://github.com/iotaledger/twin-framework/issues/527)) ([dc77328](https://github.com/iotaledger/twin-framework/commit/dc77328df201ff77809d8e8792c27780bfb6d8c4))
40
+ * optimistic locking version property for schemas ([#443](https://github.com/iotaledger/twin-framework/issues/443)) ([1b90987](https://github.com/iotaledger/twin-framework/commit/1b909875dc915a0ec133d8424d013ae7e4ddfb48))
41
+ * refactor EntityConditions compare ([576f894](https://github.com/iotaledger/twin-framework/commit/576f894f67186353d112a95ff57c94bc92e7f93c))
42
+ * typescript 6 update ([1d10f31](https://github.com/iotaledger/twin-framework/commit/1d10f31e6516ec622773f45e88af82fe749b384a))
43
+ * update dependencies ([4da77ab](https://github.com/iotaledger/twin-framework/commit/4da77ab30f499e52825ac5a76f51436ceb59c26e))
44
+
45
+
46
+ ### Bug Fixes
47
+
48
+ * allow vacuous truth to match SQL semantics ([5bbc5e5](https://github.com/iotaledger/twin-framework/commit/5bbc5e53069f90bd39485a74a284eda9e5864e66))
49
+ * coerce and includes ([#429](https://github.com/iotaledger/twin-framework/issues/429)) ([98c9132](https://github.com/iotaledger/twin-framework/commit/98c9132316c0393ede9d6e634bbae9e5ece2d6b9))
50
+ * ensure __decorate is defined for decorators ([103a563](https://github.com/iotaledger/twin-framework/commit/103a563ce01ebdef6240d2e590e7b026e8692684))
51
+ * update copyright year ([#260](https://github.com/iotaledger/twin-framework/issues/260)) ([c4ad930](https://github.com/iotaledger/twin-framework/commit/c4ad930fcc84ba6b5447a8074574329870b4c3f5))
52
+ * vacuous entity condition ([#385](https://github.com/iotaledger/twin-framework/issues/385)) ([e1082bf](https://github.com/iotaledger/twin-framework/commit/e1082bff4a66ac270efc9c6654d974c19f889d2d))
53
+
54
+
55
+ ### Dependencies
56
+
57
+ * The following workspace dependencies were updated
58
+ * dependencies
59
+ * @twin.org/core bumped from 0.10.1-next.8 to 0.10.1-next.9
60
+ * @twin.org/nameof bumped from 0.10.1-next.8 to 0.10.1-next.9
61
+ * devDependencies
62
+ * @twin.org/nameof-transformer bumped from 0.10.1-next.8 to 0.10.1-next.9
63
+ * @twin.org/nameof-vitest-plugin bumped from 0.10.1-next.8 to 0.10.1-next.9
64
+ * @twin.org/validate-locales bumped from 0.10.1-next.8 to 0.10.1-next.9
65
+
66
+ ## [0.10.1-next.8](https://github.com/iotaledger/twin-framework/compare/entity-v0.10.1-next.7...entity-v0.10.1-next.8) (2026-09-21)
67
+
68
+
69
+ ### Features
70
+
71
+ * add IEntitySchemaDiff and entitySchemaDiff utility ([#282](https://github.com/iotaledger/twin-framework/issues/282)) ([9d63e94](https://github.com/iotaledger/twin-framework/commit/9d63e94021ee2ffc138004ee68cf53d08a6b17f9))
72
+ * add StartsWith comparison operator ([#517](https://github.com/iotaledger/twin-framework/issues/517)) ([5c39b5b](https://github.com/iotaledger/twin-framework/commit/5c39b5b77bb5d59199e6dad2000d1806e7f4035d))
73
+ * add support for null in EntityConditions.compare ([922c4ba](https://github.com/iotaledger/twin-framework/commit/922c4ba8af578b4e7eaaf21b3c37a9d788941487))
74
+ * 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))
75
+ * composite indexes ([b506d95](https://github.com/iotaledger/twin-framework/commit/b506d957a5eff6ff6012be773e98af36e750be4a))
76
+ * composite indexes ([#509](https://github.com/iotaledger/twin-framework/issues/509)) ([44b3327](https://github.com/iotaledger/twin-framework/commit/44b33270e6c8dabcc81cc4aa8d27a69255a5e7d4))
77
+ * dev tools ([#500](https://github.com/iotaledger/twin-framework/issues/500)) ([0879e55](https://github.com/iotaledger/twin-framework/commit/0879e558c71dca5e992d14914c6bde560a191df2))
78
+ * entity conditions boolean in ([#530](https://github.com/iotaledger/twin-framework/issues/530)) ([50cc09a](https://github.com/iotaledger/twin-framework/commit/50cc09aa7b32e0dd7936ed0c2f074b0b166bcbf5))
79
+ * entity max string lengths ([#497](https://github.com/iotaledger/twin-framework/issues/497)) ([7fc91e2](https://github.com/iotaledger/twin-framework/commit/7fc91e2a49b9f1e2113bfe10983e083298ef2539))
80
+ * entity schema decorators default value ([33397c2](https://github.com/iotaledger/twin-framework/commit/33397c2e24978a91257371a4c63ce7f6a7125d0c))
81
+ * entity schema diff updates ([#294](https://github.com/iotaledger/twin-framework/issues/294)) ([7a7a94d](https://github.com/iotaledger/twin-framework/commit/7a7a94d14ea5e785dd68fd6de1c5a84941721d28))
82
+ * linting and dependency update ([676b4e9](https://github.com/iotaledger/twin-framework/commit/676b4e9d9bce158065200bbf875bb31da81d166d))
83
+ * native enhancements ([#527](https://github.com/iotaledger/twin-framework/issues/527)) ([dc77328](https://github.com/iotaledger/twin-framework/commit/dc77328df201ff77809d8e8792c27780bfb6d8c4))
84
+ * optimistic locking version property for schemas ([#443](https://github.com/iotaledger/twin-framework/issues/443)) ([1b90987](https://github.com/iotaledger/twin-framework/commit/1b909875dc915a0ec133d8424d013ae7e4ddfb48))
85
+ * refactor EntityConditions compare ([576f894](https://github.com/iotaledger/twin-framework/commit/576f894f67186353d112a95ff57c94bc92e7f93c))
86
+ * typescript 6 update ([1d10f31](https://github.com/iotaledger/twin-framework/commit/1d10f31e6516ec622773f45e88af82fe749b384a))
87
+ * update dependencies ([4da77ab](https://github.com/iotaledger/twin-framework/commit/4da77ab30f499e52825ac5a76f51436ceb59c26e))
88
+
89
+
90
+ ### Bug Fixes
91
+
92
+ * allow vacuous truth to match SQL semantics ([5bbc5e5](https://github.com/iotaledger/twin-framework/commit/5bbc5e53069f90bd39485a74a284eda9e5864e66))
93
+ * coerce and includes ([#429](https://github.com/iotaledger/twin-framework/issues/429)) ([98c9132](https://github.com/iotaledger/twin-framework/commit/98c9132316c0393ede9d6e634bbae9e5ece2d6b9))
94
+ * ensure __decorate is defined for decorators ([103a563](https://github.com/iotaledger/twin-framework/commit/103a563ce01ebdef6240d2e590e7b026e8692684))
95
+ * update copyright year ([#260](https://github.com/iotaledger/twin-framework/issues/260)) ([c4ad930](https://github.com/iotaledger/twin-framework/commit/c4ad930fcc84ba6b5447a8074574329870b4c3f5))
96
+ * vacuous entity condition ([#385](https://github.com/iotaledger/twin-framework/issues/385)) ([e1082bf](https://github.com/iotaledger/twin-framework/commit/e1082bff4a66ac270efc9c6654d974c19f889d2d))
97
+
98
+
99
+ ### Dependencies
100
+
101
+ * The following workspace dependencies were updated
102
+ * dependencies
103
+ * @twin.org/core bumped from 0.10.1-next.7 to 0.10.1-next.8
104
+ * @twin.org/nameof bumped from 0.10.1-next.7 to 0.10.1-next.8
105
+ * devDependencies
106
+ * @twin.org/nameof-transformer bumped from 0.10.1-next.7 to 0.10.1-next.8
107
+ * @twin.org/nameof-vitest-plugin bumped from 0.10.1-next.7 to 0.10.1-next.8
108
+ * @twin.org/validate-locales bumped from 0.10.1-next.7 to 0.10.1-next.8
109
+
110
+ ## [0.10.1-next.7](https://github.com/iotaledger/twin-framework/compare/entity-v0.10.1-next.6...entity-v0.10.1-next.7) (2026-09-21)
111
+
112
+
113
+ ### Features
114
+
115
+ * dev tools ([#500](https://github.com/iotaledger/twin-framework/issues/500)) ([0879e55](https://github.com/iotaledger/twin-framework/commit/0879e558c71dca5e992d14914c6bde560a191df2))
116
+ * entity conditions boolean in ([#530](https://github.com/iotaledger/twin-framework/issues/530)) ([50cc09a](https://github.com/iotaledger/twin-framework/commit/50cc09aa7b32e0dd7936ed0c2f074b0b166bcbf5))
117
+ * refactor EntityConditions compare ([576f894](https://github.com/iotaledger/twin-framework/commit/576f894f67186353d112a95ff57c94bc92e7f93c))
118
+
119
+
120
+ ### Dependencies
121
+
122
+ * The following workspace dependencies were updated
123
+ * dependencies
124
+ * @twin.org/core bumped from 0.10.1-next.6 to 0.10.1-next.7
125
+ * @twin.org/nameof bumped from 0.10.1-next.6 to 0.10.1-next.7
126
+ * devDependencies
127
+ * @twin.org/nameof-transformer bumped from 0.10.1-next.6 to 0.10.1-next.7
128
+ * @twin.org/nameof-vitest-plugin bumped from 0.10.1-next.6 to 0.10.1-next.7
129
+ * @twin.org/validate-locales bumped from 0.10.1-next.6 to 0.10.1-next.7
130
+
131
+ ## [0.10.1-next.6](https://github.com/iotaledger/twin-framework/compare/entity-v0.10.1-next.5...entity-v0.10.1-next.6) (2026-09-21)
132
+
133
+
134
+ ### Features
135
+
136
+ * native enhancements ([#527](https://github.com/iotaledger/twin-framework/issues/527)) ([dc77328](https://github.com/iotaledger/twin-framework/commit/dc77328df201ff77809d8e8792c27780bfb6d8c4))
137
+
138
+
139
+ ### Dependencies
140
+
141
+ * The following workspace dependencies were updated
142
+ * dependencies
143
+ * @twin.org/core bumped from 0.10.1-next.5 to 0.10.1-next.6
144
+ * @twin.org/nameof bumped from 0.10.1-next.5 to 0.10.1-next.6
145
+ * devDependencies
146
+ * @twin.org/nameof-transformer bumped from 0.10.1-next.5 to 0.10.1-next.6
147
+ * @twin.org/nameof-vitest-plugin bumped from 0.10.1-next.5 to 0.10.1-next.6
148
+ * @twin.org/validate-locales bumped from 0.10.1-next.5 to 0.10.1-next.6
149
+
150
+ ## [0.10.1-next.5](https://github.com/iotaledger/twin-framework/compare/entity-v0.10.1-next.4...entity-v0.10.1-next.5) (2026-09-18)
151
+
152
+
153
+ ### Miscellaneous Chores
154
+
155
+ * **entity:** Synchronize repo versions
156
+
157
+
158
+ ### Dependencies
159
+
160
+ * The following workspace dependencies were updated
161
+ * dependencies
162
+ * @twin.org/core bumped from 0.10.1-next.4 to 0.10.1-next.5
163
+ * @twin.org/nameof bumped from 0.10.1-next.4 to 0.10.1-next.5
164
+ * devDependencies
165
+ * @twin.org/nameof-transformer bumped from 0.10.1-next.4 to 0.10.1-next.5
166
+ * @twin.org/nameof-vitest-plugin bumped from 0.10.1-next.4 to 0.10.1-next.5
167
+ * @twin.org/validate-locales bumped from 0.10.1-next.4 to 0.10.1-next.5
168
+
169
+ ## [0.10.1-next.4](https://github.com/iotaledger/twin-framework/compare/entity-v0.10.1-next.3...entity-v0.10.1-next.4) (2026-09-18)
170
+
171
+
172
+ ### Miscellaneous Chores
173
+
174
+ * **entity:** Synchronize repo versions
175
+
176
+
177
+ ### Dependencies
178
+
179
+ * The following workspace dependencies were updated
180
+ * dependencies
181
+ * @twin.org/core bumped from 0.10.1-next.3 to 0.10.1-next.4
182
+ * @twin.org/nameof bumped from 0.10.1-next.3 to 0.10.1-next.4
183
+ * devDependencies
184
+ * @twin.org/nameof-transformer bumped from 0.10.1-next.3 to 0.10.1-next.4
185
+ * @twin.org/nameof-vitest-plugin bumped from 0.10.1-next.3 to 0.10.1-next.4
186
+ * @twin.org/validate-locales bumped from 0.10.1-next.3 to 0.10.1-next.4
187
+
188
+ ## [0.10.1-next.3](https://github.com/iotaledger/twin-framework/compare/entity-v0.10.1-next.2...entity-v0.10.1-next.3) (2026-09-18)
189
+
190
+
191
+ ### Features
192
+
193
+ * add StartsWith comparison operator ([#517](https://github.com/iotaledger/twin-framework/issues/517)) ([5c39b5b](https://github.com/iotaledger/twin-framework/commit/5c39b5b77bb5d59199e6dad2000d1806e7f4035d))
194
+
195
+
196
+ ### Dependencies
197
+
198
+ * The following workspace dependencies were updated
199
+ * dependencies
200
+ * @twin.org/core bumped from 0.10.1-next.2 to 0.10.1-next.3
201
+ * @twin.org/nameof bumped from 0.10.1-next.2 to 0.10.1-next.3
202
+ * devDependencies
203
+ * @twin.org/nameof-transformer bumped from 0.10.1-next.2 to 0.10.1-next.3
204
+ * @twin.org/nameof-vitest-plugin bumped from 0.10.1-next.2 to 0.10.1-next.3
205
+ * @twin.org/validate-locales bumped from 0.10.1-next.2 to 0.10.1-next.3
206
+
207
+ ## [0.10.1-next.2](https://github.com/iotaledger/twin-framework/compare/entity-v0.10.1-next.1...entity-v0.10.1-next.2) (2026-09-17)
208
+
209
+
210
+ ### Miscellaneous Chores
211
+
212
+ * **entity:** Synchronize repo versions
213
+
214
+
215
+ ### Dependencies
216
+
217
+ * The following workspace dependencies were updated
218
+ * dependencies
219
+ * @twin.org/core bumped from 0.10.1-next.1 to 0.10.1-next.2
220
+ * @twin.org/nameof bumped from 0.10.1-next.1 to 0.10.1-next.2
221
+ * devDependencies
222
+ * @twin.org/nameof-transformer bumped from 0.10.1-next.1 to 0.10.1-next.2
223
+ * @twin.org/nameof-vitest-plugin bumped from 0.10.1-next.1 to 0.10.1-next.2
224
+ * @twin.org/validate-locales bumped from 0.10.1-next.1 to 0.10.1-next.2
225
+
226
+ ## [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)
227
+
228
+
229
+ ### Features
230
+
231
+ * add IEntitySchemaDiff and entitySchemaDiff utility ([#282](https://github.com/iotaledger/twin-framework/issues/282)) ([9d63e94](https://github.com/iotaledger/twin-framework/commit/9d63e94021ee2ffc138004ee68cf53d08a6b17f9))
232
+ * add support for null in EntityConditions.compare ([922c4ba](https://github.com/iotaledger/twin-framework/commit/922c4ba8af578b4e7eaaf21b3c37a9d788941487))
233
+ * 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))
234
+ * composite indexes ([b506d95](https://github.com/iotaledger/twin-framework/commit/b506d957a5eff6ff6012be773e98af36e750be4a))
235
+ * composite indexes ([#509](https://github.com/iotaledger/twin-framework/issues/509)) ([44b3327](https://github.com/iotaledger/twin-framework/commit/44b33270e6c8dabcc81cc4aa8d27a69255a5e7d4))
236
+ * entity max string lengths ([#497](https://github.com/iotaledger/twin-framework/issues/497)) ([7fc91e2](https://github.com/iotaledger/twin-framework/commit/7fc91e2a49b9f1e2113bfe10983e083298ef2539))
237
+ * entity schema decorators default value ([33397c2](https://github.com/iotaledger/twin-framework/commit/33397c2e24978a91257371a4c63ce7f6a7125d0c))
238
+ * entity schema diff updates ([#294](https://github.com/iotaledger/twin-framework/issues/294)) ([7a7a94d](https://github.com/iotaledger/twin-framework/commit/7a7a94d14ea5e785dd68fd6de1c5a84941721d28))
239
+ * linting and dependency update ([676b4e9](https://github.com/iotaledger/twin-framework/commit/676b4e9d9bce158065200bbf875bb31da81d166d))
240
+ * optimistic locking version property for schemas ([#443](https://github.com/iotaledger/twin-framework/issues/443)) ([1b90987](https://github.com/iotaledger/twin-framework/commit/1b909875dc915a0ec133d8424d013ae7e4ddfb48))
241
+ * support for object comparisons in entity conditions ([edae91d](https://github.com/iotaledger/twin-framework/commit/edae91d3205524080188a35e0ab04da036fa4f39))
242
+ * typescript 6 update ([1d10f31](https://github.com/iotaledger/twin-framework/commit/1d10f31e6516ec622773f45e88af82fe749b384a))
243
+ * update dependencies ([4da77ab](https://github.com/iotaledger/twin-framework/commit/4da77ab30f499e52825ac5a76f51436ceb59c26e))
244
+
245
+
246
+ ### Bug Fixes
247
+
248
+ * allow vacuous truth to match SQL semantics ([5bbc5e5](https://github.com/iotaledger/twin-framework/commit/5bbc5e53069f90bd39485a74a284eda9e5864e66))
249
+ * coerce and includes ([#429](https://github.com/iotaledger/twin-framework/issues/429)) ([98c9132](https://github.com/iotaledger/twin-framework/commit/98c9132316c0393ede9d6e634bbae9e5ece2d6b9))
250
+ * ensure __decorate is defined for decorators ([103a563](https://github.com/iotaledger/twin-framework/commit/103a563ce01ebdef6240d2e590e7b026e8692684))
251
+ * update copyright year ([#260](https://github.com/iotaledger/twin-framework/issues/260)) ([c4ad930](https://github.com/iotaledger/twin-framework/commit/c4ad930fcc84ba6b5447a8074574329870b4c3f5))
252
+ * vacuous entity condition ([#385](https://github.com/iotaledger/twin-framework/issues/385)) ([e1082bf](https://github.com/iotaledger/twin-framework/commit/e1082bff4a66ac270efc9c6654d974c19f889d2d))
253
+
254
+
255
+ ### Dependencies
256
+
257
+ * The following workspace dependencies were updated
258
+ * dependencies
259
+ * @twin.org/core bumped from 0.10.1-next.0 to 0.10.1-next.1
260
+ * @twin.org/nameof bumped from 0.10.1-next.0 to 0.10.1-next.1
261
+ * devDependencies
262
+ * @twin.org/nameof-transformer bumped from 0.10.1-next.0 to 0.10.1-next.1
263
+ * @twin.org/nameof-vitest-plugin bumped from 0.10.1-next.0 to 0.10.1-next.1
264
+ * @twin.org/validate-locales bumped from 0.10.1-next.0 to 0.10.1-next.1
265
+
3
266
  ## [0.10.0](https://github.com/iotaledger/twin-framework/compare/entity-v0.10.0...entity-v0.10.0) (2026-09-16)
4
267
 
5
268
 
@@ -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.