Future-Proofing Code: Type-Safe KeyPath and Value Validation for Evolving Client-Backend Systems

Future-proof code means that it is designed and written in such a way that it remains maintainable in the long term and can be adapted to new requirements with minimal changes.

Let’s assume we have three business entities in TypeScript: a user, the te…


This content originally appeared on DEV Community and was authored by János Kukoda

Future-proof code means that it is designed and written in such a way that it remains maintainable in the long term and can be adapted to new requirements with minimal changes.

Let's assume we have three business entities in TypeScript: a user, the teams associated with the user, and, for each team, a set of transformations. Each transformation has a name and texts.

export interface UserDTO {
    teams: { [teamId: string]: TeamDTO };
}

export interface TeamDTO {
    transformations: { [transformationId: string]: Transformation };
}

export interface Transformation {
    name: string;
    texts: { [id: string]: string };
}

To modify these entities, we follow the command pattern. The modification parameters are wrapped in an object: the value, and the keyPath which indicates where the modification should occur, the type of modification operation is represented by the setValue keyword.

await auth.updateClientAndServerArray([
    {
        keyPath: ['teams', 'response', 'transformations', id, 'name'],
        value: 'some-name',
        operation: 'setValue',
    },
]);

The advantage of this approach is that a single abstract manipulation method with conditional types can handle all future modifications of the business entities on both the client-side and server-side. On the backend, if you're using a document-based database with a similar enclosing structure for business entities, the modification can also be implemented using a single endpoint.

This approach ensures strong type safety, but it requires some conditional types.

We perform two operations: the first duplicates a transformation and sets a new name for the newly created transformation. The second example modifies only the transformation's name.

Type safety is ensured in two ways:

  1. If the keyPath points to an invalid location, a static error is generated.
  2. If the value doesn’t match the type expected at the keyPath, a type error occurs.

For example, in TypeScript, value assignments can be triggered like this:

<button
    onClick={async () => {
        const id = randomString(4);
        const _transformation = {
            ...transformation,
            name: transformation?.name + ' copy',
            texts: {}
        };
        await auth.updateClientAndServerArray([
            {
                keyPath: ['teams', 'response', 'transformations', id],
                value: _transformation,
                operation: 'setValue',
            },
        ]);
    }}
>
    Duplicate transformation
</button>

Here, the implementation of the updateClientAndServerArray method itself is not shown, but it uses a recursive approach. The same method handles both duplication and name setting.

To update the name field, updateClientAndServerArray can be used like this:

await auth.updateClientAndServerArray([
    {
        keyPath: ['teams', 'response', 'transformations', id, 'name'],
        value: 'some-name',
        operation: 'setValue',
    },
]);

The key advantage here is that if a new field or container is added to any business entity in the future, no new manipulation methods are needed. The modification can be carried out without requiring changes to the backend or the client, and type safety is preserved, ensuring that no invalid values can be assigned. This approach can handle any JSON type, including strings, numbers, objects, or arrays.

Type Definitions

These two types are used to ensure type safety. The first type recursively traverses the business entities to verify whether the provided key path is valid:

export type KeyPath<T> = T extends object
    ? {
        [K in keyof T]: T[K] extends (infer U)[]
        ? [K] | [K, number, ...KeyPath<U>] // Arrays
        : T[K] extends Record<string, infer V>
        ? [K] | [K, string] | [K, string, ...KeyPath<V>] // Objects
        : T[K] extends object
        ? [K] | [K, ...KeyPath<T[K]>]
        : [K]; // Simple fields
    }[keyof T]
    : never;

Next, we determine the value type associated with a specific key path:

export type ValueForKeyPath<T, P extends KeyPath<T>> =
    P extends [infer K, ...infer Rest]
    ? K extends keyof T
        ? Rest extends []
            ? T[K] // Return current value
            : T[K] extends (infer U)[]
            ? Rest extends [number, ...infer R]
                ? ValueForKeyPath<U, R extends KeyPath<U> ? R : never>
                : never
            : T[K] extends Record<string, infer U>
            ? Rest extends [string, ...infer R]
                ? ValueForKeyPath<U, R extends KeyPath<U> ? R : never> | U
                : U
            : T[K] extends object
            ? ValueForKeyPath<T[K], Rest extends KeyPath<T[K]> ? Rest : never>
            : never
        : never
    : never;

If the value being assigned doesn’t match the type derived from the key path, a static error is raised, ensuring that only valid data can be assigned to the given key path.

For example:

await auth.updateClientAndServerArray([
    {
        keyPath: ['teams', 'response', 'transformations', id, 'name'],
        value: 'some-name',
        operation: 'setValue',
    },
]);

This type-safe approach ensures that modifying nested structures is both flexible and reliable, providing a solid foundation for future-proofing your code.


This content originally appeared on DEV Community and was authored by János Kukoda


Print Share Comment Cite Upload Translate Updates
APA

János Kukoda | Sciencx (2024-10-18T15:52:22+00:00) Future-Proofing Code: Type-Safe KeyPath and Value Validation for Evolving Client-Backend Systems. Retrieved from https://www.scien.cx/2024/10/18/future-proofing-code-type-safe-keypath-and-value-validation-for-evolving-client-backend-systems/

MLA
" » Future-Proofing Code: Type-Safe KeyPath and Value Validation for Evolving Client-Backend Systems." János Kukoda | Sciencx - Friday October 18, 2024, https://www.scien.cx/2024/10/18/future-proofing-code-type-safe-keypath-and-value-validation-for-evolving-client-backend-systems/
HARVARD
János Kukoda | Sciencx Friday October 18, 2024 » Future-Proofing Code: Type-Safe KeyPath and Value Validation for Evolving Client-Backend Systems., viewed ,<https://www.scien.cx/2024/10/18/future-proofing-code-type-safe-keypath-and-value-validation-for-evolving-client-backend-systems/>
VANCOUVER
János Kukoda | Sciencx - » Future-Proofing Code: Type-Safe KeyPath and Value Validation for Evolving Client-Backend Systems. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2024/10/18/future-proofing-code-type-safe-keypath-and-value-validation-for-evolving-client-backend-systems/
CHICAGO
" » Future-Proofing Code: Type-Safe KeyPath and Value Validation for Evolving Client-Backend Systems." János Kukoda | Sciencx - Accessed . https://www.scien.cx/2024/10/18/future-proofing-code-type-safe-keypath-and-value-validation-for-evolving-client-backend-systems/
IEEE
" » Future-Proofing Code: Type-Safe KeyPath and Value Validation for Evolving Client-Backend Systems." János Kukoda | Sciencx [Online]. Available: https://www.scien.cx/2024/10/18/future-proofing-code-type-safe-keypath-and-value-validation-for-evolving-client-backend-systems/. [Accessed: ]
rf:citation
» Future-Proofing Code: Type-Safe KeyPath and Value Validation for Evolving Client-Backend Systems | János Kukoda | Sciencx | https://www.scien.cx/2024/10/18/future-proofing-code-type-safe-keypath-and-value-validation-for-evolving-client-backend-systems/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.