Skip to content

feat!: move knex loaders into entity-database-adapter-knex package#410

Merged
wschurman merged 1 commit intomainfrom
wschurman/01-31-feat_move_knex_loaders_into_entity-database-adapter-knex_package
Feb 9, 2026
Merged

feat!: move knex loaders into entity-database-adapter-knex package#410
wschurman merged 1 commit intomainfrom
wschurman/01-31-feat_move_knex_loaders_into_entity-database-adapter-knex_package

Conversation

@wschurman
Copy link
Member

@wschurman wschurman commented Feb 1, 2026

Why

Part 3 of #407.

How

This is the main PR of the stack. It creates a mechanism for "installing" the extension methods on required objects, and then installs them and tests them.

The goal is to make it better self-contained about what is core in the library and guaranteed to be efficient (dataloader'd and cached) and what is not (raw SQL and SQLFragment loads in a future PR).

This is becoming more critical in a time of agentic coding where having a discrete set of loaders that can be linted separately and instruct agents to run EXPLAIN ANALYZE in raw cases.

Test Plan

Full test coverage.

@codecov
Copy link

codecov bot commented Feb 1, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (567bb61) to head (2825aae).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##              main      #410    +/-   ##
==========================================
  Coverage   100.00%   100.00%            
==========================================
  Files           97       103     +6     
  Lines        13171     13590   +419     
  Branches       644      1125   +481     
==========================================
+ Hits         13171     13590   +419     
Flag Coverage Δ
integration 18.00% <70.41%> (+11.03%) ⬆️
unittest 97.35% <97.39%> (+0.16%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@wschurman wschurman force-pushed the wschurman/01-31-feat_move_knex_loaders_into_entity-database-adapter-knex_package branch 9 times, most recently from c14d00f to 9f97b92 Compare February 1, 2026 18:00
@wschurman wschurman requested a review from ide February 4, 2026 03:38
@wschurman wschurman marked this pull request as ready for review February 4, 2026 03:38
@wschurman wschurman force-pushed the wschurman/01-31-feat_move_knex_loaders_into_entity-database-adapter-knex_package branch from 9f97b92 to 648de2b Compare February 4, 2026 21:29
@wschurman wschurman force-pushed the wschurman/01-30-chore_move_knex-specific_loader_logic_out_of_entitydatamanager branch from 0be9f1d to 733b363 Compare February 4, 2026 21:29
Copy link
Member

@ide ide left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little uncertain about the install mechanism but TypeScript does appear to support it.

One potential improvement is to pass in the classes we want the extensions to modify. So instead of

function install() {
  SomeClass.knexLoader = ...
}

We'd do:

function install(SomeClass) {
  SomeClass.knexLoader = ...
}

This is (1) slightly more testable (though test frameworks do allow module mocking) and (2) sets expectations around what is supposed to be modified or not by the installer.

@wschurman wschurman force-pushed the wschurman/01-30-chore_move_knex-specific_loader_logic_out_of_entitydatamanager branch from 733b363 to b1423c1 Compare February 9, 2026 23:28
@wschurman wschurman force-pushed the wschurman/01-31-feat_move_knex_loaders_into_entity-database-adapter-knex_package branch from 648de2b to e0893f6 Compare February 9, 2026 23:28
Copy link
Member Author

I like this proposal. Will explore in a follow-up PR!

Copy link
Member Author

wschurman commented Feb 9, 2026

Merge activity

  • Feb 9, 11:36 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Feb 9, 11:41 PM UTC: Graphite rebased this pull request as part of a merge.
  • Feb 9, 11:44 PM UTC: @wschurman merged this pull request with Graphite.

wschurman added a commit that referenced this pull request Feb 9, 2026
# Why

This is a long stack, but the goal is to make a place to put postgres-specific loading logic to avoid polluting the core dataloader-based library with logic specific to knex/postgres. And potentially even postgres-specific mutation logic but that's way down the road.

The best place for this stuff is in the `entity-database-adapter-knex` library. Therefore, the strategy is (in PR order):
1. Move existing knex/postgres-specific loader methods to a new loader, `knexLoader`. I'm also open to calling this a "Postgres Loader" since the `entity-database-adapter-knex` package already uses postgres-specific queries (and more are added here since this stack eventually produces raw queries) within knex even though technically knex is database agnostic.
1. Move knex-specific logic out of EntityDataManager, move it to its own EntityKnexDataManager.
1. Move these two now-separated pieces into the `entity-database-adapter-knex` package. This requires creating a "plugin" system for database adapters, where when they are included their methods are added to the prototype so that seamless loading continues to work.
1. Add a codemod for these three pieces.
1. Add the first new feature to entity loading, load by tagged template string, which creates a better/safer way to express raw queries for loading entities.

Future other things that could be added here (after thorough thought on authorization) are:
- Pagination
- Create, update, delete by tagged template string
- Batch creation
- Upsert (maybe)

# For Reviewers

The most critical things to assess are:
- "install" method in #410
- sql tagged template API usability in #414

# How

This is part 1. It refactors the loaders by moving `loadManyByFieldEqualityConjunctionAsync`, `loadFirstByFieldEqualityConjunctionAsync`, and `loadManyByRawWhereClauseAsync` to a separate loader.

The new pattern for these is:
```
await PostgresTestEntity.knexLoader(vc1).loadManyByRawWhereClauseAsync(...)
```

# Test Plan

This has full coverage in new tests.
@wschurman wschurman changed the base branch from wschurman/01-30-chore_move_knex-specific_loader_logic_out_of_entitydatamanager to graphite-base/410 February 9, 2026 23:37
@wschurman wschurman changed the base branch from graphite-base/410 to main February 9, 2026 23:40
@wschurman wschurman force-pushed the wschurman/01-31-feat_move_knex_loaders_into_entity-database-adapter-knex_package branch from e0893f6 to 2825aae Compare February 9, 2026 23:41
@wschurman wschurman merged commit 6fce3a7 into main Feb 9, 2026
5 checks passed
@wschurman wschurman deleted the wschurman/01-31-feat_move_knex_loaders_into_entity-database-adapter-knex_package branch February 9, 2026 23:44
wschurman added a commit that referenced this pull request Feb 9, 2026
# Why

This adds a codemod for #407-#410 changes.

# How

Replace `.loader` calls with `.knexLoader` where applicable.

# Test Plan

Run the test.
wschurman added a commit that referenced this pull request Feb 16, 2026
# Why

An idea proposed by @ide in #410 (review).

This makes it more testable and clearer about what objects installations are "allowed" to augment.

# How

Pass in the classes explicitly.

# Test Plan

`yarn tsc`
wschurman added a commit that referenced this pull request Feb 16, 2026
# Why

Asked claude extended thinking to analyze the `installExtensions` monkey-patching strategy we went with in  #410 and compare it to other typescript ORM libraries.

It said essentially that it was ok, but most modern libraries choose not to do it that way, and instead recommended "free functions" (TIL about that term) to instantiate the loaders. (`knexLoader(BlahEntity, vc)`)

Working with it even more, I came up with an even better method on top of free functions to keep the `BlahEntity.knexLoader(vc)` syntax. The strategy is to:
1. Convert to free functions (this PR).
2. Add `PostgresEntity`/`ReadonlyPostgresEntity` subclasses that application entities can extend that implement the static extension methods.
3. Revert the codemod change from this PR to keep the codemod to prefer the `BlahEntity.knexLoader(vc)` syntax since most applications should use the subclasses (though it's not strictly necessary if they prefer the free function at callsites).
4. Revert the codemod in the integration tests to express better syntax pattern.

# How

Claude did most of this, but broadly speaking the strategy is to have the free functions, and then instead of patching the prototypes/classes for singleton caching, use a WeakMap where the core package singleton is the key and the value is the new knex-specific object.

# Test Plan

Run all tests.
wschurman added a commit that referenced this pull request Feb 17, 2026
# Why

In #410 we split out `EntityLoaderUtils` into two classes:
- EntityInvalidationUtils - functions for invalidating entity caches from application code when underlying data is mutated outside of the entity framework
- EntityConstructorUtils - functions for constructing and authorizing entities.

Originally, these were both public due to them being the same class. Now, with them separated, we can better restrict access to entity construction which should only be done within the framework itself.

# How

Make constructor utils private everywhere except EntityLoaderFactory, which is deep enough in the abstraction that it's not immediately usable by top-level entity APIs (static methods on Entity/ReadonlyEntity, loader classes).

# Test Plan

`yarn tsc`, CI
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.

2 participants

Comments