* refactor: transaction handling by integrating pricing and bulk write operations
- Updated `recordCollectedUsage` to accept pricing functions and bulk write operations, improving transaction management.
- Refactored `AgentClient` and related controllers to utilize the new transaction handling capabilities, ensuring better performance and accuracy in token spending.
- Added tests to validate the new functionality, ensuring correct behavior for both standard and bulk transaction paths.
- Introduced a new `transactions.ts` file to encapsulate transaction-related logic and types, enhancing code organization and maintainability.
* chore: reorganize imports in agents client controller
- Moved `getMultiplier` and `getCacheMultiplier` imports to maintain consistency and clarity in the import structure.
- Removed duplicate import of `updateBalance` and `bulkInsertTransactions`, streamlining the code for better readability.
* refactor: add TransactionData type and CANCEL_RATE constant to data-schemas
Establishes a single source of truth for the transaction document shape
and the incomplete-context billing rate constant, both consumed by
packages/api and api/.
* refactor: use proper types in data-schemas transaction methods
- Replace `as unknown as { tokenCredits }` with `lean<IBalance>()`
- Use `TransactionData[]` instead of `Record<string, unknown>[]`
for bulkInsertTransactions parameter
- Add JSDoc noting insertMany bypasses document middleware
- Remove orphan section comment in methods/index.ts
* refactor: use shared types in transactions.ts, fix bulk write logic
- Import CANCEL_RATE from data-schemas instead of local duplicate
- Import TransactionData from data-schemas for PreparedEntry/BulkWriteDeps
- Use tilde alias for EndpointTokenConfig import
- Pass valueKey through to getMultiplier
- Only sum tokenValue for balance-enabled docs in bulkWriteTransactions
- Consolidate two loops into single-pass map
* refactor: remove duplicate updateBalance from Transaction.js
Import updateBalance from ~/models (sourced from data-schemas) instead
of maintaining a second copy. Also import CANCEL_RATE from data-schemas
and remove the Balance model import (no longer needed directly).
* fix: test real spendCollectedUsage instead of IIFE replica
Export spendCollectedUsage from abortMiddleware.js and rewrite the test
file to import and test the actual function. Previously the tests ran
against a hand-written replica that could silently diverge from the real
implementation.
* test: add transactions.spec.ts and restore regression comments
Add 22 direct unit tests for transactions.ts financial logic covering
prepareTokenSpend, prepareStructuredTokenSpend, bulkWriteTransactions,
CANCEL_RATE paths, NaN guards, disabled transactions, zero tokens,
cache multipliers, and balance-enabled filtering.
Restore critical regression documentation comments in
recordCollectedUsage.spec.js explaining which production bugs the
tests guard against.
* fix: widen setValues type to include lastRefill
The UpdateBalanceParams.setValues type was Partial<Pick<IBalance,
'tokenCredits'>> which excluded lastRefill — used by
createAutoRefillTransaction. Widen to also pick 'lastRefill'.
* test: use real MongoDB for bulkWriteTransactions tests
Replace mock-based bulkWriteTransactions tests with real DB tests using
MongoMemoryServer. Pure function tests (prepareTokenSpend,
prepareStructuredTokenSpend) remain mock-based since they don't touch
DB. Add end-to-end integration tests that verify the full prepare →
bulk write → DB state pipeline with real Transaction and Balance models.
* chore: update @librechat/agents dependency to version 3.1.54 in package-lock.json and related package.json files
* test: add bulk path parity tests proving identical DB outcomes
Three test suites proving the bulk path (prepareTokenSpend/
prepareStructuredTokenSpend + bulkWriteTransactions) produces
numerically identical results to the legacy path for all scenarios:
- usage.bulk-parity.spec.ts: mirrors all legacy recordCollectedUsage
tests; asserts same return values and verifies metadata fields on
the insertMany docs match what spendTokens args would carry
- transactions.bulk-parity.spec.ts: real-DB tests using actual
getMultiplier/getCacheMultiplier pricing functions; asserts exact
tokenValue, rate, rawAmount and balance deductions for standard
tokens, structured/cache tokens, CANCEL_RATE, premium pricing,
multi-entry batches, and edge cases (NaN, zero, disabled)
- Transaction.spec.js: adds describe('Bulk path parity') that mirrors
7 key legacy tests via recordCollectedUsage + bulk deps against
real MongoDB, asserting same balance deductions and doc counts
* refactor: update llmConfig structure to use modelKwargs for reasoning effort
Refactor the llmConfig in getOpenAILLMConfig to store reasoning effort within modelKwargs instead of directly on llmConfig. This change ensures consistency in the configuration structure and improves clarity in the handling of reasoning properties in the tests.
* test: update performance checks in processAssistantMessage tests
Revise the performance assertions in the processAssistantMessage tests to ensure that each message processing time remains under 100ms, addressing potential ReDoS vulnerabilities. This change enhances the reliability of the tests by focusing on maximum processing time rather than relative ratios.
* test: fill parity test gaps — model fallback, abort context, structured edge cases
- usage.bulk-parity: add undefined model fallback test
- transactions.bulk-parity: add abort context test (txns inserted,
balance unchanged when balance not passed), fix readTokens type cast
- Transaction.spec: add 3 missing mirrors — balance disabled with
transactions enabled, structured transactions disabled, structured
balance disabled
* fix: deduct balance before inserting transactions to prevent orphaned docs
Swap the order in bulkWriteTransactions: updateBalance runs before
insertMany. If updateBalance fails (after exhausting retries), no
transaction documents are written — avoiding the inconsistent state
where transactions exist in MongoDB with no corresponding balance
deduction.
* chore: import order
* test: update config.spec.ts for OpenRouter reasoning in modelKwargs
Same fix as llm.spec.ts — OpenRouter reasoning is now passed via
modelKwargs instead of llmConfig.reasoning directly.
|
||
|---|---|---|
| .. | ||
| src | ||
| .gitignore | ||
| babel.config.cjs | ||
| jest.config.mjs | ||
| LICENSE | ||
| package.json | ||
| README.md | ||
| rollup.config.js | ||
| tsconfig.json | ||
| tsconfig.spec.json | ||
LibreChat Data Schemas Package
This package provides the database schemas, models, types, and methods for LibreChat using Mongoose ODM.
📁 Package Structure
packages/data-schemas/
├── src/
│ ├── schema/ # Mongoose schema definitions
│ ├── models/ # Model factory functions
│ ├── types/ # TypeScript type definitions
│ ├── methods/ # Database operation methods
│ ├── common/ # Shared constants and enums
│ ├── config/ # Configuration files (winston, etc.)
│ └── index.ts # Main package exports
🏗️ Architecture Patterns
1. Schema Files (src/schema/)
Schema files define the Mongoose schema structure. They follow these conventions:
- Naming: Use lowercase filenames (e.g.,
user.ts,accessRole.ts) - Imports: Import types from
~/typesfor TypeScript support - Exports: Export only the schema as default
Example:
import { Schema } from 'mongoose';
import type { IUser } from '~/types';
const userSchema = new Schema<IUser>(
{
name: { type: String },
email: { type: String, required: true },
// ... other fields
},
{ timestamps: true }
);
export default userSchema;
2. Type Definitions (src/types/)
Type files define TypeScript interfaces and types. They follow these conventions:
- Base Type: Define a plain type without Mongoose Document properties
- Document Interface: Extend the base type with Document and
_id - Enums/Constants: Place related enums in the type file or
common/if shared
Example:
import type { Document, Types } from 'mongoose';
export type User = {
name?: string;
email: string;
// ... other fields
};
export type IUser = User &
Document & {
_id: Types.ObjectId;
};
3. Model Factory Functions (src/models/)
Model files create Mongoose models using factory functions. They follow these conventions:
- Function Name:
create[EntityName]Model - Singleton Pattern: Check if model exists before creating
- Type Safety: Use the corresponding interface from types
Example:
import userSchema from '~/schema/user';
import type * as t from '~/types';
export function createUserModel(mongoose: typeof import('mongoose')) {
return mongoose.models.User || mongoose.model<t.IUser>('User', userSchema);
}
4. Database Methods (src/methods/)
Method files contain database operations for each entity. They follow these conventions:
- Function Name:
create[EntityName]Methods - Return Type: Export a type for the methods object
- Operations: Include CRUD operations and entity-specific queries
Example:
import type { Model } from 'mongoose';
import type { IUser } from '~/types';
export function createUserMethods(mongoose: typeof import('mongoose')) {
async function findUserById(userId: string): Promise<IUser | null> {
const User = mongoose.models.User as Model<IUser>;
return await User.findById(userId).lean();
}
async function createUser(userData: Partial<IUser>): Promise<IUser> {
const User = mongoose.models.User as Model<IUser>;
return await User.create(userData);
}
return {
findUserById,
createUser,
// ... other methods
};
}
export type UserMethods = ReturnType<typeof createUserMethods>;
5. Main Exports (src/index.ts)
The main index file exports:
createModels()- Factory function for all modelscreateMethods()- Factory function for all methods- Type exports from
~/types - Shared utilities and constants
🚀 Adding a New Entity
To add a new entity to the data-schemas package, follow these steps:
Step 1: Create the Type Definition
Create src/types/[entityName].ts:
import type { Document, Types } from 'mongoose';
export type EntityName = {
/** Field description */
fieldName: string;
// ... other fields
};
export type IEntityName = EntityName &
Document & {
_id: Types.ObjectId;
};
Step 2: Update Types Index
Add to src/types/index.ts:
export * from './entityName';
Step 3: Create the Schema
Create src/schema/[entityName].ts:
import { Schema } from 'mongoose';
import type { IEntityName } from '~/types';
const entityNameSchema = new Schema<IEntityName>(
{
fieldName: { type: String, required: true },
// ... other fields
},
{ timestamps: true }
);
export default entityNameSchema;
Step 4: Create the Model Factory
Create src/models/[entityName].ts:
import entityNameSchema from '~/schema/entityName';
import type * as t from '~/types';
export function createEntityNameModel(mongoose: typeof import('mongoose')) {
return (
mongoose.models.EntityName ||
mongoose.model<t.IEntityName>('EntityName', entityNameSchema)
);
}
Step 5: Update Models Index
Add to src/models/index.ts:
- Import the factory function:
import { createEntityNameModel } from './entityName';
- Add to the return object in
createModels():
EntityName: createEntityNameModel(mongoose),
Step 6: Create Database Methods
Create src/methods/[entityName].ts:
import type { Model, Types } from 'mongoose';
import type { IEntityName } from '~/types';
export function createEntityNameMethods(mongoose: typeof import('mongoose')) {
async function findEntityById(id: string | Types.ObjectId): Promise<IEntityName | null> {
const EntityName = mongoose.models.EntityName as Model<IEntityName>;
return await EntityName.findById(id).lean();
}
// ... other methods
return {
findEntityById,
// ... other methods
};
}
export type EntityNameMethods = ReturnType<typeof createEntityNameMethods>;
Step 7: Update Methods Index
Add to src/methods/index.ts:
- Import the methods:
import { createEntityNameMethods, type EntityNameMethods } from './entityName';
- Add to the return object in
createMethods():
...createEntityNameMethods(mongoose),
- Add to the
AllMethodstype:
export type AllMethods = UserMethods &
// ... other methods
EntityNameMethods;
📝 Best Practices
- Consistent Naming: Use lowercase for filenames, PascalCase for types/interfaces
- Type Safety: Always use TypeScript types, avoid
any - JSDoc Comments: Document complex fields and methods
- Indexes: Define database indexes in schema files for query performance
- Validation: Use Mongoose schema validation for data integrity
- Lean Queries: Use
.lean()for read operations when you don't need Mongoose document methods
🔧 Common Patterns
Enums and Constants
Place shared enums in src/common/:
// src/common/permissions.ts
export enum PermissionBits {
VIEW = 1,
EDIT = 2,
DELETE = 4,
SHARE = 8,
}
Compound Indexes
For complex queries, add compound indexes:
schema.index({ field1: 1, field2: 1 });
schema.index(
{ uniqueField: 1 },
{
unique: true,
partialFilterExpression: { uniqueField: { $exists: true } }
}
);
Virtual Properties
Add computed properties using virtuals:
schema.virtual('fullName').get(function() {
return `${this.firstName} ${this.lastName}`;
});
🧪 Testing
When adding new entities, ensure:
- Types compile without errors
- Models can be created successfully
- Methods handle edge cases (null checks, validation)
- Indexes are properly defined for query patterns