Skip to content

feat(database): relational reads with defineRelations and findMany - #110

Open
artile wants to merge 3 commits into
MapleTechLabs:mainfrom
artile:feat/relations
Open

artile wants to merge 3 commits into
MapleTechLabs:mainfrom
artile:feat/relations

Conversation

@artile

@artile artile commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

effect-orm has no relational read API. Each nested read becomes manual joins and grouping in app code.

Closes #79.
Part of #52.

Changes

  • Db.defineRelations(tables, (r) => ...) in /database: r.one and r.many relations, declared over all tables in one callback, as Drizzle v2 defineRelations. A relation names its target by key, so tables can point at each other. The types check the target, the from and to columns, that both are string | number | bigint keys, and that a relation name is not a column name.
  • Db.findMany(schema.table, { where, orderBy, limit, offset, with, crossTenant }): every column of the table plus each relation in with, nested to any depth. Nested levels take where, orderBy and with. The row type follows the with tree: many is an array, one is a row or null.
  • One statement per level: WHERE <to> IN (<keys from the level above>), then grouping in memory. A level with no keys sends nothing. Each level is an ordinary builder query, so each level decodes through its own row codec (a nested timestamptz is a DateTime.Utc) and has its own tenantScope.
  • Tenant scope on every level: when both tables declare a tenantColumn and the parent rows carry one tenant, the nested query gets <tenant column> = <that tenant>. Otherwise the level is cross-tenant, and requireTenantScope refuses it. crossTenant: true declares every level.
  • design/relations.md: why one statement per level and not JSON aggregation, and the tenant rule.
  • docs/relations.md, a typechecked example, and reference / README rows. A minor changeset.

Left out

  • JSON aggregation (jsonb_agg, one statement). Each column type would need a JSON codec per dialect: int8 loses precision as a JSON number, timestamptz and bytea change format. It can come later as a second strategy behind the same API. See design/relations.md section 3.
  • columns per level, a limit per parent on a nested level, composite keys, and findFirst. Smallest slice first.
  • _count and some / every / none (Relation counts and filters: _count, some, every, none #80).
  • param.* in a nested where: findMany takes no params.
  • Chunking of long IN lists (Postgres has a 65,535 bound-value limit). This belongs with Bound-parameter limits: chunk IN lists and multi-row inserts #57.
  • SQLite, MySQL and D1: not on main. The SQL is a plain IN, so no dialect work is expected.
  • ClickHouse is not left out: relations need no foreign keys, and one live test runs the loader there.

Validation

  • bun run build: pass.
  • bun run typecheck: pass (includes src/database/relations.test-d.ts).
  • bunx vitest run --testTimeout=60000 --hookTimeout=60000 with live ClickHouse 26.8.2.7 (EFFECT_ORM_CLICKHOUSE_* set): 62 files, 1043 tests passed, 0 skipped.
  • node scripts/check-doc-citations.mjs, node scripts/check-exports-documented.mjs, node scripts/check-doc-examples.mjs: pass.
  • bun scripts/check-package.ts: pass.
  • Upstream CI for a fork PR waits for a maintainer to approve the run.

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

@coderabbitai

coderabbitai Bot commented Oct 25, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 31 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 9871a919-a86d-4f5b-8335-55e88dbab4e3
Commits

Reviewing files that changed from the base of the PR and between c5fa679 and 1311ae8.

Files selected for processing: 11
  • .changeset/relational-reads.md
  • README.md
  • design/relations.md
  • docs/README.md
  • docs/reference.md
  • docs/relations.md
  • src/database.ts
  • src/database/relations.test-d.ts
  • src/database/relations.test.ts
  • src/database/relations.ts
  • tests/relations.clickhouse.test.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Relational queries: nested with loading, decoded by Schema

1 participant