Microsoft SQL Server

The @event-nest/mssql adapter stores aggregate versions, events, and optional snapshots in Microsoft SQL Server through Knex and Tedious.

pnpm add @event-nest/core @event-nest/mssql tedious
bash

Configure the module

forRoot registers a global module and exports EVENT_STORE application-wide.

import { EventNestMSSQLModule } from "@event-nest/mssql";
import { Module } from "@nestjs/common";

@Module({
    imports: [
        EventNestMSSQLModule.forRoot({
            aggregatesTableName: "aggregates",
            connection: {
                database: "event_nest",
                password: process.env.SQL_SERVER_PASSWORD!,
                port: 1433,
                server: "localhost",
                user: "event_nest"
            },
            ensureTablesExist: true,
            eventsTableName: "events",
            schemaName: "dbo"
        })
    ]
})
export class AppModule {}
ts

register accepts the same options and creates a non-global module whose EVENT_STORE export remains scoped to the importing module.

EventNestMSSQLModule.register({
    aggregatesTableName: "aggregates",
    connection: {
        database: "event_nest",
        password: process.env.SQL_SERVER_PASSWORD!,
        server: "sql.example.test",
        user: "event_nest"
    },
    eventsTableName: "events",
    schemaName: "event_nest"
})
ts

The asynchronous variants accept only inject and useFactory. The factory may return MSSQLModuleOptions or Promise<MSSQLModuleOptions>. forRootAsync is global; registerAsync is scoped.

EventNestMSSQLModule.forRootAsync({
    inject: [DatabaseSettings],
    useFactory: async (settings: DatabaseSettings) => ({
        aggregatesTableName: "aggregates",
        connection: await settings.sqlServerConnection(),
        eventsTableName: "events",
        schemaName: "event_nest"
    })
})

EventNestMSSQLModule.registerAsync({
    inject: [DatabaseSettings],
    useFactory: (settings: DatabaseSettings) => ({
        aggregatesTableName: "aggregates",
        connection: settings.sqlServerConnectionSync(),
        eventsTableName: "events",
        schemaName: "event_nest"
    })
})
ts

The async options type does not provide imports; injected providers must already be visible in the module context.

Module options

OptionRequiredDefaultBehavior and validation
connectionYesNoneStructured Tedious connection settings described below.
schemaNameYesNoneExisting SQL Server schema containing all tables. Must pass identifier validation.
aggregatesTableNameYesNoneAggregate-version table. Must pass identifier validation.
eventsTableNameYesNoneEvent table. Must pass identifier validation.
ensureTablesExistNofalseCreates missing configured tables during application bootstrap.
connectionPoolNomin: 0, other Knex/Tarn defaultsMerged into the Knex pool after min: 0, so an explicit min overrides it. Supported keys are acquireTimeoutMillis, afterCreate, createRetryIntervalMillis, createTimeoutMillis, destroyTimeoutMillis, idleTimeoutMillis, log, max, min, name, priorityRange, propagateCreateError, reapIntervalMillis, refreshIdle, and returnToHead.
concurrentSubscriptionsNofalseProcesses emitted events concurrently instead of sequentially. It does not change transaction behavior.
snapshotStrategyNoSnapshots disabledMust be supplied together with snapshotTableName.
snapshotTableNameNoSnapshots disabledMust pass identifier validation and be supplied with snapshotStrategy.

schemaName, aggregatesTableName, eventsTableName, and a configured snapshotTableName must be non-empty, at most 128 characters, and contain no dot. Invalid values fail provider creation. Pass the schema separately rather than using a qualified table name.

An incomplete snapshot pair causes provider creation to fail.

Connection options

connection fieldRequiredAdapter defaultNotes
serverYesNoneSQL Server host passed to Tedious.
databaseYesNoneDatabase name.
userYesNoneLogin user.
passwordYesNoneLogin password.
portNoDriver behaviorMutually exclusive with instanceName.
instanceNameNoDriver behaviorMutually exclusive with port. Both together throw during provider creation.
connectionTimeoutNoDriver behaviorPassed through to the connection.
requestTimeoutNoDriver behaviorPassed through to the connection.
encryptNotrueSecure adapter default; may be explicitly overridden.
trustServerCertificateNofalseSecure adapter default; may be explicitly overridden.
serverNameNoDriver behaviorPassed to Tedious TLS options.

The adapter also fixes lowerCaseGuids: true and useUTC: true, and binds JavaScript Date values as DateTime2.

Tables and bootstrap

ensureTablesExist checks and creates tables in order: aggregates, events, then the optional snapshots table. The configured schema must already exist. Unlike PostgreSQL, a SQL Server initialization error is logged and rethrown, so Nest application bootstrap fails.

See the exact SQL Server schema for migrations and generated indexes.

Transactions and lifecycle

Every non-empty save is a Knex transaction. The aggregate lookup uses UPDLOCK, HOLDLOCK; event inserts are sent in chunks of 250; and the aggregate version update includes the previously read version. The generated unique event-stream index also turns competing duplicate versions into a concurrency signal. Aggregate metadata, event rows, and the version advance are committed atomically.

Snapshot creation, when selected, occurs after that transaction. It is not rolled back with the event write. See Storage model for the shared commit boundary.

For aggregate deletion semantics, see Purging aggregates .

Last update at: 2026/09/02 00:14