{"id":26572377,"url":"https://github.com/devnax/xansql","last_synced_at":"2026-04-12T21:47:48.576Z","repository":{"id":283649643,"uuid":"951802918","full_name":"devnax/xansql","owner":"devnax","description":null,"archived":false,"fork":false,"pushed_at":"2025-03-21T10:29:38.000Z","size":48,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-21T11:24:21.242Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/devnax.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"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}},"created_at":"2025-03-20T09:13:12.000Z","updated_at":"2025-03-21T10:29:41.000Z","dependencies_parsed_at":"2025-03-21T11:24:24.264Z","dependency_job_id":"ba2cb981-9f0d-443a-99b2-30def414d9f8","html_url":"https://github.com/devnax/xansql","commit_stats":null,"previous_names":["devnax/xansql"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devnax%2Fxansql","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devnax%2Fxansql/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devnax%2Fxansql/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devnax%2Fxansql/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/devnax","download_url":"https://codeload.github.com/devnax/xansql/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245040235,"owners_count":20551297,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":"2025-03-23T00:33:46.000Z","updated_at":"2026-04-12T21:47:48.569Z","avatar_url":"https://github.com/devnax.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# xansql\n\n\u003cp align=\"center\"\u003e\n  \u003cstrong\u003eType-safe, event-driven SQL ORM with automatic schema synchronization, composable relations, granular hooks, and optional client execution bridge.\u003c/strong\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n\u003c!-- Badges (replace placeholders when public) --\u003e\n\u003ca href=\"#\"\u003e\u003cimg alt=\"license\" src=\"https://img.shields.io/badge/license-MIT-blue\"/\u003e\u003c/a\u003e\n\u003ca href=\"#\"\u003e\u003cimg alt=\"status\" src=\"https://img.shields.io/badge/status-beta-orange\"/\u003e\u003c/a\u003e\n\u003ca href=\"#\"\u003e\u003cimg alt=\"dialects\" src=\"https://img.shields.io/badge/dialects-mysql%20%7C%20postgresql%20%7C%20sqlite-6A5ACD\"/\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\n## Executive Summary\nxansql is a minimalist but powerful ORM focusing on:\n- Deterministic schema definition (single source of truth) with non-destructive migration.\n- Relation traversal via declarative `select` trees (preventing circular graphs).\n- Rich predicate language in `where` supporting deep `EXISTS` on nested relations.\n- Event system \u0026 lifecycle hooks (global + per-model) for observability \u0026 cross-cutting concerns.\n- Pluggable caching, file storage, fetch bridge (browser safe), and socket integration.\n- Lightweight execution pipeline: thin SQL generation, no heavy runtime proxies.\n\n---\n## Contents\n1. Features\n2. Architecture Overview\n3. Installation\n4. Quick Start\n5. Configuration Reference\n6. Defining Models \u0026 Fields\n7. Relations\n8. Querying \u0026 Predicates\n9. Aggregation \u0026 Helpers\n10. Pagination \u0026 Convenience APIs\n11. Transactions\n12. Migrations\n13. Events \u0026 Hooks\n14. File Handling\n15. Client Fetch Bridge\n16. Caching Interface\n17. Dialects \u0026 Custom Implementation\n18. Error Handling \u0026 Validation\n19. Security Considerations\n20. Performance Guidance\n21. FAQ\n22. Roadmap\n23. License\n\n---\n## 1. Features\n- Multi-dialect: MySQL, PostgreSQL, SQLite (custom adapter friendly)\n- Auto aliasing + integrity checks\n- Declarative relations (`xt.schema` / array reverse mapping)\n- Non-destructive migrate (add/modify/remove columns) + force rebuild\n- Granular lifecycle hooks \u0026 event emission\n- Rich `where` condition operators (logical AND/OR composition)\n- Nested relational filtering through `EXISTS` semantics\n- Aggregation inline or via helper methods\n- Optional caching module contract\n- Integrated file meta handling \u0026 streaming upload abstraction\n- Client-side safe execution (no raw SQL leakage) via signed execution meta\n\n---\n## 2. Architecture Overview\nLayered components:\n- Core: `Xansql` orchestrates config, model registry, transactions, migration, fetch bridge and events.\n- Model: Provides CRUD + query generation + relation resolution.\n- Executers: Specialized operation builders (Find / Create / Update / Delete / Aggregate).\n- Migration: Computes delta from declared schema vs dialect metadata and issues SQL.\n- Types System: Field factories (`xt.*`) with metadata (length, unique, index, validators, transforms).\n- Foreign Resolver: Normalizes forward \u0026 reverse relation mapping for join/exists generation.\n- Fetch Bridge: Validates request meta for client-originated operations (server controlled).\n\n---\n## 3. Installation\n```bash\nnpm install xansql mysql2 pg better-sqlite3\n# Or only the drivers you need\n```\nSQLite usage recommends `better-sqlite3` for synchronous performance.\n\n---\n## 4. Quick Start\n```ts\nimport { Xansql, Model, xt } from 'xansql';\nimport MysqlDialect from 'xansql/dist/libs/MysqlDialect';\n\nconst db = new Xansql({\n  dialect: MysqlDialect({ host: '127.0.0.1', user: 'root', password: '', database: 'app' })\n});\n\nconst User = db.model('users', {\n  id: xt.id(),\n  username: xt.username(),\n  email: xt.email().unique(),\n  password: xt.password().strong(),\n  role: xt.role(['admin', 'member']),\n  createdAt: xt.createdAt(),\n  updatedAt: xt.updatedAt()\n});\n\nawait db.migrate();\nawait User.create({ data: [{ username: 'alice', email: 'a@b.com', password: 'Pwd@1234', role: 'member' }] });\nconst result = await User.find({ where: { username: { equals: 'alice' } } });\n```\n\n---\n## 5. Configuration Reference\n```ts\nnew Xansql({\n  dialect: MysqlDialect({...}),            // REQUIRED\n  fetch: { url: '/xansql', mode: 'production' }, // optional (client bridge)\n  socket: { open, message, close },        // optional WebSocket handlers\n  cache: { cache, clear, onFind, onCreate, onUpdate, onDelete }, // optional\n  file: { maxFilesize, chunkSize, upload, delete }, // optional file storage\n  maxLimit: { find, create, update, delete },       // safety caps (default 100)\n  hooks: { beforeFind, afterFind, transform, ... }  // global async hooks\n});\n```\nRequired dialect interface:\n```ts\ninterface XansqlDialect {\n  engine: 'mysql' | 'postgresql' | 'sqlite';\n  execute(sql: string): Promise\u003c{ results: any[]; affectedRows: number; insertId: number | null }\u003e;\n  getSchema(): Promise\u003c{ [table: string]: { name: string; type: string; notnull: boolean; default_value: any; pk: boolean; index: boolean; unique: boolean }[] }\u003e\n}\n```\n\n---\n## 6. Defining Models \u0026 Fields\n```ts\nconst Post = db.model('posts', {\n  id: xt.id(),\n  title: xt.title().index(),\n  slug: xt.slug().unique(),\n  author: xt.schema('users', 'id'),         // FK forward\n  tags: xt.array(xt.string(30)),            // array (not in where predicate)\n  images: xt.array(xt.file()),              // file metadata entries\n  createdAt: xt.createdAt(),\n  updatedAt: xt.updatedAt()\n});\n```\nPer-model hooks:\n```ts\nPost.options.hooks = {\n  beforeCreate: async (args) =\u003e args,\n  transform: async (row) =\u003e { delete row.password; return row; }\n};\n```\nField factory highlights: `id, string, number, boolean, date, enum, array, object, record, tuple, union, file, schema` + semantic shortcuts (`username`, `email`, `password`, `slug`, `role`, `title`, `amount`, etc.). Most fields accept chainable validators (`min`, `max`, `unique`, `index`, `transform`).\n\nForeign key patterns:\n- Forward: `xt.schema('users','id')`\n- Reverse (one-to-many): `xt.array(xt.schema('posts','id'))`\n\n---\n## 7. Relations\nSelect nested relations:\n```ts\nawait User.find({\n  select: {\n    id: true,\n    username: true,\n    posts: {\n      select: { id: true, title: true },\n      where: { title: { contains: 'SQL' } },\n      limit: { take: 5 }\n    }\n  }\n});\n```\nCircular graphs are rejected early.\n\n---\n## 8. Querying \u0026 Predicates\nOperators: `equals, not, lt, lte, gt, gte, in, notIn, between, notBetween, contains, notContains, startsWith, endsWith, isNull, isNotNull, isEmpty, isNotEmpty, isTrue, isFalse`.\n- Object =\u003e AND\n- Array of objects =\u003e OR\n- Nested relation in `where` =\u003e EXISTS subquery\nExample:\n```ts\nawait Post.find({\n  where: {\n    author: { username: { startsWith: 'a' } },\n    slug: { notContains: 'draft' },\n    title: [{ contains: 'Guide' }, { contains: 'Intro' }]\n  }\n});\n```\n\n---\n## 9. Aggregation \u0026 Helpers\nInline:\n```ts\nawait User.find({ aggregate: { id: { count: true } } });\n```\nHelpers: `count(where)`, `min(col, where)`, `max`, `sum`, `avg`, `exists(where)`.\n\n---\n## 10. Pagination \u0026 Convenience\n```ts\nconst page = await User.paginate(2, { perpage: 20, where: { role: { equals: 'member' } } });\n// { page, perpage, pagecount, rowcount, results }\n```\nAlso: `findOne(args)`, `findById(id, args)`.\n\n---\n## 11. Transactions\nAutomatic for create/update/delete unless within chained relation execution.\nManual wrapper:\n```ts\nawait db.transaction(async () =\u003e {\n  await User.create({ data: [{ username: 'temp' }] });\n  await User.update({ data: { role: 'admin' }, where: { username: 'temp' } });\n});\n```\nRollback on error.\n\n---\n## 12. Migrations\n```ts\nawait db.migrate();        // sync non-destructively\nawait db.migrate(true);    // drop + recreate (files cleaned)\nconst preview = await db.generateMigration(); // array of SQL statements\n```\nRules:\n- Skips ID column alterations.\n- Adds new columns; drops removed ones; issues ALTER for changed definition.\n- Force rebuild executes reverse-order drops then creates.\n\n---\n## 13. Events \u0026 Hooks\nEvents emitted: `BEFORE_CREATE, CREATE, BEFORE_UPDATE, UPDATE, BEFORE_DELETE, DELETE, BEFORE_FIND, FIND, BEFORE_AGGREGATE, AGGREGATE, BEFORE_FETCH, FETCH`.\nUsage:\n```ts\ndb.on('CREATE', ({ model, results }) =\u003e { /* audit */ });\n```\nHooks (global \u0026 model-level) allow mutation of args/results or row transform.\n\n---\n## 14. File Handling\nDefine file fields: `xt.file(size?)` / arrays.\nConfigure storage:\n```ts\nfile: {\n  maxFilesize: 2048,        // KB\n  chunkSize: 256,           // KB (streaming)\n  upload: async (chunk, meta) =\u003e {},\n  delete: async (filename) =\u003e {}\n}\n```\nClient helpers: `uploadFile(file, executeId)`, `deleteFile(name, executeId)`.\n\n---\n## 15. Client Fetch Bridge\nProvide `fetch: string | { url, mode }`.\nClient side raw SQL blocked; operations require internally generated `executeId` (granted per model action via metadata).\nServer integrates:\n```ts\nconst response = await db.onFetch(req.url, {\n  body: req.body,\n  headers: req.headers,\n  cookies: parseCookies(req),\n  isAuthorized: async (meta) =\u003e {/* check meta.action, meta.model */ return true; }\n});\n```\n\n---\n## 16. Caching Interface\nImplement partial or full row caching:\n```ts\ncache: {\n  cache: async (sql, model) =\u003e /* rows or undefined */,\n  clear: async (model) =\u003e {},\n  onFind: async (sql, model, row) =\u003e {},\n  onCreate: async (model, insertId) =\u003e {},\n  onUpdate: async (model, rows) =\u003e {},\n  onDelete: async (model, rows) =\u003e {},\n}\n```\nYou decide strategy (memory, redis, browser IndexedDB via example adapters).\n\n---\n## 17. Dialects \u0026 Custom Implementation\nBuilt-ins: `MysqlDialect`, `PostgresDialect`, `SqliteDialect`.\nCustom:\n```ts\nconst CustomDialect = () =\u003e ({\n  engine: 'mysql',\n  execute: async (sql) =\u003e {/* run */ return { results: [], affectedRows: 0, insertId: 0 };},\n  getSchema: async () =\u003e ({ /* table: columns[] */ })\n});\n```\n`getSchema` must supply column index/unique flags for migration diffing.\n\n---\n## 18. Error Handling \u0026 Validation\nCommon thrown errors:\n- Missing dialect or execute function\n- Unsupported engine\n- Model without ID field\n- Duplicate model name / alias collision\n- Invalid where operator or disallowed field type in predicate (array/object/record/tuple)\n- Circular relation selection / where nesting\n- Client usage without fetch configuration\n- Raw query attempt from client without `executeId`\n- Invalid foreign key definition\n\n---\n## 19. Security Considerations\n- All value interpolation passes through escaping utilities.\n- Client cannot send arbitrary SQL (requires signed meta created server-side).\n- Hooks \u0026 events can enforce auditing, RBAC, masking.\n- Password field helper automatically hashes via SHA-256 transform.\n- Recommend additional app-layer input validation before invoking ORM.\n\n---\n## 20. Performance Guidance\n- Prefer selective `select` trees over full-table scans.\n- Use indexes via field `.index()` / `.unique()` early (migration will create).\n- Enable caching for heavy read patterns.\n- Use pagination helpers (`paginate`) to avoid large offset scans.\n- Keep relation depth shallow to limit EXISTS nesting.\n- Batch `create` with array `data` for reduced round trips.\n\n---\n## 21. FAQ\nQ: Does xansql generate JOINs?  \nA: Relation filters use `EXISTS` subqueries; selection fetches related sets separately.\n\nQ: How are reverse (one-to-many) relations defined?  \nA: `xt.array(xt.schema('childTable','id'))` inside the parent references children.\n\nQ: Can I rename columns automatically?  \nA: Rename support is planned (see roadmap). Current diff treats rename as drop + add.\n\nQ: Can I use raw SQL?  \nA: Server side `db.execute(sql)` is allowed; client side raw is blocked.\n\n---\n## 22. Roadmap\n- Column / index rename migration operations\n- CLI code generation \u0026 schema inspector\n- Enhanced diff reporting (explain changes)\n- Advanced relation eager constraints (depth limiting strategies)\n- Pluggable authorization middleware bundle\n\n---\n## 23. License\nMIT\n\n---\n## Attributions\nInternal field validation leverages concepts from `xanv`. File handling meta uses `securequ` upload structures.\n\n---\n\u003e Need adjustments (badges, examples, tutorials)? Open an issue or contribute.\n# xansql\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevnax%2Fxansql","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdevnax%2Fxansql","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevnax%2Fxansql/lists"}