Roadmap
Egon ORM is under construction: nothing is published to npm yet.
Built
| Feature | Pages |
|---|---|
| Schemas, types, associations and embeds | Schemas, Types, Associations, Embeds |
| Changesets, validation (built-in rules, Zod and any Standard Schema library), constraints, nested changesets | Changesets, Validation, Constraints, Nested changesets |
| The query builder: joins, subqueries, CTEs, set operations, window functions, fragments, named parameters | Queries, Expressions, Advanced queries |
| The repo: reads, writes, upserts, bulk writes, preloads, streams, transactions, batches, the test sandbox | Repo, Transactions, Batch, Testing |
| Postgres, SQLite and MySQL adapters | Adapters, Dialect support |
Migrations, their runner and the egon command | Migrations, CLI |
Before the first release
| Work | Notes |
|---|---|
| Performance | Benchmarks against the raw drivers. No numbers are published until then. |
| Release hardening | Publishing the packages, @egon-orm/cli included, versioning and the changelog. |
Planned after the first release
| Feature | Notes |
|---|---|
| Faster editor completions in large models | After an edit, completions inside a query callback take about 40 ms plus 1.1–1.2 ms per schema reachable through associations, so they pass 200 ms at about 140–170 connected schemas on the machine measured, sooner on slower ones. The likely cause is that the association and preload types infer every static member of each reachable schema; the plan is to make them read the schema's association metadata and expand only where preload is used. Until then, declaring the return type of changeset methods lowers the cost (to about 114 ms at 200 schemas in one measurement); include in it the error keys the validations add, as in Changeset<typeof User, boolean, "passwordConfirmation">, or errorMessages() loses them. |
| Lateral joins | joinLateral, leftJoinLateral. |
VALUES lists as a query source | from(values(...), "v"). |
| Table-valued fragments | Such as generate_series as a source. |
| Index hints | MySQL. |
| Data-modifying CTEs | UPDATE or DELETE inside a WITH. |
| Named server-side prepared statements | For prepare. |
| Form helpers for nested changesets | Sort and drop parameters, and index-keyed objects in place of arrays. |
| Other databases through a custom dialect | |
| Built-in multi-tenancy | Tenants as a column of shared tables, with the tenant given explicitly on the repo or the call: a query or write on a tenant-scoped schema class without a tenant is refused, inserts fill in the tenant, and preloads stay within it. A database schema per tenant stays a recipe, using the repo's schema option. |
| Built-in soft deletes | Support for what the soft-deletes recipe does by hand: a schema option for the deleted-at field, soft delete and restore in the repo, and an explicit opt-in to leave deleted rows out of a query. No default scope applies by itself. |
| Postgres row-level security | Operations in migrations to enable row-level security on a table and to create and drop policies, each with the operation that undoes it, for down; settings such as the application's user id (app.user_id) given per transaction, always transaction-scoped and bound; a new row a policy refuses (SQLSTATE 42501, "new row violates row-level security policy") reported as an error of its own. Rows a policy hides are not errors: a read leaves them out and a write affects no rows. Postgres only. |
| Query observability | Built on the query event of repo.on, which already reports each statement's SQL and timings: slow-query detection, OpenTelemetry tracing and warnings for N+1 query patterns. |
| A browser entry point | egon-orm/client, with cast and the validations, so a form can run the same casts and field validations in the browser, without bundling the query builder or the repo. Constraints and checks that ask the database stay on the server. |
| Test factories | Beside the test sandbox: build valid rows for a schema, override single fields, create association graphs, and generate deterministic test data. For integration tests in large applications. |
Not planned
These are deliberate choices, not gaps:
- Active-record methods such as
user.save(). Rows are data. - Lazy loading. Associations are loaded only when asked for.
- An identity map or unit of work. Two queries for the same row return two objects.
- Implicit transactions. Nothing joins a transaction you did not open: only calls on the transaction object are inside it. (A single call that writes several statements, a parent with its children or a chunked
insertAll, is atomic on its own.) - Generating migrations from schema differences. Migrations are written by hand.
- Non-SQL stores.

