@sealant/db Effect service layer
Purpose
This page explains how @sealant/db composes PostgreSQL access with an Effect service tag and a
live integration layer.
It is the step-by-step reference for packages/db/src/service.ts and how it uses
packages/db/src/client.ts.
Architecture at a glance
client.tsis the low-level Postgres integration adapter.service.tsdefines the Effect service contract and integration layers.- Consumers should depend on the service tag contract and provide layers at boundaries.
Step-by-step flow
-
Config tag defines required inputs
DatabaseServiceConfigis a tag forDatabaseClientOptions(currently centered onconnectionString). -
Service tag defines API surface
DatabaseServiceTagis the runtime contract. It currently exposes only one capability:db(typed Drizzle instance). -
Acquire phase reads config and opens connection
makeDatabaseClientResourcereadsDatabaseServiceConfigand callscreateDatabaseClient(options)fromclient.ts. -
Release phase closes the pool
releaseDatabaseClientResourcecallscloseDatabaseClient(client)to drain and close the Postgres pool. -
Live layer manages resource lifecycle
databaseServiceLiveLayerusesLayer.scoped+Effect.acquireReleaseso opening and closing the DB client is automatic and bound to layer scope. -
Configured layer wires config into live layer
databaseServiceLayer(options)buildsdatabaseServiceConfigLayer(options)and provides it todatabaseServiceLiveLayer. -
Env helper composes from runtime env
databaseServiceFromEnvLayer(env)mapsDATABASE_URLinto client options, then delegates todatabaseServiceLayer(...).
Why client.ts and service.ts are separate
client.tskeeps integration details (pgpool, Drizzle construction, probe query).service.tskeeps dependency injection shape (tags, layers, scope).- This separation lets repositories stay stable while runtime composition evolves.
Typical usage pattern
Provide once at an app boundary, then consume the service in effects.
import { Effect } from "effect";
import { DatabaseServiceTag, databaseServiceFromEnvLayer } from "@sealant/db";
const program = Effect.gen(function* () {
const dbService = yield* DatabaseServiceTag;
return yield* dbService.db.query.repositories.findMany();
});
const runnable = program.pipe(Effect.provide(databaseServiceFromEnvLayer()));Current notes
- Some existing app/package call sites still use imperative DB factories while migration is in progress.
- The target architecture is service-tag contract first, integration in layers, and boundary-level composition.