{"id":44022358,"url":"https://github.com/runtarahq/runtara-object-store","last_synced_at":"2026-02-07T16:36:39.878Z","repository":{"id":331908565,"uuid":"1127437867","full_name":"runtarahq/runtara-object-store","owner":"runtarahq","description":"A schema-driven dynamic PostgreSQL object store for Rust.  Define schemas at runtime, create tables automatically, and query data with type-safe filtering — all without writing SQL or managing migrations.","archived":false,"fork":false,"pushed_at":"2026-01-29T20:19:49.000Z","size":90,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-30T08:46:00.620Z","etag":null,"topics":["database","database-schema","no-code","postgres","runtara","workflows"],"latest_commit_sha":null,"homepage":"https://runtara.com","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/runtarahq.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":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":"CLA.md"}},"created_at":"2026-01-03T22:10:15.000Z","updated_at":"2026-01-11T15:47:32.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/runtarahq/runtara-object-store","commit_stats":null,"previous_names":["runtarahq/runtara-object-store"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/runtarahq/runtara-object-store","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/runtarahq%2Fruntara-object-store","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/runtarahq%2Fruntara-object-store/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/runtarahq%2Fruntara-object-store/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/runtarahq%2Fruntara-object-store/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/runtarahq","download_url":"https://codeload.github.com/runtarahq/runtara-object-store/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/runtarahq%2Fruntara-object-store/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29199919,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-07T16:28:23.579Z","status":"ssl_error","status_checked_at":"2026-02-07T16:28:22.566Z","response_time":63,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6: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":["database","database-schema","no-code","postgres","runtara","workflows"],"created_at":"2026-02-07T16:36:39.090Z","updated_at":"2026-02-07T16:36:39.868Z","avatar_url":"https://github.com/runtarahq.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# runtara-object-store\n\nA schema-driven dynamic PostgreSQL object store for Rust.\n\nDefine schemas at runtime, create tables automatically, and query data with type-safe filtering — all without writing SQL or managing migrations.\n\n## Features\n\n- **Dynamic Schema Management** — Create, update, and delete schemas at runtime\n- **Type-Safe Columns** — String, Integer, Decimal, Boolean, Timestamp, JSON, and Enum types with validation\n- **Automatic Columns** — Configurable auto-managed `id`, `created_at`, `updated_at`\n- **Soft Delete** — Optional soft delete with `deleted` flag (enabled by default)\n- **Flexible Querying** — Condition-based filtering with AND/OR/NOT operators\n- **Bulk Operations** — Batch create, update, delete, and upsert with transaction guarantees\n- **SQL Injection Prevention** — All identifiers properly quoted and validated\n- **Multi-Tenant Ready** — Database-per-tenant isolation strategy\n\n## Installation\n\nAdd to your `Cargo.toml`:\n\n```toml\n[dependencies]\nruntara-object-store = \"0.1\"\n```\n\n## Quick Start\n\n```rust\nuse runtara_object_store::{\n    ObjectStore, StoreConfig, CreateSchemaRequest,\n    ColumnDefinition, ColumnType, SimpleFilter,\n};\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c(), Box\u003cdyn std::error::Error\u003e\u003e {\n    // Connect to PostgreSQL\n    let config = StoreConfig::builder(\"postgres://localhost/mydb\").build();\n    let store = ObjectStore::new(config).await?;\n\n    // Define a schema\n    let schema = store.create_schema(\n        CreateSchemaRequest::new(\n            \"Products\",\n            \"products\",\n            vec![\n                ColumnDefinition::new(\"sku\", ColumnType::String).unique().not_null(),\n                ColumnDefinition::new(\"name\", ColumnType::String).not_null(),\n                ColumnDefinition::new(\"price\", ColumnType::decimal(10, 2)),\n                ColumnDefinition::new(\"in_stock\", ColumnType::Boolean).default(\"true\"),\n            ],\n        )\n    ).await?;\n\n    // Create an instance\n    let id = store.create_instance(\n        \"Products\",\n        serde_json::json!({\n            \"sku\": \"WIDGET-001\",\n            \"name\": \"Blue Widget\",\n            \"price\": 29.99,\n            \"in_stock\": true\n        })\n    ).await?;\n\n    // Query instances\n    let (products, total) = store.query_instances(\n        SimpleFilter::new(\"Products\")\n            .filter(\"in_stock\", true)\n            .paginate(0, 10)\n    ).await?;\n\n    println!(\"Found {} products\", total);\n    Ok(())\n}\n```\n\n## Column Types\n\n| Type | Rust | PostgreSQL | Notes |\n|------|------|------------|-------|\n| `String` | `String` | `TEXT` | Unlimited length |\n| `Integer` | `i64` | `BIGINT` | 64-bit signed |\n| `Decimal` | `Decimal` | `NUMERIC(p,s)` | Configurable precision/scale |\n| `Boolean` | `bool` | `BOOLEAN` | |\n| `Timestamp` | `DateTime\u003cUtc\u003e` | `TIMESTAMPTZ` | RFC3339 format in JSON |\n| `Json` | `Value` | `JSONB` | Any valid JSON |\n| `Enum` | `String` | `TEXT + CHECK` | Validated against allowed values |\n\n### Type Coercion\n\nFor convenience (especially when importing from CSV), string values are automatically coerced:\n\n- `\"123\"` → Integer `123`\n- `\"12.34\"` → Decimal `12.34`\n- `\"true\"`, `\"1\"`, `\"yes\"` → Boolean `true`\n\n## Configuration\n\n```rust\nuse runtara_object_store::StoreConfig;\n\nlet config = StoreConfig::builder(\"postgres://localhost/mydb\")\n    .metadata_table(\"__schema\")  // Table for schema metadata (default)\n    .soft_delete(true)           // Enable soft delete (default: true)\n    .auto_id(true)               // Auto-generate UUID id (default: true)\n    .auto_created_at(true)       // Auto-manage created_at (default: true)\n    .auto_updated_at(true)       // Auto-manage updated_at (default: true)\n    .build();\n```\n\n## Filtering \u0026 Queries\n\n### Simple Filters\n\n```rust\nuse runtara_object_store::SimpleFilter;\n\n// Basic equality filter\nlet filter = SimpleFilter::new(\"Products\")\n    .filter(\"in_stock\", true)\n    .filter(\"category\", \"electronics\");\n\n// With pagination and sorting\nlet filter = SimpleFilter::new(\"Products\")\n    .filter(\"in_stock\", true)\n    .sort_by(\"created_at\")\n    .sort_desc()\n    .paginate(0, 20);\n\nlet (instances, total_count) = store.query_instances(filter).await?;\n```\n\n### Advanced Conditions\n\nFor complex queries, use `Condition` with AND/OR/NOT operators:\n\n```rust\nuse runtara_object_store::{Condition, FilterRequest};\n\n// (price \u003e 100 AND in_stock = true) OR featured = true\nlet condition = Condition::Or(vec![\n    Condition::And(vec![\n        Condition::gt(\"price\", 100),\n        Condition::eq(\"in_stock\", true),\n    ]),\n    Condition::eq(\"featured\", true),\n]);\n\nlet filter = FilterRequest {\n    condition: Some(condition),\n    sort_by: Some(\"price\".to_string()),\n    sort_order: Some(\"desc\".to_string()),\n    limit: 50,\n    offset: 0,\n};\n\nlet (instances, total) = store.filter_instances(\"Products\", filter).await?;\n```\n\n### Available Operators\n\n| Method | SQL Equivalent |\n|--------|---------------|\n| `Condition::eq(field, value)` | `field = value` |\n| `Condition::ne(field, value)` | `field != value` |\n| `Condition::gt(field, value)` | `field \u003e value` |\n| `Condition::gte(field, value)` | `field \u003e= value` |\n| `Condition::lt(field, value)` | `field \u003c value` |\n| `Condition::lte(field, value)` | `field \u003c= value` |\n| `Condition::like(field, pattern)` | `field LIKE pattern` |\n| `Condition::is_null(field)` | `field IS NULL` |\n| `Condition::is_not_null(field)` | `field IS NOT NULL` |\n| `Condition::And(vec![...])` | `(... AND ...)` |\n| `Condition::Or(vec![...])` | `(... OR ...)` |\n| `Condition::Not(box condition)` | `NOT (...)` |\n\n## Schema Operations\n\n```rust\n// Create schema\nlet schema = store.create_schema(CreateSchemaRequest::new(...)).await?;\n\n// Get schema by name\nlet schema = store.get_schema(\"Products\").await?;\n\n// List all schemas\nlet schemas = store.list_schemas().await?;\n\n// Update schema (adds/removes columns, alters table)\nlet updated = store.update_schema(\"Products\", UpdateSchemaRequest {\n    columns: Some(vec![/* new column definitions */]),\n    ..Default::default()\n}).await?;\n\n// Delete schema (soft delete by default)\nstore.delete_schema(\"Products\").await?;\n```\n\n## Instance Operations\n\n```rust\n// Create\nlet id = store.create_instance(\"Products\", json!({...})).await?;\n\n// Read\nlet instance = store.get_instance(\"Products\", \u0026id).await?;\n\n// Update\nstore.update_instance(\"Products\", \u0026id, json!({\"price\": 39.99})).await?;\n\n// Delete (soft delete by default)\nstore.delete_instance(\"Products\", \u0026id).await?;\n\n// Check existence\nlet exists = store.instance_exists(\n    SimpleFilter::new(\"Products\").filter(\"sku\", \"WIDGET-001\")\n).await?;\n```\n\n## Bulk Operations\n\nAll bulk operations run within a transaction and return the number of affected rows. If any operation fails, the entire transaction is rolled back.\n\n### Batch Create\n\nInsert multiple instances in a single transaction:\n\n```rust\nlet instances = vec![\n    json!({\"sku\": \"A001\", \"name\": \"Widget A\", \"price\": 10.00}),\n    json!({\"sku\": \"A002\", \"name\": \"Widget B\", \"price\": 20.00}),\n    json!({\"sku\": \"A003\", \"name\": \"Widget C\", \"price\": 30.00}),\n];\n\nlet count = store.create_instances(\"Products\", instances).await?;\nprintln!(\"Created {} products\", count); // Created 3 products\n```\n\n### Bulk Update\n\nUpdate all instances matching a condition:\n\n```rust\nuse runtara_object_store::Condition;\n\n// Increase price by setting new values for all in-stock items\nlet count = store.update_instances(\n    \"Products\",\n    json!({\"in_stock\": false}),           // New values to set\n    Condition::lt(\"price\", 15.00),        // Condition: price \u003c 15\n).await?;\n\nprintln!(\"Marked {} products as out of stock\", count);\n```\n\n### Bulk Delete\n\nDelete all instances matching a condition (respects soft delete setting):\n\n```rust\n// Delete all products with price = 0\nlet count = store.delete_instances(\n    \"Products\",\n    Condition::eq(\"price\", 0),\n).await?;\n\nprintln!(\"Deleted {} products\", count);\n```\n\n### Upsert (Insert or Update)\n\nInsert new instances or update existing ones based on conflict columns:\n\n```rust\nlet instances = vec![\n    json!({\"sku\": \"A001\", \"name\": \"Widget A\", \"price\": 15.00}),  // Update existing\n    json!({\"sku\": \"A004\", \"name\": \"Widget D\", \"price\": 40.00}),  // Insert new\n];\n\n// Use \"sku\" as the conflict key\nlet count = store.upsert_instances(\n    \"Products\",\n    instances,\n    vec![\"sku\".to_string()],  // Conflict columns\n).await?;\n\nprintln!(\"Upserted {} products\", count);\n```\n\nFor multi-column unique constraints:\n\n```rust\n// Upsert with composite key (region + product_code)\nlet count = store.upsert_instances(\n    \"Inventory\",\n    instances,\n    vec![\"region\".to_string(), \"product_code\".to_string()],\n).await?;\n```\n\n## Multi-Tenancy\n\nThis crate uses a **database-per-tenant** strategy. There is no `tenant_id` column — tenant isolation is achieved by connecting to different databases:\n\n```rust\n// Tenant A\nlet config_a = StoreConfig::builder(\"postgres://localhost/tenant_a\").build();\nlet store_a = ObjectStore::new(config_a).await?;\n\n// Tenant B\nlet config_b = StoreConfig::builder(\"postgres://localhost/tenant_b\").build();\nlet store_b = ObjectStore::new(config_b).await?;\n```\n\n## Sharing Connection Pools\n\nIf you already have a `sqlx::PgPool`, you can share it:\n\n```rust\nuse sqlx::PgPool;\n\nlet pool = PgPool::connect(\"postgres://localhost/mydb\").await?;\nlet config = StoreConfig::builder(\"\").build(); // URL ignored when using from_pool\n\nlet store = ObjectStore::from_pool(pool.clone(), config).await?;\n```\n\n## Error Handling\n\nAll operations return `Result\u003cT, ObjectStoreError\u003e`:\n\n```rust\nuse runtara_object_store::{ObjectStoreError, Result};\n\nmatch store.get_instance(\"Products\", \"nonexistent\").await {\n    Ok(Some(instance)) =\u003e println!(\"Found: {:?}\", instance),\n    Ok(None) =\u003e println!(\"Not found\"),\n    Err(ObjectStoreError::SchemaNotFound(name)) =\u003e println!(\"Schema {} doesn't exist\", name),\n    Err(e) =\u003e eprintln!(\"Error: {}\", e),\n}\n```\n\n## License\n\nThis project is licensed under AGPL-3.0. See [LICENSE](LICENSE) for details.\n\nFor commercial licensing options, contact hello@syncmyorders.com.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fruntarahq%2Fruntara-object-store","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fruntarahq%2Fruntara-object-store","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fruntarahq%2Fruntara-object-store/lists"}