Skip to content


Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time

CI Gitter

Tinkerbell SQL

This library embeds SQL right into the Haxe language. Think LINQ (not the syntax sugar, but the framework).

Breaking Change (2021-09-16)

Transaction is now supported with a minor breaking change. Migration guide: #130 (comment)


Most developers tend to dislike SQL, at least for a significant part of their career. A symptom of that are ever-repeating attempts to hide the database layer behind ORMs, with very limited success.

Relational databases are however a very powerful concept, that was pioneered over 40 years ago.

Tink SQL has been built to embrace using SQL for your database interactions, but in a type safe way that fits with the Haxe ecosystem.

Defining your schema

Define a database like so:

import tink.sql.Types;

typedef User = {
  var id:Id<User>;
  var name:VarChar<255>;
  @:unique var email:VarChar<255>;
  var password:VarChar<255>;

typedef Post = {
  var id:Id<Post>;
  var author:Id<User>;
  var title:LongText;
  var url:VarChar<255>;

typedef Tag = {
  var id:Id<Tag>;
  var name:VarChar<20>;
  var desc:Null<Text>;

typedef PostTags = {
  var post:Id<Post>;
  var tag:Id<Tag>;

@:tables(User, Post, Tag, PostTags)
class Db extends tink.sql.Database {}

Redefining table names

class Db extends tink.sql.Database {
  @:table("blog_users") var user:User;
  @:table("blog_posts") var post:Post;
  @:table("news_tags") var tag:Tag;
  @:table("post_tags") var postTags:PostTags;

Connecting to the database

import tink.sql.drivers.MySql;

var driver = new tink.sql.drivers.MySql({
  user: 'user',
  password: 'pass'
var db = new Db('db_name', driver);

Tables API

  • Table setup
    • db.User.create(): Promise<Noise>;
    • db.User.drop(): Promise<Noise>;
    • db.User.truncate(): Promise<Noise>;
  • Selecting
    • db.User.count(): Promise<Int>;
    • db.User.all(limit, orderBy): Promise<Array<User>>;
    • db.User.first(orderBy): Promise<User>;
    • db.User.where(filter)
      • Example, select name of user:{name:}).where( == 1).first()
  • Writing
    • db.User.insertOne(row: User): Promise<Id<User>>;
    • db.User.insertMany(rows: Array<User>): Promise<Id<User>>;
    • db.User.update(f:Fields->Update<Row>, options:{ where: Filter, ?max:Int }): Promise<{rowsAffected: Int}>;
      • Example, rename all users called 'Dave' to 'Donald': db.User.update(function (u) return ['Donald')], { where: function (u) return == 'Dave' } );
    • db.User.delete(options:{ where: Filter, ?max:Int }): Promise<{rowsAffected: Int}>;
  • Advanced Selecting
    • db.User.join(db.User).on(id == copiedFrom).all();
    • db.User.leftJoin(db.User);
    • db.User.rightJoin(db.User);
    •, orderBy): tink.streams.RealStream<User>;

... to be continued ...


// transaction with a commit
db.transaction(trx -> {
  trx.User.insertOne(...).next(id -> Commit(id)); // user is inserted

// transaction with explicit rollback
db.transaction(trx -> {
  trx.User.insertOne(...).next(_ -> Rollback); // user insert is rolled back

// transaction with implicit rollback upon error
db.transaction(trx -> {
  trx.User.insertOne(...).next(_ -> new Error('Aborted')); // user insert is rolled back


SQL embedded into Haxe








No releases published


No packages published