@fluentity/core
    Preparing search index...

    @fluentity/core

    Coverage npm CI TypeScript tree-shakable Tests

    Fluentity

    Fluentity is a lightweight and flexible library for TypeScript/JavaScript applications to consume RESTFul API using models. It's inspired by Active Record and Laravel Eloquent. It provides a simple and intuitive way to interact with your API endpoints while maintaining type safety and following object-oriented principles. Fluentity has also a small caching mechinism.

    • 💯 Written in TypeScript
    • 🧪 99% test coverage with Vitest
    • ⚡️ Designed for fast, type-safe API interactions
    npm install @fluentity/core
    

    Run tests

    npm test
    

    In JavaScript, property decorators are not natively supported yet (as of 2025), but they can be enabled using transpilers like Babel or TypeScript with experimental support.

    Here's how to enable and use them in TypeScript, which has the best support for decorators:

    In your tsconfig.json

    {
    "compilerOptions": {
    "target": "ESNext",
    "experimentalDecorators": true,
    "useDefineForClassFields": false
    }
    }
    {
    "plugins": [
    ["@babel/plugin-proposal-decorators", { "version": "legacy" }],
    ["@babel/plugin-proposal-class-properties", { "loose": true }]
    ]
    }

    After you need to initialize Fluentity with an adapter:

    import { Fluentity, RestAdapter, RestAdapterOptions } from '@fluentity/core';

    /**
    * Configuration options for initializing Fluentity
    */
    interface FluentityConfig {
    /** The adapter to use for making API requests */
    adapter: RestAdapter;
    }

    /**
    * Initialize Fluentity with the given configuration
    * @param config - The configuration options
    * @returns The Fluentity instance
    */
    const fluentity = Fluentity.initialize({
    adapter: new RestAdapter({
    baseUrl: 'https://api.example.com'
    })
    });

    Currently, Fluentity supports only one adapter: RestAdapter. This allows you to make Restful API calls using the models. In the future we are planning to add more adapters like GraphQL.

    import { RestAdapter } from '@fluentity/core';

    const adapter = new RestAdapter({
    baseUrl: 'https://api.example.com',
    });

    Models are the core of Fluentity. Here's how to create a model:

    import { Model, Attributes } from '@fluentity/core';

    /**
    * Interface defining the attributes for a User model
    * @interface UserAttributes
    * @extends {Attributes}
    */
    interface UserAttributes extends Attributes {
    /** The user's full name */
    name: string;
    /** The user's email address */
    email: string;
    /** Optional phone number */
    phone?: number;
    /** Optional associated company */
    company?: Company;
    }

    /**
    * User model class for interacting with the users API endpoint
    * @class User
    * @extends {Model<UserAttributes>}
    */
    export class User extends Model<UserAttributes> {
    /** The API endpoint resource name for this model */
    static resource = 'users';
    }

    Have a look at this package to generate complete models: Fluentity CLI

    Models come with several static methods for querying and manipulating data:

    /**
    * Get all records from the API
    * @returns Promise resolving to an array of model instances
    */
    Model.all(): Promise<Model[]>

    /**
    * Find a record by ID
    * @param id - The ID of the record to find
    * @returns Promise resolving to a model instance
    */
    Model.find(id: string | number): Promise<Model>

    /**
    * Start a query for a specific ID
    * @param id - The ID to query
    * @returns A query builder instance
    */
    Model.id(id: string | number): QueryBuilder

    /**
    * Create a new record
    * @param data - The data to create the record with
    * @returns Promise resolving to the created model instance
    */
    Model.create(data: Partial<Attributes>): Promise<Model>

    /**
    * Update a record
    * @param id - The ID of the record to update
    * @param data - The data to update the record with
    * @returns Promise resolving to the updated model instance
    */
    Model.update(id: string | number, data: Partial<Attributes>): Promise<Model>

    /**
    * Delete a record
    * @param id - The ID of the record to delete
    * @returns Promise that resolves when the deletion is complete
    */
    Model.delete(id: string | number): Promise<void>
    /**
    * Save the instance (create or update)
    * @returns Promise resolving to the saved model instance
    */
    model.save(): Promise<Model>

    /**
    * Update the instance with new data
    * @param data - The data to update the instance with
    * @returns Promise resolving to the updated model instance
    */
    model.update(data: Partial<Attributes>): Promise<Model>

    /**
    * Delete the instance
    * @returns Promise that resolves when the deletion is complete
    */
    model.delete(): Promise<void>

    Example usage:

    // Working with relations
    const post = await Post.find(1);
    const comments = await post.comments.all();
    const comment = await post.comments.create({
    name: 'John',
    email: 'john@example.com'
    });

    // Using pagination
    const comments = await post.comments.limit(10).offset(10).all();

    Fluentity provides a simple caching mechanism that can be configured through the adapter:

    const fluentity = Fluentity.initialize({
    adapter: new RestAdapter({
    baseUrl: 'https://api.example.com',
    cacheOptions: {
    enabled: true,
    ttl: 1000 // Time to live in milliseconds
    }
    })
    });

    // Clear cache for a specific endpoint
    fluentity.adapter.deleteCache("users/1");

    // Get cache for a specific endpoint
    const cache = fluentity.adapter.getCache("users/1");

    Fluentity provides several decorators to define relationships and type casting:

    import { HasOne, HasMany, BelongsTo, BelongsToMany, Relation } from '@fluentity/core';

    /**
    * User model with relationship decorators
    * @class User
    * @extends {Model<UserAttributes>}
    */
    class User extends Model<UserAttributes> {
    /** One-to-one relationship with Profile model */
    @HasOne(() => Profile)
    profile!: Relation<Profile>;

    /** One-to-many relationship with Post model */
    @HasMany(() => Post)
    posts!: Relation<Post[]>;

    /** One-to-many relationship with Media model using custom resource name */
    @HasMany(() => Media, 'libraries')
    medias!: Relation<Media[]>;

    /** Many-to-many relationship with Role model */
    @BelongsToMany(() => Role)
    roles!: Relation<Role[]>;
    }
    import { Cast } from '@fluentity/core';

    /**
    * User model with type casting decorators
    * @class User
    * @extends {Model<UserAttributes>}
    */
    class User extends Model<UserAttributes> {
    /** Cast created_at to Date type */
    @Cast(() => Date)
    created_at?: Date;

    /** Cast thumbnail to Thumbnail type */
    @Cast(() => Thumbnail)
    thumbnail?: Thumbnail;

    /** Cast thumbnails array to array of Thumbnail type */
    @Cast(() => Thumbnail)
    thumbnails?: Thumbnail[];
    }

    Scopes allow you to define reusable query constraints:

    class User extends Model<UserAttributes> {
    static scopes = {
    active: (query) => query.where({ status: 'active' })
    };
    }

    Models come with several static methods for querying and manipulating data:

    • Model.all(): Get all records
    • Model.find(id): Find a record by ID
    • Model.create(data): Create a new record
    • Model.update(id, data): Update a record
    • Model.delete(id): Delete a record
    • model.get(): Fetch the instance using the id, if defined
    • model.id(id): Return a new instance with id.
    • model.update(data): Update the instance with data
    • model.delete(): Delete the instance
    • model.save(): Save the instance
    • query(): Start a new query builder
    • where(conditions): Add where conditions
    • filter(filters): Add filter conditions

    Example usage:

    // Query with conditions
    const activeUsers = await User.where({ status: 'active' }).all();

    // Deep chaining
    const thumbails = User.id(1).medias.id(2).thumnails.all();
    // Will make a call to /users/1/medias/2/thumbails

    Model instances have the following methods:

    • save(): Create or update the record
    • update(data): Update the record
    • delete(): Delete the record

    Example usage:

    const user = new User({
    name: 'John Doe',
    email: 'john@example.com'
    });

    // Save new user
    await user.save();

    // Update user
    user.name = 'Jane Doe';
    await user.update({ email: "test@example.com" });

    // Delete user
    await user.delete();

    You can use the relations declared in the model to create API calls.

    const user = User.find(1)

    // Will create an API call: GET /users/1/medias
    user.medias.all()

    // Will create an API call: GET /users/1/medias/2
    user.medias.find(2)

    // Will create an API call: GET /users/1/medias/2/thumbnails
    user.medias.id(2).thumbnails.all()
    const user = User.find(1) // Will make an API call to /users/1

    const user = User.id(1) // return an instance of a new User with id equals 1. Then this instance can be used to query relations.

    user.medias.all() // Will create an API call: GET /users/1/medias

    The toObject() method converts a model instance and its related models into a plain JavaScript object:

    const user = await User.find(1);
    const userObject = user.toObject();
    // Returns a plain object with all properties and nested related models

    Fluentity includes comprehensive error handling for common scenarios:

    • Missing required parameters (ID, data, where conditions)
    • Undefined resource names
    • API request failures
    • Invalid model operations

    Example error handling:

    try {
    const user = await User.find(1);
    } catch (error) {
    if (error instanceof Error) {
    console.error(`Failed to find user: ${error.message}`);
    }
    }

    try {
    await User.update(1, { name: 'John' });
    } catch (error) {
    if (error instanceof Error) {
    console.error(`Failed to update user: ${error.message}`);
    }
    }

    MIT