{"id":51132677,"url":"https://github.com/igorjs/pure-orm","last_synced_at":"2026-06-25T14:30:31.347Z","repository":{"id":355398476,"uuid":"1203518148","full_name":"igorjs/pure-orm","owner":"igorjs","description":"Type-safe database toolkit for TypeScript. Built on Pure TS primitives.","archived":false,"fork":false,"pushed_at":"2026-06-18T05:36:02.000Z","size":823,"stargazers_count":0,"open_issues_count":13,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-18T07:25:32.028Z","etag":null,"topics":["database","orm","typescript","zero-dependencies"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/igorjs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":".github/CONTRIBUTING-RULES.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":"CITATION.cff","codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":"NOTICE","maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["igorjs"]}},"created_at":"2026-04-07T05:36:23.000Z","updated_at":"2026-06-18T05:35:47.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/igorjs/pure-orm","commit_stats":null,"previous_names":["igorjs/pure-orm"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/igorjs/pure-orm","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igorjs%2Fpure-orm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igorjs%2Fpure-orm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igorjs%2Fpure-orm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igorjs%2Fpure-orm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/igorjs","download_url":"https://codeload.github.com/igorjs/pure-orm/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igorjs%2Fpure-orm/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34780124,"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-25T02:00:05.521Z","response_time":101,"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":["database","orm","typescript","zero-dependencies"],"created_at":"2026-06-25T14:30:30.018Z","updated_at":"2026-06-25T14:30:31.335Z","avatar_url":"https://github.com/igorjs.png","language":"TypeScript","funding_links":["https://github.com/sponsors/igorjs"],"categories":[],"sub_categories":[],"readme":"# @igorjs/pure-orm\n\nFunctional-first, type-safe ORM built on [@igorjs/pure-fx](https://github.com/igorjs/pure-fx). Pure query composition, immutable AST, PostgreSQL and SQLite dialects, Lambda-ready connections, migration CLI with locking and checksums.\n\n![Node.js](https://img.shields.io/badge/Node.js_22+-339933?logo=nodedotjs\u0026logoColor=white)\n![Deno](https://img.shields.io/badge/Deno_2.0+-000000?logo=deno\u0026logoColor=white)\n![Bun](https://img.shields.io/badge/Bun-000000?logo=bun\u0026logoColor=white)\n![Tests](https://img.shields.io/badge/tests-935_passing-brightgreen)\n\n## Install\n\n```bash\nnpm install @igorjs/pure-orm @igorjs/pure-fx\n```\n\nDatabase drivers are optional peer dependencies. Install only what you need:\n\n```bash\nnpm install pg                # PostgreSQL\nnpm install better-sqlite3    # SQLite\n```\n\n## Quick Start\n\n```typescript\nimport { pipe, Schema } from \"@igorjs/pure-fx\";\nimport {\n  Model, Field,\n  from, select, where, orderBy, limit,\n  eq, gt,\n  execute, Database,\n} from \"@igorjs/pure-orm\";\n\n// Define a model\nconst User = Model(\"users\", {\n  fields: {\n    id: Field(Schema.string, { primaryKey: true, default: \"uuid\" }),\n    name: Field(Schema.string),\n    email: Field(Schema.string, { unique: true }),\n    age: Field(Schema.number),\n  },\n  options: { timestamps: true, softDelete: true },\n});\n\n// Connect\nconst db = Database({ dialect: \"postgresql\", driver, connection: config });\n\n// Build and execute a query\nconst result = await pipe(\n  from(User),\n  where(gt(\"age\", 18)),\n  orderBy(\"name\", \"asc\"),\n  limit(50),\n  execute(db),\n).run();\n```\n\n## Features\n\n### Query Building\n\nEvery query is an immutable AST node composed via `pipe()`. No SQL is generated until `execute()`.\n\n```typescript\n// Select with conditions\npipe(from(User), where(eq(\"role\", \"admin\")), select(\"name\", \"email\"))\n\n// Joins\npipe(from(Post), join(User, on(\"authorId\", \"id\")), where(eq(\"published\", true)))\n\n// Aggregates with groupBy\npipe(from(Post), select(\"authorId\", count(\"id\").as(\"cnt\")), groupBy(\"authorId\"))\n\n// Window functions\npipe(from(Post), select(\"title\", rowNumber().partitionBy(\"authorId\").orderBy(\"createdAt\", \"desc\").as(\"rank\")))\n\n// CTEs\npipe(from(Post), withCte(\"recent\", pipe(from(Post), where(gt(\"createdAt\", cutoff)))))\n\n// Subqueries\npipe(from(User), where(exists(pipe(from(Post), where(eq(\"authorId\", \"users.id\"))))))\n\n// Raw SQL escape hatch\nraw(\"SELECT * FROM users WHERE id = $1\", [\"user-1\"])\nsql`SELECT * FROM users WHERE age \u003e ${minAge}`\n```\n\n### Mutations\n\n```typescript\n// Insert with returning\npipe(insert(User, { name: \"Alice\", email: \"alice@example.com\" }), returning(\"id\"), execute(db))\n\n// Upsert (ON CONFLICT)\npipe(insert(User, { email: \"alice@example.com\" }), onConflict(\"email\", { update: [\"name\"] }), execute(db))\n\n// Update\npipe(update(User, { name: \"Bob\" }), where(eq(\"id\", userId)), execute(db))\n\n// Soft delete (auto when model has softDelete: true)\npipe(remove(User), where(eq(\"id\", userId)), execute(db))\n\n// Hard delete (bypasses soft delete)\npipe(hardRemove(User), where(eq(\"id\", userId)), execute(db))\n\n// Restore soft-deleted row\npipe(restore(User), where(eq(\"id\", userId)), execute(db))\n```\n\n### Relations\n\n```typescript\nconst User = Model(\"users\", {\n  fields: { id: Field(Schema.string, { primaryKey: true }), name: Field(Schema.string) },\n  relations: () =\u003e ({\n    posts: hasMany(() =\u003e Post, { foreignKey: \"authorId\", localKey: \"id\" }),\n    profile: hasOne(() =\u003e Profile, { foreignKey: \"userId\", localKey: \"id\" }),\n  }),\n});\n\n// Eager loading (hasOne/belongsTo via LEFT JOIN)\npipe(from(Post), include(Post, \"author\"), execute(db))\n\n// Lazy loading (on-demand query)\nconst postsQuery = lazy(User, userRecord, \"posts\");\npipe(postsQuery, execute(db))\n```\n\n### Soft Deletes\n\nModels with `softDelete: true` auto-filter deleted rows.\n\n```typescript\npipe(from(User))                        // WHERE deleted_at IS NULL (auto)\npipe(from(User), withDeleted())         // include deleted rows\npipe(from(User), onlyDeleted())         // only deleted rows\npipe(restore(User), where(eq(\"id\", x))) // restore a soft-deleted row\n```\n\n### Transactions\n\n```typescript\nconst result = await transaction(db, async (tx) =\u003e {\n  await execute(tx)(insert(User, { name: \"Alice\" })).run();\n  await execute(tx)(insert(Post, { title: \"Hello\", authorId: \"...\" })).run();\n}, { isolationLevel: \"serializable\" }).run();\n```\n\n### Migrations\n\n#### CLI\n\n```bash\n# Generate a migration from model changes\nnpx pure-orm migrate:generate create_users\n\n# Apply all pending migrations\nnpx pure-orm migrate:up\n\n# Preview SQL without executing\nnpx pure-orm migrate:up --dry-run\n\n# Roll back the last migration\nnpx pure-orm migrate:down\n\n# Roll back the last 3 migrations\nnpx pure-orm migrate:down --step 3\n\n# Show migration status\nnpx pure-orm migrate:status\n\n# Verify migration checksums\nnpx pure-orm migrate:validate\n```\n\n#### Configuration\n\nCreate `pure-orm.config.ts` in your project root:\n\n```typescript\nimport type { PureOrmConfig } from \"@igorjs/pure-orm/cli\";\n\nexport const config: PureOrmConfig = {\n  dialect: \"postgresql\",\n  connection: {\n    host: \"localhost\",\n    port: 5432,\n    database: \"mydb\",\n    user: \"postgres\",\n    password: \"\",\n  },\n  migrations: {\n    directory: \"migrations\",\n  },\n  models: () =\u003e [User, Post, Comment],\n};\n```\n\n#### SQL Migration Files\n\nMigrations use `-- @up` and `-- @down` markers:\n\n```sql\n-- @up\nCREATE TABLE \"users\" (\n  \"id\" SERIAL PRIMARY KEY,\n  \"name\" TEXT NOT NULL,\n  \"email\" TEXT NOT NULL UNIQUE\n);\n\n-- @down\nDROP TABLE \"users\";\n```\n\nFor concurrent index creation (PostgreSQL):\n\n```sql\n-- @transaction false\n-- @concurrent true\n\n-- @up\nCREATE INDEX CONCURRENTLY \"idx_users_email\" ON \"users\" (\"email\");\n\n-- @down\nDROP INDEX \"idx_users_email\";\n```\n\n#### Programmatic API\n\n```typescript\nimport { createSnapshot, diffSnapshots, generateMigration, executeBatch } from \"@igorjs/pure-orm\";\n\n// Snapshot current schema\nconst snapshot = createSnapshot([User, Post, Comment]);\n\n// Diff against previous snapshot\nconst changes = diffSnapshots(previousSnapshot, snapshot);\n\n// Generate up/down SQL\nconst { up, down } = generateMigration(changes, dialect);\n\n// Execute a batch with locking and checksum validation\nawait executeBatch(db, migrations, { dryRun: false }).run();\n```\n\n### Dialects\n\n```typescript\nimport { createPostgresDialect, createSqliteDialect } from \"@igorjs/pure-orm\";\n\n// PostgreSQL: $1, $2, ... params, ILIKE, NOW()\nconst pg = createPostgresDialect();\n\n// SQLite: ? params, LIKE for ILIKE, datetime('now')\nconst sqlite = createSqliteDialect();\n```\n\n### Audit\n\n```typescript\n// Hooks-based mutation logging\nconst hooks = createAuditHooks({\n  callback: (entry) =\u003e console.log(entry.operation, entry.tableName),\n  context: { actorId: user.id },\n});\n\n// Query audit history\npipe(auditLog(User), where(eq(\"rowId\", userId)), orderBy(\"createdAt\", \"desc\"), limit(50), execute(db))\n```\n\n## Design Principles\n\n- **Pure composition**: queries are data (immutable AST nodes), not strings\n- **Dialect-agnostic AST**: SQL is generated only at execution time\n- **Zero mutation**: every builder returns a new frozen object\n- **Bring your own driver**: works with pg, postgres.js, better-sqlite3, etc.\n- **Lambda-ready**: connection pooling designed for serverless\n- **Cross-runtime**: core query builder works on Node, Deno, and Bun\n\n## Requirements\n\n- Node.js \u003e= 22 (also works on Deno 2.x and Bun)\n- @igorjs/pure-fx \u003e= 0.1.0\n- TypeScript \u003e= 5.7 (optional)\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, coding standards, and how to submit changes.\n\n## Disclaimer\n\nTHIS SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n## License\n\n[Apache-2.0](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Figorjs%2Fpure-orm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Figorjs%2Fpure-orm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Figorjs%2Fpure-orm/lists"}