{"id":50998976,"url":"https://github.com/hebertcisco/gdo","last_synced_at":"2026-06-20T12:34:15.774Z","repository":{"id":360884258,"uuid":"1247152448","full_name":"hebertcisco/gdo","owner":"hebertcisco","description":"A typed database access library for Gleam.","archived":false,"fork":false,"pushed_at":"2026-05-25T01:10:36.000Z","size":104,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-28T10:12:50.167Z","etag":null,"topics":["data-access","data-access-layer","data-access-library","data-access-object","database","gleam","gleam-lang","gleam-language","gleam-library","library","prepared-statements","sql","sql-query","sqlite","sqlite-database","transactions"],"latest_commit_sha":null,"homepage":"https://hexdocs.pm/gdo","language":"Gleam","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hebertcisco.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","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":"2026-05-23T00:51:40.000Z","updated_at":"2026-05-25T01:08:55.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/hebertcisco/gdo","commit_stats":null,"previous_names":["hebertcisco/gdo"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/hebertcisco/gdo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hebertcisco%2Fgdo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hebertcisco%2Fgdo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hebertcisco%2Fgdo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hebertcisco%2Fgdo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hebertcisco","download_url":"https://codeload.github.com/hebertcisco/gdo/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hebertcisco%2Fgdo/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34570538,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-20T02:00:06.407Z","response_time":98,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["data-access","data-access-layer","data-access-library","data-access-object","database","gleam","gleam-lang","gleam-language","gleam-library","library","prepared-statements","sql","sql-query","sqlite","sqlite-database","transactions"],"created_at":"2026-06-20T12:34:14.889Z","updated_at":"2026-06-20T12:34:15.769Z","avatar_url":"https://github.com/hebertcisco.png","language":"Gleam","funding_links":[],"categories":[],"sub_categories":[],"readme":"# gdo\n\n[![test](https://github.com/hebertcisco/gdo/actions/workflows/test.yml/badge.svg)](https://github.com/hebertcisco/gdo/actions/workflows/test.yml)\n\n`gdo` is a typed database access library for Gleam.\n\nIt provides a small, functional API for:\n\n- opening SQLite and MySQL connections\n- executing prepared statements\n- reading rows through typed decoders\n- running explicit transactions\n- handling failures through structured errors\n\n## Features\n\n- idiomatic Gleam API\n- `Result`-based error handling\n- SQLite and MySQL drivers\n- positional and named parameters\n- prepared statement workflow\n- explicit transaction control\n- typed row decoding helpers\n- structured connection, query, transaction, and decode errors\n\n## Installation\n\n```sh\ngleam add gdo\n```\n\n## Quick Start\n\n```gleam\nimport gdo\nimport gdo/connection\nimport gdo/decode\nimport gdo/error.{type Error}\nimport gdo/result\nimport gdo/value.{Int, Positional, String}\nimport gleam/option.{None, Some}\n\npub fn main() -\u003e Result(Nil, Error) {\n  use db \u003c- result.try(gdo.open_sqlite(\"file:app.sqlite\"))\n\n  use _ \u003c- result.try(\n    connection.exec(\n      db,\n      \"create table if not exists users (id integer primary key, name text not null)\",\n      [],\n    ),\n  )\n\n  use insert_result \u003c- result.try(\n    connection.exec(db, \"insert into users (id, name) values (?, ?)\", [\n      Positional(Int(1)),\n      Positional(String(\"Ana\")),\n    ]),\n  )\n\n  let last_id = result.last_insert_id(insert_result)\n  let _ = last_id\n\n  use maybe_row \u003c- result.try(\n    connection.query_one(db, \"select id, name from users where id = ?\", [\n      Positional(Int(1)),\n    ]),\n  )\n\n  case maybe_row {\n    Some(current_row) -\u003e {\n      let decoder =\n        decode.map2(\n          decode.column_at(0, using: decode.int()),\n          decode.column_at(1, using: decode.string()),\n          with: fn(id, name) { #(id, name) },\n        )\n\n      let assert Ok(#(id, name)) = decode.decode(current_row, using: decoder)\n      let _ = #(id, name)\n      connection.close(db)\n    }\n\n    None -\u003e connection.close(db)\n  }\n}\n```\n\n## Connection Workflow\n\nUse `gdo.open_sqlite` for embedded SQLite databases, or `gdo.open_mysql` for\nnetwork MySQL connections. When you want to build the configuration explicitly,\nuse `connection.open` with a driver-specific config helper.\n\n```gleam\nimport gdo\nimport gdo/connection\n\npub fn open_database() {\n  let assert Ok(db) =\n    gdo.sqlite_config(\"file:app.sqlite\")\n    |\u003e connection.open\n\n  assert connection.in_transaction(db) == False\n}\n```\n\nMySQL configuration follows the same shape, while using the shared server\nconnection primitives under the hood:\n\n```gleam\nimport gdo\nimport gdo/connection\n\npub fn open_mysql_database() {\n  let assert Ok(db) =\n    gdo.mysql_config(\"127.0.0.1\", 3306, \"app\", \"root\", \"secret\")\n    |\u003e connection.with_option(\"connect_timeout\", \"2000\")\n    |\u003e connection.open\n\n  assert connection.in_transaction(db) == False\n}\n```\n\nFor one-shot calls there are root helpers for both drivers:\n\n- `gdo.exec_sqlite`\n- `gdo.query_one_sqlite`\n- `gdo.query_all_sqlite`\n- `gdo.exec_mysql`\n- `gdo.query_one_mysql`\n- `gdo.query_all_mysql`\n\nThese helpers open a connection for the operation and return the typed result\ndirectly.\n\n## Statement Workflow\n\nPrepare statements when you want to reuse SQL, validate placeholder style once,\nor keep execution and reading separate.\n\n```gleam\nimport gdo\nimport gdo/connection\nimport gdo/statement\nimport gdo/value.{Named, String}\n\npub fn find_user_by_email() {\n  let assert Ok(db) = gdo.open_sqlite(\"file:app.sqlite\")\n  let assert Ok(stmt) =\n    connection.prepare(db, \"select id, email from users where email = :email\")\n\n  let assert Ok(Some(current_row)) =\n    statement.query_one(stmt, [Named(\"email\", String(\"ana@example.com\"))])\n\n  let _ = current_row\n}\n```\n\n`gdo` supports:\n\n- positional placeholders: `?`\n- named placeholders: `:name`\n\nMixing placeholder styles in the same statement is rejected during preparation.\n\n## Transaction Workflow\n\nTransactions are explicit and keep the connection immutable from the caller's\npoint of view.\n\n```gleam\nimport gdo\nimport gdo/connection\nimport gdo/value.{Int, Positional, String}\n\npub fn create_user() {\n  let assert Ok(db) = gdo.open_sqlite(\"file:app.sqlite\")\n  let assert Ok(db) = connection.begin(db)\n\n  let assert Ok(_) =\n    connection.exec(db, \"insert into users (id, name) values (?, ?)\", [\n      Positional(Int(1)),\n      Positional(String(\"Ana\")),\n    ])\n\n  let assert Ok(db) = connection.commit(db)\n  let _ = db\n}\n```\n\nUse `connection.rollback` when the unit of work should be discarded.\n\n## Row Decoding\n\nRows can be inspected directly with `row.get` and `row.get_at`, or decoded into\napplication values with `gdo/decode`.\n\n```gleam\nimport gdo/decode\n\npub fn user_decoder() {\n  decode.map2(\n    decode.column_at(0, using: decode.int()),\n    decode.column_at(1, using: decode.string()),\n    with: fn(id, name) { User(id:, name:) },\n  )\n}\n\npub type User {\n  User(id: Int, name: String)\n}\n```\n\nAvailable decoders include:\n\n- `decode.int`\n- `decode.float`\n- `decode.bool`\n- `decode.string`\n- `decode.bytes`\n- `decode.nullable`\n- `decode.map`\n- `decode.map2`\n- `decode.map3`\n\n## Error Model\n\n`gdo` keeps failures inside the public `Error` type:\n\n- `ConnectionError`\n- `QueryError`\n- `TransactionError`\n- `DecodeError`\n- `UnsupportedFeature`\n- `InvalidConfiguration`\n\nHelpers in `gdo/error` expose the common fields:\n\n- `error.message`\n- `error.code`\n- `error.sqlstate`\n\nTypical handling looks like this:\n\n```gleam\nimport gdo\nimport gdo/error\n\npub fn run_query() {\n  case gdo.open_sqlite(\"file:app.sqlite\") {\n    Ok(_) -\u003e Nil\n    Error(err) -\u003e {\n      let _message = error.message(err)\n      let _code = error.code(err)\n      let _sqlstate = error.sqlstate(err)\n      Nil\n    }\n  }\n}\n```\n\n## SQLite Notes\n\nCurrent SQLite support includes:\n\n- real connection open and close\n- prepared statement execution\n- reads through `query_one` and `query_all`\n- transaction operations\n- `last_insert_id`\n- driver error mapping into `gdo/error`\n\nCurrent SQLite limitation:\n\n- the JavaScript SQLite path is currently Deno-specific through `sqlight`\n- result rows are most reliable through positional access and\n  `decode.column_at`. The current SQLite path does not yet expose real column\n  names from backend metadata, so rows returned from queries use synthetic\n  column names internally.\n\n## MySQL Notes\n\nCurrent MySQL support includes:\n\n- network connection lifecycle\n- prepared execution through the shared statement API\n- reads through `query_one` and `query_all`\n- transaction operations\n- `last_insert_id`\n- driver error mapping with MySQL code and SQLSTATE when available\n\nCurrent MySQL limitations:\n\n- the first MySQL runtime path is Erlang-only\n- the JavaScript target returns an explicit unsupported-driver error for MySQL\n- named placeholders are rewritten to positional execution before reaching the\n  MySQL client runtime\n- multiple result sets are not exposed through the current `gdo` query API\n\n## Contributing\n\nContributions are welcome. Read [CONTRIBUTING.md](./CONTRIBUTING.md),\n[CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md), and [SECURITY.md](./SECURITY.md)\nbefore opening a pull request.\n\n## License\n\nThis project is licensed under the Apache License 2.0. See [LICENSE](./LICENSE).\n\n## Development\n\nFrom the `gdo` directory:\n\n```sh\ngleam test\ngleam format --check src test\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhebertcisco%2Fgdo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhebertcisco%2Fgdo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhebertcisco%2Fgdo/lists"}