{"id":48768512,"url":"https://github.com/thachp/ezorm","last_synced_at":"2026-04-13T09:01:44.003Z","repository":{"id":347512215,"uuid":"1194271333","full_name":"thachp/ezorm","owner":"thachp","description":"Just another ORM for Typescript but a little easier to use.","archived":false,"fork":false,"pushed_at":"2026-03-28T11:37:20.000Z","size":442,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-28T12:29:09.492Z","etag":null,"topics":["mssql","mysql","orm","postgresql","sqlite"],"latest_commit_sha":null,"homepage":"","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/thachp.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":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-03-28T05:54:59.000Z","updated_at":"2026-03-28T12:28:23.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/thachp/ezorm","commit_stats":null,"previous_names":["thachp/ezorm"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/thachp/ezorm","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thachp%2Fezorm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thachp%2Fezorm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thachp%2Fezorm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thachp%2Fezorm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thachp","download_url":"https://codeload.github.com/thachp/ezorm/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thachp%2Fezorm/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31746113,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-13T06:26:45.479Z","status":"ssl_error","status_checked_at":"2026-04-13T06:26:44.645Z","response_time":93,"last_error":"SSL_read: 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":["mssql","mysql","orm","postgresql","sqlite"],"created_at":"2026-04-13T09:01:43.189Z","updated_at":"2026-04-13T09:01:43.988Z","avatar_url":"https://github.com/thachp.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ezorm\n\nEzorm is a TypeScript ORM built around decorated models, repository CRUD, explicit read queries, and an optional managed proxy runtime.\n\nThe practical path today is:\n\n- `@ezorm/core` defines decorated models plus runtime metadata and validation.\n- `@ezorm/orm` is the primary direct Node.js ORM for SQLite, PostgreSQL, MySQL, and MSSQL.\n- `ezorm` is the CLI for migrations and schema workflows from an explicit `ezorm.config.*` file.\n- The maintained Nest and Next todo apps are the best end-to-end references in this repository.\n- The maintained examples default to `sqlite::memory:`, so restarting those processes clears data.\n\n## Start Here\n\nIf you are new to ezorm, use this order:\n\n1. Install `@ezorm/core` and `@ezorm/orm`.\n2. Define a decorated model.\n3. Create a client, run `pushSchema`, and use a repository.\n4. Add the CLI for checked-in migration workflows.\n5. Add a framework adapter or proxy runtime only after the direct ORM flow works.\n\nRecommended first install:\n\n```sh\nnpm install @ezorm/core @ezorm/orm\n```\n\nInstall by intent:\n\n| Goal | Package |\n| --- | --- |\n| Define model metadata with decorators | `@ezorm/core` |\n| Use direct Node.js ORM repositories and queries | `@ezorm/orm` |\n| Use the CLI for migrations and schema workflows | `ezorm` |\n| Wrap the direct ORM with a Node runtime helper | `@ezorm/runtime-node` |\n| Reuse direct ORM clients in Next.js Node runtimes | `@ezorm/next` |\n| Wire ORM clients and repositories into Nest DI | `@ezorm/nestjs` |\n| Use the pooled HTTP proxy client | `@ezorm/runtime-proxy` |\n| Start and manage the packaged proxy process from Node.js | `@ezorm/proxy-node` |\n\nTo inspect the current CLI surface without installing anything:\n\n```sh\nnpx ezorm --help\n```\n\n## Define Your First Model\n\nStart with `@ezorm/core` when you want model metadata and input validation from the same decorated class.\n\n```ts\nimport {\n  Field,\n  Model,\n  PrimaryKey,\n  getModelMetadata,\n  validateModelInput\n} from \"@ezorm/core\";\n\n@Model({ table: \"todos\" })\nclass Todo {\n  @PrimaryKey()\n  @Field.string()\n  id!: string;\n\n  @Field.string()\n  title!: string;\n\n  @Field.boolean({ defaultValue: false })\n  completed!: boolean;\n}\n\nconsole.log(getModelMetadata(Todo));\nconsole.log(\n  validateModelInput(Todo, {\n    id: \"todo_1\",\n    title: \"Ship the README\",\n    completed: false\n  })\n);\n```\n\nThat gives you:\n\n- runtime metadata for tables, fields, indices, and relations\n- input validation from the same model definition\n\n## Build Your First CRUD Flow\n\n`@ezorm/orm` is the primary Node.js ORM surface. The fastest first run is SQLite in memory.\n\n```ts\nimport { Field, Model, PrimaryKey } from '@ezorm/core';\nimport { createOrmClient } from '@ezorm/orm';\n\n@Model({ table: 'todos' })\nclass Todo {\n  @PrimaryKey()\n  @Field.string()\n  id!: string;\n\n  @Field.string()\n  title!: string;\n\n  @Field.boolean({ defaultValue: false })\n  completed!: boolean;\n}\n\nconst run = async () =\u003e {\n  const client = await createOrmClient({\n    databaseUrl: 'sqlite::memory:',\n  });\n\n  await client.pushSchema([Todo]);\n\n  const todos = client.repository(Todo);\n\n  await todos.create({\n    id: 'todo_1',\n    title: 'Ship the README',\n    completed: false,\n  });\n\n  console.log(await todos.findById('todo_1'));\n\n  console.log(\n    await todos.findMany({\n      orderBy: { field: 'title', direction: 'asc' },\n    }),\n  );\n\n  console.log(\n    await todos.update('todo_1', {\n      completed: true,\n    }),\n  );\n\n  await todos.delete('todo_1');\n\n  await client.close();\n};\n\nvoid run();\n```\n\nThe repository API is intentionally small in v1:\n\n- `create`\n- `findById`\n- `findMany`\n- `update`\n- `delete`\n\n`findMany(...)` supports exact-match scalar filters and simple ordering for single-table CRUD.\n\n## Enable Repository Read Cache\n\n`readCache` is opt-in on the direct ORM path. Use it when you want cached repository reads for `findById(...)` and `findMany(...)`.\n\n```ts\nimport { createOrmClient } from \"@ezorm/orm\";\n\nconst client = await createOrmClient({\n  databaseUrl: \"sqlite::memory:\",\n  readCache: {\n    default: {\n      backend: \"memory\",\n      ttlSeconds: 30\n    }\n  }\n});\n```\n\nThe same cache configuration is available through `@ezorm/runtime-node`:\n\n```ts\nimport { createNodeRuntime } from \"@ezorm/runtime-node\";\n\nconst client = await createNodeRuntime({\n  connect: {\n    databaseUrl: \"sqlite::memory:\",\n    readCache: {\n      default: {\n        backend: \"memory\",\n        ttlSeconds: 30\n      }\n    }\n  }\n});\n```\n\nCurrent cache behavior:\n\n- `readCache` is opt-in.\n- It applies only to `repository.findById(...)` and `repository.findMany(...)`.\n- TTL is absolute from write time.\n- `create`, `update`, and `delete` clear that model's cached repository entries.\n\n## Manage Schema With The CLI\n\nThe `ezorm` CLI uses a project-level config file named one of:\n\n- `ezorm.config.ts`\n- `ezorm.config.mts`\n- `ezorm.config.cts`\n- `ezorm.config.mjs`\n- `ezorm.config.js`\n- `ezorm.config.cjs`\n\nThe config must export:\n\n- `databaseUrl`\n- optional `models`\n- optional `modelPaths`\n- optional `migrationsDir`\n\nExample:\n\n```ts\nexport default {\n  databaseUrl: \"sqlite:///tmp/ezorm.db\",\n  modelPaths: [\"src/models\"],\n  migrationsDir: \"migrations\"\n};\n```\n\nIf `models` is omitted, the CLI scans `modelPaths` and imports files containing `@Model` or `Model(...)` before deriving schema metadata. Generated configs prefer `src/models` or `models`. Explicit `models` still override scan mode, and broad scan roots such as `[\"src\"]` or `[\".\"]` may import unrelated matching modules.\n\nUse `npx ezorm init` to scaffold the config in the nearest package root, add a minimal Todo model when the project does not already have one, and patch `tsconfig.json` for decorator support in TypeScript projects. TypeScript and JavaScript scaffolds default `modelPaths` to `[\"src/models\"]` when `src/` exists or `[\"models\"]` otherwise.\n\nTypeScript config files can still import decorator-authored `.ts` model classes directly. JavaScript config files remain supported for ESM and CommonJS projects.\n\nCurrent CLI commands:\n\n```text\nezorm init [--ts|--js]\nezorm migrate generate [name]\nezorm migrate apply\nezorm migrate status\nezorm migrate resolve --applied \u003cfilename\u003e\nezorm migrate resolve --rolled-back \u003cfilename\u003e\nezorm db pull\nezorm db push\n```\n\nTypical workflow:\n\n```sh\nnpx ezorm init\nnpx ezorm migrate generate init\nnpx ezorm migrate apply\nnpx ezorm migrate status\nnpx ezorm db pull\nnpx ezorm db push\n```\n\nCommand behavior today:\n\n- `init` writes `ezorm.config.*`, adds an example Todo model when needed, and patches TypeScript decorator compiler flags for TS scaffolds.\n- `migrate generate` writes additive SQL migration files.\n- `migrate apply` executes pending migration files and records them in `_ezorm_migrations`.\n- `migrate status` shows migration state.\n- `migrate resolve` only reconciles migration history. It does not execute SQL.\n- `db pull` prints the live schema as JSON.\n- `db push` applies additive schema drift directly without updating migration history, which makes it the development shortcut rather than the checked-in migration path.\n\n## Read Relations As A Next Step\n\nUse repository CRUD for simple writes and single-table reads. Use `client.query(...)` plus explicit relation metadata for relation-aware reads.\n\n```ts\nimport { BelongsTo, Field, HasMany, Model, PrimaryKey } from \"@ezorm/core\";\nimport { createOrmClient } from \"@ezorm/orm\";\n\n@Model({ table: \"users\" })\nclass User {\n  @PrimaryKey()\n  @Field.string()\n  id!: string;\n\n  @Field.string()\n  email!: string;\n\n  @HasMany(() =\u003e Post, { localKey: \"id\", foreignKey: \"userId\" })\n  posts!: Post[];\n}\n\n@Model({ table: \"posts\" })\nclass Post {\n  @PrimaryKey()\n  @Field.string()\n  id!: string;\n\n  @Field.string()\n  userId!: string;\n\n  @Field.string()\n  title!: string;\n\n  @BelongsTo(() =\u003e User, { foreignKey: \"userId\", targetKey: \"id\" })\n  author!: User | undefined;\n}\n\nconst client = await createOrmClient({\n  databaseUrl: \"sqlite::memory:\"\n});\n\nawait client.pushSchema([User, Post]);\n\nconst posts = await client\n  .query(Post)\n  .join(\"author\")\n  .where(\"author.email\", \"=\", \"alice@example.com\")\n  .include(\"author\")\n  .orderBy(\"title\", \"asc\")\n  .all();\n\nconst users = await client.query(User).include(\"posts\").all();\n\nawait posts[0].author;\nawait users[0].posts;\nawait client.load(Post, posts[0], \"author\");\nawait client.loadMany(User, users, \"posts\");\n\nconst projected = await client\n  .query(Post)\n  .join(\"author\")\n  .select\u003c{ title: string; authorEmail: string }\u003e({\n    title: \"title\",\n    authorEmail: \"author.email\"\n  })\n  .orderBy(\"title\", \"asc\")\n  .all();\n\nconsole.log(projected);\n```\n\nCurrent relation behavior:\n\n- `BelongsTo`, `HasMany`, and `ManyToMany` are supported.\n- Relation metadata requires explicit key mappings.\n- `client.query(Model)` is read-only.\n- `include(...)` prewarms lazy relation caches on query entities.\n- `await post.author` and `await user.posts` read lazy relation properties from query results.\n- `load(...)` and `loadMany(...)` are the explicit plain-object relation loaders.\n- `select(...)` switches the query into flat projection mode and returns plain rows.\n\nRelation-aware `query(...)`, `load(...)`, and `loadMany(...)` are available on the direct ORM path. They are not implemented on proxy-backed runtimes yet.\n\n## Choose A Runtime Or Framework Adapter\n\nChoose the smallest layer that matches your deployment shape.\n\n### Direct `@ezorm/orm`\n\nUse this first. It is the primary direct Node.js ORM surface for SQLite, PostgreSQL, MySQL, and MSSQL.\n\n### `@ezorm/runtime-node`\n\nUse this when you want a thin Node runtime wrapper but the same direct ORM behavior surface.\n\n```ts\nimport { createNodeRuntime } from \"@ezorm/runtime-node\";\n\nconst client = await createNodeRuntime({\n  connect: { databaseUrl: \"sqlite::memory:\" }\n});\n```\n\n### `@ezorm/next/node`\n\nUse this in Next.js server components, route handlers, and server actions when you want a cached direct ORM client.\n\n```ts\nimport { getNextNodeClient } from \"@ezorm/next/node\";\n\nconst client = await getNextNodeClient({\n  cacheKey: \"app\",\n  connect: { databaseUrl: \"sqlite::memory:\" }\n});\n```\n\n### `@ezorm/nestjs`\n\nUse this when you want an `OrmClient` and repositories wired through Nest dependency injection.\n\n```ts\nimport { Module } from \"@nestjs/common\";\nimport { EzormModule } from \"@ezorm/nestjs\";\nimport { Todo } from \"./todo.model\";\n\n@Module({\n  imports: [\n    EzormModule.forRoot({\n      connect: { databaseUrl: \"sqlite::memory:\" }\n    }),\n    EzormModule.forFeature([Todo])\n  ]\n})\nexport class AppModule {}\n```\n\n### Optional proxy runtime\n\nUse `@ezorm/runtime-proxy` and `@ezorm/proxy-node` only when you specifically need the managed proxy flow.\n\n- `@ezorm/proxy-node` starts and manages the packaged proxy binary from Node.js.\n- `@ezorm/runtime-proxy` is the HTTP client for that proxy.\n- The managed proxy supports pooled repository CRUD plus `pushSchema` and `pullSchema` for SQLite, PostgreSQL, MySQL, and MSSQL.\n- Relation-aware `query(...)`, `load(...)`, and `loadMany(...)` are not implemented on the pooled proxy runtime yet.\n- For Node-managed proxy usage, prefer `@ezorm/proxy-node` instead of documenting manual Cargo startup as the default workflow.\n\n## Examples And Current Limits\n\nUse these examples when you want a complete application reference:\n\n- NestJS todo backend: [examples/apps/nest-todo-api](examples/apps/nest-todo-api)\n- Next.js todo frontend: [examples/apps/next-todo-web](examples/apps/next-todo-web)\n- Shared todo domain code: [examples/packages/todo-domain](examples/packages/todo-domain)\n\nCurrent limits that matter when you are evaluating the workflow:\n\n- The maintained todo examples default to `sqlite::memory:`, so process restarts clear state.\n- Direct `@ezorm/orm` and `@ezorm/runtime-node` support SQLite, PostgreSQL, MySQL, and MSSQL.\n- Proxy-backed runtimes support pooled CRUD plus `pushSchema` and `pullSchema` for SQLite, PostgreSQL, MySQL, and MSSQL.\n- Relation-aware `query(...)`, `load(...)`, and `loadMany(...)` remain direct-ORM features today.\n- Primary key handling is intentionally simple in v1: application-supplied keys and single-column primary keys only.\n\n## Why Ezorm Is Different\n\nEzorm keeps a few design choices explicit:\n\n- decorated model classes are the source for metadata, validation, indices, and relations\n- repository CRUD stays small, while relation-aware reads move into explicit `query(...)` flows\n- runtime shape is an architectural choice, with a clear split between direct ORM usage and the managed proxy path\n- schema workflows stay explicit through `pushSchema`, `pullSchema`, and CLI migrations driven by config\n\n## Maintainer Release Workflow\n\nUse the committed package manifests as the source of truth for npm releases.\n\n1. Choose the smallest semver bump that matches the change scope.\n2. Update versions with `pnpm version:workspace \u003cversion\u003e`.\n3. Refresh `pnpm-lock.yaml` with `pnpm install --lockfile-only`.\n4. Commit the manifest and lockfile changes together, then merge to `main`.\n5. GitHub Actions publishes the npm packages automatically and pushes `v\u003cversion\u003e` after the publish step succeeds.\n\n## License\n\nEzorm is available under the [MIT License](LICENSE). Copyright (c) 2026 ezorm contributors.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthachp%2Fezorm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthachp%2Fezorm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthachp%2Fezorm/lists"}