{"id":25875222,"url":"https://github.com/masudur-rahman/styx","last_synced_at":"2026-05-06T00:06:08.118Z","repository":{"id":189593152,"uuid":"649078040","full_name":"masudur-rahman/styx","owner":"masudur-rahman","description":"Database Engine for different SQL and NoSQL databases","archived":false,"fork":false,"pushed_at":"2026-03-07T19:00:49.000Z","size":99,"stargazers_count":1,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-03-08T00:37:36.707Z","etag":null,"topics":["arangodb","go","go-orm","golang","golang-orm","orm","postgres","postgresql","sqlite"],"latest_commit_sha":null,"homepage":"","language":"Go","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/masudur-rahman.png","metadata":{"files":{"readme":"README.md","changelog":null,"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":"2023-06-03T17:48:52.000Z","updated_at":"2026-03-07T18:58:49.000Z","dependencies_parsed_at":"2023-08-20T22:39:51.519Z","dependency_job_id":"95fcaa86-4a06-4537-90c0-b9805c845530","html_url":"https://github.com/masudur-rahman/styx","commit_stats":null,"previous_names":["masudur-rahman/database","masudur-rahman/styx"],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/masudur-rahman/styx","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/masudur-rahman%2Fstyx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/masudur-rahman%2Fstyx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/masudur-rahman%2Fstyx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/masudur-rahman%2Fstyx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/masudur-rahman","download_url":"https://codeload.github.com/masudur-rahman/styx/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/masudur-rahman%2Fstyx/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32672687,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-05T11:29:49.557Z","status":"ssl_error","status_checked_at":"2026-05-05T11:29:48.587Z","response_time":54,"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":["arangodb","go","go-orm","golang","golang-orm","orm","postgres","postgresql","sqlite"],"created_at":"2025-03-02T09:32:13.794Z","updated_at":"2026-05-06T00:06:08.112Z","avatar_url":"https://github.com/masudur-rahman.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Styx\n\nDatabase Engine for different SQL and NoSQL databases.\n\n## Install\n\n```shell\ngo get -u github.com/masudur-rahman/styx\n```\n\n## Supported Databases\n\n| Database   | Package              | Status     |\n|------------|----------------------|------------|\n| SQLite     | `sql/sqlite`         | Stable     |\n| PostgreSQL | `sql/postgres`       | Stable     |\n| Supabase   | `sql/supabase`       | Partial    |\n| ArangoDB   | `nosql/arango`       | Stable     |\n| MongoDB    | `nosql/mongo`        | Stable     |\n\n## Quickstart\n\nCheck out the [Quickstart Example](examples/quickstart.go) for a complete guide.\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/masudur-rahman/styx/sql\"\n\t\"github.com/masudur-rahman/styx/sql/sqlite\"\n\t\"github.com/masudur-rahman/styx/sql/sqlite/lib\"\n)\n\ntype User struct {\n\tID        int64     `db:\"id,pk autoincr\"`\n\tName      string    `db:\"name,uq\"`\n\tFullName  string    `db:\"full_name,uqs\"`\n\tEmail     string    `db:\",uqs\"`\n\tCreatedAt time.Time `db:\"created_at\"`\n}\n\nfunc main() {\n\tctx := context.Background()\n\tconn, _ := lib.GetSQLiteConnection(\"test.db\")\n\n\tdb := sqlite.NewSQLite(conn)\n\n\t// Migrate database\n\tdb.Sync(ctx, User{})\n\n\t// Fluent CRUD\n\tdb.Table(\"user\").InsertOne(ctx, \u0026User{Name: \"masud\", FullName: \"Masudur Rahman\", Email: \"masud@example.com\"})\n\n\tvar user User\n\tdb.Table(\"user\").ID(1).FindOne(ctx, \u0026user)\n\tdb.Table(\"user\").Where(\"email=?\", \"masud@example.com\").FindOne(ctx, \u0026user)\n\n\tdb.Table(\"user\").ID(user.ID).UpdateOne(ctx, User{Email: \"test@example.com\"})\n\n\tdb.Table(\"user\").ID(1).DeleteOne(ctx)\n}\n```\n\n## Struct Tags\n\nStyx uses the `db` struct tag to map Go struct fields to database columns and define schema constraints.\n\n### Tag Format\n\n```\ndb:\"column_name,options\"\n```\n\n- **column_name** (before the comma): Sets the database column name. If empty, the field name is converted to `snake_case` automatically.\n- **options** (after the comma): Space-separated list of constraint/behavior flags.\n\n### Available Options\n\n| Tag        | Purpose                          | DDL Effect                                       | Query Effect |\n|------------|----------------------------------|--------------------------------------------------|--------------|\n| `pk`       | Primary key                      | Adds `PRIMARY KEY` constraint                    | -            |\n| `autoincr` | Auto-increment                   | `INTEGER PRIMARY KEY AUTOINCREMENT` (SQLite) / `SERIAL`/`BIGSERIAL` (Postgres) | -            |\n| `uq`       | Unique constraint (single column)| Adds `UNIQUE` constraint                         | -            |\n| `uqs`      | Unique composite group           | Adds composite `UNIQUE(col1, col2, ...)` across all `uqs` fields | -            |\n| `req`      | Required (never skip zero-value) | None                                             | Always includes the field in WHERE, INSERT, and UPDATE queries, even when zero-valued |\n\n### Examples\n\n```go\ntype Budget struct {\n\tID         int64  `db:\"id,pk autoincr\"`     // primary key, auto-increment\n\tUserID     int64  `db:\"user_id,uqs\"`        // part of composite unique constraint\n\tCategoryID string `db:\"category_id,uqs req\"` // composite unique + required (never skipped)\n\tAlertAt    int64  `db:\"alert_at,req\"`        // required: always included even when 0\n\tAmount     int64  `db:\"amount\"`              // regular field, skipped when zero\n\tLabel      string `db:\"label,uq\"`           // single-column unique constraint\n}\n```\n\n### How Zero-Value Handling Works\n\nBy default, Styx skips zero-valued fields (`\"\"`, `0`, `false`, `time.Time{}`) in:\n- **WHERE clauses** (struct filters passed to `FindOne`, `FindMany`, `DeleteOne`)\n- **INSERT** queries\n- **UPDATE** queries\n\nThis is useful most of the time (you don't want `WHERE id=0 AND created_at='0001-01-01'`), but it causes bugs when a zero value is intentional (e.g., `CategoryID=\"\"` means \"overall budget\").\n\nThere are three ways to override this:\n\n#### 1. `req` tag (declarative, per-field)\n\nMark the field once in the struct definition. It applies to all operations automatically.\n\n```go\ntype Budget struct {\n\tCategoryID string `db:\"category_id,req\"` // \"\" is always included\n}\n\ndb.FindOne(\u0026b, Budget{UserID: 99, CategoryID: \"\"})\n// WHERE user_id=99 AND category_id=''\n\ndb.InsertOne(\u0026Budget{UserID: 99, CategoryID: \"\", Amount: 500})\n// INSERT INTO \"budget\" (user_id, category_id, amount) VALUES (99, '', 500)\n```\n\n#### 2. `MustFilterCols` (per-query, WHERE only)\n\nOpt in per query for specific columns in WHERE clauses.\n\n```go\ndb.MustFilterCols(\"category_id\").FindOne(\u0026b, Budget{UserID: 99, CategoryID: \"\"})\n// WHERE user_id=99 AND category_id=''\n\ndb.MustFilterCols(\"category_id\").DeleteOne(Budget{UserID: 99, CategoryID: \"\"})\n// DELETE FROM \"budget\" WHERE user_id=99 AND category_id=''\n```\n\n#### 3. `MustCols` (per-query, INSERT/UPDATE only)\n\nOpt in per query for specific columns in INSERT and UPDATE.\n\n```go\ndb.MustCols(\"alert_at\", \"category_id\").InsertOne(\u0026budget)\n// Includes alert_at and category_id even when zero\n```\n\n#### 4. `AllCols` (per-query, all fields)\n\nInclude every field regardless of zero value. Use with caution.\n\n```go\ndb.AllCols().InsertOne(\u0026budget)\n// Includes all fields, including id=0, created_at=zero, etc.\n```\n\n## Engine API\n\nAll database engines implement the `sql.Engine` interface. Methods are chainable.\n\n### Query Building\n\n| Method                              | Description                                |\n|-------------------------------------|--------------------------------------------|\n| `Table(name string)`                | Set target table name                      |\n| `ID(id any)`                        | Filter by primary key                      |\n| `Where(cond string, args ...any)`   | Add raw WHERE condition with `?` placeholders |\n| `In(col string, values ...any)`     | Add `col IN (...)` filter                  |\n| `Columns(cols ...string)`           | Select specific columns (default: `*`)     |\n| `OrderBy(col, dir)`                 | Sort results (`ASC` or `DESC`)             |\n| `Paginate(page, perPage)`           | Automatic `LIMIT` and `OFFSET`             |\n| `Join(table, on)`                   | Add `JOIN` (also `LeftJoin`, `InnerJoin`)  |\n| `GroupBy(cols...)`                  | Add `GROUP BY` clause                      |\n| `Having(cond, args...)`             | Add `HAVING` clause for groups             |\n| `Distinct()`                        | Enable `SELECT DISTINCT`                   |\n\n### Features\n\n#### Aggregates\nPerform calculations directly through the query builder:\n```go\ndb.Table(\"user\").Count(\"id\", \"total_users\").FindMany(\u0026results)\ndb.Table(\"user\").Avg(\"age\", \"average_age\").FindMany(\u0026results)\n// Supported: Count, Sum, Avg, Min, Max\n```\n\n#### Soft Delete\nDeclaratively enable soft deletes using struct tags:\n```go\ntype User struct {\n    ID        int64      `db:\"id,pk\"`\n    DeletedAt *time.Time `db:\"deleted_at,soft_delete\"`\n}\n\ndb.DeleteOne(User{ID: 1}) // Sets deleted_at = CURRENT_TIMESTAMP\ndb.FindMany(\u0026users)       // Automatically filters out rows where deleted_at IS NOT NULL\ndb.WithDeleted().FindMany(\u0026users) // Includes deleted rows\n```\n\n#### Struct Validation\nIntegrate validation rules into your models:\n```go\ntype User struct {\n    Email string `db:\"email\" validate:\"required,email\"`\n}\n\ndb.EnableValidation(true).InsertOne(\u0026user) // Returns error if validation fails\n```\n\n### Zero-Value Control\n\n| Method                              | Description                                |\n|-------------------------------------|--------------------------------------------|\n| `AllCols()`                         | Include all fields (INSERT/UPDATE/WHERE)   |\n| `MustCols(cols ...string)`          | Force specific columns in INSERT/UPDATE    |\n| `MustFilterCols(cols ...string)`    | Force specific columns in WHERE clauses    |\n\n### CRUD Operations\n\n| Method                                          | Description                          |\n|-------------------------------------------------|--------------------------------------|\n| `FindOne(doc any, filter ...any) (bool, error)` | Find one record. Returns false if not found. |\n| `FindMany(docs any, filter ...any) error`       | Find multiple records into a slice   |\n| `InsertOne(doc any) (id any, err error)`        | Insert one record. Returns inserted ID. |\n| `InsertMany(docs []any) ([]any, error)`         | Insert multiple records              |\n| `UpdateOne(doc any) error`                      | Update one record (requires WHERE)   |\n| `DeleteOne(filter ...any) error`                | Delete one record (requires WHERE)   |\n\n### Transactions\n\n```go\ntx, err := db.BeginTx()\ntx.Table(\"user\").InsertOne(\u0026user)\ntx.Commit()   // or tx.Rollback()\n```\n\n### Schema Migration\n\n```go\ndb.Sync(User{}, Budget{}, Wallet{})\n```\n\nCreates tables if they don't exist, adds missing columns to existing tables.\n\n### Raw Queries\n\n```go\nrows, err := db.Query(\"SELECT * FROM user WHERE name = ?\", \"masud\")\nresult, err := db.Exec(\"DELETE FROM user WHERE id = ?\", 1)\n```\n## Unit of Work\n\nStyx provides a Unit of Work pattern to coordinate transactions across multiple database engines (SQL + NoSQL). See [Unit of Work Documentation](docs/unit_of_work.md) for more details.\n\n```go\nuow := styx.NewUnitOfWork(sqlEngine, nosqlEngine)\n\nerr := uow.Execute(func(sqlTx sql.Engine, nosqlTx nosql.Engine) error {\n\tsqlTx.Table(\"user\").InsertOne(\u0026user)\n\tnosqlTx.Collection(\"logs\").InsertOne(logEntry)\n\treturn nil\n})\n```\n\n## Project Structure\n\n```\nsql/            SQL Engine interface + implementations\n  sqlite/       SQLite (via modernc.org/sqlite, pure Go)\n  postgres/     PostgreSQL (direct + gRPC remote access)\n  supabase/     Supabase REST-based\n  mock/         Mock SQL engine (GoMock)\nnosql/          NoSQL Engine interface + implementations\n  arango/       ArangoDB\n  mongo/        MongoDB\n  mock/         Mock NoSQL engine\ndberr/          Shared error types (DataNotFound, RequirementMissing)\nuow.go          Unit of Work coordinator\n```\n\n---\n\n### Why the name Styx?\n\nIn Greek mythology, the River Styx separates the world of the living from the world of the dead.\nSimilarly, this ORM acts as a bridge between your application code and the database,\nfacilitating the flow of data between the two realms while ensuring data integrity and controlled access.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmasudur-rahman%2Fstyx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmasudur-rahman%2Fstyx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmasudur-rahman%2Fstyx/lists"}