mirror of
https://github.com/MAKS-IT-COM/maksit-certs-ui.git
synced 2025-12-31 04:00:03 +01:00
(bugfix): use custom id for deepDelta
This commit is contained in:
parent
f31176085c
commit
105ca2aa70
@ -142,7 +142,10 @@ const EditAccount: FC<EditAccountProps> = (props) => {
|
|||||||
|
|
||||||
const delta = deepDelta(fromFormState, fromBackupState, {
|
const delta = deepDelta(fromFormState, fromBackupState, {
|
||||||
arrays: {
|
arrays: {
|
||||||
hostnames: { identityKey: 'hostname' }
|
hostnames: {
|
||||||
|
identityKey: 'hostname',
|
||||||
|
idFieldKey: 'hostname'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -49,6 +49,18 @@ export type ArrayPolicy = {
|
|||||||
* Without identityKey AND without item.id, the array falls back to full replace.
|
* Without identityKey AND without item.id, the array falls back to full replace.
|
||||||
*/
|
*/
|
||||||
identityKey?: string | ((item: Record<string, unknown>) => string | number)
|
identityKey?: string | ((item: Record<string, unknown>) => string | number)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of the field used in the delta payload to carry the identity.
|
||||||
|
* Defaults to:
|
||||||
|
* - identityKey (if it is a string), otherwise
|
||||||
|
* - "id".
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* { identityKey: "hostname", idFieldKey: "hostname" }
|
||||||
|
* will emit { hostname: "...", operations: {...} } instead of { id: "..." }.
|
||||||
|
*/
|
||||||
|
idFieldKey?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DeepDeltaOptions<T> = {
|
export type DeepDeltaOptions<T> = {
|
||||||
@ -81,12 +93,17 @@ export type Delta<T> =
|
|||||||
: T[K]
|
: T[K]
|
||||||
}> & OperationBag<Extract<keyof T, string>>
|
}> & OperationBag<Extract<keyof T, string>>
|
||||||
|
|
||||||
/** Safe index to avoid TS2536 when addressing dynamic keys */
|
/**
|
||||||
|
* Safe index to avoid TS2536 when addressing dynamic keys.
|
||||||
|
*/
|
||||||
const getArrayPolicy = <T>(options: DeepDeltaOptions<T> | undefined, key: string): ArrayPolicy | undefined => {
|
const getArrayPolicy = <T>(options: DeepDeltaOptions<T> | undefined, key: string): ArrayPolicy | undefined => {
|
||||||
const arrays = options?.arrays as Partial<Record<string, ArrayPolicy>> | undefined
|
const arrays = options?.arrays as Partial<Record<string, ArrayPolicy>> | undefined
|
||||||
return arrays?.[key]
|
return arrays?.[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the value is a non-null plain object (and not an array).
|
||||||
|
*/
|
||||||
const isPlainObject = (value: unknown): value is PlainObject =>
|
const isPlainObject = (value: unknown): value is PlainObject =>
|
||||||
typeof value === 'object' && value !== null && !Array.isArray(value)
|
typeof value === 'object' && value !== null && !Array.isArray(value)
|
||||||
|
|
||||||
@ -99,6 +116,11 @@ const isPlainObject = (value: unknown): value is PlainObject =>
|
|||||||
* - Object arrays:
|
* - Object arrays:
|
||||||
* * if items have id or identityKey → itemized collection diff
|
* * if items have id or identityKey → itemized collection diff
|
||||||
* * otherwise → full replace (SetField)
|
* * otherwise → full replace (SetField)
|
||||||
|
*
|
||||||
|
* @param formState Current form state.
|
||||||
|
* @param backupState Original/backup state to diff against.
|
||||||
|
* @param options Optional per-array policies.
|
||||||
|
* @returns Delta<T> structure with changes and operations.
|
||||||
*/
|
*/
|
||||||
export const deepDelta = <T extends Record<string, unknown>>(
|
export const deepDelta = <T extends Record<string, unknown>>(
|
||||||
formState: T,
|
formState: T,
|
||||||
@ -107,6 +129,9 @@ export const deepDelta = <T extends Record<string, unknown>>(
|
|||||||
): Delta<T> => {
|
): Delta<T> => {
|
||||||
const delta = {} as Delta<T>
|
const delta = {} as Delta<T>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets an operation flag into the provided bag for a given key.
|
||||||
|
*/
|
||||||
// Sets an operation flag into the provided bag for a given key
|
// Sets an operation flag into the provided bag for a given key
|
||||||
const setOp = (bag: OperationBag, key: string, op: PatchOperation) => {
|
const setOp = (bag: OperationBag, key: string, op: PatchOperation) => {
|
||||||
const ops = (bag.operations ??= {} as Record<string, PatchOperation>)
|
const ops = (bag.operations ??= {} as Record<string, PatchOperation>)
|
||||||
@ -187,7 +212,6 @@ export const deepDelta = <T extends Record<string, unknown>>(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// --- OBJECT ---
|
// --- OBJECT ---
|
||||||
if (isPlainObject(formValue) && isPlainObject(backupValue)) {
|
if (isPlainObject(formValue) && isPlainObject(backupValue)) {
|
||||||
if (!deepEqual(formValue, backupValue)) {
|
if (!deepEqual(formValue, backupValue)) {
|
||||||
@ -255,6 +279,10 @@ export const deepDelta = <T extends Record<string, unknown>>(
|
|||||||
const dropChildren = policy?.dropChildrenOnRootChange ?? true
|
const dropChildren = policy?.dropChildrenOnRootChange ?? true
|
||||||
const roleKey = (policy?.roleFieldKey ?? 'role') as keyof U & string
|
const roleKey = (policy?.roleFieldKey ?? 'role') as keyof U & string
|
||||||
const rootKey = policy?.rootKey
|
const rootKey = policy?.rootKey
|
||||||
|
const identityKey = policy?.identityKey
|
||||||
|
const idFieldKey =
|
||||||
|
(policy?.idFieldKey ??
|
||||||
|
(typeof identityKey === 'string' ? identityKey : 'id')) as keyof U & string
|
||||||
|
|
||||||
const sameRoot = (f: U, b: U): boolean => {
|
const sameRoot = (f: U, b: U): boolean => {
|
||||||
if (!rootKey) return true
|
if (!rootKey) return true
|
||||||
@ -293,7 +321,7 @@ export const deepDelta = <T extends Record<string, unknown>>(
|
|||||||
c.operations = { collectionItemOperation: PatchOperation.AddToCollection }
|
c.operations = { collectionItemOperation: PatchOperation.AddToCollection }
|
||||||
return c
|
return c
|
||||||
})
|
})
|
||||||
;(addItem as PlainObject)[ck] = normalized
|
;(addItem as PlainObject)[ck] = normalized
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -306,7 +334,7 @@ export const deepDelta = <T extends Record<string, unknown>>(
|
|||||||
if (!backupItem) {
|
if (!backupItem) {
|
||||||
const addItem = {} as DeltaArrayItem<U>
|
const addItem = {} as DeltaArrayItem<U>
|
||||||
Object.assign(addItem, formItem as Partial<U>)
|
Object.assign(addItem, formItem as Partial<U>)
|
||||||
addItem.id = fid as U['id'] // store identity for server convenience
|
;(addItem as PlainObject)[idFieldKey] = fid as IdLike // store identity for server convenience
|
||||||
addItem.operations = { collectionItemOperation: PatchOperation.AddToCollection }
|
addItem.operations = { collectionItemOperation: PatchOperation.AddToCollection }
|
||||||
|
|
||||||
for (const ck of childrenKeys) {
|
for (const ck of childrenKeys) {
|
||||||
@ -318,7 +346,7 @@ export const deepDelta = <T extends Record<string, unknown>>(
|
|||||||
c.operations = { collectionItemOperation: PatchOperation.AddToCollection }
|
c.operations = { collectionItemOperation: PatchOperation.AddToCollection }
|
||||||
return c
|
return c
|
||||||
})
|
})
|
||||||
;(addItem as PlainObject)[ck] = normalized
|
;(addItem as PlainObject)[ck] = normalized
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -329,7 +357,7 @@ export const deepDelta = <T extends Record<string, unknown>>(
|
|||||||
// 1.c) Re-parenting: root changed
|
// 1.c) Re-parenting: root changed
|
||||||
if (!sameRoot(formItem, backupItem)) {
|
if (!sameRoot(formItem, backupItem)) {
|
||||||
const removeItem = {} as DeltaArrayItem<U>
|
const removeItem = {} as DeltaArrayItem<U>
|
||||||
removeItem.id = fid as U['id']
|
;(removeItem as PlainObject)[idFieldKey] = fid as IdLike
|
||||||
removeItem.operations = { collectionItemOperation: PatchOperation.RemoveFromCollection }
|
removeItem.operations = { collectionItemOperation: PatchOperation.RemoveFromCollection }
|
||||||
arrayDelta.push(removeItem)
|
arrayDelta.push(removeItem)
|
||||||
|
|
||||||
@ -353,7 +381,7 @@ export const deepDelta = <T extends Record<string, unknown>>(
|
|||||||
c.operations = { collectionItemOperation: PatchOperation.AddToCollection }
|
c.operations = { collectionItemOperation: PatchOperation.AddToCollection }
|
||||||
return c
|
return c
|
||||||
})
|
})
|
||||||
;(addItem as PlainObject)[ck] = normalized
|
;(addItem as PlainObject)[ck] = normalized
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -370,7 +398,7 @@ export const deepDelta = <T extends Record<string, unknown>>(
|
|||||||
const roleBecameNull = backupRole !== null && formRole === null
|
const roleBecameNull = backupRole !== null && formRole === null
|
||||||
if (roleBecameNull) {
|
if (roleBecameNull) {
|
||||||
const removeItem = {} as DeltaArrayItem<U>
|
const removeItem = {} as DeltaArrayItem<U>
|
||||||
removeItem.id = fid as U['id']
|
;(removeItem as PlainObject)[idFieldKey] = fid as IdLike
|
||||||
removeItem.operations = { collectionItemOperation: PatchOperation.RemoveFromCollection }
|
removeItem.operations = { collectionItemOperation: PatchOperation.RemoveFromCollection }
|
||||||
arrayDelta.push(removeItem)
|
arrayDelta.push(removeItem)
|
||||||
continue
|
continue
|
||||||
@ -379,15 +407,15 @@ export const deepDelta = <T extends Record<string, unknown>>(
|
|||||||
|
|
||||||
// 1.e) Field-level diff
|
// 1.e) Field-level diff
|
||||||
const itemDeltaBase = {} as (PlainObject & OperationBag & { id?: U['id'] })
|
const itemDeltaBase = {} as (PlainObject & OperationBag & { id?: U['id'] })
|
||||||
itemDeltaBase.id = fid as U['id']
|
;(itemDeltaBase as PlainObject)[idFieldKey] = fid as IdLike
|
||||||
|
|
||||||
calculateDelta(
|
calculateDelta(
|
||||||
formItem as PlainObject,
|
formItem as PlainObject,
|
||||||
backupItem as PlainObject,
|
backupItem as PlainObject,
|
||||||
itemDeltaBase
|
itemDeltaBase
|
||||||
)
|
)
|
||||||
|
|
||||||
const hasMeaningfulChanges = Object.keys(itemDeltaBase).some(k => k !== 'id')
|
const hasMeaningfulChanges = Object.keys(itemDeltaBase).some(k => k !== idFieldKey)
|
||||||
if (hasMeaningfulChanges) {
|
if (hasMeaningfulChanges) {
|
||||||
arrayDelta.push(itemDeltaBase as DeltaArrayItem<U>)
|
arrayDelta.push(itemDeltaBase as DeltaArrayItem<U>)
|
||||||
}
|
}
|
||||||
@ -399,7 +427,7 @@ export const deepDelta = <T extends Record<string, unknown>>(
|
|||||||
if (bid === null || bid === undefined) continue
|
if (bid === null || bid === undefined) continue
|
||||||
if (!formMap.has(bid as string | number)) {
|
if (!formMap.has(bid as string | number)) {
|
||||||
const removeItem = {} as DeltaArrayItem<U>
|
const removeItem = {} as DeltaArrayItem<U>
|
||||||
removeItem.id = bid as U['id']
|
;(removeItem as PlainObject)[idFieldKey] = bid as IdLike
|
||||||
removeItem.operations = { collectionItemOperation: PatchOperation.RemoveFromCollection }
|
removeItem.operations = { collectionItemOperation: PatchOperation.RemoveFromCollection }
|
||||||
arrayDelta.push(removeItem)
|
arrayDelta.push(removeItem)
|
||||||
}
|
}
|
||||||
@ -424,6 +452,9 @@ export const deepDelta = <T extends Record<string, unknown>>(
|
|||||||
* - parent-level operations exist, or
|
* - parent-level operations exist, or
|
||||||
* - nested object deltas contain operations, or
|
* - nested object deltas contain operations, or
|
||||||
* - any array item contains operations.
|
* - any array item contains operations.
|
||||||
|
*
|
||||||
|
* @param delta Delta object to inspect.
|
||||||
|
* @returns True if any operations are present, otherwise false.
|
||||||
*/
|
*/
|
||||||
export const deltaHasOperations = <T extends Record<string, unknown>>(delta: Delta<T>): boolean => {
|
export const deltaHasOperations = <T extends Record<string, unknown>>(delta: Delta<T>): boolean => {
|
||||||
if (!isPlainObject(delta)) return false
|
if (!isPlainObject(delta)) return false
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user