Datasources is a data-access and object-relational mapper library. It aims for parity across Languages and Datasource Providers.
The Feature Stack: starts with commands, connections, and object mapping. Higher up, the API is more opinionated. Types and repositories are generated from a Types Specification, which can be the source of truth for the datasource schema and the generated code. The specification describes unions and inherited types. A type may contain referenced lists and dictionaries, and those references are generated as enriched repositories.
Thus, due to the well-defined Feature stack, the library can be used as a high-level repository builder or as a micro-ORM, alike.
The specification is declarative, so it suits generation by AI agents. Progressive skills keep that generation economical and repeatable. They use fewer tokens, and they keep generated code and mappings constant and deterministic. The library was written so TypeScript could be translated to Rust, and so data-access code written by an agent would remain coherent as an application scales.
npx skills add deterministic-code/datasourcesnpm install @deterministic-code/datasourcesdotnet add package Datasourcescargo add datasourcesPrompt
Run setup and add sqlite and postgres for csharp and rust
The skill runs the CLI. setup writes .env and installs the requested datasources for each language.
npx @deterministic-code/datasources-cli setup --yes --languages csharp rust --datasources sqlite postgresRun interactive setup when you want prompts instead of --yes.
npx @deterministic-code/datasources-cli setupPrompt
Generate all types, repositories, and SQL for csharp, rust, and typescript
A Types Specification is the deterministic directory. types.yaml lists each entity. Tag a type datasource_type when it is a table.
version: 1.0.0
types:
- item:
tags:
- datasource_type
fields:
- id:
type: integer
is_id: true
- name:
type: string
size: 128generate all writes the types, the repositories, and SQL for one language. It also writes SQL migrations and runs migrate-up. The default input directory is ./deterministic. The default output directory is ./generated. See Migrating.
npx @deterministic-code/datasources-cli generate all --dialect csharpnpx @deterministic-code/datasources-cli generate all --dialect rustnpx @deterministic-code/datasources-cli generate all --dialect typescriptSee Generate. Walkthroughs for a User, an enriched users-and-roles spec, and a legacy table mapping are on Repository Generation.
Prompt
Create migrations and migrate up for sqlite
generate all writes SQL migrations under sql/<dialect>/migrations and runs migrate-up. The connection is PROVIDER and DATASOURCE_URL from .env. Process env overrides the file. An in-memory DATASOURCE_URL skips migrate-up.
PROVIDER=sqlite
DATASOURCE_URL=sqlite:///./app.sqlitenpx @deterministic-code/datasources-cli generate all --dialect typescript--no-migrations skips writing the migration files. --no-auto-migrate-up skips migrate-up. --dry-run prints the migrate-up command and leaves the migration files unwritten.
npx @deterministic-code/datasources-cli generate all --dialect typescript --no-migrations
npx @deterministic-code/datasources-cli generate all --dialect typescript --no-auto-migrate-upSee Migrations.
migrations create writes the same SQL files as generate all and leaves the database unchanged. migrate-up applies every pending file. migrate-down rolls one step back and needs the token it prints, passed as --confirm.
npx @deterministic-code/datasources-cli migrations create
npx --yes --package @deterministic-code/migraters migrate-up --provider sqlite --connection ./app.sqlite
npx --yes --package @deterministic-code/migraters migrate-down --provider sqlite --connection ./app.sqlite --confirm TOKENAdd a migration that generate keeps. Put an up file and a down file under deterministic/custom/<dialect>/. The file name is <order>_<name>_<up|down>.sql. order is 1–999. name starts with a letter or digit. The next generate all copies the pair as <order>_up_custom_<order>_<name>_<up|down>.sql into sql/<dialect>/migrations and runs migrate-up. Order N runs after generated migration N (0001_initial, 000N_up, 000N_stored_procedures) and before N+1, so a seed can use 0001 when the schema is 0001_initial.
deterministic/custom/sqlite/0001_add_notes_up.sql
deterministic/custom/sqlite/0001_add_notes_down.sql
Scaffold an empty pair in the migrations directory with migraters:
npx --yes --package @deterministic-code/migraters migrate-create --provider sqlite --name add_notessetup and generate wire the generated registry so Datasource.Default / Datasource.default / default() binds Items / items / items(). Create / create / create returns that entity. ReadSingle / readSingle / read_single returns the same entity.
using Backend.Types.Datasource;
dynamic ds = Datasource.Default;
Item created = await ds.Items.Create(new Item { Name = "Ada" });
Item? loaded = await ds.Items.ReadSingle();
created.Name = "Ada Lovelace";
Response updated = await ds.Items.Update(created);
Response deleted = await ds.Items.Delete(created);use datasources::{default, Response, Row, Value};
let ds = default()?;
let items = ds.as_ref().items();
let mut created = Row::new();
created.insert("name".into(), Value::from("Ada"));
let mut created = items.create(&mut created).await?;
let loaded = items.read_single(None, None, None).await?;
created.insert("name".into(), Value::from("Ada Lovelace"));
let updated: Response = items.update(&created, None).await?;
let deleted: Response = items.delete(&created, None).await?;import { Datasource } from "@deterministic-code/datasources";
const ds = Datasource.default;
const created = await ds.items.create({ id: 0, name: "Ada" });
const loaded = await ds.items.readSingle();
created.name = "Ada Lovelace";
const updated = await ds.items.update(created);
const deleted = await ds.items.delete(created);See Datasource.
Start with createCommand and executeReader. The connection must be open.
var ds = Datasource.Default;
var connection = await ds.CreateConnection();
await connection.Open();
var command = await ds.CreateCommand(connection);
var reader = await command.ExecuteReader(
"SELECT id, name FROM item WHERE id = $id",
[new Parameter("id", 1)]);
while (await reader.Next())
Console.WriteLine(reader.Current);use datasources::{default, Parameter};
let ds = default()?;
let connection = ds.create_connection().await?;
connection.open().await?;
let command = ds.create_command(Some(connection)).await?;
let reader = command
.execute_reader("SELECT id, name FROM item WHERE id = $id", Some(&[Parameter::new("id", 1)]))
.await?;
while reader.next().await? {
println!("{:?}", reader.current());
}import { Datasource } from "@deterministic-code/datasources";
const ds = Datasource.default;
const connection = await ds.createConnection();
await connection.open();
const command = await ds.createCommand(connection);
const reader = await command.executeReader("SELECT id, name FROM item WHERE id = $id", [
{ name: "id", value: 1 },
]);
while (await reader.next()) console.log(reader.current);See Command for middleware and execute.
A CRUDRepository fills related fields after read once enrichments are registered. When the related repository is also a CRUD repository, nested List children are written on create / update / delete. A readonly repository only fills.
var contacts = new CRUDRepository<Contact>(connection);
contacts.AddEnriched(new("contact_type_name", "contact_type_id", "id", "name"), types);
contacts.AddEnriched(new("addresses", "id", "contact_id", Type: EnrichmentPropertyType.List), addresses);
var loaded = await contacts.ReadSingle(new Where(new Operator.Equals("id", 10)));
await contacts.Create(new Contact { first_name = "Ada", addresses = [new Address { line1 = "1 Main" }] });let mut contacts = CRUDRepository::from_type::<Contact>(connection)?;
contacts
.add_enriched(Enrichment::new("contact_type_name", "contact_type_id", "id", Some("name".into()), EnrichmentPropertyType::Object), types, None)
.add_enriched(Enrichment::new("addresses", "id", "contact_id", None, EnrichmentPropertyType::List), addresses, None);
let loaded = contacts.read_single(Some(&Where::new(Operator::equals("id", 10))), None).await?;
contacts.create(contact).await?;const contacts = new CRUDRepository(Contact, connection);
contacts.addEnriched({ destinationProperty: "contact_type_name", sourceProperty: "contact_type_id", relatedKey: "id", relatedProperty: "name" }, types);
contacts.addEnriched({ destinationProperty: "addresses", sourceProperty: "id", relatedKey: "contact_id", type: "list" }, addresses);
const loaded = await contacts.readSingle(new Where(new Operator.Equals("id", 10)));
await contacts.create({ first_name: "Ada", addresses: [{ line1: "1 Main" }] });See Repositories for readonly, CRUD, and bulk.
TransformMapper reshapes an already-converted object. Repository read is convert, then map.
var mapper = new TransformMapper<Nested, Flat>(
n => new Flat { UserId = n.User.Id, UserName = n.User.Name },
f => new Nested { User = new User { Id = f.UserId, Name = f.UserName } });
var flat = mapper.Map(nested);
var back = mapper.ReverseMap(flat);let mapper = TransformMapper::new(
|n: Nested| Flat { user_id: n.user.id, user_name: n.user.name },
|f: Flat| Nested { user: User { id: f.user_id, name: f.user_name } },
);
let flat = mapper.map(nested);
let back = mapper.reverse_map(flat);const mapper = new TransformMapper<Nested, Flat>(
(n) => ({ userId: n.user.id, userName: n.user.name }),
(f) => ({ user: { id: f.userId, name: f.userName } }),
);
const flat = mapper.map(nested);
const back = mapper.reverseMap(flat);See Mappers for TransformMapper, FlatToNestedMapper, and DictionaryMapper.
TypeConverter is only Convert / convert / convert and ReverseConvert / reverseConvert / reverse_convert. Attach field converters on MetaDataRowTypeConverter when the native provider cannot map the type.
sealed class FlagConverter : TypeConverter<Value, Value>
{
public override Value Convert(Value from) => from.ToObject() is long n && n == 1;
public override Value ReverseConvert(Value to) => to;
}
var rows = new MetaDataRowTypeConverter<Item>(fields: new Dictionary<string, TypeConverter<Value, Value>>
{
["flag"] = new FlagConverter()
});
var item = rows.Convert(row);struct FlagConverter;
impl TypeConverter<Value, Value> for FlagConverter {
fn convert(&self, from: &Value) -> Result<Value> {
Ok(match from {
Value::Int(n) => Value::Bool(*n == 1),
other => other.clone(),
})
}
fn reverse_convert(&self, to: &Value) -> Result<Value> {
Ok(to.clone())
}
}
let rows = MetaDataRowTypeConverter::new(vec!["flag".into()]).with_fields(HashMap::from([(
"flag".into(),
Arc::new(FlagConverter) as Arc<dyn TypeConverter<Value, Value>>,
)]));
let item = rows.convert(&row)?;class FlagConverter extends TypeConverter {
convert = (value: Cell): Cell => value === 1;
reverseConvert = (value: Cell): Cell => value;
}
const rows = new MetaDataRowTypeConverter({ flag: new FlagConverter() });
const item = rows.convert(row);See Type Converters.
Contacts CLI copies a generated contacts app, then setup, generate, build, and run.
Prompt
Run setup and add sqlite and postgres for csharp and rust
Expected
npx @deterministic-code/datasources-cli setup --yes --languages csharp rust --datasources sqlite postgresPrompt
Generate a user with field id: username, is_enabled in types.yaml and generate repositories for csharp
Expected
version: 1.0.0
types:
- user:
tags:
- datasource_type
fields:
- id:
type: integer
is_id: true
- username:
type: string
size: 128
- is_enabled:
type: booleannpx @deterministic-code/datasources-cli generate all --dialect csharpusing Backend.Types.Datasource;
dynamic ds = Datasource.Default;
User created = await ds.Users.Create(new User { Username = "ada", IsEnabled = true });
User? loaded = await ds.Users.ReadSingle();
created.Username = "ada-v2";
Response updated = await ds.Users.Update(created);
Response deleted = await ds.Users.Delete(created);Prompt
The same `user` table, plus `role` and a `user_role` junction tagged `many_to_many`, with a `roles` list on `user`. Generate TypeScript and create a user that has a role.
Expected
version: 1.0.0
types:
- user:
tags:
- datasource_type
fields:
- id:
type: integer
is_id: true
- username:
type: string
size: 128
- is_enabled:
type: boolean
- roles:
type: role[]
references: user_role.user_id
- role:
tags:
- datasource_type
fields:
- id:
type: integer
is_id: true
- name:
type: string
size: 64
- user_role:
tags:
- datasource_type
- many_to_many
fields:
- user_id:
references: user.id
- role_id:
references: role.idnpx @deterministic-code/datasources-cli generate all --dialect typescriptusing Backend.Types.Datasource;
dynamic ds = Datasource.Default;
Role admin = await ds.Roles.Create(new Role { Name = "admin" });
User created = await ds.Users.Create(new User
{
Username = "ada",
IsEnabled = true,
Roles = [admin],
});
User? loaded = await ds.Users.ReadSingle();
Response deleted = await ds.Users.Delete(created);Prompt
Insert an item and read it back with a command and SQL text. Do not use a repository.
Expected
sealed class Item
{
public long id { get; set; }
public string name { get; set; } = "";
}
var ds = Datasource.Default;
await using var connection = await ds.CreateConnection();
await connection.Open();
var command = connection.CreateCommand();
await command.Execute(
Native.N("INSERT INTO items (id, name) VALUES ($id, $name)"),
[new Parameter("id", 1), new Parameter("name", "a")]);
var converter = new MetaDataRowTypeConverter<Item>();
await using var createdReader = await command.ExecuteReader(
Native.N("SELECT id, name FROM items WHERE id = $id"),
[new Parameter("id", 1)]);
var created = await createdReader.Read().Select(row => converter.Convert(row)).ToListAsync();Prompt
Read users with a command, convert each row, then build a `contacts` list from `contact_name1` and `contact_name2`.
Expected
record User
{
public int id { get; set; }
public string username { get; set; } = "";
public string email { get; set; } = "";
public string contact_name1 { get; set; } = "";
public string contact_name2 { get; set; } = "";
public List<Contact> contacts { get; set; } = [];
public record Contact
{
public string name { get; set; } = "";
}
}
var ds = Datasource.Default;
var command = await ds.CreateCommand();
await using var reader = await command.ExecuteReader(
"SELECT contact_name1, contact_name2, email, id, username FROM users");
var converter = new MetaDataRowTypeConverter<User>();
TransformMapper<User, User> UserContactsMapper() =>
new(
row =>
{
row.contacts =
[
new User.Contact { name = row.contact_name1 },
new User.Contact { name = row.contact_name2 }
];
return row;
},
user =>
{
user.contact_name1 = user.contacts.ElementAtOrDefault(0)?.name ?? "";
user.contact_name2 = user.contacts.ElementAtOrDefault(1)?.name ?? "";
return user;
});
var mapper = UserContactsMapper();
var users = await reader.Read().Select(row => mapper.Map(converter.Convert(row))).ToListAsync();Prompt
Write a `SelectStatement` for `Contact.FirstName` and `ContactType.Name`, inner-joining `Contact` to `ContactType` on `Contact.ContactTypeId` = `ContactType.Id`.
Expected
new SelectStatement(new()
{
Table = "Contact",
Fields = ["Contact.FirstName AS FirstName", "ContactType.Name AS ContactTypeName"],
Joins = [Join.Inner("ContactType", "Contact.ContactTypeId", "ContactType.Id")],
});Prompt
The type stays `user`, but the stored table is `OldUsersTbl`. `username` is unique and stored as `LoginNm`, `id` as `UsrID`, and `is_enabled` as `ActvFlg`. Add `datasource.yaml` mappings and generate.
Expected
version: 1.0.0
types:
- user:
tags:
- datasource_type
fields:
- id:
type: integer
is_id: true
- username:
type: string
size: 128
- is_enabled:
type: booleanversion: 1.0.0
includes:
- types:
filter: tag == "datasource_type"
types:
- user:
mapping: OldUsersTbl
fields:
- id:
mapping: UsrID
- username:
is_unique: true
mapping: LoginNm
- is_enabled:
mapping: ActvFlgnpx @deterministic-code/datasources-cli generate all --dialect typescriptusing Backend.Types.Datasource;
dynamic ds = Datasource.Default;
User created = await ds.Users.Create(new User { Username = "ada", IsEnabled = true });
User? loaded = await ds.Users.ReadSingle();
created.Username = "ada-v2";
Response updated = await ds.Users.Update(created);
Response deleted = await ds.Users.Delete(created);Parity across C#, Rust, and TypeScript. Standard modules first; opinionated repositories above them.
| Feature | C# | Rust | TypeScript |
|---|---|---|---|
| Repository Generation | ✅ | ✅ | ✅ |
| Datasource Factory | ✅ | ✅ | ✅ |
| Datasource | ✅ | ✅ | ✅ |
| Mappers | ✅ | ✅ | ✅ |
| Type Converters | ✅ | ✅ | ✅ |
| Connection | ✅ | ✅ | ✅ |
| Transaction | ✅ | ✅ | ✅ |
| Command | ✅ | ✅ | ✅ |
| Reader | ✅ | ✅ | ✅ |
| Statements | ✅ | ✅ | ✅ |
| Repositories | ✅ | ✅ | ✅ |
| Readonly Repository | ✅ | ✅ | ✅ |
| CRUD Repository | ✅ | ✅ | ✅ |
| Bulk Repository | ✅ | ✅ | ✅ |
| Repository Factories | ✅ | ✅ | ✅ |
generate all reads a Types Specification and writes the types, the repositories, and SQL for one language. setup and generate wire the generated registry so Datasource.Default / Datasource.default / default() binds each plural name.
See Generate for flags and output paths.
A Types Specification is the deterministic directory. types.yaml lists each entity. Tag a type datasource_type when it is a table.
version: 1.0.0
types:
- user:
tags:
- datasource_type
fields:
- id:
type: integer
is_id: true
- username:
type: string
size: 128
- is_enabled:
type: booleangenerate all writes the User type, the Users / users / users() repository, and SQL.
npx @deterministic-code/datasources-cli generate all --dialect csharpnpx @deterministic-code/datasources-cli generate all --dialect rustnpx @deterministic-code/datasources-cli generate all --dialect typescriptgenerate all writes the SQL migrations and runs migrate-up against DATASOURCE_URL. --no-migrations skips the files. --no-auto-migrate-up skips applying them. See Migrations to write, apply, or roll back a migration on its own.
Create / create / create returns that entity. ReadSingle / readSingle / read_single returns the same entity. Update and Delete return Response.
using Backend.Types.Datasource;
dynamic ds = Datasource.Default;
User created = await ds.Users.Create(new User { Username = "ada", IsEnabled = true });
User? loaded = await ds.Users.ReadSingle();
created.Username = "ada-v2";
Response updated = await ds.Users.Update(created);
Response deleted = await ds.Users.Delete(created);use datasources::{default, Response, Row, Value};
let ds = default()?;
let users = ds.as_ref().users();
let mut created = Row::new();
created.insert("username".into(), Value::from("ada"));
created.insert("is_enabled".into(), Value::from(true));
let mut created = users.create(&mut created).await?;
let loaded = users.read_single(None, None, None).await?;
created.insert("username".into(), Value::from("ada-v2"));
let updated: Response = users.update(&created, None).await?;
let deleted: Response = users.delete(&created, None).await?;import { Datasource } from "@deterministic-code/datasources";
const ds = Datasource.default;
const created = await ds.users.create({ id: 0, username: "ada", isEnabled: true });
const loaded = await ds.users.readSingle();
created.username = "ada-v2";
const updated = await ds.users.update(created);
const deleted = await ds.users.delete(created);The same user table, plus role and a user_role junction tagged many_to_many. A roles list on user that references user_role.user_id generates a linked enrichment: AddLinked / addLinked / add_linked.
version: 1.0.0
types:
- user:
tags:
- datasource_type
fields:
- id:
type: integer
is_id: true
- username:
type: string
size: 128
- is_enabled:
type: boolean
- roles:
type: role[]
references: user_role.user_id
- role:
tags:
- datasource_type
fields:
- id:
type: integer
is_id: true
- name:
type: string
size: 64
- user_role:
tags:
- datasource_type
- many_to_many
fields:
- user_id:
references: user.id
- role_id:
references: role.idnpx @deterministic-code/datasources-cli generate all --dialect csharpnpx @deterministic-code/datasources-cli generate all --dialect rustnpx @deterministic-code/datasources-cli generate all --dialect typescriptgenerate all writes the SQL migrations and runs migrate-up against DATASOURCE_URL. --no-migrations skips the files. --no-auto-migrate-up skips applying them. See Migrations to write, apply, or roll back a migration on its own.
Create a role, then a user that lists that role. Create / create / create writes the junction row. ReadSingle / readSingle / read_single fills Roles / roles / roles. Delete removes the user and the junction; the role row stays.
using Backend.Types.Datasource;
dynamic ds = Datasource.Default;
Role admin = await ds.Roles.Create(new Role { Name = "admin" });
User created = await ds.Users.Create(new User
{
Username = "ada",
IsEnabled = true,
Roles = [admin],
});
User? loaded = await ds.Users.ReadSingle();
Response deleted = await ds.Users.Delete(created);use datasources::{default, encode_rows, Response, Row, Value};
let ds = default()?;
let roles = ds.as_ref().roles();
let mut admin = Row::new();
admin.insert("name".into(), Value::from("admin"));
let admin = roles.create(&mut admin).await?;
let users = ds.as_ref().users();
let mut created = Row::new();
created.insert("username".into(), Value::from("ada"));
created.insert("is_enabled".into(), Value::from(true));
created.insert("roles".into(), encode_rows(&[admin]));
let created = users.create(&mut created).await?;
let loaded = users.read_single(None, None, None).await?;
let deleted: Response = users.delete(&created, None).await?;import { Datasource } from "@deterministic-code/datasources";
const ds = Datasource.default;
const admin = await ds.roles.create({ id: 0, name: "admin" });
const created = await ds.users.create({
id: 0,
username: "ada",
isEnabled: true,
roles: [admin],
});
const loaded = await ds.users.readSingle();
const deleted = await ds.users.delete(created);When datasource.yaml is omitted, generate maps a plural snake table and snake columns. An explicit datasource.yaml still wins. mapping on a type is the stored table. mapping on a field is the stored column. is_unique writes a unique index.
The type stays user. The stored table is OldUsersTbl. Username is unique and stored as LoginNm.
version: 1.0.0
types:
- user:
tags:
- datasource_type
fields:
- id:
type: integer
is_id: true
- username:
type: string
size: 128
- is_enabled:
type: booleanversion: 1.0.0
includes:
- types:
filter: tag == "datasource_type"
types:
- user:
mapping: OldUsersTbl
fields:
- id:
mapping: UsrID
- username:
is_unique: true
mapping: LoginNm
- is_enabled:
mapping: ActvFlgnpx @deterministic-code/datasources-cli generate all --dialect csharpnpx @deterministic-code/datasources-cli generate all --dialect rustnpx @deterministic-code/datasources-cli generate all --dialect typescriptgenerate all writes the SQL migrations and runs migrate-up against DATASOURCE_URL. --no-migrations skips the files. --no-auto-migrate-up skips applying them. See Migrations to write, apply, or roll back a migration on its own.
The generated repository maps those stored names. The CRUD call still uses Username / username / username.
using Backend.Types.Datasource;
dynamic ds = Datasource.Default;
User created = await ds.Users.Create(new User { Username = "ada", IsEnabled = true });
User? loaded = await ds.Users.ReadSingle();
created.Username = "ada-v2";
Response updated = await ds.Users.Update(created);
Response deleted = await ds.Users.Delete(created);use datasources::{default, Response, Row, Value};
let ds = default()?;
let users = ds.as_ref().users();
let mut created = Row::new();
created.insert("username".into(), Value::from("ada"));
created.insert("is_enabled".into(), Value::from(true));
let mut created = users.create(&mut created).await?;
let loaded = users.read_single(None, None, None).await?;
created.insert("username".into(), Value::from("ada-v2"));
let updated: Response = users.update(&created, None).await?;
let deleted: Response = users.delete(&created, None).await?;import { Datasource } from "@deterministic-code/datasources";
const ds = Datasource.default;
const created = await ds.users.create({ id: 0, username: "ada", isEnabled: true });
const loaded = await ds.users.readSingle();
created.username = "ada-v2";
const updated = await ds.users.update(created);
const deleted = await ds.users.delete(created);Pick a language, then a driver. Each driver section lists Information / information / information, Overrides, and RowTypeConverters / rowTypeConverters / row_type_converters from that language's IDriver / IDriver / Driver.
← Languages and Datasource Providers
Drivers for C#. Click a tile for Information, Overrides, and RowTypeConverters from that IDriver.
- Embedded SQL database for local development and tests.
- Full connection, command, transaction, and repository support.
- Overrides SelectInsertedIdStatement to SELECT last_insert_rowid() AS {column}
- Overrides CreateTableStatement type mapping (int→INTEGER, bool→BOOLEAN, uuid/string/datetime→TEXT, decimal→NUMERIC, float→REAL, binary→BLOB)
booldatetimeuuiddecimalbinary
None.
- MySQL / MariaDB server driver.
- Full connection, command, transaction, and repository support.
- Overrides SelectInsertedIdStatement to SELECT LAST_INSERT_ID() AS {column}
- Overrides CreateTableStatement identity to AUTO_INCREMENT
- Overrides CreateTableStatement type mapping (int→INT, uuid→CHAR(36), string/datetime→TEXT, bool→TINYINT, decimal→DECIMAL(18,4), float→DOUBLE, binary→BLOB)
booldatetimeuuiddecimalbinary
None.
- PostgreSQL server driver.
- Full connection, command, transaction, and repository support.
- Overrides SelectInsertedIdStatement to SELECT lastval() AS {column}
- Overrides CreateTableStatement identity to GENERATED BY DEFAULT AS IDENTITY
- Overrides CreateTableStatement boolean defaults (0/1 → TRUE/FALSE)
- Overrides CreateTableStatement type mapping (int→INTEGER, uuid→UUID, string→TEXT, bool→BOOLEAN, decimal→NUMERIC, float→DOUBLE PRECISION, datetime→TIMESTAMPTZ, binary→BYTEA)
booldatetimeuuiddecimalbinary
None.
← Languages and Datasource Providers
Drivers for Rust. Click a tile for information, Overrides, and row_type_converters from that Driver.
- Embedded SQL database for local development and tests.
- Full connection, command, transaction, and repository support.
- Overrides SelectInsertedIdStatement to SELECT last_insert_rowid() AS {column}
- Overrides CreateTableStatement type mapping (int→INTEGER, bool→BOOLEAN, uuid/string/datetime→TEXT, decimal→NUMERIC, float→REAL, binary→BLOB)
booldatetimeuuiddecimalbinary
None.
- MySQL / MariaDB server driver.
- Full connection, command, transaction, and repository support.
- Overrides SelectInsertedIdStatement to SELECT LAST_INSERT_ID() AS {column}
- Overrides CreateTableStatement identity to AUTO_INCREMENT
- Overrides CreateTableStatement type mapping (int→INT, uuid→CHAR(36), string/datetime→TEXT, bool→TINYINT, decimal→DECIMAL(18,4), float→DOUBLE, binary→BLOB)
booldatetimeuuiddecimalbinary
None.
- PostgreSQL server driver.
- Full connection, command, transaction, and repository support.
- Overrides SelectInsertedIdStatement to SELECT lastval() AS {column}
- Overrides CreateTableStatement identity to GENERATED BY DEFAULT AS IDENTITY
- Overrides CreateTableStatement boolean defaults (0/1 → TRUE/FALSE)
- Overrides CreateTableStatement type mapping (int→INTEGER, uuid→UUID, string→TEXT, bool→BOOLEAN, decimal→NUMERIC, float→DOUBLE PRECISION, datetime→TIMESTAMPTZ, binary→BYTEA)
booldatetimeuuiddecimalbinary
None.
← Languages and Datasource Providers
Drivers for TypeScript. Click a tile for information, Overrides, and rowTypeConverters from that IDriver.
- Embedded SQL database for local development and tests.
- Full connection, command, transaction, and repository support.
- Overrides SelectInsertedIdStatement to SELECT last_insert_rowid() AS {column}
- Overrides CreateTableStatement type mapping (int→INTEGER, bool→BOOLEAN, uuid/string/datetime→TEXT, decimal→NUMERIC, float→REAL, binary→BLOB)
booldatetimeuuiddecimalbinary
None.
- MySQL / MariaDB server driver.
- Full connection, command, transaction, and repository support.
- Overrides SelectInsertedIdStatement to SELECT LAST_INSERT_ID() AS {column}
- Overrides CreateTableStatement identity to AUTO_INCREMENT
- Overrides CreateTableStatement type mapping (int→INT, uuid→CHAR(36), string/datetime→TEXT, bool→TINYINT, decimal→DECIMAL(18,4), float→DOUBLE, binary→BLOB)
booldatetimeuuiddecimalbinary
None.
- PostgreSQL server driver.
- Full connection, command, transaction, and repository support.
- Overrides SelectInsertedIdStatement to SELECT lastval() AS {column}
- Overrides CreateTableStatement identity to GENERATED BY DEFAULT AS IDENTITY
- Overrides CreateTableStatement boolean defaults (0/1 → TRUE/FALSE)
- Overrides CreateTableStatement type mapping (int→INTEGER, uuid→UUID, string→TEXT, bool→BOOLEAN, decimal→NUMERIC, float→DOUBLE PRECISION, datetime→TIMESTAMPTZ, binary→BYTEA)
booldatetimeuuiddecimalbinary
None.
- Datasource
- Connection
- Transaction
- Command
- Reader
- Repositories
- Mappers
- Type Converters
- Statements
- Other
- Advanced
- Enrichment
- IEnriched
- Enrichment
- IReadonlyEnrichmentStrategy
- DefaultReadonlyEnrichmentStrategy
- HashsetOptimizedReadonlyEnrichmentStrategy
- NestedToObjectEnrichmentStrategy
- IWriteEnrichmentStrategy
- DefaultWriteEnrichmentStrategy
- LazyWriteEnrichmentStrategy
- CreateEnrichmentStrategy
- DefaultCreateEnrichmentStrategy
- UpdateEnrichmentStrategy
- DefaultUpdateEnrichmentStrategy
- CascadeDeleteUpdateEnrichmentStrategy
- DeleteEnrichmentStrategy
- CascadeDeleteEnrichmentStrategy
- Bulk
- Enrichment