New:Microsoft Teams Notifications Are Now Available in Socket.Learn more
Get Started

@fluojs/drizzle

Package Overview
Dependencies
Maintainers
1
Versions
12
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@fluojs/drizzle

Drizzle ORM integration for Fluo with ALS transaction context, async module factory, and optional dispose hook.

latest
Source
npmnpm
Version
2.1.0
Version published
Weekly downloads
68
1600%
Maintainers
1
Weekly downloads
 
Created
Source

@fluojs/drizzle

English 한국어

Node.js-only Drizzle ORM integration for fluo with a transaction-aware database wrapper and an optional dispose hook.

Table of Contents

Installation

npm install @fluojs/drizzle drizzle-orm@^0.45.2
# Install the driver for your Drizzle adapter as well, for example:
npm install pg

@fluojs/drizzle requires Drizzle ORM >=0.45.2. Consumers using an older Drizzle ORM release must upgrade the peer and refresh their lockfile before adopting this major @fluojs/drizzle release. The fluo integration API is unchanged, but applications should run their driver-specific query and migration tests against the upgraded ORM.

Runtime Support

The root @fluojs/drizzle package requires Node.js >=24.0.0 <27. It imports Node's node:async_hooks module to maintain the ambient transaction context and its package manifest declares that package-owned support contract. Upgrade Node 20 and Node 22 hosts to Node.js >=24.0.0 <27; Node versions below 24 and Node 27+ are unsupported.

Drizzle ORM itself can target drivers such as Bun SQL or Cloudflare D1, but those driver runtimes are outside this fluo wrapper until a non-Node transaction-context adapter is documented.

Non-Node runtimes should not import the root package. For Bun, Deno, Cloudflare Workers, or other non-Node Drizzle drivers, register the raw Drizzle driver handle behind application-owned fluo providers such as { provide, useFactory } or { provide, useValue }, then inject that application token into repositories. The canonical package chooser/surface docs and the Bun/Cloudflare book chapters show those raw-provider patterns.

When to Use

  • when an application running Node.js >=24.0.0 <27 needs Drizzle to participate in the same module, DI, and lifecycle model as the rest of the app
  • when repositories need a single current() seam that switches between the root handle and the active transaction handle
  • when application shutdown should also run an explicit cleanup hook for the underlying driver resources

Quick Start

import { ConfigModule, ConfigService } from '@fluojs/config';
import { Module } from '@fluojs/core';
import { DrizzleModule } from '@fluojs/drizzle';
import { drizzle } from 'drizzle-orm/node-postgres';
import { Pool } from 'pg';

@Module({
  imports: [
    ConfigModule.forRoot({
      global: true,
      processEnv: {
        DATABASE_URL: process.env.DATABASE_URL,
      },
    }),
    DrizzleModule.forRootAsync({
      inject: [ConfigService],
      useFactory: async (config: ConfigService) => {
        const pool = new Pool({
          connectionString: config.getOrThrow<string>('DATABASE_URL'),
        });

        return {
          database: drizzle(pool),
          dispose: async () => {
            await pool.end();
          },
        };
      },
    }),
  ],
})
export class AppModule {}

forRootAsync(...) accepts only inject and useFactory for its factory dependencies; it does not discover NestJS imports, useClass, useExisting, or decorator metadata. Its generated async module has no imports, so a token exported only by a sibling module or by a parent module's import is not visible to the options provider. Register factory dependencies through a global module instead. The ConfigModule.forRoot(...) registration above exports ConfigService globally by default; global: true is shown explicitly because that global export makes ConfigService visible to the generated async Drizzle module. For another token, make the module that owns and exports it global before bootstrap rather than relying on the importing application's providers or imports.

Common Patterns

Service Transaction Boundary (@Transaction)

The @Transaction() decorator is the recommended way to define transaction boundaries in your service layer. It ensures that all repository calls made within the decorated method share the same Drizzle transaction.

import { Inject } from '@fluojs/core';
import { Transaction, DrizzleDatabase, type DrizzleDatabaseFacade } from '@fluojs/drizzle';
import { drizzle } from 'drizzle-orm/node-postgres';
import { users, profiles } from './schema';

type AppDatabase = ReturnType<typeof drizzle>;

@Inject(DrizzleDatabase)
export class UserRepository {
  constructor(private readonly db: DrizzleDatabaseFacade<AppDatabase>) {}

  async create(data: any) {
    // The facade type exposes standard Drizzle methods.
    // When called inside @Transaction(), they automatically participate in the ambient transaction.
    const [user] = await this.db.insert(users).values(data).returning();

    if (!user) {
      throw new Error('User insert did not return a row.');
    }

    return user;
  }

  async initProfile(userId: string) {
    return this.db.insert(profiles).values({ userId });
  }
}

@Inject(UserRepository)
export class UserService {
  constructor(private readonly repo: UserRepository) {}

  @Transaction()
  async onboardUser(dto: any) {
    const user = await this.repo.create(dto);
    await this.repo.initProfile(user.id);
    return user;
  }
}

Calls to @Transaction() methods are reentrant. If a decorated method calls another decorated method, they share the same underlying Drizzle transaction.

By default, @Transaction() selects its target with a small host-object heuristic: it first checks this.db, then direct properties on the decorated instance, then a nested .db property on those values, and uses the first value that exposes a transaction(...) method. If none of those candidates match, the decorated instance itself becomes the transaction target. This keeps common constructor(private readonly db: DrizzleDatabase<...>) services and self-contained facade hosts concise, but services with more than one Drizzle wrapper should not rely on property order. Pass an explicit accessor such as @Transaction((self) => self.ordersDb) or @Transaction((self) => self.analyticsDb, options) whenever the decorated host owns multiple transaction-capable clients or wraps a repository that also exposes .db.

Manual Transactions and current()

The DrizzleDatabase provides a current() method that returns the active transaction handle if inside a transaction scope, or the root handle otherwise. Use this as an escape hatch when you need to pass the handle to external utilities or perform advanced manual transaction plumbing.

import { DrizzleDatabase } from '@fluojs/drizzle';
import { drizzle } from 'drizzle-orm/node-postgres';
import { users } from './schema';

type AppDatabase = ReturnType<typeof drizzle>;

export class AdvancedRepository {
  constructor(private readonly db: DrizzleDatabase<AppDatabase>) {}

  async customOperation() {
    const tx = this.db.current();
    // Use tx for operations that fluo doesn't automatically wrap,
    // or when passing to an external utility that expects a Drizzle database handle.
    return tx.select().from(users);
  }
}

Use db.transaction() for manual transaction blocks:

await this.db.transaction(async () => {
  const current = this.db.current();

  await current.insert(users).values(user);
  await current.insert(profiles).values(profile);
});

Nested calls reuse the active transaction boundary. If a nested call passes native transaction options while a boundary is already active, the package rejects those nested options instead of silently changing the existing transaction. requireAfterCommit in the separate boundary is a capability requirement on the current boundary, not a native option.

When database.transaction(...) is unavailable and strictTransactions is false (the default), transaction() and requestTransaction() intentionally fail open (fail-open fallback) by running the callback directly against the root handle. This is useful for local fakes, read-only adapters, or gradual migrations, but it is not atomic and should not be treated as a real database transaction. Set strictTransactions: true in production paths that require rollback guarantees; startup and readiness diagnostics then surface missing database.transaction(...) support and transaction helpers throw instead of silently running without a transaction. Fail-open callbacks still run in a root-handle ALS context, so nested helpers reuse the fallback boundary, nested request work inherits the ambient request AbortSignal, and shutdown drains nested direct execution before disposal. This context preservation does not add rollback atomicity.

Async work created inside a transaction can inherit its ALS context even when it runs after the owning transaction has committed, rolled back, or otherwise settled. A later transaction(...) or requestTransaction(...) call from that inherited continuation is treated as a fresh lifecycle-tracked root instead of reusing the closed transaction handle. Shutdown drains that fresh root before dispose(database), while calls that begin before the owner settles continue to share the active boundary.

Choosing Rollback from a Result

First register native rollback confirmation. Include the module returned by this complete helper in application imports. A forRootAsync factory can return the same rollbackObserver; directly constructed wrappers/facades accept it in their existing runtime-options object too. It is not a native driver option.

import { createDrizzleRollbackObserver, DrizzleModule } from '@fluojs/drizzle';
import { drizzle } from 'drizzle-orm/node-postgres';
import { Pool } from 'pg';

export function resultTransactions(databaseUrl: string) {
  const pool = new Pool({ connectionString: databaseUrl });
  const observed = createDrizzleRollbackObserver(pool);
  const database = drizzle(observed.client);
  type NativeTransaction = Parameters<Parameters<typeof database.transaction>[0]>[0];
  type NativeOptions = Parameters<typeof database.transaction>[1];
  return DrizzleModule.forRoot<typeof database, NativeTransaction, NativeOptions>({
    database,
    rollbackObserver: observed.rollbackObserver,
    dispose: () => pool.end(),
  });
}

Use the shouldRollback example below with this configuration. Existing wrappers without an observer still support ordinary transactions, but Result opt-in rejects before callbacks. These capability types and errors are also package root exports.

  • createDrizzleRollbackObserver(...): the public native observation helper above.
  • TransactionRollbackObserver: advanced capability contract whose run<T>(callback): Promise<T> opens an owner observation scope and whose beginAttempt(transaction) binds a native attempt.
  • TransactionRollbackObservation: confirmRollback(): true | Promise<true> returns only independently confirmed rollback success and reports failure/uncertainty by throwing. Do not implement it using sentinel identity or a no-op.
  • TransactionRollbackUnconfirmedError: positive native rollback confirmation is absent, so a normal Result cannot be returned.

shouldRollback is an opt-in synchronous predicate for a callback's normal return value. This complete consumer helper accepts a registered Node PostgreSQL wrapper. persist performs DB work through that same wrapper's current() or facade. Result is consumer-defined; Fluo introduces no global result shape.

import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
import type { DrizzleDatabase, TransactionBoundaryOptions } from '@fluojs/drizzle';

type Result<T> =
  | { readonly ok: true; readonly value: T }
  | { readonly ok: false; readonly error: string };

export function persistWithResult<T>(
  db: DrizzleDatabase<NodePgDatabase>,
  persist: () => Promise<Result<T>>,
): Promise<Result<T>> {
  const boundary: TransactionBoundaryOptions<Result<T>> = {
    shouldRollback: (value) => !value.ok,
  };
  return db.transaction(persist, undefined, boundary);
}

Requests retain requestTransaction(fn, signal?, nativeOptions?, boundary?); decorators retain @Transaction(accessorOrOptions?, nativeOptions?, boundary?). Declare the predicate in the third position, for example @Transaction(undefined, undefined, { shouldRollback: (value: Result<string>) => !value.ok }). Do not merge it into native options; nested native options remain rejected.

If the root predicate returns true, the same root value is returned after native rollback and required cleanup succeed. A nested predicate returning true returns the original nested value while marking the shared owner sticky rollback-only. If the root also rejects its own result, its root failure value is returned; otherwise, TransactionRollbackOnlyError reports the first nested failure in readonly result: unknown after rollback. Omission preserves existing exception-based behavior.

A fallback or legacy target that cannot own rollback rejects with TransactionRollbackCapabilityError before the opted-in callback. Both errors are root exports of @fluojs/drizzle; distinguish them with instanceof. Native commit, rollback, and cleanup errors are not hidden by domain values. These errors differ from AfterCommitError, which reports hook failure after commit.

Rollback discards all owner hooks. Ordinary caught nested exceptions do not mark rollback-only, so a final commit retains writes and hooks. Native callback retries receive a fresh owner per attempt. External raw transactions and Redis MULTI/EXEC are unsupported; this adds no savepoint or durability guarantee. Follow the shared-owner contract for the complete rules.

Result rollback also requires a registered rollbackObserver backed by native evidence. A sentinel or local session state is not proof of rollback. Missing capability rejects before the callback; missing or failed confirmation rejects with a native error or TransactionRollbackUnconfirmedError, never a normal Result. The shared contract above specifies registration helpers and supported configurations.

Cache Invalidation After Commit

DrizzleDatabase.afterCommit(...) registers work on an open native transaction owned by the same wrapper. This application function assumes an existing users table (id, name) in ./schema, a registered Node PostgreSQL Drizzle handle, and CacheService from a registered CacheModule.

import type { CacheService } from '@fluojs/cache-manager';
import { DrizzleDatabase } from '@fluojs/drizzle';
import { eq } from 'drizzle-orm';
import type { drizzle } from 'drizzle-orm/node-postgres';
import { users } from './schema';

async function renameUser(
  db: DrizzleDatabase<ReturnType<typeof drizzle>>,
  cache: CacheService,
  id: string,
  name: string,
) {
  return db.transaction(async () => {
    await db.current().update(users).set({ name }).where(eq(users.id, id));
    db.afterCommit(async () => {
      await cache.del(`user:${id}`);
    });
  }, undefined, { requireAfterCommit: true });
}

undefined preserves the existing native-options position. requireAfterCommit: true checks native commit observation capability before the user callback and rejects with AfterCommitCapabilityError when it is unavailable. Omitting it or passing false preserves existing strictTransactions: false and fail-open fallback, but hook registration is rejected in a fallback without a native transaction. The decorator takes the requirement as its final, third argument: @Transaction(undefined, undefined, { requireAfterCommit: true }) or @Transaction((self) => self.db, nativeOptions, { requireAfterCommit: true }).

After successful outer native commit and scope closure, hooks run sequentially in FIFO order outside the ended ALS context. Nested boundaries share the queue without a separate savepoint, so a caught nested exception follows the final outer outcome. Hooks from rollback, failed commit, and discarded callback attempts do not run. Root reads in hooks do not receive the ended handle; new transactions own fresh queues. Registration outside a scope, in a closed scope, or late during drain is rejected. Shutdown waits for hooks before calling dispose(database).

All remaining hooks run after a hook failure, then AfterCommitError is thrown. committed is true, results contains every fulfilled and rejected FIFO outcome, and inherited errors contains all failure reasons. Distinguish it with error instanceof AfterCommitError and do not repeat the already-committed DB write. Cache-only recovery and reconciliation are application policy; Fluo does not retry or roll back the native transaction because of a hook error.

If a requestTransaction(...) boundary has registered hooks or its owning boundary requires requireAfterCommit: true (including a requirement set by a nested call), confirmed commit success takes precedence over request cancellation during native commit after the callback completes. The boundary still waits for all hooks to drain and reports hook failures as AfterCommitError rather than replacing them with the late abort. If all hooks succeed, it returns the original callback result.

Commits from external raw-client transactions, other wrappers, or other connections are not observed. Redis has no supported Fluo-owned commit tracking, and calling Redis from a hook provides no DB+Redis atomicity. This covers successful in-process owner invocations, not a durable outbox or crash/network exactly-once. Follow the Transaction Context Contract for the full contract.

Request-Wide Controller Boundaries

Prefer service-level @Transaction() for business operations. If you are migrating a NestJS controller/interceptor pattern where an entire request must be transactional, call requestTransaction(...) explicitly at the controller, route adapter, or request orchestration boundary and pass the request AbortSignal when one is available:

import { Inject } from '@fluojs/core';
import { Controller, Post, type RequestContext } from '@fluojs/http';
import { DrizzleDatabase } from '@fluojs/drizzle';
import { drizzle } from 'drizzle-orm/node-postgres';
import { CheckoutService } from './checkout.service';

type AppDatabase = ReturnType<typeof drizzle>;

@Controller('/checkout')
@Inject(DrizzleDatabase, CheckoutService)
export class CheckoutController {
  constructor(
    private readonly db: DrizzleDatabase<AppDatabase>,
    private readonly checkout: CheckoutService,
  ) {}

  @Post()
  create(input: CheckoutInput, context: RequestContext) {
    return this.db.requestTransaction(
      () => this.checkout.createOrder(input),
      context.request.signal,
    );
  }
}

DrizzleTransactionInterceptor is a deprecated 1.x compatibility bridge for existing NestJS interceptor imports. It delegates to requestTransaction(...) and forwards the request AbortSignal. New code should move business transaction boundaries to services and reserve explicit requestTransaction(...) for rare controller-level cases where all request work, not just a service method, must share the same boundary. Decorating a controller method with @Transaction() remains a compatibility path when the controller owns an explicit DrizzleDatabase target, but requestTransaction(...) is the clearer request-wide API because it can receive the request AbortSignal directly.

Named clients

Register each additional client with a non-empty name and inject its package-owned token instead of the DrizzleDatabase class token:

const ANALYTICS_DRIZZLE = getDrizzleHandleProviderToken('analytics');

DrizzleModule.forRoot({ database: primaryDatabase });
DrizzleModule.forRoot({ database: analyticsDatabase, name: 'analytics' });

@Inject(ANALYTICS_DRIZZLE)
class AnalyticsService {
  constructor(private readonly analytics: DrizzleDatabase<AnalyticsDatabase>) {}

  @Transaction((self: AnalyticsService) => self.analytics)
  async rebuild() {}
}

getDrizzleDatabaseToken, getDrizzleDisposeToken, getDrizzleOptionsToken, and getDrizzleHandleProviderToken return distinct stable identities for each trimmed name. Named clients are non-global and independently own ALS transaction context, shutdown drain, disposal, and status. A consumer must import a module that exports the matching named token; names do not create isolated runtime containers. Omitting name preserves the existing default tokens, DrizzleDatabase class token, and interceptor behavior.

Shutdown and status contracts

During application shutdown, DrizzleDatabase aborts any still-active request transaction, waits for open request and manual transaction callbacks to settle or roll back, and only then runs the optional dispose(database) hook. This includes fail-open manual transaction(...) callbacks when database.transaction(...) is unavailable and strictTransactions is false, so direct-execution fallbacks still drain before pools or externally managed resources are closed. Transaction continuations that start a new boundary after their inherited owner settles no longer reuse the closed transaction handle. They become independently tracked roots, and shutdown waits for those continuation roots before disposal. Nested requestTransaction(...) calls opened inside an existing request boundary observe the ambient request abort signal while still reusing the active Drizzle transaction. Nested requestTransaction(...) calls opened inside an existing manual transaction boundary also join shutdown settlement tracking without opening a second Drizzle transaction, and their settlement handle remains tracked until the outer manual transaction settles so shutdown drains that outer boundary before dispose(database) runs. The platform status activity count is intentionally shorter lived: once the nested request callback settles, details.activeRequestTransactions is decremented even if the outer manual transaction continues running. New transaction(...) and requestTransaction(...) calls are rejected once shutdown begins, so disposal cannot overtake a late transaction that starts after the shutdown boundary is crossed. For legacy boundaries with neither registered hooks nor an owner-level requireAfterCommit requirement, if the request signal aborts after the request callback has completed but before the underlying Drizzle transaction runner finishes committing or rolling back, requestTransaction(...) waits for that runner to settle first and then rejects with the abort reason. This keeps Drizzle cleanup serialized with request cancellation while making the late request abort visible to the caller instead of returning the completed callback result.

createDrizzlePlatformStatusSnapshot(...) and DrizzleDatabase.createPlatformStatusSnapshot() expose the same contract to diagnostics surfaces:

  • readiness.status is not-ready while Drizzle is shutting down or stopped, and when strictTransactions is enabled without database.transaction(...) support.
  • health.status is degraded while request transactions are draining during shutdown and unhealthy after disposal.
  • details.activeRequestTransactions, details.lifecycleState, details.strictTransactions, and details.supportsTransaction describe the current request transaction and transaction-capability state.
  • details.transactionContext: 'als' identifies the async-local transaction context used by request and service transaction boundaries.
  • ownership.externallyManaged: true and ownership.ownsResources: false mean the package runs your configured dispose hook but does not claim ownership of the underlying driver resources.

Manual Module Composition

Use DrizzleModule.forRoot(...) / forRootAsync(...) to register Drizzle. When you need to compose Drizzle support inside a custom defineModule(...) registration, import the module entrypoint there as well.

import { defineModule } from '@fluojs/runtime';
import { DrizzleModule } from '@fluojs/drizzle';

const database = {
  transaction: async <T>(callback: (tx: typeof database) => Promise<T>) => callback(database),
};

class ManualDrizzleModule {}

defineModule(ManualDrizzleModule, {
  imports: [DrizzleModule.forRoot({ database })],
});

Public API Overview

Transactions and After-Commit Work

APIInputs and completion
transaction(fn, nativeOptions?, boundary?): Promise<T>Appends boundary after the existing async fn and Drizzle options. The commit path returns the original result after hook drain; opt-in rollback follows the return and error rules above.
requestTransaction(fn, signal?, nativeOptions?, boundary?): Promise<T>Preserves the existing request AbortSignal and native-options positions, with boundary last.
afterCommit(callback: AfterCommitCallback): voidRegisters work in an open native scope without running it immediately. Unsupported boundaries, no native transaction, missing scope, and closed scopes reject registration.
Transaction(accessorOrOptions?, nativeOptions?, boundary?)Preserves the existing interpretation of the first argument as an accessor or native options. The second native-options argument keeps its existing accessor-only role; Fluo boundary is always third.

Import these values and types from the root @fluojs/drizzle package.

import {
  AfterCommitCapabilityError,
  AfterCommitError,
  TransactionRollbackCapabilityError,
  TransactionRollbackOnlyError,
  type AfterCommitCallback,
  type TransactionBoundaryOptions,
} from '@fluojs/drizzle';

AfterCommitCallback is () => void | Promise<void> and TransactionBoundaryOptions<T = unknown> is { readonly requireAfterCommit?: boolean; readonly shouldRollback?: (value: T) => boolean }. AfterCommitCapabilityError rejects missing required native commit capability or hook registration on an unsupported boundary. AfterCommitError extends AggregateError exposes readonly committed = true, results: readonly PromiseSettledResult<void>[], and all failures in inherited errors. boundary?: TransactionBoundaryOptions<T> is Fluo-only; do not merge it into native options. See Choosing Rollback from a Result for the failure conditions and result of TransactionRollbackCapabilityError and TransactionRollbackOnlyError.

  • DrizzleModule.forRoot(options) / DrizzleModule.forRootAsync(options)
  • DrizzleDatabase
  • DrizzleDatabaseFacade<TDatabase>
  • DrizzleTransactionInterceptor (deprecated 1.x request-transaction compatibility bridge)
  • Transaction
  • DRIZZLE_DATABASE, DRIZZLE_DISPOSE, DRIZZLE_HANDLE_PROVIDER, DRIZZLE_OPTIONS
  • getDrizzleDatabaseToken(name?), getDrizzleDisposeToken(name?), getDrizzleHandleProviderToken(name?), getDrizzleOptionsToken(name?)
  • DrizzleDatabase.createFacade(...) (compatibility-only provider wiring helper; prefer DrizzleModule.forRoot(...) / forRootAsync(...) for application registration)
  • createDrizzlePlatformStatusSnapshot(...)
  • DrizzleDatabaseLike
  • DrizzleModuleOptions
  • DrizzleHandleProvider

DRIZZLE_HANDLE_PROVIDER is an alias token for the lifecycle-aware DrizzleDatabase wrapper. Health integrations such as @fluojs/terminus use this token to read createPlatformStatusSnapshot() before falling back to raw database pings.

DrizzleModule exports DRIZZLE_DATABASE, DRIZZLE_DISPOSE, and DRIZZLE_OPTIONS for importing modules. DRIZZLE_DATABASE injects the configured raw Drizzle handle, so it bypasses the lifecycle-aware facade and ambient transaction-handle selection. Prefer DrizzleDatabase or DrizzleDatabaseFacade for application repositories; inject the raw token only for integrations that require the configured driver handle. DRIZZLE_DISPOSE exposes the configured optional cleanup hook, and DRIZZLE_OPTIONS exposes normalized runtime options.

Use DrizzleDatabase<TDatabase> when a provider only needs wrapper methods such as current(), transaction(...), requestTransaction(...), or createPlatformStatusSnapshot(). Use DrizzleDatabaseFacade<TDatabase> for repository injections that call Drizzle query methods directly; the facade forwards those calls to the active transaction handle when one exists and to the root handle otherwise. DrizzleDatabase.createFacade(...) is retained as a low-level compatibility helper for module-provider wiring; application code should prefer DrizzleModule.forRoot(...) / forRootAsync(...).

Transaction is a standard TC39 method decorator for service-layer transaction boundaries. It resolves a transaction-capable target from the decorated host by checking this.db, then direct properties, then nested .db properties, then falling back to the decorated instance itself; it also accepts an accessor for explicit client selection and can forward Drizzle transaction options to the outer boundary.

DrizzleModule

  • DrizzleModule.forRoot(options) / DrizzleModule.forRootAsync(options)
  • forRootAsync(...) accepts DI-aware Drizzle options whose factory returns the database/dispose/transaction settings; pass global on the top-level async registration when the providers should be visible globally.
  • forRootAsync(...) resolves options once per application container. Reusing the same module definition across tests or multi-app processes creates isolated database/dispose results for each container instead of sharing a memoized factory result.
  • Supports strictTransactions: true to throw if transaction support is missing.
  • Additional named registrations are non-global. Consumers import a module that exports the matching getDrizzle*Token(name) and inject through that token; names do not create isolated runtime containers. Each registration owns independent ALS transaction context, drain, disposal, and status; select it explicitly with @Transaction((self) => self.analytics).
  • database must be a concrete object/function handle for both sync and async registration; missing handles are rejected during module registration or async bootstrap.
  • @fluojs/runtime: owns module startup and shutdown sequencing
  • @fluojs/http: provides request lifecycle primitives that can be paired with explicit requestTransaction(...) boundaries
  • @fluojs/prisma and @fluojs/mongoose: alternate ORM/ODM integrations with the same fluo runtime model

Example Sources

  • packages/drizzle/src/after-commit.test.ts: after-commit verification target. The common behavior matrix is tooling/governance/after-commit-contract.test.ts and the native commit fixture is packages/prisma/fixtures/after-commit/; the Prisma fixture does not verify every Drizzle driver. Execution results require a separate verification receipt.

  • packages/drizzle/src/vertical-slice.test.ts

  • packages/drizzle/src/module.test.ts

  • packages/drizzle/src/public-api.test.ts

Keywords

fluo

FAQs

Package last updated on 08 Sep 2026

Related posts