Model Embedding
Model embedding allows you to attach models directly to a SQL data source instance, providing a Prisma-like API for accessing your models.
Overview
When you embed models in a SQL data source instance, they become available via the .models property on the data source object, allowing you to access them directly without importing the model classes separately.
Basic Usage
Connecting with Embedded Models
import { SqlDataSource } from "hysteria-orm";
import { User } from "./models/User";
import { Post } from "./models/Post";
// Create instance with embedded models
const sql = new SqlDataSource({
type: "postgres",
host: "localhost",
database: "mydb",
models: {
user: User,
post: Post,
},
});
await sql.connect();
// Access models via .models property
const users = await sql.models.user.query().many();
const posts = await sql.models.post.query().many();
Using with Default Connection
await sql.startGlobalTransaction();
await sql.models.user.insert({
name: "John",
});
const user = await sql.models.user.query().first();
expect(user).toBeDefined();
expect(user?.name).toBe("John");
await sql.rollbackGlobalTransaction();
await sql.closeConnection();
Using with Secondary Connections
// Create a secondary connection with embedded models
const anotherSql = await SqlDataSource.connectToSecondarySource({
type: "postgres",
host: "replica.db.com",
database: "mydb",
models: {
user: User,
post: Post,
},
});
await anotherSql.startGlobalTransaction();
await anotherSql.models.user.insert(
{ name: "John" },
{ connection: anotherSql }
);
const user = await anotherSql.models.user
.query({ connection: anotherSql })
.first();
await anotherSql.rollbackGlobalTransaction();
await anotherSql.closeConnection();
Using with Temporary Connections
await SqlDataSource.useConnection(
{
type: "postgres",
host: "localhost",
port: 5432,
username: "user",
password: "password",
database: "mydb",
models: {
user: User,
post: Post,
},
},
async (sql) => {
await sql.startGlobalTransaction();
await sql.models.user.insert(
{ name: "John" },
{ connection: sql }
);
const user = await sql.models.user
.query({ connection: sql })
.first();
await sql.rollbackGlobalTransaction();
await sql.closeConnection();
}
);
Using with Cloned Connections
const clonedSql = await sql.clone({ shouldRecreatePool: true });
await clonedSql.startGlobalTransaction();
await clonedSql.models.user.insert(
{ name: "John" },
{ connection: clonedSql }
);
const user = await clonedSql.models.user
.query({ connection: clonedSql })
.first();
await clonedSql.rollbackGlobalTransaction();
await clonedSql.closeConnection();
TypeScript Support
import { SqlDataSource } from "hysteria-orm";
import { User } from "./models/User";
import { Post } from "./models/Post";
let sql: SqlDataSource<
"postgres",
{ user: typeof User; post: typeof Post }
>;
beforeAll(async () => {
sql = new SqlDataSource({
type: "postgres",
host: "localhost",
database: "mydb",
models: {
user: User,
post: Post,
},
}) as typeof sql;
await sql.connect();
});
// Access models via .models with full type safety
const users = await sql.models.user.query().many();
const posts = await sql.models.post.query().many();
Error Handling
Duplicate Model Keys
The most common error occurs when you try to use a model key that conflicts with existing sql properties or methods. The following will throw a HysteriaError:
// ❌ This will throw an error - 'connect' is a reserved method
new SqlDataSource({
type: "postgres",
models: {
connect: User, // Error: Duplicate model keys while instantiating models
},
});
// ❌ This will also throw an error - 'query' is a reserved method
new SqlDataSource({
type: "postgres",
models: {
query: User, // Error: Duplicate model keys while instantiating models
},
});
// ✅ This works correctly
new SqlDataSource({
type: "postgres",
models: {
user: User,
post: Post,
},
});
Reserved Keywords
The following keywords are reserved and cannot be used as model keys:
connectquerydisconnectstartGlobalTransactioncommitGlobalTransactionrollbackGlobalTransactioncloseConnectionisConnectedgetDbTyperawQueryuseConnectionconnectToSecondarySource
Important Notes
Model Independence
Embedded models can still be used as standalone entities. Embedding them in a data source instance is optional and provides convenience but doesn't change their core functionality.
// Models work both ways
const sql = new SqlDataSource({
type: "postgres",
models: { user: User },
});
await sql.connect();
// Using embedded model via .models
const user1 = await sql.models.user.query().first();
// Using standalone model (still works)
const user2 = await User.query().first();
Best Practices
-
Use descriptive model keys: Choose meaningful names for your model keys that reflect the model's purpose.
-
Type your data sources: Always use
SqlDataSourceWithModels<D, T>for proper TypeScript support when embedding models. -
Avoid reserved keywords: Check that your model keys don't conflict with sql methods or properties.
-
Consider connection scope: Use embedded models when you need model access within a specific connection context.
-
Error handling: Always handle potential
HysteriaErrorexceptions when creating instances with embedded models.