esix/effect supports Effect 3.21.3 and later 3.x
versions. The Promise API remains available from esix without installing
Effect.
Queries and missing records
Define a reusable model facade withconst users = Esix.model(User) at module
scope. Its operations return Effect<A, EsixOperationError, Esix> and resolve
the database from the Layer provided at execution. If you already extracted the
service with yield* Esix, db.model(User) remains available and returns
operations without an additional environment requirement. Fluent methods return
independent query descriptions: deriving one query never changes another. Every
execution constructs a fresh query, and mutable query inputs are copied when the
query is described.
find, findBy, findOne, and first return Option<User>. An absent record
is Option.none(), not a query failure.
Filtering includes
where, orWhere, membership/null checks, sorting,
pagination, and text search. Terminal methods also include count, pluck,
distinct, sum, average, min, max, percentile, aggregate,
increment, decrement, and query delete. Field types and numeric increment
restrictions are preserved. Raw query objects retain the existing sanitization
rules.
Writes and relationships
create and firstOrCreate preserve model defaults and wasRecentlyCreated.
Instance writes execute only when their Effect runs. Query delete() deletes
matching records; remove(model) deletes one model.
hasMany returns an Effect query; hasOne and belongsTo return Effects of
Option. Their optional foreign/local/owner key arguments follow the
relationship guide.
Hydrated instances retain their database connection. Their original Promise
methods, including relationship helpers, use that same connection. Custom
methods remain Promise methods; the adapter does not rewrite them. Static
methods such as User.find() continue using the default global connection.
Saving a model through a facade for a different connection fails instead of
moving the model silently. New, unbound instances can be saved through their
facade.
Typed errors
EsixConnectionError describes Layer acquisition failure. Terminal operations
fail with EsixOperationError, a union of:
Use
Effect.catchTag to handle specific outcomes. Errors carry operation,
collection, and an optional numeric MongoDB code. The original error is
retained as the non-enumerable native cause; it is excluded from Schema
encoding so arbitrary driver objects do not cross serialization boundaries.
Generated messages omit driver text, which may contain credentials or document
values. Treat raw causes as potentially sensitive if you log them.
Configuration and connection lifetime
Esix.layer({ url, database?, clientOptions? }) owns a native MongoDB client.
The URL accepts a string or Redacted<string> and is held redacted internally.
Options are explicit and do not change DB_URL, DB_DATABASE, or DB_ADAPTER
used by the Promise API. Native Effect connections always use native driver
behavior, even if the global adapter is set to mock.
To read environment configuration with Effect:
layerConfig accepts Effect Config values for its options. Configuration
failures remain typed ConfigErrors; missing DB_URL fails before a client is
allocated. Plain layer continues to accept already-resolved options.
Provide one Layer for the application’s lifetime. Owned connections close when
their scope ends; captured models and queries cannot reopen a closed connection.
Separate Layer instances own separate clients. For a client managed elsewhere,
use Esix.layerFromDb(db); it never connects or closes the borrowed client’s
resources.
Streaming
Use Stream combinators instead of a Promise callback to process batches:Stream.take to stop consumption. Each run gets a fresh iterator. Ending
consumption closes the iterator and prevents further batches.
Interruption cannot cancel or roll back an already-running MongoDB Promise.
Iterator cleanup may wait for a pending batch; connection acquisition likewise
settles before cleanup. Configure driver timeouts when bounded waits matter.
Testing with a borrowed database
Use a disposable database and provide it as a Layer. This example accepts a Db owned by the test fixture:In-process mock databases
Pass a Db opened bymongo-mock with
Esix.layerFromDb(mockDb, { adapter: 'mock' }). The explicit adapter selects
the existing mock-compatible BSON and aggregation paths; omitting it means a
native MongoDB Db. The caller closes the mock Db after the test. This provides
in-process tests without another connection-owning mock factory.
Runtime validation
Results are hydrated model instances, not Schema-decoded data. TypeScript field declarations and class defaults do not validate stored documents at runtime. UseSchema.decodeUnknown with your application’s schema at a boundary when runtime
validation is required. Query sanitization protects query construction; it does
not validate result shapes.