{"id":13508927,"url":"https://github.com/blitzstudios/triton","last_synced_at":"2026-02-23T05:01:43.559Z","repository":{"id":26529147,"uuid":"106939249","full_name":"blitzstudios/triton","owner":"blitzstudios","description":"a Cassandra ORM for Elixir","archived":false,"fork":false,"pushed_at":"2025-09-10T14:38:47.000Z","size":133,"stargazers_count":82,"open_issues_count":13,"forks_count":23,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-10-21T17:48:18.664Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/blitzstudios.png","metadata":{"files":{"readme":"README.md","changelog":"changelog.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2017-10-14T15:30:25.000Z","updated_at":"2025-05-12T13:36:59.000Z","dependencies_parsed_at":"2023-01-14T04:51:55.073Z","dependency_job_id":"aa7578c6-406e-4833-8551-8cff81b904e1","html_url":"https://github.com/blitzstudios/triton","commit_stats":{"total_commits":54,"total_committers":7,"mean_commits":7.714285714285714,"dds":"0.37037037037037035","last_synced_commit":"ebf86ede071a222c6970fee7baa5fd33bd3939cb"},"previous_names":[],"tags_count":16,"template":false,"template_full_name":null,"purl":"pkg:github/blitzstudios/triton","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blitzstudios%2Ftriton","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blitzstudios%2Ftriton/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blitzstudios%2Ftriton/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blitzstudios%2Ftriton/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/blitzstudios","download_url":"https://codeload.github.com/blitzstudios/triton/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blitzstudios%2Ftriton/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29738083,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-23T04:51:08.365Z","status":"ssl_error","status_checked_at":"2026-02-23T04:49:15.865Z","response_time":90,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T02:01:00.557Z","updated_at":"2026-02-23T05:01:43.522Z","avatar_url":"https://github.com/blitzstudios.png","language":"Elixir","funding_links":[],"categories":["ORM and Datamapping"],"sub_categories":[],"readme":"# Triton\n\nPure Elixir Cassandra ORM built on top of Xandra.\n\n[Blog Post](https://blog.sleeper.app/triton---a-cassandra-orm-for-elixir/)\n\n## Add Triton to your deps\n\nAdd triton to your deps.\n\n```elixir\ndef deps() do\n  [{:triton, \"~\u003e 0.2\"}]\nend\n```\n\n## Configure Triton\n\nSingle Cluster\n\n```elixir\nconfig :triton,\n  clusters: [\n    [\n      conn: Triton.Conn,\n      nodes: [\"127.0.0.1\"],\n      pool: Xandra.Cluster,\n      underlying_pool: DBConnection.Poolboy,\n      pool_size: 10,\n      keyspace: \"my_keyspace\",\n      health_check_delay: 2500,  # optional: (default is 5000)\n      health_check_interval: 500  # optional: (default is 1000)\n    ]\n  ]\n```\n\nMulti-Cluster\n\n```elixir\nconfig :triton,\n  clusters: [\n    [\n      conn: Cluster1.Conn,\n      nodes: [\"127.0.0.1\"],\n      pool: Xandra.Cluster,\n      underlying_pool: DBConnection.Poolboy,\n      pool_size: 10,\n      keyspace: \"cluster_1_keyspace\",\n      health_check_delay: 2500,  # optional: (default is 5000)\n      health_check_interval: 500  # optional: (default is 1000)\n    ],\n    [\n      conn: Cluster2.Conn,\n      nodes: [\"127.0.0.1\"],\n      pool: Xandra.Cluster,\n      underlying_pool: DBConnection.Poolboy,\n      pool_size: 10,\n      keyspace: \"cluster_2_keyspace\",\n      health_check_delay: 2500,  # optional: (default is 5000)\n      health_check_interval: 500  # optional: (default is 1000)\n    ]\n  ]\n```\n\n## Health Check\n\nIf DB gets disconnected, resulting in a DBConnection error, Triton will attempt to reconnect.\n\nYou can specify the **health_check_delay** and **health_check_interval** via the config for each cluster.\n\n## Defining a Keyspace\n\nFirst, define your keyspace.  Triton will create the keyspace for your after compile if it does not exist.\n\n```elixir\ndefmodule Schema.Keyspace do\n  use Triton.Keyspace\n\n  keyspace :my_keyspace, conn: Triton.Conn do\n    with_options [\n      replication: \"{'class' : 'SimpleStrategy', 'replication_factor': 3}\"\n    ]\n  end\nend\n```\n\n## Defining a Table\n\nYou can define as many tables as you want.  Triton will create tables for you if they do not exist.\n\nIf you would like Triton to auto-create tables for you after compile, you must require your Keyspace module.\n\n```elixir\ndefmodule Schema.User do\n  require Schema.Keyspace  \n  use Triton.Table\n\n  table :users, keyspace: Schema.Keyspace do\n    field :user_id, :bigint, validators: [presence: true]  # validators using vex\n    field :username, :text\n    field :display_name, :text\n    field :password, :text\n    field :email, :text\n    field :phone, :text\n    field :notifications, {:map, \"\u003ctext, text\u003e\"}\n    field :friends, {:set, \"\u003ctext\u003e\"}\n    field :posts, {:list, \"\u003ctext\u003e\"}\n    field :updated, :timestamp\n    field :created, :timestamp, transform: \u0026Schema.Helper.DateHelper.to_ms/1  # transform field data\n    partition_key [:user_id]\n  end\nend\n```\n\n## Defining a Materialized View\n\nAn example of a materialized view **users_by_email** with fields **user_id**, **email**, **display_name**, **password**.\n\nAlso demonstrates adding options like gc_grace_seconds and clustering_order_by.\n\n```elixir\ndefmodule Schema.UserByEmail do\n  require Schema.User  # if you want to auto-create after compile\n  use Triton.MaterializedView\n\n  materialized_view :users_by_email, from: Schema.User do\n    fields [\n      :user_id,\n      :email,\n      :display_name,\n      :password\n    ]\n    partition_key [:email]\n    cluster_columns [:user_id]\n    with_options [\n      gc_grace_seconds: 172_800,\n      clustering_order_by: [\n        email: :asc,\n        user_id: :desc\n      ]\n    ]\n  end\nend\n```\n\nAn example of materialized view **users_by_email** with all fields\n\n```elixir\ndefmodule Schema.UserByEmail do\n  require Schema.User  \n  use Triton.MaterializedView\n\n  materialized_view :users_by_email, from: Schema.User do\n    fields :all\n    partition_key [:email]\n    cluster_columns [:user_id]\n  end\nend\n```\n\n## Querying\n\nFirst, import Triton.Query\n\n```elixir\nalias Schema.User\nimport Triton.Query\n```\n\nSelect a single user where user_id = \u003cid\u003e using a prepared statement.\n\n```elixir\nUser\n|\u003e prepared(user_id: id)\n|\u003e select([:user_id, :username])\n|\u003e where(user_id: :user_id)\n|\u003e User.one\n```\n\nSelect users with IDs of 1, 2, or 3\n\n```elixir\nUser\n|\u003e select([:user_id, :username])\n|\u003e where(user_id: [in: [1, 2, 3]])\n|\u003e limit(10)\n|\u003e allow_filtering  # you can allow filtering on any query\n|\u003e User.all\n```\n\nSelect user with email **someone@gmail.com**\n\n```elixir\nUserByEmail\n|\u003e select([:display_name])\n|\u003e where(email: \"someone@gmail.com\")\n|\u003e User.one\n```\n\n## Comparison / Range Queries\n\nSelect messages created before **timestamp**\n\n```elixir\nMessagesByDate\n|\u003e select([:message_id, :text])\n|\u003e where(channel_id: 1, created: [\"\u003c=\": timestamp])\n|\u003e limit(20)\n|\u003e MessagesByDate.all\n```\n\nSelect messages created between **timestamp_a** and **timestamp_b**\n\n```elixir\nMessagesByDate\n|\u003e select([:message_id, :text])\n|\u003e where(channel_id: 1, created: [\"\u003e=\": timestamp_a], created: [\u003c: timestamp_b])\n|\u003e MessagesByDate.all\n```\n\n## Streaming\n\nStream all messages\n\n```elixir\nMessagesByDate\n|\u003e select(:all)\n|\u003e where(channel_id: 1)\n|\u003e MessagesByDate.stream(page_size: 20)\n```\n\nWhich returns {:ok, stream} or {:error, msg}\n\n## Inserting, Updating, \u0026 Deleting\n\nAgain, lets import Triton.Query for the necessary macros.\n\n```elixir\nalias Schema.User\nimport Triton.Query\n```\n\nAdd a user (if it doesn't already exist) with username **username** using a prepared statement that substitutes **user_id** into **:user_id**\n\n```elixir\nUser\n|\u003e prepared(user_id: user_id, username: username)\n|\u003e insert(user_id: :user_id, username: :username)\n|\u003e if_not_exists\n|\u003e User.save\n```\n\nUpdate a user's username, and make sure to check that their previous username was what we expected.\n\n```elixir\nUser\n|\u003e update(username: username)\n|\u003e where(user_id: user_id)\n|\u003e constrain(username: previous_username)\n|\u003e User.save\n```\n\nLets delete a user given a **user_id**\n\n```elixir\nUser\n|\u003e prepared(user_id: user_id)\n|\u003e delete(:all)  # here :all refers to all fields\n|\u003e where(user_id: :user_id)\n|\u003e User.del\n```\n\nLets delete that same user, with consistency: :quorum\n\n```elixir\nUser\n|\u003e prepared(user_id: user_id)\n|\u003e delete(:all)  # here :all refers to all fields\n|\u003e where(user_id: :user_id)\n|\u003e User.del(consistency: :quorum)\n```\n\nBatch update 4 users in 1 Cassandra request.\n\n```elixir\n[\n  User |\u003e update(username: \"username1\") |\u003e where(user_id: 1),\n  User |\u003e update(username: \"username2\") |\u003e where(user_id: 2),\n  User |\u003e update(username: \"username3\") |\u003e where(user_id: 3),\n  User |\u003e update(username: \"username4\") |\u003e where(user_id: 4)\n] |\u003e User.batch_execute\n```\n\n## Working with Collections\n\nUpdate the **notifications** map to {'mentions': '3', 'replies': '3'}.  Overwrites the entire map.\n\n```elixir\nUser\n|\u003e update(notifications: \"{'mentions': '5', 'replies': '3'}\")\n|\u003e where(user_id: 10)\n|\u003e User.save\n```\n\nUpdate notification mentions to '5'.\n\n```elixir\nUser\n|\u003e update(\"notifications['mentions']\": \"5\")\n|\u003e where(user_id: 10)\n|\u003e User.save\n```\n\nUpdate the friends set\n\n```elixir\nUser\n|\u003e update(friends: \"{'jill', 'bob', 'emma'}\")\n|\u003e where(user_id: 10)\n|\u003e User.save\n```\n\nAdd a friend_id to friends set\n\n```elixir\nUser\n|\u003e update(friends: \"friends + {'oscar'}\")\n|\u003e where(user_id: 10)\n|\u003e User.save\n```\n\nRemove friend from set\n\n```elixir\nUser\n|\u003e update(friends: \"friends - {'oscar'}\")\n|\u003e where(user_id: 10)\n|\u003e User.save\n```\n\nUpdate the posts list\n\n```elixir\nUser\n|\u003e update(posts: \"['post1', 'post2', 'post3']\")\n|\u003e where(user_id: 10)\n|\u003e User.save\n```\n\nAppend to posts list\n\n```elixir\nUser\n|\u003e update(posts: \"posts + ['post4']\")\n|\u003e where(user_id: 10)\n|\u003e User.save\n```\n\nPrepend to posts list\n\n```elixir\nUser\n|\u003e update(posts: \"['post0'] + posts\")\n|\u003e where(user_id: 10)\n|\u003e User.save\n```\n\n## Pre-populating data\n\nYou can pre-populate data with Triton at compile time with **Triton.Setup**\n\n```elixir\ndefmodule PrepopulateModule do\n  use Triton.Setup\n  import Triton.Query\n  require Schema.User\n  alias Schema.User\n\n  # create an admin user if it doesn't exist\n\n  setup do\n    User\n    |\u003e insert(\n      user_id: @admin_user_id,\n      username: @admin_user_username,\n      display_name: @admin_user_display_name,\n      password: Bcrypt.hashpwsalt(@admin_user_password),\n      email: @admin_user_email,\n      created: @admin_user_created\n    ) |\u003e if_not_exists\n  end\nend\n```\n\n## Automatic Schema Creation\n\nTriton attempts to create your keyspace, tables, and materialized views after compile if they do not exist.\n\nThis means that your build server will need access to your production DB if you want to automatically create your schema in prod.  The alternative is simply to create your production schemas yourself.\n\n## Consistency levels\n\nFor dev, you may want to consider running ccm with more than 1 node if you are doing queries at anything more than consistency: :one.\n\n## Testing\n\nFor testing, run `mix test` and `mix test --only integration` to run integration tests.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fblitzstudios%2Ftriton","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fblitzstudios%2Ftriton","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fblitzstudios%2Ftriton/lists"}