typeorm-procedure-kit
v1.4.0
Published
TypeScript toolkit for TypeORM-based Oracle and PostgreSQL apps: stored procedure calls, raw SQL transactions, database notifications, serializers, and a bundled typed TypeORM 0.3.28 fork
Downloads
652
Maintainers
Readme
Краткая сводка
typeorm-procedure-kit — TypeScript toolkit для Node.js сервисов, которые
работают с Oracle или PostgreSQL stored procedures, raw SQL transactions,
database notifications и TypeORM-style entity API.
| Поле | Значение |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------- |
| npm package | typeorm-procedure-kit |
| Runtime | Node.js >=20 |
| Module formats | ESM и CommonJS |
| Поддерживаемые базы | PostgreSQL через pg; Oracle через oracledb |
| Main API | TypeOrmProcedureKit |
| NestJS API | typeorm-procedure-kit/nestjs |
| TypeORM-compatible API | typeorm-procedure-kit/typeorm |
| Entity extension API | typeorm-procedure-kit/typeorm-extend |
| Основные темы | stored procedures, raw SQL transactions, LISTEN/NOTIFY, Oracle CQN, serializers, TypeORM-compatible repositories |
Сценарии использования
Используйте пакет, если сервису нужны одна или несколько возможностей:
- Вызов Oracle packages или PostgreSQL schema procedures с учетом database metadata.
- Выполнение raw SQL через тот же transaction и error-handling flow.
- Подписка на PostgreSQL
LISTEN/NOTIFYили Oracle Continuous Query Notification с восстановлением subscriptions после обрыва соединения. - Обновление procedure metadata во время работы через database change notifications.
- Нормализация регистра result keys для raw rows и TypeORM-compatible entity column names.
- Встроенный TypeORM-compatible API без установки upstream
typeorm. - Переиспользование базовой entity metadata между Oracle и PostgreSQL entity variants.
Карта API
| Задача | API | Import path |
| ------------------------------------- | ------------------------------------------------------ | -------------------------------------- |
| Инициализация доступа к базе | new TypeOrmProcedureKit(config), initDatabase() | typeorm-procedure-kit |
| Вызов stored procedure | db.call<T>(name, params, options?) | typeorm-procedure-kit |
| Выполнение raw SQL transaction | db.callSqlTransaction<T>(sql, params?, options?) | typeorm-procedure-kit |
| Подписка на database notifications | db.makeNotify<T>(options, oracleOptions?) | typeorm-procedure-kit |
| Отписка от notifications | db.unlistenNotify(channel) | typeorm-procedure-kit |
| Регистрация serializers | db.setSerializer(), db.deleteSerializer() | typeorm-procedure-kit |
| Доступ к DataSource или EntityManager | db.dataSource, db.getEntityManager() | typeorm-procedure-kit |
| NestJS integration | TypeOrmProcedureKitNestModule и injection decorators | typeorm-procedure-kit/nestjs |
| TypeORM-compatible APIs | Entity, Column, DataSource, Repository | typeorm-procedure-kit/typeorm |
| Расширение entity metadata | ExtendEntity, ExtendColumn | typeorm-procedure-kit/typeorm-extend |
Кратко
| Область | Что входит |
| -------------- | --------------------------------------------------------------------------------------------- |
| Процедуры | Вызов хранимых процедур с учетом метаданных для Oracle и PostgreSQL packages/schemas. |
| SQL | Выполнение SQL через тот же транзакционный поток, что и вызовы процедур. |
| Уведомления | Поддержка PostgreSQL LISTEN/NOTIFY и Oracle Continuous Query Notification. |
| Case strategy | Общие правила регистра для native result keys и встроенных TypeORM-compatible column names. |
| Сериализация | Встроенные и пользовательские сериализаторы значений из базы. |
| NestJS | Global dynamic module и точечные injection decorators для публичных runtime methods. |
| TypeORM API | Встроенные TypeORM-совместимые экспорты со строгими локальными типами repository и query API. |
| Идентификаторы | TypeORM-compatible query builders по умолчанию не оборачивают identifiers в двойные кавычки. |
Автор и сопровождающий: Paul Budanov.
Что входит в пакет
TypeOrmProcedureKitкак основной API времени выполнения для инициализации базы, вызова процедур, SQL-запросов напрямую, уведомлений, сериализаторов и корректного завершения работы.- Общий контракт адаптеров для Oracle и PostgreSQL.
- Автоматическая загрузка метаданных процедур из database packages/schemas.
- Обновление метаданных процедур во время работы через уведомления базы.
- Встроенная case-strategy для native result keys и TypeORM-compatible column
names:
camelCase,lowerCase,snakeCase. - Встроенные и пользовательские сериализаторы значений из базы.
- Интеграция с NestJS через global dynamic module и decorators для инжекта отдельных публичных методов.
- Встроенный TypeORM-совместимый экспорт для Oracle/PostgreSQL проектов; TypeORM уже включен в пакет с type fixes для строгих TypeScript-проектов.
typeorm-extendдекораторы для наследования и переопределения метаданных entities.- Экранирование identifiers отключено по умолчанию для встроенного TypeORM-compatible DataSource, поэтому generated SQL избегает случайных двойных кавычек вокруг table и column names, пока вы явно не включите escaping.
Требования
- Node.js
>=20 - Любой package manager, работающий с npm registry:
- npm
- Yarn
- pnpm
- TypeScript с включенными decorators, если используются entities
- Драйвер для целевой базы:
- PostgreSQL:
pg - Oracle:
oracledb
- PostgreSQL:
- Опциональная streaming-зависимость для PostgreSQL:
pg-query-stream; она нужна только при вызове публичных stream API, напримерSelectQueryBuilder.stream()илиQueryRunner.stream(). - Опционально для NestJS:
@nestjs/commonи@nestjs/core
Установка
Используйте удобный package manager:
| Package manager | Команда |
| --------------- | ----------------------------------- |
| npm | npm install typeorm-procedure-kit |
| Yarn | yarn add typeorm-procedure-kit |
| pnpm | pnpm add typeorm-procedure-kit |
Установите драйвер для нужной базы.
PostgreSQL:
| Package manager | Команда |
| --------------- | ---------------- |
| npm | npm install pg |
| Yarn | yarn add pg |
| pnpm | pnpm add pg |
Oracle:
| Package manager | Команда |
| --------------- | ---------------------- |
| npm | npm install oracledb |
| Yarn | yarn add oracledb |
| pnpm | pnpm add oracledb |
Установите pg-query-stream только если используете PostgreSQL streaming:
| Package manager | Команда |
| --------------- | ----------------------------- |
| npm | npm install pg-query-stream |
| Yarn | yarn add pg-query-stream |
| pnpm | pnpm add pg-query-stream |
Быстрый старт
Минимальный PostgreSQL пример инициализирует kit, вызывает одну настроенную процедуру и освобождает ресурсы:
import { TypeOrmProcedureKit } from 'typeorm-procedure-kit';
import type { IModuleConfig, ILoggerModule } from 'typeorm-procedure-kit';
const logger: ILoggerModule = {
error: console.error,
log: console.log,
warn: console.warn,
debug: console.debug,
verbose: console.debug,
};
const settings: IModuleConfig = {
logger,
config: {
type: 'postgres',
parseInt8AsBigInt: true,
master: {
host: 'localhost',
port: 5432,
username: 'app',
password: 'secret',
database: 'app_db',
},
poolSize: 10,
packagesSettings: {
packages: ['billing'],
procedureObjectList: {
findInvoices: 'billing.find_invoices',
},
},
},
};
const db = new TypeOrmProcedureKit(settings);
await db.initDatabase();
try {
const invoices = await db.call<{ invoiceId: number }>(
'billing.find_invoices',
{ customerId: 42 }
);
console.log(invoices);
} finally {
await db.destroy();
}Точки импорта
import { TypeOrmProcedureKit } from 'typeorm-procedure-kit';
import type { IModuleConfig } from 'typeorm-procedure-kit';
import { TypeOrmProcedureKitNestModule } from 'typeorm-procedure-kit/nestjs';
import { Entity, Column, PrimaryColumn } from 'typeorm-procedure-kit/typeorm';
import {
ExtendColumn,
ExtendEntity,
} from 'typeorm-procedure-kit/typeorm-extend';Root import экспортирует kit, публичные типы, constants и utilities. TypeORM
уже включен в этот пакет: decorators, DataSource, EntityManager, repositories,
query builders и связанные классы экспортируются из ./typeorm. Для entities,
которыми управляет kit, импортируйте TypeORM API из
typeorm-procedure-kit/typeorm, а не из отдельного upstream-пакета typeorm.
| Import path | Для чего использовать |
| -------------------------------------- | ---------------------------------------------------------------------------------- |
| typeorm-procedure-kit | TypeOrmProcedureKit, публичные типы, utilities, constants |
| typeorm-procedure-kit/nestjs | NestJS module, service, method injection decorators |
| typeorm-procedure-kit/typeorm | Встроенные TypeORM-compatible decorators, DataSource, repositories, query builders |
| typeorm-procedure-kit/typeorm-extend | ExtendEntity, ExtendColumn и связанные metadata extension decorators |
Конфигурация
Настройка выполняется через IModuleConfig:
import type { IModuleConfig, ILoggerModule } from 'typeorm-procedure-kit';
const logger: ILoggerModule = {
error: (message, ...optionalParams) =>
console.error(message, ...optionalParams),
log: (message, ...optionalParams) => console.log(message, ...optionalParams),
warn: (message, ...optionalParams) =>
console.warn(message, ...optionalParams),
debug: (message, ...optionalParams) =>
console.debug(message, ...optionalParams),
verbose: (message, ...optionalParams) =>
console.debug(message, ...optionalParams),
};
const config: IModuleConfig = {
logger,
isRegisterShutdownHandlers: true,
config: {
type: 'postgres',
master: {
host: 'localhost',
port: 5432,
username: 'app',
password: 'secret',
database: 'app_db',
},
poolSize: 10,
parseInt8AsBigInt: true,
appName: 'procedure-service',
callTimeout: 30_000,
outKeyTransformCase: 'camelCase',
isNeedRegisterDefaultSerializers: true,
packagesSettings: {
packages: ['billing'],
procedureObjectList: {
createInvoice: 'billing.create_invoice',
findInvoices: 'billing.find_invoices',
},
isNeedDynamicallyUpdatePackagesInfo: true,
listenEventName: 'package_changed',
},
},
entity: {
isNeedEntitySync: false,
entityPath: ['dist/entities/*.js'],
},
migration: {
isNeedMigrationStart: false,
migrationPath: ['dist/migrations/*.js'],
},
};Общие параметры:
master: учетные данные основного подключения к базе.slaves: read replicas для TypeORM replication.poolSize: размер пула соединений.appName: имя приложения для драйвера, если он это поддерживает.callTimeout: порог логирования медленных запросов для TypeORM.parseInt8AsBigInt: PostgreSQL-only параметр, который передается во встроенный драйвер какparseInt8. Когда значение равноtrue,node-postgresразбираетint8как JavaScript numbers вместо strings; значения вышеNumber.MAX_SAFE_INTEGERмогут потерять точность, несмотря на название параметра.outKeyTransformCase: регистр ключей результата, по умолчаниюcamelCase.isNeedRegisterDefaultSerializers: включает стандартные date/time serializers.isNeedClientNotificationInit: режим Oracle CQN по умолчанию.trueвключает client-initiated notifications,falseиспользуется вместе сcqnPortдля server callback notifications.packagesSettings: packages/schemas и процедуры, доступные черезcall().
packagesSettings.packages нужно указывать в lowercase. Имена процедур при
вызове нормализуются внутри библиотеки.
В примерах для PostgreSQL слово "package" означает настроенное namespace schema, которое использует kit. В примерах для Oracle это Oracle package.
procedureObjectList используется для разрешения настроенных процедур и, когда
настроено несколько packages/schemas, для пропуска процедур вне текущего
package. Ключи служат только метками внутри конфигурации; значения должны быть
реальными именами процедур. Используйте формат package.procedure или
schema.procedure, когда настроено несколько packages. Bare-имя procedure
допустимо, когда настроен ровно один package. Runtime вызовы разрешаются по
загруженным database names, поэтому используйте
db.call('billing.find_invoices') или db.call('find_invoices'), если настроен
только один package. Не используйте ключи procedureObjectList как aliases для
call().
Если задан packagesSettings.packages и packages array не пустой,
initDatabase() также создает подписку на уведомления об изменении packages.
Перед использованием этих примеров без изменений настройте источник
уведомлений в базе.
Встроенная case-strategy
typeorm-procedure-kit включает встроенную case-strategy для двух мест, где
имена из базы встречаются с application code: native result objects и
встроенная TypeORM-compatible naming strategy. Настройка задается через
config.outKeyTransformCase внутри database config.
Поддерживаемые значения:
| Значение | Ключ из базы | Ключ на выходе |
| ----------- | ------------------------------- | -------------- |
| camelCase | USER_ID, user_id, user id | userId |
| snakeCase | USER_ID, userId, User Id | user_id |
| lowerCase | USER_ID, User_Id | user_id |
Если outKeyTransformCase не задан, используется camelCase. Неизвестные
значения во время выполнения также откатываются к camelCase.
Пакет создает два strategy objects из одной настройки:
NativeStrategyпреобразует имена колонок, которые приходят из metadatapgилиoracledb, перед возвратом native rows из вызовов процедур и raw SQL. SQL aliases тоже преобразуются, потому что drivers отдают aliases как result metadata.OrmStrategyустанавливается как DataSource naming strategy при инициализации. Она преобразует имена свойств entity перед передачей во встроенную default naming strategy, поэтому generated column names следуют выбранному формату, если decorator не задает explicit name.
Пример:
const config: IModuleConfig = {
logger,
config: {
type: 'postgres',
master,
poolSize: 10,
parseInt8AsBigInt: true,
outKeyTransformCase: 'snakeCase',
},
entity: {
isNeedEntitySync: false,
entityPath: ['dist/entities/*.js'],
},
migration: {
isNeedMigrationStart: false,
migrationPath: ['dist/migrations/*.js'],
},
};При такой настройке колонка raw result USER_ID или userId вернется как
user_id. При camelCase тот же ключ будет возвращен как userId.
Важные детали:
- Стратегия меняет output object keys и generated ORM column names. Она не переписывает package names, procedure names, SQL text, bind placeholders, table names, relation names или notification channel names.
packagesSettings.packagesпо-прежнему нужно указывать в lowercase, потому что procedure resolution нормализует configured package/schema names внутри.- Raw SQL bind placeholders по-прежнему используют документированный uppercase
стиль, например
:USER_ID. - Явно заданные custom column names в decorators учитываются встроенной default naming strategy, поэтому стандартное TypeORM override behavior сохраняется.
lowerCaseтолько приводит входную строку к нижнему регистру. ИспользуйтеsnakeCase, если нужно разбиение слов, напримерUser Id->user_id.- Преобразованные имена кэшируются на время жизни kit instance, а cache
очищается при
destroy().
Oracle CQN можно настроить в двух режимах. Server callback mode открывает локальный порт для уведомлений от базы:
const oracleServerCallbackConfig: IModuleConfig['config'] = {
type: 'oracle',
master: {
host: 'localhost',
port: 1521,
username: 'APP',
password: 'secret',
database: 'ORCLCDB',
},
poolSize: 10,
libraryPath: '/opt/oracle/instantclient',
cqnPort: 9090,
isNeedClientNotificationInit: false,
};Client-initiated mode не требует cqnPort и обычно удобнее, когда база не
может подключиться обратно к application host:
const oracleClientInitiatedConfig: IModuleConfig['config'] = {
type: 'oracle',
master: {
host: 'localhost',
port: 1521,
username: 'APP',
password: 'secret',
database: 'ORCLCDB',
},
poolSize: 10,
libraryPath: '/opt/oracle/instantclient',
isNeedClientNotificationInit: true,
};Oracle option clientInitiated в makeNotify() может переопределить это
значение для отдельной subscription.
PostgreSQL
import { TypeOrmProcedureKit } from 'typeorm-procedure-kit';
import type { ILoggerModule, IModuleConfig } from 'typeorm-procedure-kit';
const logger: ILoggerModule = {
error: (message, ...optionalParams) =>
console.error(message, ...optionalParams),
log: (message, ...optionalParams) => console.log(message, ...optionalParams),
warn: (message, ...optionalParams) =>
console.warn(message, ...optionalParams),
debug: (message, ...optionalParams) =>
console.debug(message, ...optionalParams),
verbose: (message, ...optionalParams) =>
console.debug(message, ...optionalParams),
};
const settings: IModuleConfig = {
logger,
config: {
type: 'postgres',
parseInt8AsBigInt: true,
master: {
host: 'localhost',
port: 5432,
username: 'app',
password: 'secret',
database: 'app_db',
},
poolSize: 10,
packagesSettings: {
packages: ['api'],
procedureObjectList: {
getUser: 'api.get_user',
searchUsers: 'api.search_users',
},
},
},
};
const db = new TypeOrmProcedureKit(settings);
await db.initDatabase();
const users = await db.call<{ id: number; fullName: string }>(
'api.search_users',
{
query: 'Paul',
}
);
await db.destroy();Для PostgreSQL вызов процедуры формируется так:
CALL "package"."procedure"($1, $2, ...)Выходные аргументы PostgreSQL типа refcursor читаются и закрываются внутри
той же транзакции.
Oracle
import { TypeOrmProcedureKit } from 'typeorm-procedure-kit';
import type { ILoggerModule, IModuleConfig } from 'typeorm-procedure-kit';
const logger: ILoggerModule = {
error: (message, ...optionalParams) =>
console.error(message, ...optionalParams),
log: (message, ...optionalParams) => console.log(message, ...optionalParams),
warn: (message, ...optionalParams) =>
console.warn(message, ...optionalParams),
debug: (message, ...optionalParams) =>
console.debug(message, ...optionalParams),
verbose: (message, ...optionalParams) =>
console.debug(message, ...optionalParams),
};
const settings: IModuleConfig = {
logger,
config: {
type: 'oracle',
master: {
host: 'localhost',
port: 1521,
username: 'APP',
password: 'secret',
database: 'ORCLCDB',
},
poolSize: 10,
libraryPath: '/opt/oracle/instantclient',
cqnPort: 9090,
isNeedClientNotificationInit: false,
packagesSettings: {
packages: ['billing'],
procedureObjectList: {
findInvoices: 'billing.find_invoices',
},
},
},
};
const db = new TypeOrmProcedureKit(settings);
await db.initDatabase();
const invoices = await db.call<{ invoiceId: number }>('billing.find_invoices', {
customerId: 100,
});
await db.destroy();Для Oracle вызов процедуры формируется так:
BEGIN PACKAGE.PROCEDURE(:arg1, :arg2); END;Результаты Oracle REF CURSOR считываются потоково и возвращаются как строки
результата.
Вызов процедур
call<T>() требует config.packagesSettings.
const rows = await db.call<{ id: number; status: string }>(
'billing.find_invoices',
{ customerId: 42, status: 'OPEN' }
);Если настроен только один package, имя package можно опустить:
const rows = await db.call('find_invoices', { customerId: 42 });Правила payload:
- Object bind выполняется по имени аргумента. Адаптеры также пробуют имя без
префикса
p_, поэтомуp_customer_idможно передать какcustomer_id. - Array bind выполняется по позиции аргумента.
- Если payload не передан или передан
undefined, все non-cursor значения будут привязаны какnull. - Scalar string/number нельзя передавать как payload процедуры.
await db.call('billing.update_invoice', [42, 'PAID']);Третий аргумент — массив SQL-команд, которые выполняются перед основным вызовом внутри той же транзакции:
await db.call('billing.recalculate', { invoiceId: 42 }, [
'SET LOCAL statement_timeout = 30000',
]);Транзакции с SQL-запросами напрямую
callSqlTransaction<T>() выполняет SQL-запрос напрямую через тот же поток
транзакции, логирование, обработку ошибок и освобождение соединения.
Параметры находятся только по плейсхолдерам в верхнем регистре:
:PARAM_NAME.
const rows = await db.callSqlTransaction<{ id: number; name: string }>(
'SELECT id, name FROM users WHERE id = :USER_ID',
{ USER_ID: 7 }
);PostgreSQL переписывает плейсхолдеры в $1, $2, а Oracle оставляет
:PARAM.
Уведомления
PostgreSQL LISTEN/NOTIFY
const channel = await db.makeNotify<{ event: string; object: string }>({
sql: 'LISTEN package_changed',
notifyCallback: (payload) => {
console.log(payload.event, payload.object);
},
});
await db.unlistenNotify(channel);PostgreSQL adapter проверяет имя channel, открывает отдельный pg.Client,
пытается разобрать JSON payload и восстанавливает listener после ошибок
соединения. Также выполняется периодический health-check соединения, поэтому
listener может быть восстановлен после тихого сетевого обрыва, когда
PostgreSQL не прислал явное событие.
Retry options для восстановления notification subscriptions можно передать
вторым аргументом makeNotify() для Oracle, а при прямом использовании adapter
API — как notification options адаптера:
maxRetries: число попыток восстановления до длинной задержки, default5.retryDelayMs: задержка между обычными попытками восстановления, default30000.retryAfterMaxDelayMs: задержка после исчерпанияmaxRetriesперед новым циклом попыток, default1800000.
Oracle Continuous Query Notification
import oracledb from 'oracledb';
const channel = await db.makeNotify<Array<{ ID: number }>>(
{
sql: 'SELECT ID, STATUS FROM BILLING.INVOICES',
notifyCallback: (rows) => {
console.log(rows);
},
},
{
operations: oracledb.CQN_OPCODE_ALL_OPS,
qos: oracledb.SUBSCR_QOS_ROWIDS,
timeout: 60 * 60,
clientInitiated: true,
}
);
await db.unlistenNotify(channel);Oracle создает внутреннее UUID-имя subscription. Когда Oracle возвращает ROWID,
adapter читает измененные строки и передает их в callback.
Если передать operations как array, создается отдельная CQN subscription на
каждую operation; возвращенная строка channel содержит сгенерированные имена
subscriptions, и ее можно без изменений передать в unlistenNotify().
Дефолты Oracle notifications:
operations:oracledb.CQN_OPCODE_ALL_OPSqos:oracledb.SUBSCR_QOS_ROWIDStimeout: 12 часовclientInitiated: option конкретной subscription, затем общий config, затемfalse
Если operations передан как array, в нем должно быть меньше четырех operation
codes. Для подписки на все операции используйте
oracledb.CQN_OPCODE_ALL_OPS, а не ручной список всех операций. Oracle
subscriptions также проверяются периодическим health-check и восстанавливаются
после CQN deregistration, shutdown events, ошибок соединения или тихого обрыва
соединения.
Обновление метаданных packages
Если настроен packagesSettings.packages, initDatabase() загружает
метаданные процедур из базы. Также создается подписка на уведомления об
изменении packages всякий раз, когда packages array не пустой. У пользователя
базы должен быть доступ к источнику уведомлений: PostgreSQL по умолчанию слушает
db_object_event, а Oracle читает SOLUTION_ROOT.DB_OBJECT_LOG.
Для PostgreSQL можно переопределить notification channel, когда
isNeedDynamicallyUpdatePackagesInfo равен true:
packagesSettings: {
packages: ['api'],
procedureObjectList: {
getUser: 'api.get_user',
},
isNeedDynamicallyUpdatePackagesInfo: true,
listenEventName: 'package_changed',
}Сериализаторы
Включение встроенных serializers:
const settings = {
config: {
// ...
isNeedRegisterDefaultSerializers: true,
},
};Стандартные serializers форматируют:
DATEкакyyyy-MM-ddTIMESTAMPкакyyyy-MM-dd HH:mm:ss ZTIMESTAMP_TZкакyyyy-MM-dd HH:mm:ss Z
Пользовательский serializer:
db.setSerializer({
serializerType: 'JSON',
strategy: (value) => JSON.parse(value.toString()),
});
const serializers = db.serializerReadOnlyMapping;
db.deleteSerializer({ serializerType: 'JSON' });
db.deleteAllSerializers();Поддерживаемые ключи: DATE, TIMESTAMP, TIMESTAMP_TZ, BOOLEAN, CHAR,
VARCHAR, JSON, BINARY, XML.
NestJS
import { Logger, Module } from '@nestjs/common';
import { TypeOrmProcedureKitNestModule } from 'typeorm-procedure-kit/nestjs';
@Module({
imports: [
TypeOrmProcedureKitNestModule.forRoot({
logger: new Logger('TypeOrmProcedureKit'),
config: {
type: 'postgres',
parseInt8AsBigInt: true,
master: {
host: 'localhost',
port: 5432,
username: 'app',
password: 'secret',
database: 'app_db',
},
poolSize: 10,
},
}),
],
})
export class AppModule {}Async setup:
TypeOrmProcedureKitNestModule.forRootAsync({
useFactory: async () => ({
logger: new Logger('TypeOrmProcedureKit'),
config: {
type: 'postgres',
parseInt8AsBigInt: true,
master: {
host: process.env.DB_HOST ?? 'localhost',
port: Number(process.env.DB_PORT ?? 5432),
username: process.env.DB_USER ?? 'app',
password: process.env.DB_PASSWORD ?? 'secret',
database: process.env.DB_NAME ?? 'app_db',
},
poolSize: 10,
},
}),
});Nest service наследует TypeOrmProcedureKit, вызывает initDatabase() в
onModuleInit() и destroy() в onApplicationShutdown().
NestJS entry point также экспортирует точечные decorators для инжекта отдельных публичных методов вместо всего service:
| Decorator | Тип injected function | Делегирует в |
| ------------------------------- | ----------------------- | -------------------------------------------- |
| @InjectCallProcedure() | TCallProcedure | TypeOrmProcedureKit.call() |
| @InjectCallSql() | TCallSql | TypeOrmProcedureKit.callSqlTransaction() |
| @InjectMakeNotify() | TMakeNotify | TypeOrmProcedureKit.makeNotify() |
| @InjectUnlistenNotify() | TUnlistenNotify | TypeOrmProcedureKit.unlistenNotify() |
| @InjectSetSerializer() | TSetSerializer | TypeOrmProcedureKit.setSerializer() |
| @InjectDeleteSerializer() | TDeleteSerializer | TypeOrmProcedureKit.deleteSerializer() |
| @InjectDeleteAllSerializers() | TDeleteAllSerializers | TypeOrmProcedureKit.deleteAllSerializers() |
import { Injectable } from '@nestjs/common';
import {
InjectCallProcedure,
InjectCallSql,
type TCallProcedure,
type TCallSql,
} from 'typeorm-procedure-kit/nestjs';
@Injectable()
export class BillingRepository {
public constructor(
@InjectCallProcedure()
private readonly callProcedure: TCallProcedure,
@InjectCallSql()
private readonly callSql: TCallSql
) {}
public findInvoices(customerId: number): Promise<Array<{ id: number }>> {
return this.callProcedure<{ id: number }>('billing.find_invoices', {
customerId,
});
}
public countInvoices(customerId: number): Promise<Array<{ total: number }>> {
return this.callSql<{ total: number }>(
'SELECT COUNT(*) AS total FROM invoices WHERE customer_id = :CUSTOMER_ID',
{ CUSTOMER_ID: customerId }
);
}
}EntityManager и DataSource
Для низкоуровневой работы можно получить TypeORM-compatible объекты:
const manager = await db.getEntityManager('master');
try {
const rows = await manager.query('SELECT 1 AS value');
console.log(rows);
} finally {
await db.releaseEntityManager(manager);
}
const dataSource = db.dataSource;
const adapter = db.databaseAdapter;getEntityManager() принимает master или slave.
databaseAdapter открывает низкоуровневый adapter contract для диагностики и
расширенной интеграции.
isRegisterShutdownHandlers регистрирует shutdown handlers в constructor. Если
нужно подключить их позже, вызовите db.registerShutdownHandlers().
Встроенный TypeORM-compatible API
Встроенный TypeORM-совместимый API адаптирован для этой библиотеки. TypeORM
уже включен в пакет как поддерживаемый fork; версия fork здесь — 0.3.28.
Сейчас поддерживаются PostgreSQL и Oracle.
Слой типизации TypeORM был переработан для проектов со строгим TypeScript:
- entity metadata лучше сохраняет generic-типы сущностей;
- repository, query builder и entity manager методы имеют более строгие generic return types;
- common repository inputs вроде
FindOptionsWhere,FindManyOptions,FindOneOptions,DeepPartialиQueryPartialEntityсогласованы с формой entity, которую экспортирует этот пакет; - decorator и metadata argument types адаптированы для database-specific вариантов entity;
Column,PrimaryColumn,PrimaryGeneratedColumn, relation decorators и entity schema options раскрывают database-specific surfaces, которые использует kit;- Oracle/PostgreSQL column types и query-runner API сужены под поддерживаемые драйверы.
Карты свойств metadata
EntityMetadata.propertiesMap типизирована как EntityPropertiesMap<T> и
сохраняет TypeORM-compatible property-path семантику. Callback decorators для
Index, Unique, RelationId, RelationCount и EntityOptions.orderBy
получают эту карту на runtime, поэтому их значения по-прежнему резолвятся в
property paths сущности. orderBy callbacks вычисляются после создания
propertiesMap.
EntityMetadata.databasePropertiesMap — отдельная
EntityDatabasePropertiesMap<T>. Это column-only карта: leaf values содержат
database column names или database paths после применения явных
@Column({ name }) options и правил naming strategy. Relation virtual join
columns намеренно исключены из этой карты.
import { Column, Entity, PrimaryColumn } from 'typeorm-procedure-kit/typeorm';
@Entity()
class User {
@PrimaryColumn()
id!: number;
@Column({ name: 'user_id' })
userId!: string;
}
// metadata.propertiesMap.userId === 'userId'
// metadata.databasePropertiesMap.userId === 'user_id'Для callback decorators user.userId в @Index((user) => [user.userId])
по-прежнему означает property path userId; используйте
databasePropertiesMap только там, где из metadata напрямую нужны database
column names или paths.
Kit устанавливает isQuotingDisabled: true при инициализации DataSource.
Query builder по умолчанию не экранирует identifiers, поэтому generated SQL не
получает случайные "USERS" или "CREATED_AT", когда схема базы ожидает
обычные uppercase identifiers. При необходимости quoting можно включить через
enableEscaping() или принудительно экранировать отдельный identifier через
escape(name, true).
Этот режим quoting относится к SQL, который генерируют entity, repository и
query builder. Вызовы процедур и notification channels проходят через отдельные
adapter paths: identifiers валидируются через SqlIdentifier и экранируются
или форматируются там, где этого требует целевая база.
Будущее направление ORM
Текущая точка входа typeorm-procedure-kit/typeorm остается TypeORM-compatible
и сегодня является поддерживаемым entity API пакета. Дальнейшее развитие
планируется вести в сторону собственной ORM-системы библиотеки вместо
долгосрочной опоры на встроенный fork TypeORM.
Цель этого направления — сохранить database workflow вокруг возможностей, которые уже контролирует пакет: metadata хранимых процедур, адаптеры Oracle и PostgreSQL, явную работу с identifiers, repository/query API и строгие TypeScript-типы. Такой переход предполагается делать постепенно и описывать через публичные точки входа, без обещаний конкретной даты релиза.
Расширяющие декораторы TypeORM
./typeorm-extend экспортирует decorators для переиспользования базовых
метаданных entity и переопределения options для database-specific вариантов.
import {
Column,
Entity,
PrimaryGeneratedColumn,
} from 'typeorm-procedure-kit/typeorm';
abstract class SoftDelete {
@Column({
type: Date,
name: 'DELETED_AT',
nullable: true,
comment: 'Дата мягкого удаления',
})
protected abstract readonly deletedAt: Date | null;
@Column({
type: Number,
name: 'IS_DELETED',
nullable: false,
default: 0,
comment: 'Флаг мягкого удаления',
})
protected abstract readonly isDeleted: number;
}
@Entity({
name: 'OUTBOUND_MESSAGES',
schema: 'APP_CORE',
comment: 'Сообщения для внешней доставки',
})
export abstract class OutboundMessage extends SoftDelete {
@PrimaryGeneratedColumn('uuid', {
name: 'UUID',
comment: 'Идентификатор записи',
})
protected abstract readonly uuid: string | undefined | null;
@Column({
type: Date,
name: 'CREATED_AT',
nullable: false,
comment: 'Дата создания',
})
protected abstract readonly createdAt: Date;
@Column({
type: Date,
name: 'SEND_AT',
nullable: true,
comment: 'Плановая дата отправки',
})
protected abstract readonly sendAt: Date | null;
@Column({
type: Number,
name: 'RECIPIENT_ID',
nullable: false,
comment: 'Идентификатор получателя',
})
protected abstract readonly recipientId: number;
@Column({
type: String,
name: 'CONTENT',
length: 4000,
nullable: true,
comment: 'Текст сообщения',
})
protected abstract readonly content: string | null;
@Column({
type: String,
name: 'DELIVERY_STATUS',
default: 'CREATED',
nullable: false,
comment: 'Статус доставки',
})
protected abstract readonly deliveryStatus: string;
}import {
ExtendColumn,
ExtendEntity,
ExtendPrimaryGeneratedColumn,
} from 'typeorm-procedure-kit/typeorm-extend';
import { OutboundMessage } from '../general/outbound-message.entity.js';
@ExtendEntity()
export class OutboundMessageOracle extends OutboundMessage {
@ExtendPrimaryGeneratedColumn()
declare public readonly uuid: string | null | undefined;
@ExtendColumn({ type: 'date', default: 'SYSDATE' })
declare public readonly createdAt: Date;
@ExtendColumn({ type: 'date' })
declare public readonly sendAt: Date | null;
@ExtendColumn({ type: 'number' })
declare public readonly recipientId: number;
@ExtendColumn({ type: 'varchar2' })
declare public readonly content: string | null;
@ExtendColumn({ type: 'varchar2' })
declare public readonly deliveryStatus: string;
@ExtendColumn({ type: 'date' })
declare public readonly deletedAt: Date | null;
@ExtendColumn({ type: 'number' })
declare public readonly isDeleted: number;
}import {
ExtendColumn,
ExtendEntity,
ExtendPrimaryGeneratedColumn,
} from 'typeorm-procedure-kit/typeorm-extend';
import { OutboundMessage } from '../general/outbound-message.entity.js';
@ExtendEntity()
export class OutboundMessagePostgres extends OutboundMessage {
@ExtendPrimaryGeneratedColumn()
declare public readonly uuid: string | null | undefined;
@ExtendColumn({ type: 'timestamp', default: 'CURRENT_TIMESTAMP' })
declare public readonly createdAt: Date;
@ExtendColumn({ type: 'timestamp' })
declare public readonly sendAt: Date | null;
@ExtendColumn({ type: 'int8' })
declare public readonly recipientId: number;
@ExtendColumn({ type: 'varchar' })
declare public readonly content: string | null;
@ExtendColumn({ type: 'varchar' })
declare public readonly deliveryStatus: string;
@ExtendColumn({ type: 'timestamp' })
declare public readonly deletedAt: Date | null;
@ExtendColumn({ type: 'int2' })
declare public readonly isDeleted: number;
}Базовые метаданные должны уже существовать через @Entity, @Column,
@PrimaryColumn или @PrimaryGeneratedColumn.
Завершение работы
await db.destroy();destroy():
- отписывает notifications;
- уничтожает пул соединений DataSource;
- очищает procedure и naming caches;
- выбрасывает
AggregateError, если часть очистки завершилась ошибкой.
isRegisterShutdownHandlers: true регистрирует обработчики сигналов процесса,
которые автоматически вызывают destroy().
Важные runtime особенности
- PostgreSQL serializer глобально переопределяет
pg.Result.prototype.parseRow. - Oracle serializer глобально устанавливает
oracledb.fetchTypeHandler. - Oracle adapter устанавливает
oracledb.outFormat = oracledb.OUT_FORMAT_OBJECT. - Метаданные процедур загружаются из базы во время
initDatabase(), поэтому packages должны существовать и быть видимыми пользователю базы. call()нельзя использовать безpackagesSettings.- Плейсхолдеры для SQL-запросов напрямую должны быть в верхнем регистре,
например
:USER_ID. - PostgreSQL
parseInt8AsBigIntследует поведению bundled TypeORM/PostgresparseInt8:trueвозвращает JavaScript numbers дляint8, а не nativebigint.
Частые ошибки
TypeOrmProcedureKit is not initialized: вызовитеawait initDatabase().Procedure packages are not configured: настройтеconfig.packagesSettingsперед использованиемcall().Package "... " or process "... " not found: это ошибка адаптера для неизвестной процедуры; проверьте имена packages,procedureObjectListи доступность метаданных в базе.Payload for call procedure must be an object or array or undefined or null: не передавайте скалярный payload вcall().Unsafe SQL identifier for ...: имена процедур, cursors или notification channels должны соответствовать поддерживаемому identifier pattern до того, как adapter встроит их в SQL.- Результаты базы с nonzero
error_codeилиerr_codeпревращаются вServerError.
Лицензия
MIT.
