Skip to main content
Start with the Effect Quickstart for installation and a runnable first program. 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 with const 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.
A timeout does not prove that a write was unapplied. There are no automatic retries. Apply retry policies deliberately, especially for writes that might have reached MongoDB before an error was returned. A client-close failure is retained as a finalizer defect.

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:
Streams use the existing keyset batch iteration: ascending id, homogeneous BSON id types, and no query sort/skip/limit. Use 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:
The test fixture remains responsible for cleaning its database and closing its client. Application services can also replace their repository dependencies with test Layers. See testing for the ordinary Promise API’s mock adapter.

In-process mock databases

Pass a Db opened by mongo-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. Use Schema.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.