nestjs/store-kit

A store-kit module for the NestJS framework (node.js) 🏪

★ 1Forks 0TypeScriptGitHub ↗Compare

README

Nest Logo

A progressive Node.js framework for building efficient and scalable server-side applications.

NPM Version Package License NPM Downloads Discord Backers on Open Collective Sponsors on Open Collective

Description

The building blocks of the first-party SQL stores that Nest's packages ship (PostgresWorkflowStore in @nestjs/workflows/postgres, and more to come, on PostgreSQL and MySQL): executors that run a store's SQL through the client the application already has (node-postgres or mysql2, Drizzle, TypeORM, Prisma, Kysely) and join its transactions, a store's versioned migrations with their SQL and its command line, the readiness a store awaits before its statements, and helpers for writing them.

Applications don't install or import it: a package depends on it, and its /postgres and /mysql subpaths re-export the executors and their types.

Installation

For a package that ships a store, as a regular dependency (never a peer, so the kit's patch releases dedupe across an application's packages):

$ npm i --save @nestjs/store-kit

Entries

  • @nestjs/store-kit: what every dialect shares: the executor types (SqlExecutor, SqlTransaction, SqlTransactionOptions, SqlIsolationLevel, SqlExecuteResult, SqlDialect), isNotATransactionError() (an executor's refusal of an object that isn't a transaction, by its code ERR_SQL_NOT_A_TRANSACTION), the readers of a column's text (toText/toInt/toBool/toJson) and runStoreCli(), a package's command. SqlExecutor<'postgres'> and SqlExecutor<'mysql'> are each dialect's executors (what the executors return, and what a store's options take): the root's SqlExecutor alone is either, and /postgres's and /mysql's SqlExecutor (with their StoreOptions) are their dialect's.
  • @nestjs/store-kit/postgres: the executors (fromPg, fromDrizzle, fromTypeOrm, fromPrisma, fromKysely, and isNotATransactionError()), StoreSchema (migrations, their SQL, a store's options and readiness), the statement helpers (SqlParams, columns, toText/toInt/toBool/toJson, quoteSchema, advisoryLock) and assertReadCommittedTransaction().
  • @nestjs/store-kit/mysql: the same for MySQL 8.4 LTS and 9.x: the executors (fromMysql2, fromDrizzle, fromTypeOrm, fromPrisma, fromKysely, and isNotATransactionError()), StoreSchema (a store's tables in the connection's database as <schema>_<table>, migrations applied statement by statement under GET_LOCK() and resumed where a failed run stopped), and the statement helpers (SqlParams, columns, toText/toInt/toBool/toJson, quoteIdentifier, quoteTable, keyColumn, lockKeys, ensureLockRows, lockRowId, retryOnDeadlock, mysqlErrorCode).
  • @nestjs/store-kit/testing: sqlExecutorContract(), for an executor of another client.

A store in brief

export const outboxSchema = new StoreSchema({
  packageName: '@nestjs/outbox',
  storeName: 'PostgresOutboxStore',
  command: 'nest-outbox',
  defaultSchema: 'nest_outbox',
  migrations: [initialMigration], // { version: 1, name: 'initial', up: (s) => [`CREATE TABLE ${s}.messages (...)`] }
  createError: (message, details) => new OutboxSchemaError(message, details),
});

export class PostgresOutboxStore implements OutboxStore, OnModuleInit {
  static migrationSql(options?: MigrationSqlOptions): string {
    return outboxSchema.sql(options);
  }

  static readonly schemaVersion = outboxSchema.latest;

  private readonly executor: SqlExecutor;
  private readonly readiness: StoreReadiness;

  constructor(options: PostgresOutboxStoreOptions, storage?: OutboxStorage) {
    const resolved = outboxSchema.resolveOptions(options); // { executor, schema, migrate }, checked
    this.executor = resolved.executor;
    this.readiness = outboxSchema.readiness({ ...resolved, logger: new Logger('OutboxModule') });
    storage?.registerSource({ messages: this, inbox: this });
  }

  onModuleInit(): Promise<void> {
    return this.readiness.ready(); // migrates (or checks) the schema before the workers start
  }

  migrate(): Promise<number[]> {
    return this.readiness.migrate();
  }
}

The package's bin (nest-outbox migrate|status|sql):

#!/usr/bin/env node
import { runStoreCli } from '@nestjs/store-kit';
import { outboxSchema } from './outbox.schema.js';

process.exitCode = await runStoreCli([outboxSchema], process.argv.slice(2));

Tests

npm run test:e2e runs the suite on PGlite, and on PostgreSQL through every executor: SQL_TEST_PG_URL (docker compose up -d starts one on port 55432), else a throwaway cluster from local PostgreSQL binaries, else those tests are skipped with the reason. The MySQL tests (a vitest project of their own, two files at a time, after the PostgreSQL ones) run on SQL_TEST_MYSQL_URL (mysql://root:<password>@127.0.0.1:3306), else they are skipped with the reason. Test databases are named skit_<host>_... on both servers and swept when their process is gone.

Support

Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please read more here.

Stay in touch

License

Nest is MIT licensed.

Contributors

kamilmysliwiec

Issues