## Core Concepts

The foundation of Xtrem development rests on these fundamental concepts. Master these before diving into specific components.

### Property Decorators

Properties in Xtrem nodes use decorators to define their type, storage, and behavior. Here are examples from the actual ShowCase package:

#### String Properties

NOTE: String properties do not use the isNullable property attribute they will use the isNotEmpty boolean

```typescript
// String property with data type
@decorators.stringProperty<ShowCaseProduct, 'product'>({
    isStored: true,
    isPublished: true,
    lookupAccess: true,
    isNotEmpty: true,
    duplicateRequiresPrompt: true,
    dataType: () => xtremShowCase.dataTypes.descriptionDataType,
    async duplicatedValue() {
        return `New ${await this.product}`;
    },
})
readonly product: Promise<string>;

// String property with computed value
@decorators.stringProperty<ShowCaseProduct, 'computedEmail'>({
    isPublished: true,
    dataType: () => xtremShowCase.dataTypes.emailDataType,
    async getValue() {
        return `Sage.${await this.email}`;
    },
})
readonly computedEmail: Promise<string>;

// String array property
@decorators.stringArrayProperty<ShowCaseProduct, 'entries'>({
    isStored: true,
    isPublished: true,
    dataType: () => new StringArrayDataType({ maxLength: 30 }),
})
readonly entries: Promise<string[] | null>;
```

#### Localized String Properties

**IMPORTANT RULE: String properties that contain user-facing text like names, descriptions, titles, or labels MUST use localized data types to support internationalization.**

Use these patterns for localized strings:

```typescript
// Localized name (max 200 characters, supports multiple languages)
@decorators.stringProperty<MyNode, 'name'>({
    isStored: true,
    isPublished: true,
    isRequired: true,
    lookupAccess: true,
    dataType: () => xtremSystem.dataTypes.localizedName, // Max 200 chars, isLocalized: true
})
readonly name: Promise<string>;

// Localized description (max 4000 characters, supports multiple languages)
@decorators.stringProperty<MyNode, 'description'>({
    isStored: true,
    isPublished: true,
    isNullable: true,
    dataType: () => xtremSystem.dataTypes.localizedDescription, // Max 4000 chars, isLocalized: true
})
readonly description: Promise<string | null>;

// Localized short description (max 80 characters, supports multiple languages)
@decorators.stringProperty<MyNode, 'shortDescription'>({
    isStored: true,
    isPublished: true,
    isNullable: true,
    dataType: () => xtremSystem.dataTypes.localizedShortDescription, // Max 80 chars, isLocalized: true
})
readonly shortDescription: Promise<string | null>;

// Custom localized string (use when standard types don't fit)
@decorators.stringProperty<MyNode, 'title'>({
    isStored: true,
    isPublished: true,
    isRequired: true,
    dataType: () => new StringDataType({ maxLength: 150, isLocalized: true }),
})
readonly title: Promise<string>;
```

**When to use localized strings:**

- ✅ **User-facing names**: Product names, customer names, item names
- ✅ **Descriptions**: Product descriptions, category descriptions, help text
- ✅ **Titles and labels**: Form titles, section headers, field labels
- ✅ **Messages**: Error messages, notifications, status text
- ❌ **Technical codes**: IDs, technical references, API keys
- ❌ **System data**: File paths, URLs, technical configuration

**Available localized data types for nodes where storage type is "sql":**

- `xtremSystem.dataTypes.localizedName` - For names (200 chars)
- `xtremSystem.dataTypes.localizedDescription` - For long descriptions (4000 chars)
- `xtremSystem.dataTypes.localizedShortDescription` - For short descriptions (80 chars)
- Custom: `new StringDataType({ maxLength: X, isLocalized: true })`

### Numeric Properties

```typescript
// Integer property with dependencies and validation
@decorators.integerProperty<ShowCaseProduct, 'qty'>({
    isStored: true,
    isPublished: true,
    lookupAccess: true,
    defaultValue() {
        return 1;
    },
    dependsOn: ['fixedQuantity', 'provider'],
    async updatedValue() {
        if ((await this.fixedQuantity) > 0) {
            return this.fixedQuantity;
        }
        return this.qty;
    },
    async control(ctx, val) {
        await ctx.error.if(val).is.not.greater.than(await (await this.provider)!.minQuantity);
    },
})
readonly qty: Promise<integer>;

// Decimal property with data type
@decorators.decimalProperty<ShowCaseProduct, 'listPrice'>({
    isStored: true,
    isPublished: true,
    dataType: () => xtremShowCase.dataTypes.defaultDecimalDataType,
})
readonly listPrice: Promise<decimal>;

// Decimal with dependencies and default value
@decorators.decimalProperty<ShowCaseProduct, 'netPrice'>({
    isStored: true,
    isPublished: true,
    lookupAccess: true,
    dataType: () => xtremShowCase.dataTypes.defaultDecimalDataType,
    dependsOn: ['listPrice', 'qty'],
    async defaultValue() {
        if ((await this.qty) > 10) {
            return (await this.listPrice) * 0.9;
        }
        return this.listPrice;
    },
    async control(ctx, val) {
        await ctx.error.if(val).is.negative();
    },
})
readonly netPrice: Promise<decimal>;

// Computed decimal property
@decorators.decimalProperty<ShowCaseProduct, 'total'>({
    isPublished: true,
    lookupAccess: true,
    dataType: () => xtremShowCase.dataTypes.defaultDecimalDataType,
    dependsOn: ['amount', 'tax'],
    async getValue() {
        return (await this.amount) + (await this.tax);
    },
})
readonly total: Promise<decimal>;
```

### Date and Time Properties

```typescript
// Date property with default value
@decorators.dateProperty<ShowCaseProduct, 'releaseDate'>({
    isStored: true,
    isPublished: true,
    defaultValue() {
        return date.today();
    },
})
readonly releaseDate: Promise<date | null>;

// Date with validation
@decorators.dateProperty<ShowCaseProduct, 'endingDate'>({
    isStored: true,
    isNullable: true,
    isPublished: true,
    async control(ctx, val) {
        if ((await this.releaseDate) && val) {
            await ctx.error.if(val).is.less.than((await this.releaseDate)!);
        }
    },
})
readonly endingDate: Promise<date | null>;

// DateTime property
@decorators.datetimeProperty<ShowCaseProduct, 'importedAt'>({
    isPublished: true,
    isStored: true,
    isNullable: true,
})
readonly importedAt: Promise<datetime | null>;

// DateTime range property
@decorators.datetimeRangeProperty<ShowCaseProduct, 'manufacturedWithin'>({
    isPublished: true,
    isStored: true,
    isNullable: true,
    duplicatedValue: null,
})
readonly manufacturedWithin: Promise<DatetimeRange | null>;
```

### Boolean and Enum Properties

```typescript
// Boolean property
@decorators.booleanProperty<ShowCaseProduct, 'hotProduct'>({
    isStored: true,
    isPublished: true,
    lookupAccess: true,
})
readonly hotProduct: Promise<boolean>;

// Enum property
@decorators.enumProperty<ShowCaseProduct, 'category'>({
    dataType: () => xtremShowCase.enums.showCaseProductCategoryDataType,
    isStored: true,
    isPublished: true,
    lookupAccess: true,
    isNullable: true,
})
readonly category: Promise<xtremShowCase.enums.ShowCaseProductCategory | null>;

// Enum array property
@decorators.enumArrayProperty<ShowCaseProduct, 'subcategories'>({
    dataType: () => xtremShowCase.enums.showCaseProductCategoryDataType,
    isStored: true,
    isPublished: true,
    isNullable: true,
})
readonly subcategories: Promise<xtremShowCase.enums.ShowCaseProductCategory[] | null>;
```

### Reference and Collection Properties

```typescript
// Reference property with vital parent
@decorators.referenceProperty<ShowCaseProduct, 'provider'>({
    isStored: true,
    isPublished: true,
    lookupAccess: true,
    node: () => xtremShowCase.nodes.ShowCaseProvider,
    isVitalParent: true,
    duplicateRequiresPrompt: true,
})
readonly provider: Reference<xtremShowCase.nodes.ShowCaseProvider | null>;

// Reference with filters
@decorators.referenceProperty<ShowCaseProvider, 'flagshipProduct'>({
    filters: {
        lookup: {
            provider() {
                return this;
            },
        },
    },
    isNullable: true,
    isPublished: true,
    isStored: true,
    node: () => xtremShowCase.nodes.ShowCaseProduct,
})
readonly flagshipProduct: Reference<xtremShowCase.nodes.ShowCaseProduct | null>;

// Collection property with reverse reference
@decorators.collectionProperty<ShowCaseProvider, 'products'>({
    isPublished: true,
    isVital: true,
    lookupAccess: true,
    forceFullSave: true,
    node: () => xtremShowCase.nodes.ShowCaseProduct,
    reverseReference: 'provider',
})
readonly products: Collection<xtremShowCase.nodes.ShowCaseProduct>;
```

### Special Properties

```typescript
// JSON property
@decorators.jsonProperty<ShowCaseProvider, 'additionalInfo'>({
    isNullable: true,
    isPublished: true,
    isStored: true,
})
readonly additionalInfo: Promise<any | null>;

// Binary stream property with computed value
@decorators.binaryStreamProperty<ShowCaseProduct, 'imageField'>({
    isNullable: true,
    isPublished: true,
    lookupAccess: true,
    computeValue() {
        return BinaryStream.fromBuffer(
            Buffer.from('base64data...', 'base64'),
        );
    },
})
readonly imageField: Promise<BinaryStream | null>;

// Text stream property with data type
@decorators.textStreamProperty<ShowCaseProvider, 'document'>({
    dataType: () =>
        new TextStreamDataType({
            maxLength: 1000000,
            allowedContentTypes: ['text/html', 'application/json', 'text/plain'],
        }),
    isPublished: true,
    isStored: true,
})
readonly document: Promise<TextStream>;

// Computed string property with dependencies
@decorators.stringProperty<ShowCaseProvider, 'concatenatedAddress'>({
    async computeValue() {
        if (!(await this.siteAddress)) {
            return '';
        }
        const address: string[] = [];
        if (await (await this.siteAddress)!.addressLine1) {
            address.push(await (await this.siteAddress)!.addressLine1);
        }
        if (await (await this.siteAddress)!.addressLine2) {
            address.push(await (await this.siteAddress)!.addressLine2);
        }
        if (await (await this.siteAddress)!.city) {
            address.push(await (await this.siteAddress)!.city);
        }
        return address.join('\n');
    },
    dependsOn: ['siteAddress'],
    isPublished: true,
})
readonly concatenatedAddress: Promise<string>;
```

### UI Field Decorators

UI fields in Xtrem pages use decorators to define their appearance and behavior. Here are examples from the actual ShowCase package:

#### Text and Numeric Fields

```typescript
// Text field from ShowCaseProduct page
@ui.decorators.textField<ShowCaseProduct>({
    parent() {
        return this.block;
    },
    title: 'Product',
    isMandatory: true,
})
product: ui.fields.Text<ShowCaseProduct>;

// Numeric field with scale and validation
@ui.decorators.numericField<ShowCaseProduct>({
    parent() {
        return this.block;
    },
    title: 'List price',
    scale: 2,
    fetchesDefaults: true,
})
listPrice: ui.fields.Numeric;

// Numeric field with scale from ShowCaseProduct
@ui.decorators.numericField<ShowCaseProduct>({
    parent() {
        return this.block;
    },
    title: 'Amount',
    scale: 2,
})
amount: ui.fields.Numeric;
```

#### Date Fields

```typescript
// Date field from ShowCaseProduct page
@ui.decorators.dateField<ShowCaseProduct>({
    parent() {
        return this.block;
    },
    title: 'Release Date',
})
releaseDate: ui.fields.Date;

// Date field from DateField showcase page
@ui.decorators.dateField<DateField>({
    parent() {
        return this.configurationBlock;
    },
    title: 'Value',
    onChange() {
        this.field.value = this.value.value;
    },
})
value: ui.fields.Date;
```

#### Selection Fields

```typescript
// Checkbox field from DateField showcase
@ui.decorators.checkboxField<DateField>({
    parent() {
        return this.configurationBlock;
    },
    title: 'Is disabled',
    onChange() {
        this.field.isDisabled = this.isDisabled.value;
    },
})
isDisabled: ui.fields.Checkbox;

// Checkbox field from PodCollection showcase
@ui.decorators.checkboxField<PodCollection>({
    isTransient: true,
    parent() {
        return this.configurationBlock;
    },
    title: 'Can select',
    onChange() {
        this.lines.canSelect = this.canSelect.value;
    },
})
canSelect: ui.fields.Checkbox;

// Dropdown list field from CustomFieldWizard
@ui.decorators.dropdownListField<CustomFieldWizard>({
    parent() {
        return this.titleBlock;
    },
    isMandatory: true,
    title: 'Field type',
    helperText: 'Graphical component that represents the value on the screen.',
    isSortedAlphabetically: true,
    options: ['textField', 'checkboxField', 'switchField', 'dateField', 'numericField', 'selectField'],
    map(value) {
        switch (value) {
            case 'checkboxField':
                return ui.localize('@sage/xtrem-customization/page__custom_field__componentType_checkboxField', 'Checkbox');
            case 'textField':
                return ui.localize('@sage/xtrem-customization/page__custom_field__componentType_textField', 'Text field');
            case 'dateField':
                return ui.localize('@sage/xtrem-customization/page__custom_field__componentType_dateField', 'Date');
            default:
                return '';
        }
    },
    onChange() {
        this.onComponentTypeChange();
    },
})
componentType: ui.fields.DropdownList;
```

#### Reference Fields

```typescript
// Reference field from ShowCaseProduct page
@ui.decorators.referenceField<ShowCaseProduct, ShowCaseProvider>({
    parent() {
        return this.block;
    },
    columns: [ui.nestedFields.text({ bind: 'textField' })],
    node: '@sage/xtrem-show-case/ShowCaseProvider',
    title: 'Provider',
    valueField: 'textField',
    helperTextField: '_id',
    minLookupCharacters: 0,
    tunnelPage: '@sage/xtrem-show-case/ShowCaseProvider',
    createTunnelLinkText: 'Create new value',
})
provider: ui.fields.Reference;

// Reference field from StandardShowCaseProvider
@ui.decorators.referenceField<StandardShowCaseProvider, ShowCaseProduct>({
    parent() {
        return this.headerBlock;
    },
    valueField: 'product',
    helperTextField: '_id',
    node: '@sage/xtrem-show-case/ShowCaseProduct',
    title: 'Flagship Product',
    tunnelPage: '@sage/xtrem-show-case/StandardShowCaseProduct',
    columns: [ui.nestedFields.text({ bind: 'product', title: 'Product Name' })],
    createTunnelLinkText: 'Create a new product',
})
flagshipProduct: ui.fields.Reference;
```

#### Collection Fields

```typescript
// Pod field from PodField showcase
@ui.decorators.podField<PodField, ShowCaseProvider>({
    columns: [
        ui.nestedFields.text({ bind: 'textField', title: 'Text field' }),
        ui.nestedFields.numeric({ bind: 'integerField', title: 'Numeric field' }),
        ui.nestedFields.text({ bind: '_id', title: 'id' }),
        ui.nestedFields.date({ bind: 'dateField', title: 'date field' }),
    ],
    node: '@sage/xtrem-show-case/ShowCaseProvider',
})
provider: ui.fields.Pod;

// Pod collection field from NestedPodCollection showcase
@ui.decorators.podCollectionField<NestedPodCollection, ShowCaseProvider>({
    node: '@sage/xtrem-show-case/ShowCaseProvider',
    parent() {
        return this.fieldBlock;
    },
    fetchesDefaults: true,
    recordTitle(value: any, rowData: ShowCaseProvider | null) {
        return rowData.textField;
    },
    isDisabled: false,
    dropdownActions: [
        {
            icon: 'add',
            title: 'Add',
            isDisabled() {
                return false;
            },
            onClick(rowId: any, data: any) {
                this.$.dialog.message(
                    'info',
                    'Add Row Action was clicked',
                    `Product: ${data.product} Row: ${rowId}`,
                );
            },
        },
        {
            icon: 'minus',
            title: 'Remove',
            onClick(rowId: any) {
                this.$.dialog.message('info', 'Remove Action was clicked', `Row: ${rowId}`);
            },
        },
    ],
    columns: [
        ui.nestedFields.text({ bind: 'textField', title: 'Text field' }),
        ui.nestedFields.numeric({ bind: 'integerField', title: 'Numeric field' }),
        ui.nestedFields.date({ bind: 'dateField', title: 'Date field' }),
    ],
})
products: ui.fields.PodCollection;

// Table field from StandardShowCaseProvider
@ui.decorators.tableField<StandardShowCaseProvider, ShowCaseProduct>({
    node: '@sage/xtrem-show-case/ShowCaseProduct',
    mainField: 'description',
    parent() {
        return this.productListSection;
    },
    title: 'Products',
    isTitleHidden: true,
    orderBy: { _id: 1 },
    columns: [
        ui.nestedFields.text({ bind: '_id', isReadOnly: true, title: 'Id' }),
        ui.nestedFields.text({
            bind: 'product',
            title: 'Product',
        }),
        ui.nestedFields.text({ bind: 'description', title: 'Description' }),
        ui.nestedFields.checkbox({ bind: 'hotProduct', isReadOnly: true, title: 'Hot' }),
    ],
})
products: ui.fields.Table<ShowCaseProduct>;

// Vital pod field from ShowCaseProvider
@ui.decorators.vitalPodField<ShowCaseProvider, ShowCaseProviderAddressBinding>({
    title: 'Address',
    parent() {
        return this.block;
    },
    columns: [
        ui.nestedFields.text({ bind: 'name', title: 'Name' }),
        ui.nestedFields.text({ bind: 'addressLine1', title: 'Line1' }),
        ui.nestedFields.text({ bind: 'addressLine2', title: 'Line2' }),
        ui.nestedFields.reference({
            bind: 'country',
            title: 'Country',
            node: '@sage/xtrem-show-case/ShowCaseCountry',
            valueField: 'name',
            helperTextField: 'code',
        }),
        ui.nestedFields.text({ bind: 'zip', title: 'zip', isTransientInput: true }),
    ],
    node: '@sage/xtrem-show-case/ShowCaseProviderAddress',
})
siteAddress: ui.fields.VitalPod;
```

#### Dynamic Fields

```typescript
// Dynamic pod field from DynamicPodField showcase
@ui.decorators.dynamicPodField<DynamicPodField>({
    parent() {
        return this.fieldBlock;
    },
    title: 'Dynamic Pod field',
    width: 'extra-large',
    columns: [
        ui.nestedFields.text({ bind: 'textField', title: 'Text field' }),
        ui.nestedFields.textArea({ bind: 'internalNote', title: 'Internal notes' }),
        ui.nestedFields.reference({
            valueField: 'name',
            bind: 'country',
            node: '@sage/xtrem-show-case/ShowCaseCountry',
        }),
        ui.nestedFields.switch({
            bind: 'hotProduct',
            title: 'Hot (Switch)',
        }),
        ui.nestedFields.dynamicSelect({
            bind: 'dynamicSelect',
            title: 'Dynamic select',
            mode: 'input',
            options() {
                return [
                    { id: '1', value: '1', displayedAs: 'One' },
                    { id: '2', value: '2', displayedAs: 'Two' },
                    { id: '3', value: '3', displayedAs: 'Three' },
                ];
            },
        }),
    ],
})
additionalInfo: ui.fields.DynamicPod;
```

#### Action and Display Fields

```typescript
// Button field from PodField showcase
@ui.decorators.buttonField<PodField>({
    parent() {
        return this.configurationBlock;
    },
    isTransient: true,
    map() {
        return 'Focus field';
    },
    onClick() {
        this.provider.focus();
    },
})
focus: ui.fields.Button;

// Label field from DynamicPodField showcase
@ui.decorators.labelField<DynamicPodField>({
    parent() {
        return this.fieldBlock;
    },
    isHidden: true,
    isTransient: true,
    map() {
        return 'Change was triggered';
    },
})
changeTriggered: ui.fields.Label;

// Separator field from PodCollection showcase
@ui.decorators.separatorField<PodCollection>({
    parent() {
        return this.configurationBlock;
    },
    isFullWidth: true,
})
separator1: ui.fields.Separator;

// Card field from ShowCaseProvider page
@ui.decorators.cardField<ShowCaseProvider, ShowCaseProduct>({
    isFullWidth: true,
    parent() {
        return this.detailPanelHeaderDetailSection;
    },
    isTransient: true,
    cardDefinition: {
        title: ui.nestedFields.text({ bind: 'description' }),
        titleRight: ui.nestedFields.label({ bind: 'category' }),
        line2: ui.nestedFields.numeric({ bind: 'amount', scale: 2 }),
    },
})
flagshipProductCard: ui.fields.Card<ShowCaseProduct>;
```

#### GraphQL Query Patterns

```typescript
// Simple query with filters
const products = await context.query(ShowCaseProduct, {
    filter: { category: 'electronics' },
    orderBy: { listPrice: -1 },
    first: 10,
});

// Read single record, valids there is at least and at most a single result
const document = await context.read(Document, { _id: documentId });

// Try to read single record, valids there is a single result and will return null if no result is foudn
const document = await context.tryRead(Document, { _id: documentId });

// Simple selection with filters
const products = await context.select(
    ShowCaseProduct,
    {
        _id: true,
        product: true,
        listPrice: true,
        category: true,
    },
    {
        filter: { category: 'electronics' },
        orderBy: { listPrice: -1 },
        first: 10,
    },
);

// Complex query with nested relationships
const providersWithProducts = await context.select(
    ShowCaseProvider,
    {
        _id: true,
        textField: true,
        flagshipProduct: {
            _id: true,
            product: true,
            listPrice: true,
        },
        products: {
            query: {
                edges: {
                    node: {
                        _id: true,
                        product: true,
                        description: true,
                        listPrice: true,
                        category: true,
                    },
                },
                totalCount: true,
            },
        },
        concatenatedAddress: true,
    },
    {
        filter: { booleanField: true },
        first: 20,
    },
);

// Raw GraphQL execution
const rawResult = await context.executeGraphql<{
    xtremShowCase: {
        showCaseProduct: {
            query: {
                edges: Array<{
                    node: {
                        _id: string;
                        product: string;
                        provider?: {
                            textField: string;
                        };
                    };
                }>;
            };
        };
    };
}>(`
    query {
        xtremShowCase {
            showCaseProduct {
                query(filter: {hotProduct: true}, first: 5) {
                    edges {
                        node {
                            _id
                            product
                            provider {
                                textField
                            }
                        }
                    }
                }
            }
        }
    }
`);

// Mutation examples
const createResult = await context.executeGraphql(`
    mutation {
        xtremShowCase {
            showCaseProduct {
                create(data: {
                    product: "New Product",
                    description: "Product description",
                    listPrice: 99.99,
                    category: "electronics"
                }) {
                    _id
                    product
                    listPrice
                }
            }
        }
    }
`);

const updateResult = await context.executeGraphql(`
    mutation {
        xtremShowCase {
            showCaseProduct {
                update(data: {
                    _id: "123",
                    listPrice: 89.99
                }) {
                    _id
                    product
                    listPrice
                    _etag
                }
            }
        }
    }
`);
```

### Data Types

Data types define validation, storage, and behavior characteristics for properties. Here are the main types used in the ShowCase package:

#### String Data Types

```typescript
export const descriptionDataType = new StringDataType({ maxLength: 250 });
export const emailDataType = new StringDataType({ maxLength: 100 });
```

#### Localized String Data Types

Localized data types support multiple languages and are essential for user-facing text. Always use these for names, descriptions, and labels that users will see:

```typescript
// Standard localized data types (from xtremSystem.dataTypes)
export const localizedName = new StringDataType({
    maxLength: 200,
    isLocalized: true,
});

export const localizedDescription = new StringDataType({
    maxLength: 4000,
    isLocalized: true,
});

export const localizedShortDescription = new StringDataType({
    maxLength: 80,
    isLocalized: true,
});

// Custom localized data types for specific use cases
export const localizedTitle = new StringDataType({
    maxLength: 150,
    isLocalized: true,
});

export const localizedLabel = new StringDataType({
    maxLength: 100,
    isLocalized: true,
});

export const localizedNote = new StringDataType({
    maxLength: 500,
    isLocalized: true,
});

// Example: Product-specific localized data type
export const productDescriptionDataType = new StringDataType({
    maxLength: 2000,
    isLocalized: true,
});
```

**Key characteristics of localized data types:**

- `isLocalized: true` enables multi-language support
- Content is stored separately for each language
- Framework automatically handles language switching
- Use appropriate `maxLength` based on translated content needs
- Always prefer standard types (`localizedName`, `localizedDescription`, `localizedShortDescription`) when they fit your use case

**Usage in properties:**

```typescript
// Using standard localized data type
@decorators.stringProperty<MyNode, 'name'>({
    isStored: true,
    isPublished: true,
    isRequired: true,
    dataType: () => xtremSystem.dataTypes.localizedName,
})
readonly name: Promise<string>;

// Using custom localized data type
@decorators.stringProperty<MyNode, 'productDescription'>({
    isStored: true,
    isPublished: true,
    isNullable: true,
    dataType: () => myPackage.dataTypes.productDescriptionDataType,
})
readonly productDescription: Promise<string | null>;
```

#### Decimal Data Types

```typescript
export const defaultDecimalDataType = new DecimalDataType({
    precision: 9,
    scale: 3,
});
```

#### Reference Data Types

```typescript
export const showCaseProduct = new ReferenceDataType({
    reference: () => ShowCaseProduct,
    isDefault: true,
    lookup: {
        valuePath: 'product',
        helperTextPath: 'description',
        columnPaths: ['product', 'description', 'qty'],
        tunnelPage: '@sage/xtrem-show-case/StandardShowCaseProduct',
    },
});
```

#### Enum Data Types

IMPORTANT: Each enum must be declared in its own file.

```typescript
export enum ShowCaseProductCategoryEnum {
    electronics,
    furniture,
    clothing,
    books,
    toys,
}

export type ShowCaseProductCategory = keyof typeof ShowCaseProductCategoryEnum;

export const showCaseProductCategoryDataType = new EnumDataType<ShowCaseProductCategory>({
    enum: ShowCaseProductCategoryEnum,
    filename: __filename,
});
```
