@twin.org/entity 0.0.3-next.41 → 0.0.3-next.42

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/index.js CHANGED
@@ -18,7 +18,7 @@ export * from "./models/sortDirection.js";
18
18
  export * from "./utils/decoratorHelper.js";
19
19
  export * from "./utils/entityConditions.js";
20
20
  export * from "./utils/entitySchemaHelper.js";
21
- export * from "./utils/entitySchemaDiff.js";
21
+ export * from "./utils/entitySchemaDiffHelper.js";
22
22
  export * from "./utils/entitySorter.js";
23
23
  export * from "./models/IEntitySchemaDiff.js";
24
24
  //# sourceMappingURL=index.js.map
@@ -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,6BAA6B,CAAC;AAC5C,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/entitySchemaDiff.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,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 +1 @@
1
- {"version":3,"file":"IEntitySchemaDiff.js","sourceRoot":"","sources":["../../../src/models/IEntitySchemaDiff.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IEntitySchemaProperty } from \"./IEntitySchemaProperty.js\";\n\n/**\n * The result of comparing two sets of entity schema properties.\n */\nexport interface IEntitySchemaDiff<T = unknown> {\n\t/**\n\t * Properties present in the new schema but absent from the old one.\n\t * Each entry is the full property descriptor from the new schema, so it can\n\t * be passed directly to bootstrap / table-creation logic.\n\t */\n\tadded: IEntitySchemaProperty<T>[];\n\n\t/**\n\t * Properties present in the old schema but absent from the new one.\n\t * Each entry is the full property descriptor from the old schema, allowing\n\t * connectors to drop the correct column, index, or field by name.\n\t */\n\tremoved: IEntitySchemaProperty<T>[];\n\n\t/**\n\t * Properties that exist in both schemas but differ in at least one structural\n\t * field (type, format, isSecondary, sortDirection, optional, itemType, itemTypeRef).\n\t * `from` is the old descriptor; `to` is the new one.\n\t * Both are full property objects so connectors can drop the old definition and\n\t * create the new one using the same bootstrap code path.\n\t */\n\tmodified: {\n\t\t/**\n\t\t * The old property descriptor.\n\t\t */\n\t\tfrom: IEntitySchemaProperty<T>;\n\t\t/**\n\t\t * The new property descriptor.\n\t\t */\n\t\tto: IEntitySchemaProperty<T>;\n\t}[];\n}\n"]}
1
+ {"version":3,"file":"IEntitySchemaDiff.js","sourceRoot":"","sources":["../../../src/models/IEntitySchemaDiff.ts"],"names":[],"mappings":"","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IEntitySchemaProperty } from \"./IEntitySchemaProperty.js\";\n\n/**\n * The result of comparing two sets of entity schema properties.\n */\nexport interface IEntitySchemaDiff<T = unknown, U = unknown> {\n\t/**\n\t * Properties that are structurally identical between the old and new schemas.\n\t */\n\tunchanged: IEntitySchemaProperty<T | U>[];\n\n\t/**\n\t * Properties present in the new schema but absent from the old one.\n\t */\n\tadded: IEntitySchemaProperty<U>[];\n\n\t/**\n\t * Properties present in the old schema but absent from the new one.\n\t */\n\tremoved: IEntitySchemaProperty<T>[];\n\n\t/**\n\t * Properties that exist in both schemas but differ in at least one structural\n\t * field (property, type, format, isPrimary, isSecondary, sortDirection, optional, itemType, itemTypeRef).\n\t * `from` is the old descriptor; `to` is the new one.\n\t */\n\tmodified: {\n\t\t/**\n\t\t * The old property descriptor.\n\t\t */\n\t\tfrom: IEntitySchemaProperty<T>;\n\t\t/**\n\t\t * The new property descriptor.\n\t\t */\n\t\tto: IEntitySchemaProperty<U>;\n\t}[];\n}\n"]}
@@ -0,0 +1,139 @@
1
+ // Copyright 2026 IOTA Stiftung.
2
+ // SPDX-License-Identifier: Apache-2.0.
3
+ import { GeneralError, Guards } from "@twin.org/core";
4
+ /**
5
+ * Helper class for comparing entity schemas and generating diffs.
6
+ */
7
+ export class EntitySchemaDiffHelper {
8
+ /**
9
+ * Runtime name for the class.
10
+ */
11
+ static CLASS_NAME = "EntitySchemaDiffHelper";
12
+ /**
13
+ * Compare two arrays of entity schema properties and return a structured diff.
14
+ *
15
+ * Properties are matched by their `property` key name. A property is considered modified when any structural field differs: `type`, `format`, `isPrimary`, `isSecondary`, `sortDirection`, `optional`, `itemType`, or `itemTypeRef`.
16
+ * Documentation-only fields (`description`, `examples`) are intentionally excluded from the comparison to avoid spurious diffs.
17
+ *
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.
19
+ *
20
+ * 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.
21
+ * @param oldProperties The property descriptors from the current (live) schema.
22
+ * @param newProperties The property descriptors from the target (new) schema.
23
+ * @param renames Optional list of property renames `{ from, to }` where `from` is the old name and `to` is the new name.
24
+ * @returns A diff object with `added`, `removed`, `modified`, and `unchanged` arrays, each containing full `IEntitySchemaProperty` descriptors.
25
+ * @throws `GeneralError` if either input array contains duplicate property keys.
26
+ */
27
+ static diff(oldProperties, newProperties, renames) {
28
+ Guards.array(EntitySchemaDiffHelper.CLASS_NAME, "oldProperties", oldProperties);
29
+ Guards.array(EntitySchemaDiffHelper.CLASS_NAME, "newProperties", newProperties);
30
+ const added = [];
31
+ const removed = [];
32
+ const modified = [];
33
+ const unchanged = [];
34
+ const oldMap = new Map();
35
+ for (const prop of oldProperties) {
36
+ const propKey = prop.property;
37
+ if (oldMap.has(propKey)) {
38
+ throw new GeneralError(EntitySchemaDiffHelper.CLASS_NAME, "duplicateOldProperty", {
39
+ property: propKey
40
+ });
41
+ }
42
+ oldMap.set(propKey, prop);
43
+ }
44
+ const newMap = new Map();
45
+ for (const prop of newProperties) {
46
+ const propKey = prop.property;
47
+ if (newMap.has(propKey)) {
48
+ throw new GeneralError(EntitySchemaDiffHelper.CLASS_NAME, "duplicateNewProperty", {
49
+ property: propKey
50
+ });
51
+ }
52
+ newMap.set(propKey, prop);
53
+ }
54
+ // new-name → old-name, used when iterating newProperties
55
+ const renameToFrom = new Map();
56
+ if (renames) {
57
+ for (const rename of renames) {
58
+ renameToFrom.set(rename.to, rename.from);
59
+ }
60
+ }
61
+ // Old names that were consumed by a rename (prevents double-use of the same source).
62
+ const consumedByRename = new Set();
63
+ // New names matched via rename path (their same-named old prop is not their direct match).
64
+ const newKeyMatchedViaRename = new Set();
65
+ for (const newProp of newProperties) {
66
+ const key = newProp.property;
67
+ // Rename lookup takes priority over a direct same-name match so that:
68
+ // - swap renames work (both names exist in old and new)
69
+ // - a renamed source does not vanish when the target name already existed in old
70
+ // Self-renames (fromKey === key) are skipped so the property is classified normally.
71
+ const fromKey = renameToFrom.get(key);
72
+ const renamedSource = fromKey !== undefined ? oldMap.get(fromKey) : undefined;
73
+ if (fromKey !== undefined &&
74
+ fromKey !== key &&
75
+ renamedSource !== undefined &&
76
+ !consumedByRename.has(fromKey)) {
77
+ modified.push({ from: renamedSource, to: newProp });
78
+ consumedByRename.add(fromKey);
79
+ newKeyMatchedViaRename.add(key);
80
+ }
81
+ else {
82
+ const oldProp = oldMap.get(key);
83
+ if (oldProp !== undefined) {
84
+ if (!EntitySchemaDiffHelper.schemaPropertiesEqual(oldProp, newProp)) {
85
+ modified.push({ from: oldProp, to: newProp });
86
+ }
87
+ else {
88
+ unchanged.push(newProp);
89
+ }
90
+ }
91
+ else {
92
+ added.push(newProp);
93
+ }
94
+ }
95
+ }
96
+ for (const oldProp of oldProperties) {
97
+ const key = oldProp.property;
98
+ // Removed when absent from new, or when its same-named new prop was claimed by a rename
99
+ // (meaning this old prop was not the match for that new prop).
100
+ // Exception: skip if this old prop was itself consumed as a rename source.
101
+ if ((!newMap.has(key) || newKeyMatchedViaRename.has(key)) && !consumedByRename.has(key)) {
102
+ removed.push(oldProp);
103
+ }
104
+ }
105
+ return { added, removed, modified, unchanged };
106
+ }
107
+ /**
108
+ * Returns true when the diff contains at least one added, removed, or modified property.
109
+ * @param diff The diff to check.
110
+ * @returns True if the diff has any structural changes.
111
+ */
112
+ static hasChanges(diff) {
113
+ Guards.object(EntitySchemaDiffHelper.CLASS_NAME, "diff", diff);
114
+ Guards.array(EntitySchemaDiffHelper.CLASS_NAME, "diff.added", diff.added);
115
+ Guards.array(EntitySchemaDiffHelper.CLASS_NAME, "diff.removed", diff.removed);
116
+ Guards.array(EntitySchemaDiffHelper.CLASS_NAME, "diff.modified", diff.modified);
117
+ return diff.added.length > 0 || diff.removed.length > 0 || diff.modified.length > 0;
118
+ }
119
+ /**
120
+ * Compare two property descriptors for structural equality.
121
+ * The `property` name field and documentation fields (`description`, `examples`) are intentionally excluded — callers match by name before invoking this method.
122
+ * @param schema1 The first property descriptor.
123
+ * @param schema2 The second property descriptor.
124
+ * @returns True if all structural fields are equal.
125
+ */
126
+ static schemaPropertiesEqual(schema1, schema2) {
127
+ Guards.object(EntitySchemaDiffHelper.CLASS_NAME, "schema1", schema1);
128
+ Guards.object(EntitySchemaDiffHelper.CLASS_NAME, "schema2", schema2);
129
+ return (schema1.type === schema2.type &&
130
+ schema1.format === schema2.format &&
131
+ schema1.isPrimary === schema2.isPrimary &&
132
+ schema1.isSecondary === schema2.isSecondary &&
133
+ schema1.sortDirection === schema2.sortDirection &&
134
+ schema1.optional === schema2.optional &&
135
+ schema1.itemType === schema2.itemType &&
136
+ schema1.itemTypeRef === schema2.itemTypeRef);
137
+ }
138
+ }
139
+ //# sourceMappingURL=entitySchemaDiffHelper.js.map
@@ -0,0 +1 @@
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\";\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`, `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.isPrimary === schema2.isPrimary &&\n\t\t\tschema1.isSecondary === schema2.isSecondary &&\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"]}
@@ -16,6 +16,6 @@ export * from "./models/sortDirection.js";
16
16
  export * from "./utils/decoratorHelper.js";
17
17
  export * from "./utils/entityConditions.js";
18
18
  export * from "./utils/entitySchemaHelper.js";
19
- export * from "./utils/entitySchemaDiff.js";
19
+ export * from "./utils/entitySchemaDiffHelper.js";
20
20
  export * from "./utils/entitySorter.js";
21
21
  export * from "./models/IEntitySchemaDiff.js";
@@ -2,25 +2,23 @@ import type { IEntitySchemaProperty } from "./IEntitySchemaProperty.js";
2
2
  /**
3
3
  * The result of comparing two sets of entity schema properties.
4
4
  */
5
- export interface IEntitySchemaDiff<T = unknown> {
5
+ export interface IEntitySchemaDiff<T = unknown, U = unknown> {
6
+ /**
7
+ * Properties that are structurally identical between the old and new schemas.
8
+ */
9
+ unchanged: IEntitySchemaProperty<T | U>[];
6
10
  /**
7
11
  * Properties present in the new schema but absent from the old one.
8
- * Each entry is the full property descriptor from the new schema, so it can
9
- * be passed directly to bootstrap / table-creation logic.
10
12
  */
11
- added: IEntitySchemaProperty<T>[];
13
+ added: IEntitySchemaProperty<U>[];
12
14
  /**
13
15
  * Properties present in the old schema but absent from the new one.
14
- * Each entry is the full property descriptor from the old schema, allowing
15
- * connectors to drop the correct column, index, or field by name.
16
16
  */
17
17
  removed: IEntitySchemaProperty<T>[];
18
18
  /**
19
19
  * Properties that exist in both schemas but differ in at least one structural
20
- * field (type, format, isSecondary, sortDirection, optional, itemType, itemTypeRef).
20
+ * field (property, type, format, isPrimary, isSecondary, sortDirection, optional, itemType, itemTypeRef).
21
21
  * `from` is the old descriptor; `to` is the new one.
22
- * Both are full property objects so connectors can drop the old definition and
23
- * create the new one using the same bootstrap code path.
24
22
  */
25
23
  modified: {
26
24
  /**
@@ -30,6 +28,6 @@ export interface IEntitySchemaDiff<T = unknown> {
30
28
  /**
31
29
  * The new property descriptor.
32
30
  */
33
- to: IEntitySchemaProperty<T>;
31
+ to: IEntitySchemaProperty<U>;
34
32
  }[];
35
33
  }
@@ -0,0 +1,44 @@
1
+ import type { IEntitySchemaDiff } from "../models/IEntitySchemaDiff.js";
2
+ import type { IEntitySchemaProperty } from "../models/IEntitySchemaProperty.js";
3
+ /**
4
+ * Helper class for comparing entity schemas and generating diffs.
5
+ */
6
+ export declare class EntitySchemaDiffHelper {
7
+ /**
8
+ * Runtime name for the class.
9
+ */
10
+ static readonly CLASS_NAME: string;
11
+ /**
12
+ * Compare two arrays of entity schema properties and return a structured diff.
13
+ *
14
+ * Properties are matched by their `property` key name. A property is considered modified when any structural field differs: `type`, `format`, `isPrimary`, `isSecondary`, `sortDirection`, `optional`, `itemType`, or `itemTypeRef`.
15
+ * Documentation-only fields (`description`, `examples`) are intentionally excluded from the comparison to avoid spurious diffs.
16
+ *
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.
18
+ *
19
+ * 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.
20
+ * @param oldProperties The property descriptors from the current (live) schema.
21
+ * @param newProperties The property descriptors from the target (new) schema.
22
+ * @param renames Optional list of property renames `{ from, to }` where `from` is the old name and `to` is the new name.
23
+ * @returns A diff object with `added`, `removed`, `modified`, and `unchanged` arrays, each containing full `IEntitySchemaProperty` descriptors.
24
+ * @throws `GeneralError` if either input array contains duplicate property keys.
25
+ */
26
+ static diff<T, U = T>(oldProperties: IEntitySchemaProperty<T>[], newProperties: IEntitySchemaProperty<U>[], renames?: {
27
+ from: string;
28
+ to: string;
29
+ }[]): IEntitySchemaDiff<T, U>;
30
+ /**
31
+ * Returns true when the diff contains at least one added, removed, or modified property.
32
+ * @param diff The diff to check.
33
+ * @returns True if the diff has any structural changes.
34
+ */
35
+ static hasChanges<T, U>(diff: IEntitySchemaDiff<T, U>): boolean;
36
+ /**
37
+ * Compare two property descriptors for structural equality.
38
+ * The `property` name field and documentation fields (`description`, `examples`) are intentionally excluded — callers match by name before invoking this method.
39
+ * @param schema1 The first property descriptor.
40
+ * @param schema2 The second property descriptor.
41
+ * @returns True if all structural fields are equal.
42
+ */
43
+ static schemaPropertiesEqual<T, U>(schema1: IEntitySchemaProperty<T>, schema2: IEntitySchemaProperty<U>): boolean;
44
+ }
package/docs/changelog.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.0.3-next.42](https://github.com/iotaledger/twin-framework/compare/entity-v0.0.3-next.41...entity-v0.0.3-next.42) (2026-05-15)
4
+
5
+
6
+ ### Features
7
+
8
+ * entity schema diff updates ([#294](https://github.com/iotaledger/twin-framework/issues/294)) ([7a7a94d](https://github.com/iotaledger/twin-framework/commit/7a7a94d14ea5e785dd68fd6de1c5a84941721d28))
9
+
10
+
11
+ ### Dependencies
12
+
13
+ * The following workspace dependencies were updated
14
+ * dependencies
15
+ * @twin.org/nameof bumped from 0.0.3-next.41 to 0.0.3-next.42
16
+ * @twin.org/core bumped from 0.0.3-next.41 to 0.0.3-next.42
17
+ * devDependencies
18
+ * @twin.org/nameof-transformer bumped from 0.0.3-next.41 to 0.0.3-next.42
19
+ * @twin.org/nameof-vitest-plugin bumped from 0.0.3-next.41 to 0.0.3-next.42
20
+ * @twin.org/validate-locales bumped from 0.0.3-next.41 to 0.0.3-next.42
21
+
3
22
  ## [0.0.3-next.41](https://github.com/iotaledger/twin-framework/compare/entity-v0.0.3-next.40...entity-v0.0.3-next.41) (2026-05-13)
4
23
 
5
24
 
@@ -0,0 +1,147 @@
1
+ # Class: EntitySchemaDiffHelper
2
+
3
+ Helper class for comparing entity schemas and generating diffs.
4
+
5
+ ## Constructors
6
+
7
+ ### Constructor
8
+
9
+ > **new EntitySchemaDiffHelper**(): `EntitySchemaDiffHelper`
10
+
11
+ #### Returns
12
+
13
+ `EntitySchemaDiffHelper`
14
+
15
+ ## Properties
16
+
17
+ ### CLASS\_NAME {#class_name}
18
+
19
+ > `readonly` `static` **CLASS\_NAME**: `string`
20
+
21
+ Runtime name for the class.
22
+
23
+ ## Methods
24
+
25
+ ### diff() {#diff}
26
+
27
+ > `static` **diff**\<`T`, `U`\>(`oldProperties`, `newProperties`, `renames?`): [`IEntitySchemaDiff`](../interfaces/IEntitySchemaDiff.md)\<`T`, `U`\>
28
+
29
+ Compare two arrays of entity schema properties and return a structured diff.
30
+
31
+ Properties are matched by their `property` key name. A property is considered modified when any structural field differs: `type`, `format`, `isPrimary`, `isSecondary`, `sortDirection`, `optional`, `itemType`, or `itemTypeRef`.
32
+ Documentation-only fields (`description`, `examples`) are intentionally excluded from the comparison to avoid spurious diffs.
33
+
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.
35
+
36
+ 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.
37
+
38
+ #### Type Parameters
39
+
40
+ ##### T
41
+
42
+ `T`
43
+
44
+ ##### U
45
+
46
+ `U` = `T`
47
+
48
+ #### Parameters
49
+
50
+ ##### oldProperties
51
+
52
+ [`IEntitySchemaProperty`](../interfaces/IEntitySchemaProperty.md)\<`T`\>[]
53
+
54
+ The property descriptors from the current (live) schema.
55
+
56
+ ##### newProperties
57
+
58
+ [`IEntitySchemaProperty`](../interfaces/IEntitySchemaProperty.md)\<`U`\>[]
59
+
60
+ The property descriptors from the target (new) schema.
61
+
62
+ ##### renames?
63
+
64
+ `object`[]
65
+
66
+ Optional list of property renames `{ from, to }` where `from` is the old name and `to` is the new name.
67
+
68
+ #### Returns
69
+
70
+ [`IEntitySchemaDiff`](../interfaces/IEntitySchemaDiff.md)\<`T`, `U`\>
71
+
72
+ A diff object with `added`, `removed`, `modified`, and `unchanged` arrays, each containing full `IEntitySchemaProperty` descriptors.
73
+
74
+ #### Throws
75
+
76
+ `GeneralError` if either input array contains duplicate property keys.
77
+
78
+ ***
79
+
80
+ ### hasChanges() {#haschanges}
81
+
82
+ > `static` **hasChanges**\<`T`, `U`\>(`diff`): `boolean`
83
+
84
+ Returns true when the diff contains at least one added, removed, or modified property.
85
+
86
+ #### Type Parameters
87
+
88
+ ##### T
89
+
90
+ `T`
91
+
92
+ ##### U
93
+
94
+ `U`
95
+
96
+ #### Parameters
97
+
98
+ ##### diff
99
+
100
+ [`IEntitySchemaDiff`](../interfaces/IEntitySchemaDiff.md)\<`T`, `U`\>
101
+
102
+ The diff to check.
103
+
104
+ #### Returns
105
+
106
+ `boolean`
107
+
108
+ True if the diff has any structural changes.
109
+
110
+ ***
111
+
112
+ ### schemaPropertiesEqual() {#schemapropertiesequal}
113
+
114
+ > `static` **schemaPropertiesEqual**\<`T`, `U`\>(`schema1`, `schema2`): `boolean`
115
+
116
+ Compare two property descriptors for structural equality.
117
+ The `property` name field and documentation fields (`description`, `examples`) are intentionally excluded — callers match by name before invoking this method.
118
+
119
+ #### Type Parameters
120
+
121
+ ##### T
122
+
123
+ `T`
124
+
125
+ ##### U
126
+
127
+ `U`
128
+
129
+ #### Parameters
130
+
131
+ ##### schema1
132
+
133
+ [`IEntitySchemaProperty`](../interfaces/IEntitySchemaProperty.md)\<`T`\>
134
+
135
+ The first property descriptor.
136
+
137
+ ##### schema2
138
+
139
+ [`IEntitySchemaProperty`](../interfaces/IEntitySchemaProperty.md)\<`U`\>
140
+
141
+ The second property descriptor.
142
+
143
+ #### Returns
144
+
145
+ `boolean`
146
+
147
+ True if all structural fields are equal.
@@ -4,6 +4,7 @@
4
4
 
5
5
  - [DecoratorHelper](classes/DecoratorHelper.md)
6
6
  - [EntityConditions](classes/EntityConditions.md)
7
+ - [EntitySchemaDiffHelper](classes/EntitySchemaDiffHelper.md)
7
8
  - [EntitySchemaHelper](classes/EntitySchemaHelper.md)
8
9
  - [EntitySorter](classes/EntitySorter.md)
9
10
 
@@ -39,5 +40,3 @@
39
40
 
40
41
  - [entity](functions/entity.md)
41
42
  - [property](functions/property.md)
42
- - [entitySchemaDiff](functions/entitySchemaDiff.md)
43
- - [isEmptyDiff](functions/isEmptyDiff.md)
@@ -1,4 +1,4 @@
1
- # Interface: IEntitySchemaDiff\<T\>
1
+ # Interface: IEntitySchemaDiff\<T, U\>
2
2
 
3
3
  The result of comparing two sets of entity schema properties.
4
4
 
@@ -8,15 +8,25 @@ The result of comparing two sets of entity schema properties.
8
8
 
9
9
  `T` = `unknown`
10
10
 
11
+ ### U
12
+
13
+ `U` = `unknown`
14
+
11
15
  ## Properties
12
16
 
17
+ ### unchanged {#unchanged}
18
+
19
+ > **unchanged**: [`IEntitySchemaProperty`](IEntitySchemaProperty.md)\<`T` \| `U`\>[]
20
+
21
+ Properties that are structurally identical between the old and new schemas.
22
+
23
+ ***
24
+
13
25
  ### added {#added}
14
26
 
15
- > **added**: [`IEntitySchemaProperty`](IEntitySchemaProperty.md)\<`T`\>[]
27
+ > **added**: [`IEntitySchemaProperty`](IEntitySchemaProperty.md)\<`U`\>[]
16
28
 
17
29
  Properties present in the new schema but absent from the old one.
18
- Each entry is the full property descriptor from the new schema, so it can
19
- be passed directly to bootstrap / table-creation logic.
20
30
 
21
31
  ***
22
32
 
@@ -25,8 +35,6 @@ be passed directly to bootstrap / table-creation logic.
25
35
  > **removed**: [`IEntitySchemaProperty`](IEntitySchemaProperty.md)\<`T`\>[]
26
36
 
27
37
  Properties present in the old schema but absent from the new one.
28
- Each entry is the full property descriptor from the old schema, allowing
29
- connectors to drop the correct column, index, or field by name.
30
38
 
31
39
  ***
32
40
 
@@ -35,10 +43,8 @@ connectors to drop the correct column, index, or field by name.
35
43
  > **modified**: `object`[]
36
44
 
37
45
  Properties that exist in both schemas but differ in at least one structural
38
- field (type, format, isSecondary, sortDirection, optional, itemType, itemTypeRef).
46
+ field (property, type, format, isPrimary, isSecondary, sortDirection, optional, itemType, itemTypeRef).
39
47
  `from` is the old descriptor; `to` is the new one.
40
- Both are full property objects so connectors can drop the old definition and
41
- create the new one using the same bootstrap code path.
42
48
 
43
49
  #### from
44
50
 
@@ -48,6 +54,6 @@ The old property descriptor.
48
54
 
49
55
  #### to
50
56
 
51
- > **to**: [`IEntitySchemaProperty`](IEntitySchemaProperty.md)\<`T`\>
57
+ > **to**: [`IEntitySchemaProperty`](IEntitySchemaProperty.md)\<`U`\>
52
58
 
53
59
  The new property descriptor.
package/locales/en.json CHANGED
@@ -1,5 +1,9 @@
1
1
  {
2
2
  "error": {
3
+ "entitySchemaDiffHelper": {
4
+ "duplicateOldProperty": "The oldProperties array contains a duplicate property key \"{property}\"",
5
+ "duplicateNewProperty": "The newProperties array contains a duplicate property key \"{property}\""
6
+ },
3
7
  "entitySchemaHelper": {
4
8
  "noIsPrimary": "Property \"entitySchema.properties\" must contain a value with isPrimary set",
5
9
  "multipleIsPrimary": "Property \"entitySchema.properties\" contains more than one property with isPrimary set",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twin.org/entity",
3
- "version": "0.0.3-next.41",
3
+ "version": "0.0.3-next.42",
4
4
  "description": "Helpers for defining and working with entities",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,8 +14,8 @@
14
14
  "node": ">=20.0.0"
15
15
  },
16
16
  "dependencies": {
17
- "@twin.org/core": "0.0.3-next.41",
18
- "@twin.org/nameof": "0.0.3-next.41",
17
+ "@twin.org/core": "0.0.3-next.42",
18
+ "@twin.org/nameof": "0.0.3-next.42",
19
19
  "reflect-metadata": "0.2.2",
20
20
  "tslib": "2.8.1"
21
21
  },
@@ -1,67 +0,0 @@
1
- /**
2
- * Compare two arrays of entity schema properties and return a structured diff.
3
- *
4
- * Properties are matched by their `property` key name. A property is considered
5
- * modified when any structural field differs: `type`, `format`, `isPrimary`,
6
- * `isSecondary`, `sortDirection`, `optional`, `itemType`, or `itemTypeRef`.
7
- * Documentation-only fields (`description`, `examples`) are intentionally
8
- * excluded from the comparison to avoid spurious diffs.
9
- * @param oldProperties The property descriptors from the current (live) schema.
10
- * @param newProperties The property descriptors from the target (new) schema.
11
- * @returns A diff object with `added`, `removed`, and `modified` arrays, each
12
- * containing full `IEntitySchemaProperty` descriptors.
13
- */
14
- export function entitySchemaDiff(oldProperties, newProperties) {
15
- const added = [];
16
- const removed = [];
17
- const modified = [];
18
- const oldMap = new Map();
19
- for (const prop of oldProperties) {
20
- oldMap.set(prop.property, prop);
21
- }
22
- const newMap = new Map();
23
- for (const prop of newProperties) {
24
- newMap.set(prop.property, prop);
25
- }
26
- for (const newProp of newProperties) {
27
- const oldProp = oldMap.get(newProp.property);
28
- if (!oldProp) {
29
- added.push(newProp);
30
- }
31
- else if (!schemaPropertiesEqual(oldProp, newProp)) {
32
- modified.push({ from: oldProp, to: newProp });
33
- }
34
- }
35
- for (const oldProp of oldProperties) {
36
- if (!newMap.has(oldProp.property)) {
37
- removed.push(oldProp);
38
- }
39
- }
40
- return { added, removed, modified };
41
- }
42
- /**
43
- * Returns true when the diff contains no changes (nothing added, removed, or modified).
44
- * @param diff The diff to check.
45
- * @returns True if the diff is empty.
46
- */
47
- export function isEmptyDiff(diff) {
48
- return diff.added.length === 0 && diff.removed.length === 0 && diff.modified.length === 0;
49
- }
50
- /**
51
- * Compare two property descriptors for structural equality.
52
- * Documentation fields (description, examples) are excluded.
53
- * @param a The first property descriptor.
54
- * @param b The second property descriptor.
55
- * @returns True if all structural fields are equal.
56
- */
57
- function schemaPropertiesEqual(a, b) {
58
- return (a.type === b.type &&
59
- a.format === b.format &&
60
- a.isPrimary === b.isPrimary &&
61
- a.isSecondary === b.isSecondary &&
62
- a.sortDirection === b.sortDirection &&
63
- a.optional === b.optional &&
64
- a.itemType === b.itemType &&
65
- a.itemTypeRef === b.itemTypeRef);
66
- }
67
- //# sourceMappingURL=entitySchemaDiff.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"entitySchemaDiff.js","sourceRoot":"","sources":["../../../src/utils/entitySchemaDiff.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,gBAAgB,CAC/B,aAAyC,EACzC,aAAyC;IAEzC,MAAM,KAAK,GAA+B,EAAE,CAAC;IAC7C,MAAM,OAAO,GAA+B,EAAE,CAAC;IAC/C,MAAM,QAAQ,GAAqC,EAAE,CAAC;IAEtD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoC,CAAC;IAC3D,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;QAClC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,QAAkB,EAAE,IAAI,CAAC,CAAC;IAC3C,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoC,CAAC;IAC3D,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;QAClC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,QAAkB,EAAE,IAAI,CAAC,CAAC;IAC3C,CAAC;IAED,KAAK,MAAM,OAAO,IAAI,aAAa,EAAE,CAAC;QACrC,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,QAAkB,CAAC,CAAC;QACvD,IAAI,CAAC,OAAO,EAAE,CAAC;YACd,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACrB,CAAC;aAAM,IAAI,CAAC,qBAAqB,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC;YACrD,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;QAC/C,CAAC;IACF,CAAC;IAED,KAAK,MAAM,OAAO,IAAI,aAAa,EAAE,CAAC;QACrC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,QAAkB,CAAC,EAAE,CAAC;YAC7C,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACvB,CAAC;IACF,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;AACrC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAI,IAA0B;IACxD,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC;AAC3F,CAAC;AAED;;;;;;GAMG;AACH,SAAS,qBAAqB,CAC7B,CAA2B,EAC3B,CAA2B;IAE3B,OAAO,CACN,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI;QACjB,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QACrB,CAAC,CAAC,SAAS,KAAK,CAAC,CAAC,SAAS;QAC3B,CAAC,CAAC,WAAW,KAAK,CAAC,CAAC,WAAW;QAC/B,CAAC,CAAC,aAAa,KAAK,CAAC,CAAC,aAAa;QACnC,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,QAAQ;QACzB,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,QAAQ;QACzB,CAAC,CAAC,WAAW,KAAK,CAAC,CAAC,WAAW,CAC/B,CAAC;AACH,CAAC","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport type { IEntitySchemaDiff } from \"../models/IEntitySchemaDiff.js\";\nimport type { IEntitySchemaProperty } from \"../models/IEntitySchemaProperty.js\";\n\n/**\n * Compare two arrays of entity schema properties and return a structured diff.\n *\n * Properties are matched by their `property` key name. A property is considered\n * modified when any structural field differs: `type`, `format`, `isPrimary`,\n * `isSecondary`, `sortDirection`, `optional`, `itemType`, or `itemTypeRef`.\n * Documentation-only fields (`description`, `examples`) are intentionally\n * excluded from the comparison to avoid spurious diffs.\n * @param oldProperties The property descriptors from the current (live) schema.\n * @param newProperties The property descriptors from the target (new) schema.\n * @returns A diff object with `added`, `removed`, and `modified` arrays, each\n * containing full `IEntitySchemaProperty` descriptors.\n */\nexport function entitySchemaDiff<T>(\n\toldProperties: IEntitySchemaProperty<T>[],\n\tnewProperties: IEntitySchemaProperty<T>[]\n): IEntitySchemaDiff<T> {\n\tconst added: IEntitySchemaProperty<T>[] = [];\n\tconst removed: IEntitySchemaProperty<T>[] = [];\n\tconst modified: IEntitySchemaDiff<T>[\"modified\"] = [];\n\n\tconst oldMap = new Map<string, IEntitySchemaProperty<T>>();\n\tfor (const prop of oldProperties) {\n\t\toldMap.set(prop.property as string, prop);\n\t}\n\n\tconst newMap = new Map<string, IEntitySchemaProperty<T>>();\n\tfor (const prop of newProperties) {\n\t\tnewMap.set(prop.property as string, prop);\n\t}\n\n\tfor (const newProp of newProperties) {\n\t\tconst oldProp = oldMap.get(newProp.property as string);\n\t\tif (!oldProp) {\n\t\t\tadded.push(newProp);\n\t\t} else if (!schemaPropertiesEqual(oldProp, newProp)) {\n\t\t\tmodified.push({ from: oldProp, to: newProp });\n\t\t}\n\t}\n\n\tfor (const oldProp of oldProperties) {\n\t\tif (!newMap.has(oldProp.property as string)) {\n\t\t\tremoved.push(oldProp);\n\t\t}\n\t}\n\n\treturn { added, removed, modified };\n}\n\n/**\n * Returns true when the diff contains no changes (nothing added, removed, or modified).\n * @param diff The diff to check.\n * @returns True if the diff is empty.\n */\nexport function isEmptyDiff<T>(diff: IEntitySchemaDiff<T>): boolean {\n\treturn diff.added.length === 0 && diff.removed.length === 0 && diff.modified.length === 0;\n}\n\n/**\n * Compare two property descriptors for structural equality.\n * Documentation fields (description, examples) are excluded.\n * @param a The first property descriptor.\n * @param b The second property descriptor.\n * @returns True if all structural fields are equal.\n */\nfunction schemaPropertiesEqual<T>(\n\ta: IEntitySchemaProperty<T>,\n\tb: IEntitySchemaProperty<T>\n): boolean {\n\treturn (\n\t\ta.type === b.type &&\n\t\ta.format === b.format &&\n\t\ta.isPrimary === b.isPrimary &&\n\t\ta.isSecondary === b.isSecondary &&\n\t\ta.sortDirection === b.sortDirection &&\n\t\ta.optional === b.optional &&\n\t\ta.itemType === b.itemType &&\n\t\ta.itemTypeRef === b.itemTypeRef\n\t);\n}\n"]}
@@ -1,22 +0,0 @@
1
- import type { IEntitySchemaDiff } from "../models/IEntitySchemaDiff.js";
2
- import type { IEntitySchemaProperty } from "../models/IEntitySchemaProperty.js";
3
- /**
4
- * Compare two arrays of entity schema properties and return a structured diff.
5
- *
6
- * Properties are matched by their `property` key name. A property is considered
7
- * modified when any structural field differs: `type`, `format`, `isPrimary`,
8
- * `isSecondary`, `sortDirection`, `optional`, `itemType`, or `itemTypeRef`.
9
- * Documentation-only fields (`description`, `examples`) are intentionally
10
- * excluded from the comparison to avoid spurious diffs.
11
- * @param oldProperties The property descriptors from the current (live) schema.
12
- * @param newProperties The property descriptors from the target (new) schema.
13
- * @returns A diff object with `added`, `removed`, and `modified` arrays, each
14
- * containing full `IEntitySchemaProperty` descriptors.
15
- */
16
- export declare function entitySchemaDiff<T>(oldProperties: IEntitySchemaProperty<T>[], newProperties: IEntitySchemaProperty<T>[]): IEntitySchemaDiff<T>;
17
- /**
18
- * Returns true when the diff contains no changes (nothing added, removed, or modified).
19
- * @param diff The diff to check.
20
- * @returns True if the diff is empty.
21
- */
22
- export declare function isEmptyDiff<T>(diff: IEntitySchemaDiff<T>): boolean;
@@ -1,38 +0,0 @@
1
- # Function: entitySchemaDiff()
2
-
3
- > **entitySchemaDiff**\<`T`\>(`oldProperties`, `newProperties`): [`IEntitySchemaDiff`](../interfaces/IEntitySchemaDiff.md)\<`T`\>
4
-
5
- Compare two arrays of entity schema properties and return a structured diff.
6
-
7
- Properties are matched by their `property` key name. A property is considered
8
- modified when any structural field differs: `type`, `format`, `isPrimary`,
9
- `isSecondary`, `sortDirection`, `optional`, `itemType`, or `itemTypeRef`.
10
- Documentation-only fields (`description`, `examples`) are intentionally
11
- excluded from the comparison to avoid spurious diffs.
12
-
13
- ## Type Parameters
14
-
15
- ### T
16
-
17
- `T`
18
-
19
- ## Parameters
20
-
21
- ### oldProperties
22
-
23
- [`IEntitySchemaProperty`](../interfaces/IEntitySchemaProperty.md)\<`T`\>[]
24
-
25
- The property descriptors from the current (live) schema.
26
-
27
- ### newProperties
28
-
29
- [`IEntitySchemaProperty`](../interfaces/IEntitySchemaProperty.md)\<`T`\>[]
30
-
31
- The property descriptors from the target (new) schema.
32
-
33
- ## Returns
34
-
35
- [`IEntitySchemaDiff`](../interfaces/IEntitySchemaDiff.md)\<`T`\>
36
-
37
- A diff object with `added`, `removed`, and `modified` arrays, each
38
- containing full `IEntitySchemaProperty` descriptors.
@@ -1,25 +0,0 @@
1
- # Function: isEmptyDiff()
2
-
3
- > **isEmptyDiff**\<`T`\>(`diff`): `boolean`
4
-
5
- Returns true when the diff contains no changes (nothing added, removed, or modified).
6
-
7
- ## Type Parameters
8
-
9
- ### T
10
-
11
- `T`
12
-
13
- ## Parameters
14
-
15
- ### diff
16
-
17
- [`IEntitySchemaDiff`](../interfaces/IEntitySchemaDiff.md)\<`T`\>
18
-
19
- The diff to check.
20
-
21
- ## Returns
22
-
23
- `boolean`
24
-
25
- True if the diff is empty.