Skip to main content

Computation Providers

In SolidX, computed fields are fields whose values are automatically derived from other data — for example, totalPrice, fullName, or age. They eliminate the need to store redundant values and ensure consistency across your models.

Computed fields are powered by computation providers, which encapsulate the business logic required to calculate and assign values to these fields.

Providers are triggered automatically when relevant data changes, ensuring that the computed field always reflects the correct value without manual intervention.

How to Configure a Computed Field

Sample Metadata Configuration

Below is an example configuration for a computed field named amountPaid in the paymentCollectionItemDetail model. The field computes the total amount paid based on related payment records.

Example: Configuration for amountPaid computed field

{
"name": "paymentCollectionItemDetail",
"displayName": "Payment Collection Item Detail",
"fields": [
{
"name": "amountPaid",
"displayName": "Amount Paid",
"type": "computed",
"ormType": "varchar",
"computedFieldTriggerConfig": [
{
"modelName": "paymentCollectionItemDetail",
"moduleName": "fees-portal",
"operations": [
"after-update",
"after-insert"
]
}
],
"computedFieldValueProvider": "PaymentCollectionItemAmountProvider",
"computedFieldValueProviderCtxt": "{}"
}
]
}
  • computedFieldValueProvider specifies the provider class responsible for the computation logic.
  • computedFieldTriggerConfig defines the entities and operations that trigger the computation.

Supported operations include:

  • before-update
  • before-insert
  • before-delete
  • after-update
  • after-insert
  • after-delete

🔸 Before operations trigger preComputeValue() of IEntityPreComputeFieldProvider.
🔸 After operations trigger postComputeAndSaveValue() of IEntityPostComputeFieldProvider.

Note: For before operations, modelName must be the same as the model with the computed field. For after operations, it can be any model, related or unrelated.

You can:

  • Access the triggering entity and metadata
  • Set the computed value (auto-saved in preComputeValue)
  • Execute logic and persist data in postComputeAndSaveValue

Sample Provider Implementation

Here is a sample implementation of the PaymentCollectionItemAmountProvider:

Example: PaymentCollectionItemAmountProvider Implementation

import { Injectable } from '@nestjs/common';
import { InjectEntityManager } from '@nestjs/typeorm';
import {
ComputedFieldMetadata,
ComputedFieldProvider,
IEntityPostComputeFieldProvider
} from '@solidxai/core';
import { EntityManager } from 'typeorm';
import { PaymentCollectionItemDetail } from '../entities/payment-collection-item-detail.entity';
import { PaymentCollectionItem } from '../entities/payment-collection-item.entity';

@ComputedFieldProvider()
@Injectable()
export class PaymentCollectionItemAmountProvider implements IEntityPostComputeFieldProvider<PaymentCollectionItemDetail, any> {
constructor(
@InjectEntityManager()
private readonly entityManager: EntityManager,
) {}

async postComputeAndSaveValue(
triggerEntity: PaymentCollectionItemDetail,
computedFieldMetadata: ComputedFieldMetadata<any>,
): Promise<void> {
if (!triggerEntity?.paymentCollectionItem?.id) {
console.error('Payment Collection Item Id Missing');
}

const { amountPaid, totalAmountToBePaid, amountPending, status } =
await this.getPaymentCollectionItemAmounts(triggerEntity?.paymentCollectionItem?.id);

await this.entityManager.update(
PaymentCollectionItem,
{ id: triggerEntity?.paymentCollectionItem?.id },
{
amountPaid: String(amountPaid),
amountPending: String(amountPending),
totalAmountToBePaid: String(totalAmountToBePaid),
status: status,
},
);
}

name(): string {
return 'PaymentCollectionItemAmountProvider';
}

help(): string {
return 'Provider used to compute payment collection item amounts based on related details.';
}

private async getPaymentCollectionItemAmounts(itemId: number): Promise<{
amountPaid: number;
totalAmountToBePaid: number;
amountPending: number;
status: string;
}> {
const details = await this.entityManager.find(PaymentCollectionItemDetail, {
where: { paymentCollectionItem: { id: itemId }, paymentStatus: 'Succeeded' },
relations: ['paymentCollectionItem'],
});

const amountPaid = details.reduce(
(sum, detail) => sum + Number(detail.amountPaid || 0),
0,
);

const paymentCollectionItem = details[0]?.paymentCollectionItem;

const totalAmountToBePaid =
Number(paymentCollectionItem?.amountToBePaid || 0) +
Number(paymentCollectionItem?.lateAmountToBePaid || 0);

const amountPending = totalAmountToBePaid - amountPaid;
const status = amountPending > 0 ? 'Partially Paid' : 'Fully Paid';

return { amountPaid, totalAmountToBePaid, amountPending, status };
}
}

Computation Provider Interfaces

To implement a custom computation provider, you need to implement one or both of the following interfaces:

Interfaces to Implement

export interface IEntityPreComputeFieldProvider<TTriggerEntity, TContext, TValue=void> extends IEntityComputedFieldProvider {
preComputeValue(triggerEntity: TTriggerEntity, computedFieldMetadata: ComputedFieldMetadata<TContext>): Promise<TValue>;
}

export interface IEntityPostComputeFieldProvider<TTriggerEntity, TContext> extends IEntityComputedFieldProvider {
postComputeAndSaveValue(triggerEntity: TTriggerEntity, computedFieldMetadata: ComputedFieldMetadata<TContext>): Promise<void>;
}

export interface IEntityComputedFieldProvider {
help(): string;
name(): string;
}

How It Works

Core Mechanism

  1. ComputedEntityFieldSubscriber listens to insert, update, and delete events across all entities.
  2. For before operations:
    • preComputeValue() is called synchronously
    • Value is set directly on the entity and auto-saved
  3. For after operations:
    • postComputeAndSaveValue() is called asynchronously via Background Jobs
    • Final saving happens via ComputedFieldEvaluationSubscriber

Note

  • Computed field configurations are loaded from the database and cached in the Solid Registry at application startup. Any changes require a server restart to take effect.

In-Built Providers

This section describes the built-in computation providers available in SolidX for common use cases. Each provider includes a short description and the corresponding context interface for configuration.

AlphaNumExternalIdComputationProvider

Generates an alphanumeric external ID based on a configurable pattern.
Useful for creating unique, human-readable identifiers (like invoice numbers or record codes).

View Context Interface

export interface AlphaNumExternalIdContext {
prefix?: string; // alias -> staticPrefix
length?: number; // i.e default=5 // generated code length
dynamicFieldPrefix?: string; // field name on the entity
}

ConcatEntityComputedFieldProvider

Concatenates multiple entity fields into a single string.
Ideal for building display names, codes, or composite labels derived from existing fields.

View Context Interface

export interface ConcatComputedFieldContext {
separator: string; // concatenated values separator
// The fields to concatenate
fields: string[]; // supports "city.name" for nested paths (1-level deep)
// Optional: if true, slugify each field value before concatenation
slugify?: boolean;
}

UuidExternalIdEntityComputedFieldProvider

Generates a UUID-based external ID with an optional prefix.
Useful for creating globally unique identifiers for records. Ideal for entities requiring unique references across distributed systems.

View Context Interface

  export interface UuidExternalIdContext {
prefix?: string; // alias -> staticPrefix
}

NoopsEntityComputedFieldProviderService

A no-operation (noop) provider that does not modify entity fields.
It allows tagging fields as computed without performing redundant calculations.

This is particularly useful when one provider performs the main computation, and other computed fields are only "placeholders".

Example Use Case

  • Suppose you have multiple amount fields: amount, taxes, and totalAmount.
  • You create a computed field totalAmount linked to a provider like CalculateTotalAmountProvider that performs all computations.
  • The fields amount and taxes can still be tagged as computed but linked to NoopsEntityComputedFieldProviderService, so they don't re-compute independently.

This ensures the logic resides only in one provider while maintaining metadata consistency.