{"id":31041795,"url":"https://github.com/rvegajr/odata-active-record","last_synced_at":"2026-04-07T22:31:36.934Z","repository":{"id":312628663,"uuid":"1048119069","full_name":"rvegajr/odata-active-record","owner":"rvegajr","description":"The easiest way to interact with OData APIs ever - Active Record pattern with seamless data type handling","archived":false,"fork":false,"pushed_at":"2025-09-01T00:29:55.000Z","size":200,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-10-10T13:50:30.584Z","etag":null,"topics":["active-record","api","astro","database","interface-generation","javascript","mongodb","odata","orm","schema-first","sqlite","typescript"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/odata-active-record-core","language":"TypeScript","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/rvegajr.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","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":"2025-09-01T00:27:53.000Z","updated_at":"2025-09-01T00:29:58.000Z","dependencies_parsed_at":"2025-09-01T02:48:03.196Z","dependency_job_id":null,"html_url":"https://github.com/rvegajr/odata-active-record","commit_stats":null,"previous_names":["rvegajr/odata-active-record"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/rvegajr/odata-active-record","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rvegajr%2Fodata-active-record","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rvegajr%2Fodata-active-record/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rvegajr%2Fodata-active-record/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rvegajr%2Fodata-active-record/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rvegajr","download_url":"https://codeload.github.com/rvegajr/odata-active-record/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rvegajr%2Fodata-active-record/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31532197,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-07T16:28:08.000Z","status":"ssl_error","status_checked_at":"2026-04-07T16:28:06.951Z","response_time":105,"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":["active-record","api","astro","database","interface-generation","javascript","mongodb","odata","orm","schema-first","sqlite","typescript"],"created_at":"2025-09-14T10:47:39.720Z","updated_at":"2026-04-07T22:31:36.917Z","avatar_url":"https://github.com/rvegajr.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# OData Active Record - The Easiest Way to Interact with OData APIs\n\n\u003e **The easiest way to interact with OData APIs ever** - ORM-like simplicity with OData v4 power\n\n[![Tests](https://github.com/your-org/odata-active-record/actions/workflows/test.yml/badge.svg)](https://github.com/your-org/odata-active-record/actions/workflows/test.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n## 🚀 Quick Start\n\n```bash\nnpm install odata-active-record-core\n```\n\n```typescript\nimport { ActiveRecord, EntityNamespaceManager } from 'odata-active-record-core';\n\n// Create a namespace for your entities\nconst namespace = EntityNamespaceManager.createNamespace('my-app');\n\n// Define your entity\nconst User = namespace.registerEntity('User', {\n  name: { type: 'string', nullable: false },\n  email: { type: 'string', nullable: false },\n  age: { type: 'number', nullable: true },\n  isActive: { type: 'boolean', nullable: false, defaultValue: true },\n  createdAt: { type: 'date', nullable: false }\n});\n\n// Use it like an ORM!\nconst users = await User\n  .where('age', 'gt', 25)\n  .where('isActive', 'eq', true)\n  .select(['name', 'email'])\n  .orderBy('name', 'asc')\n  .limit(10)\n  .find();\n\nconsole.log(users.data); // Array of users\n```\n\n## ✨ Features\n\n- **🔄 ORM-like API** - Familiar Active Record pattern\n- **📅 Automatic Date Handling** - Any date format, automatically parsed\n- **🛡️ Fault Tolerant** - Graceful error handling with actionable feedback\n- **🏗️ Multi-Provider Support** - MongoDB, SQLite, HTTP OData\n- **🔒 Namespace Isolation** - Complete separation between different data sources\n- **⚡ Astro Integration** - Seamless SSR/SSG and API routes\n- **📊 Schema Validation** - Automatic drift detection and warnings\n- **🎯 TypeScript First** - Full type safety and IntelliSense\n\n## 📦 Installation\n\n```bash\n# Core package\nnpm install odata-active-record-core\n\n# With Astro integration\nnpm install odata-active-record-astro\n\n# With specific providers\nnpm install mongodb better-sqlite3\n```\n\n## 🎯 Basic Usage\n\n### 1. Simple Entity Definition\n\n```typescript\nimport { ActiveRecord, EntityNamespaceManager } from 'odata-active-record-core';\n\n// Create a namespace\nconst namespace = EntityNamespaceManager.createNamespace('blog');\n\n// Define entities\nconst Post = namespace.registerEntity('Post', {\n  title: { type: 'string', nullable: false },\n  content: { type: 'string', nullable: false },\n  publishedAt: { type: 'date', nullable: true },\n  isPublished: { type: 'boolean', nullable: false, defaultValue: false },\n  viewCount: { type: 'number', nullable: false, defaultValue: 0 }\n});\n\nconst Author = namespace.registerEntity('Author', {\n  name: { type: 'string', nullable: false },\n  email: { type: 'string', nullable: false },\n  bio: { type: 'string', nullable: true }\n});\n```\n\n### 2. CRUD Operations\n\n```typescript\n// Create\nconst newPost = await Post.create({\n  title: 'My First Post',\n  content: 'Hello, world!',\n  publishedAt: '2024-01-15', // Any date format works!\n  isPublished: true\n});\n\nconsole.log(newPost.data); // { id: 1, title: 'My First Post', ... }\n\n// Read\nconst posts = await Post\n  .where('isPublished', 'eq', true)\n  .where('publishedAt', 'gt', '2024-01-01')\n  .orderBy('publishedAt', 'desc')\n  .limit(5)\n  .find();\n\n// Update\nconst updatedPost = await Post\n  .where('id', 'eq', 1)\n  .update({ viewCount: 42 });\n\n// Delete\nawait Post.where('id', 'eq', 1).delete();\n```\n\n### 3. Advanced Queries\n\n```typescript\n// Complex filtering\nconst popularPosts = await Post\n  .where('viewCount', 'gt', 1000)\n  .where('isPublished', 'eq', true)\n  .where('title', 'contains', 'tutorial')\n  .select(['title', 'viewCount', 'publishedAt'])\n  .orderBy('viewCount', 'desc')\n  .limit(10)\n  .find();\n\n// Cross-entity queries (within namespace)\nconst postsWithAuthors = await Post\n  .expand('author')\n  .where('author.name', 'contains', 'John')\n  .find();\n\n// Aggregations\nconst stats = await Post\n  .aggregate([\n    { $group: { _id: '$isPublished', count: { $sum: 1 } } }\n  ])\n  .execute();\n```\n\n## 🗄️ Multi-Provider Support\n\n### MongoDB\n\n```typescript\nimport { MongoDBProvider } from 'odata-active-record-core';\n\nconst mongoProvider = new MongoDBProvider(\n  'mongodb://localhost:27017',\n  'my-database'\n);\n\nawait mongoProvider.connect();\n\n// Use with namespace\nconst namespace = EntityNamespaceManager.createNamespace('mongo-app');\nnamespace.setProvider(mongoProvider);\n\nconst User = namespace.registerEntity('users', {\n  username: { type: 'string', nullable: false },\n  email: { type: 'string', nullable: false },\n  profile: { type: 'json', nullable: true }\n});\n```\n\n### SQLite\n\n```typescript\nimport { SQLiteProvider } from 'odata-active-record-core';\n\nconst sqliteProvider = new SQLiteProvider('./data.db');\n\nawait sqliteProvider.connect();\n\n// Auto-create tables\nawait sqliteProvider.createTable('users', {\n  fields: {\n    username: { type: 'string', nullable: false },\n    email: { type: 'string', nullable: false },\n    created_at: { type: 'date', nullable: false }\n  }\n});\n```\n\n### HTTP OData Service\n\n```typescript\nimport { HTTPODataProvider } from 'odata-active-record-core';\n\nconst odataProvider = new HTTPODataProvider('https://services.odata.org/V4/Northwind/Northwind.svc');\n\n// Set authentication if needed\nodataProvider.setAuthHeaders({\n  'Authorization': 'Bearer your-token'\n});\n\nawait odataProvider.connect();\n\n// Use existing OData service\nconst Products = namespace.registerEntity('Products');\nconst products = await Products\n  .where('UnitPrice', 'gt', 50)\n  .select(['ProductName', 'UnitPrice'])\n  .find();\n```\n\n## 🚀 Astro Integration\n\n### API Routes\n\n```typescript\n// src/pages/api/users.ts\nimport { AstroODataIntegration } from 'odata-active-record-astro';\n\nexport const GET = AstroODataIntegration.createApiHandler({\n  entity: 'User',\n  namespace: 'my-app',\n  operations: {\n    list: true,\n    get: true,\n    create: true,\n    update: true,\n    delete: true\n  }\n});\n```\n\n### SSR/SSG Data\n\n```astro\n---\n// src/pages/blog.astro\nimport { AstroODataIntegration } from 'odata-active-record-astro';\n\nconst posts = await AstroODataIntegration.getData({\n  entity: 'Post',\n  namespace: 'blog',\n  query: {\n    where: { isPublished: true },\n    orderBy: { publishedAt: 'desc' },\n    limit: 10\n  }\n});\n---\n\n\u003chtml\u003e\n  \u003chead\u003e\u003ctitle\u003eBlog\u003c/title\u003e\u003c/head\u003e\n  \u003cbody\u003e\n    {posts.data.map(post =\u003e (\n      \u003carticle\u003e\n        \u003ch2\u003e{post.title}\u003c/h2\u003e\n        \u003cp\u003e{post.content}\u003c/p\u003e\n      \u003c/article\u003e\n    ))}\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n### Edge Runtime\n\n```typescript\n// src/pages/api/edge/users.ts\nexport const GET = AstroODataIntegration.createEdgeHandler({\n  entity: 'User',\n  namespace: 'my-app',\n  cache: {\n    ttl: 300, // 5 minutes\n    strategy: 'stale-while-revalidate'\n  }\n});\n```\n\n## 📅 Automatic Date Handling\n\nAny date format is automatically parsed:\n\n```typescript\n// All of these work automatically:\nawait Post.create({\n  title: 'Post with dates',\n  publishedAt: '2024-01-15',           // YYYY-MM-DD\n  updatedAt: '01/15/2024',             // MM/DD/YYYY\n  created: '15-01-2024',               // DD-MM-YYYY\n  scheduled: '2024-01-15T10:30:00Z',   // ISO string\n  relative: 'yesterday',               // Relative dates\n  natural: '2 days ago',               // Natural language\n  timestamp: 1705312800000             // Unix timestamp\n});\n```\n\n## 🛡️ Error Handling\n\nUser-friendly error messages with actionable feedback:\n\n```typescript\nconst result = await User.create({\n  email: 'invalid-email', // Invalid email\n  age: 'not-a-number'     // Invalid age\n});\n\nif (!result.success) {\n  console.log(result.errors);\n  // [\n  //   {\n  //     code: 'VALIDATION_ERROR',\n  //     message: 'Invalid email format',\n  //     field: 'email',\n  //     suggestions: ['Use a valid email format like user@example.com']\n  //   },\n  //   {\n  //     code: 'TYPE_MISMATCH',\n  //     message: 'Expected number, got string',\n  //     field: 'age',\n  //     suggestions: ['Provide a numeric value for age']\n  //   }\n  // ]\n}\n```\n\n## 🔒 Namespace Isolation\n\nComplete separation between different data sources:\n\n```typescript\n// E-commerce namespace\nconst ecommerce = EntityNamespaceManager.createNamespace('ecommerce');\nconst Product = ecommerce.registerEntity('Product', { /* ... */ });\nconst Order = ecommerce.registerEntity('Order', { /* ... */ });\n\n// Analytics namespace (completely separate)\nconst analytics = EntityNamespaceManager.createNamespace('analytics');\nconst PageView = analytics.registerEntity('PageView', { /* ... */ });\nconst UserEvent = analytics.registerEntity('UserEvent', { /* ... */ });\n\n// Cross-entity queries within namespace\nconst ordersWithProducts = await Order\n  .expand('product')\n  .where('product.category', 'eq', 'electronics')\n  .find();\n\n// No cross-namespace queries (maintains isolation)\n// This won't work: Order.expand('pageView') - different namespaces!\n```\n\n## 📊 Schema Validation\n\nAutomatic drift detection and warnings:\n\n```typescript\n// Schema drift detection\nconst result = await User.create({\n  name: 'John',\n  email: 'john@example.com',\n  newField: 'value' // Field not in schema\n});\n\nif (result.warnings) {\n  console.log(result.warnings);\n  // [\n  //   {\n  //     code: 'SCHEMA_DRIFT',\n  //     message: 'Unknown field \"newField\" detected',\n  //     field: 'newField',\n  //     suggestions: ['Add this field to the schema or remove it from the data']\n  //   }\n  // ]\n}\n```\n\n## 🧪 Testing\n\n```typescript\nimport { describe, it, expect } from 'vitest';\nimport { ActiveRecord, EntityNamespaceManager } from 'odata-active-record-core';\n\ndescribe('User Entity', () =\u003e {\n  it('should create a user', async () =\u003e {\n    const namespace = EntityNamespaceManager.createNamespace('test');\n    const User = namespace.registerEntity('User', {\n      name: { type: 'string', nullable: false },\n      email: { type: 'string', nullable: false }\n    });\n\n    const result = await User.create({\n      name: 'John Doe',\n      email: 'john@example.com'\n    });\n\n    expect(result.success).toBe(true);\n    expect(result.data.name).toBe('John Doe');\n  });\n});\n```\n\n## 📚 API Reference\n\n### ActiveRecord Methods\n\n- `where(field, operator, value)` - Add filter condition\n- `select(fields)` - Select specific fields\n- `orderBy(field, direction)` - Sort results\n- `limit(count)` - Limit number of results\n- `offset(count)` - Skip results\n- `expand(relation)` - Include related entities\n- `find()` - Execute query and return results\n- `findOne()` - Execute query and return single result\n- `count()` - Get count of matching records\n- `create(data)` - Create new record\n- `update(data)` - Update existing record\n- `delete()` - Delete matching records\n\n### Supported Operators\n\n- `eq` - Equal\n- `ne` - Not equal\n- `gt` - Greater than\n- `ge` - Greater than or equal\n- `lt` - Less than\n- `le` - Less than or equal\n- `contains` - Contains substring\n- `startswith` - Starts with\n- `endswith` - Ends with\n- `in` - In array\n- `notin` - Not in array\n\n## 🤝 Contributing\n\n1. Fork the repository\n2. Create a feature branch\n3. Write tests first (TDD)\n4. Implement the feature\n5. Ensure all tests pass\n6. Submit a pull request\n\n## 📄 License\n\nMIT License - see [LICENSE](LICENSE) for details.\n\n## 🆘 Support\n\n- 📖 [Documentation](https://odata-active-record.dev)\n- 🐛 [Issues](https://github.com/your-org/odata-active-record/issues)\n- 💬 [Discussions](https://github.com/your-org/odata-active-record/discussions)\n\n---\n\n**Made with ❤️ for the OData community**\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frvegajr%2Fodata-active-record","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frvegajr%2Fodata-active-record","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frvegajr%2Fodata-active-record/lists"}