@twin.org/entity 0.9.2-next.5 → 0.9.2-next.7
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.
|
@@ -118,7 +118,7 @@ export class EntitySchemaDiffHelper {
|
|
|
118
118
|
}
|
|
119
119
|
/**
|
|
120
120
|
* Compare two property descriptors for structural equality.
|
|
121
|
-
* The `property` name field and documentation fields (`description`, `examples`) are intentionally excluded
|
|
121
|
+
* The `property` name field and documentation fields (`description`, `examples`) are intentionally excluded - callers match by name before invoking this method.
|
|
122
122
|
* @param schema1 The first property descriptor.
|
|
123
123
|
* @param schema2 The second property descriptor.
|
|
124
124
|
* @returns True if all structural fields are equal.
|
|
@@ -1 +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"]}
|
|
1
|
+
{"version":3,"file":"entitySchemaDiffHelper.js","sourceRoot":"","sources":["../../../src/utils/entitySchemaDiffHelper.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,uCAAuC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAKtD;;GAEG;AACH,MAAM,OAAO,sBAAsB;IAClC;;OAEG;IACI,MAAM,CAAU,UAAU,4BAA4C;IAE7E;;;;;;;;;;;;;;OAcG;IACI,MAAM,CAAC,IAAI,CACjB,aAAyC,EACzC,aAAyC,EACzC,OAAwC;QAExC,MAAM,CAAC,KAAK,CAAC,sBAAsB,CAAC,UAAU,mBAAyB,aAAa,CAAC,CAAC;QACtF,MAAM,CAAC,KAAK,CAAC,sBAAsB,CAAC,UAAU,mBAAyB,aAAa,CAAC,CAAC;QAEtF,MAAM,KAAK,GAA+B,EAAE,CAAC;QAC7C,MAAM,OAAO,GAA+B,EAAE,CAAC;QAC/C,MAAM,QAAQ,GAAwC,EAAE,CAAC;QACzD,MAAM,SAAS,GAAmC,EAAE,CAAC;QAErD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoC,CAAC;QAC3D,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;YAClC,MAAM,OAAO,GAAG,IAAI,CAAC,QAAkB,CAAC;YACxC,IAAI,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;gBACzB,MAAM,IAAI,YAAY,CAAC,sBAAsB,CAAC,UAAU,EAAE,sBAAsB,EAAE;oBACjF,QAAQ,EAAE,OAAO;iBACjB,CAAC,CAAC;YACJ,CAAC;YACD,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAC3B,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoC,CAAC;QAC3D,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;YAClC,MAAM,OAAO,GAAG,IAAI,CAAC,QAAkB,CAAC;YACxC,IAAI,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;gBACzB,MAAM,IAAI,YAAY,CAAC,sBAAsB,CAAC,UAAU,EAAE,sBAAsB,EAAE;oBACjF,QAAQ,EAAE,OAAO;iBACjB,CAAC,CAAC;YACJ,CAAC;YACD,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAC3B,CAAC;QAED,yDAAyD;QACzD,MAAM,YAAY,GAAG,IAAI,GAAG,EAAkB,CAAC;QAC/C,IAAI,OAAO,EAAE,CAAC;YACb,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC9B,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;YAC1C,CAAC;QACF,CAAC;QAED,qFAAqF;QACrF,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAU,CAAC;QAC3C,2FAA2F;QAC3F,MAAM,sBAAsB,GAAG,IAAI,GAAG,EAAU,CAAC;QAEjD,KAAK,MAAM,OAAO,IAAI,aAAa,EAAE,CAAC;YACrC,MAAM,GAAG,GAAG,OAAO,CAAC,QAAkB,CAAC;YAEvC,sEAAsE;YACtE,wDAAwD;YACxD,iFAAiF;YACjF,qFAAqF;YACrF,MAAM,OAAO,GAAG,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACtC,MAAM,aAAa,GAAG,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YAE9E,IACC,OAAO,KAAK,SAAS;gBACrB,OAAO,KAAK,GAAG;gBACf,aAAa,KAAK,SAAS;gBAC3B,CAAC,gBAAgB,CAAC,GAAG,CAAC,OAAO,CAAC,EAC7B,CAAC;gBACF,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;gBACpD,gBAAgB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;gBAC9B,sBAAsB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACjC,CAAC;iBAAM,CAAC;gBACP,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBAChC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;oBAC3B,IAAI,CAAC,sBAAsB,CAAC,qBAAqB,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC;wBACrE,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;oBAC/C,CAAC;yBAAM,CAAC;wBACP,SAAS,CAAC,IAAI,CAAC,OAAuC,CAAC,CAAC;oBACzD,CAAC;gBACF,CAAC;qBAAM,CAAC;oBACP,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBACrB,CAAC;YACF,CAAC;QACF,CAAC;QAED,KAAK,MAAM,OAAO,IAAI,aAAa,EAAE,CAAC;YACrC,MAAM,GAAG,GAAG,OAAO,CAAC,QAAkB,CAAC;YACvC,wFAAwF;YACxF,+DAA+D;YAC/D,2EAA2E;YAC3E,IAAI,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,sBAAsB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBACzF,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACvB,CAAC;QACF,CAAC;QAED,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IAChD,CAAC;IAED;;;;OAIG;IACI,MAAM,CAAC,UAAU,CAAO,IAA6B;QAC3D,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,UAAU,UAAgB,IAAI,CAAC,CAAC;QACrE,MAAM,CAAC,KAAK,CAAC,sBAAsB,CAAC,UAAU,gBAAsB,IAAI,CAAC,KAAK,CAAC,CAAC;QAChF,MAAM,CAAC,KAAK,CAAC,sBAAsB,CAAC,UAAU,kBAAwB,IAAI,CAAC,OAAO,CAAC,CAAC;QACpF,MAAM,CAAC,KAAK,CAAC,sBAAsB,CAAC,UAAU,mBAAyB,IAAI,CAAC,QAAQ,CAAC,CAAC;QACtF,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;IACrF,CAAC;IAED;;;;;;OAMG;IACI,MAAM,CAAC,qBAAqB,CAClC,OAAiC,EACjC,OAAiC;QAEjC,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,UAAU,aAAmB,OAAO,CAAC,CAAC;QAC3E,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,UAAU,aAAmB,OAAO,CAAC,CAAC;QAC3E,OAAO,CACN,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC,IAAI;YAC7B,OAAO,CAAC,MAAM,KAAK,OAAO,CAAC,MAAM;YACjC,OAAO,CAAC,SAAS,KAAK,OAAO,CAAC,SAAS;YACvC,OAAO,CAAC,WAAW,KAAK,OAAO,CAAC,WAAW;YAC3C,OAAO,CAAC,aAAa,KAAK,OAAO,CAAC,aAAa;YAC/C,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,QAAQ;YACrC,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,QAAQ;YACrC,OAAO,CAAC,WAAW,KAAK,OAAO,CAAC,WAAW,CAC3C,CAAC;IACH,CAAC","sourcesContent":["// Copyright 2026 IOTA Stiftung.\n// SPDX-License-Identifier: Apache-2.0.\nimport { GeneralError, Guards } from \"@twin.org/core\";\nimport { nameof } from \"@twin.org/nameof\";\nimport type { IEntitySchemaDiff } from \"../models/IEntitySchemaDiff.js\";\nimport type { IEntitySchemaProperty } from \"../models/IEntitySchemaProperty.js\";\n\n/**\n * Helper class for comparing entity schemas and generating diffs.\n */\nexport class EntitySchemaDiffHelper {\n\t/**\n\t * Runtime name for the class.\n\t */\n\tpublic static readonly CLASS_NAME: string = nameof<EntitySchemaDiffHelper>();\n\n\t/**\n\t * Compare two arrays of entity schema properties and return a structured diff.\n\t *\n\t * Properties are matched by their `property` key name. A property is considered modified when any structural field differs: `type`, `format`, `isPrimary`, `isSecondary`, `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"]}
|
|
@@ -35,7 +35,7 @@ export declare class EntitySchemaDiffHelper {
|
|
|
35
35
|
static hasChanges<T, U>(diff: IEntitySchemaDiff<T, U>): boolean;
|
|
36
36
|
/**
|
|
37
37
|
* Compare two property descriptors for structural equality.
|
|
38
|
-
* The `property` name field and documentation fields (`description`, `examples`) are intentionally excluded
|
|
38
|
+
* The `property` name field and documentation fields (`description`, `examples`) are intentionally excluded - callers match by name before invoking this method.
|
|
39
39
|
* @param schema1 The first property descriptor.
|
|
40
40
|
* @param schema2 The second property descriptor.
|
|
41
41
|
* @returns True if all structural fields are equal.
|
package/docs/changelog.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.9.2-next.7](https://github.com/iotaledger/twin-framework/compare/entity-v0.9.2-next.6...entity-v0.9.2-next.7) (2026-08-07)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* linting and dependency update ([676b4e9](https://github.com/iotaledger/twin-framework/commit/676b4e9d9bce158065200bbf875bb31da81d166d))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Dependencies
|
|
12
|
+
|
|
13
|
+
* The following workspace dependencies were updated
|
|
14
|
+
* dependencies
|
|
15
|
+
* @twin.org/core bumped from 0.9.2-next.6 to 0.9.2-next.7
|
|
16
|
+
* @twin.org/nameof bumped from 0.9.2-next.6 to 0.9.2-next.7
|
|
17
|
+
* devDependencies
|
|
18
|
+
* @twin.org/nameof-transformer bumped from 0.9.2-next.6 to 0.9.2-next.7
|
|
19
|
+
* @twin.org/nameof-vitest-plugin bumped from 0.9.2-next.6 to 0.9.2-next.7
|
|
20
|
+
* @twin.org/validate-locales bumped from 0.9.2-next.6 to 0.9.2-next.7
|
|
21
|
+
|
|
22
|
+
## [0.9.2-next.6](https://github.com/iotaledger/twin-framework/compare/entity-v0.9.2-next.5...entity-v0.9.2-next.6) (2026-08-04)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Miscellaneous Chores
|
|
26
|
+
|
|
27
|
+
* **entity:** Synchronize repo versions
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### Dependencies
|
|
31
|
+
|
|
32
|
+
* The following workspace dependencies were updated
|
|
33
|
+
* dependencies
|
|
34
|
+
* @twin.org/nameof bumped from 0.9.2-next.5 to 0.9.2-next.6
|
|
35
|
+
* @twin.org/core bumped from 0.9.2-next.5 to 0.9.2-next.6
|
|
36
|
+
* devDependencies
|
|
37
|
+
* @twin.org/nameof-transformer bumped from 0.9.2-next.5 to 0.9.2-next.6
|
|
38
|
+
* @twin.org/nameof-vitest-plugin bumped from 0.9.2-next.5 to 0.9.2-next.6
|
|
39
|
+
* @twin.org/validate-locales bumped from 0.9.2-next.5 to 0.9.2-next.6
|
|
40
|
+
|
|
3
41
|
## [0.9.2-next.5](https://github.com/iotaledger/twin-framework/compare/entity-v0.9.2-next.4...entity-v0.9.2-next.5) (2026-08-03)
|
|
4
42
|
|
|
5
43
|
|
|
@@ -114,7 +114,7 @@ True if the diff has any structural changes.
|
|
|
114
114
|
> `static` **schemaPropertiesEqual**\<`T`, `U`\>(`schema1`, `schema2`): `boolean`
|
|
115
115
|
|
|
116
116
|
Compare two property descriptors for structural equality.
|
|
117
|
-
The `property` name field and documentation fields (`description`, `examples`) are intentionally excluded
|
|
117
|
+
The `property` name field and documentation fields (`description`, `examples`) are intentionally excluded - callers match by name before invoking this method.
|
|
118
118
|
|
|
119
119
|
#### Type Parameters
|
|
120
120
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twin.org/entity",
|
|
3
|
-
"version": "0.9.2-next.
|
|
3
|
+
"version": "0.9.2-next.7",
|
|
4
4
|
"description": "Helpers for defining and working with entities",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
"node": ">=24.0.0"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@twin.org/core": "0.9.2-next.
|
|
18
|
-
"@twin.org/nameof": "0.9.2-next.
|
|
17
|
+
"@twin.org/core": "0.9.2-next.7",
|
|
18
|
+
"@twin.org/nameof": "0.9.2-next.7",
|
|
19
19
|
"reflect-metadata": "0.2.2",
|
|
20
20
|
"tslib": "2.8.1"
|
|
21
21
|
},
|