# Decorators

Decorators are a powerful tool in TypeScript that allow for the abstraction of database access and transformation of objects into database records and vice versa. They provide a way to describe metadata that can be used to configure interaction with the database without directly modifying the code.

**1. Table Decorator**

**Description:** The Table decorator is used to define the database table associated with the class. This decorator takes parameters `name` and `options`, where `name` specifies the table name, and `options` allows specifying additional options.

**Code:**

```typescript
import 'reflect-metadata';
import { TableDecoratorInterface } from '@decorators/index';
import { constants } from '@core/constants';

export function Table({ name, options }: TableDecoratorInterface) {
    return function(constructor: Function) {
        if (!name) {
            console.info(
                'Ви вказали назву таблиці не коректно або вона відсутня, тому назва буде взята за назвою класу'
            );
            name = constructor.name;
        }

        Reflect.defineMetadata(constants.decoratorsMetadata.table, { name, options }, constructor.prototype);
    };
}
```

**Usage:** This decorator can be used to define how a class maps to a database table. For example:

```typescript
@Table({ name: 'users', options: { schema: 'public' } })
class User {
    @Column({ name: 'id', options: { type: 'integer', primary: true } })
    id: number;

    @Column({ name: 'username', options: { type: 'varchar', length: 255 } })
    username: string;
}
```

In this example, the Table decorator indicates that the User class corresponds to the `users` table in the `public` schema. All properties of the class will represent columns of the table.

2. **The work of a decorator** `Column`

Consider the `Column` decorator, which is medium in size and illustrates the process of working with metadata in TypeScript.

#### Decorator `Column`

**Code:**

```typescript
import 'reflect-metadata';
import { ColumnDecoratorInterface, ColumnMetadataInterface, ColumnOptionsDecoratorInterface } from '@decorators/index';
import { constants } from '@core/constants';

export function Column(decoratorParams?: ColumnDecoratorInterface) {
    return function(target: any, propertyKey: string) {
        let name = propertyKey;
        let options: ColumnOptionsDecoratorInterface = { nullable: true };

        if (decoratorParams) {
            name = decoratorParams.name || propertyKey;
            options = { ...options, ...decoratorParams.options };
        }

        const columns: ColumnMetadataInterface[] = Reflect.getMetadata(constants.decoratorsMetadata.columns, target) || [];

        columns.forEach(column => {
            if (!column.options?.dataType) {
                throw Error('Ви не вказали тип колонки!');
            }
        });

        columns.push({ name, options, propertyKey });
        Reflect.defineMetadata(constants.decoratorsMetadata.columns, columns, target);
    };
}
```

**How the Column Decorator Works**

1. **Decorator Declaration** The Column decorator takes an optional `decoratorParams` parameter that allows configuring the column's name and options. The decorator is used to attach metadata to a class property.
2. **Value Assignment** `let name = propertyKey;`: The default column name is the property name of the class. `let options: ColumnOptionsDecoratorInterface = { nullable: true };`: By default, the column can be nullable. `if (decoratorParams) { ... }`: If decorator parameters are provided, then: `name = decoratorParams.name || propertyKey;`: The column name is set based on the decorator parameters if provided. `options = { ...options, ...decoratorParams.options };`: The column options are updated from the decorator parameters.
3. **Reading Metadata** `const columns: ColumnMetadataInterface[] = Reflect.getMetadata(constants.decoratorsMetadata.columns, target) || [];`: Reads existing column metadata for the target class. If no metadata is present, an empty array is initialized.
4. **Column Type Check** `columns.forEach(column => { ... });`: Checks if a data type is specified for all columns. `if (!column.options?.dataType) { throw Error('Column type not specified!'); }`: Throws an error if a data type is not specified.
5. **Adding a New Column** `columns.push({ name, options, propertyKey });`: Adds a new column to the metadata array.
6. **Saving Updated Metadata** `Reflect.defineMetadata(constants.decoratorsMetadata.columns, columns, target);`: Updates the column metadata for the target class, saving the new data.

**Using Metadata from Decorators** Metadata defined through decorators can be retrieved and used to create database schemas or configure queries. For example:

```typescript
getPreparedModels(models: ClassInterface[]): TableIngotInterface<DT>[] {
    const preparedModels: TableIngotInterface<DT>[] = [];

    for (let model of models) {
        const table: TableInterface<DT>
            = Reflect.getMetadata(constants.decoratorsMetadata.table, model.prototype);
        const metadataColumns: ColumnMetadataInterface<DT>[]
            = Reflect.getMetadata(constants.decoratorsMetadata.columns, model.prototype);
        // Інші метадані...

        // Обробка та підготовка моделі для використання з базою даних
        preparedModels.push({
            tableName: table.name,
            columns: metadataColumns,
            // Інші властивості...
        });
    }

    return preparedModels;
}
```

#### **Usage Example**

**Class Declaration**

```typescript
import { Column } from '@decorators/column';

class User {
    @Column({ name: 'user_id', options: { primary: true, type: 'integer' } })
    id: number;

    @Column({ name: 'user_name', options: { type: 'string', length: 50 } })
    name: string;
}
```

How the Code Works

1. **Decorator Initialization**&#x20;
   * For the `id` property, the Column decorator is applied with parameters `{ name: 'user_id', options: { primary: true, type: 'integer' } }`. This indicates that the column will be named `user_id`, be a primary key, and have an integer type.&#x20;
   * For the `name` property, the Column decorator is applied with parameters `{ name: 'user_name', options: { type: 'string', length: 50 } }`. This indicates that the column will be named `user_name`, be of type string, and have a maximum length of 50 characters.
2. **Decorator Processing** When applying decorators to the `id` and `name` properties, the Column decorator stores metadata about these columns in the User object using `Reflect.defineMetadata`. The decorator checks if types are specified for all columns and throws errors if types are not provided.
3. **Metadata Storage** As a result, metadata about the columns for the User class is stored in the class metadata and can be used for generating SQL queries or other database operations.

In this example, the metadata obtained from the Table and Column decorators is used to form a table schema that can be used to interact with the database. This allows dynamic adaptation to changes in the data structure without needing to modify the code interacting with the database.
