@tsnx/mikro-orm 0.0.0-stage → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 TSNX Authors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,48 @@
1
- # Temporary Holding Version
1
+ # @tsnx/mikro-orm
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > MikroORM integration provider for TSNX Data, implementing EF Core-inspired DbContext, Scoped EntityManager forks, IDbContextFactory, and Optimistic Concurrency mapping.
4
+
5
+ ## Key Features
6
+
7
+ - **`MikroDbContext`**: Integrates MikroORM's `EntityManager` into TSNX's `DbContext` and `IUnitOfWork` contracts.
8
+ - **Scoped Identity Map**: Forked per HTTP request or background work scope (`MIKRO_ORM_EM`), preventing cross-request state pollution.
9
+ - **`IDbContextFactory`**: Creates isolated `MikroDbContext` instances outside HTTP scopes for background workers and schedulers.
10
+ - **Optimistic Concurrency**: Maps MikroORM's `OptimisticLockError` to `DbConcurrencyException` (HTTP 409 Conflict).
11
+ - **`asNoTracking` Queries**: Maps `asNoTracking: true` directly to MikroORM's `disableIdentityMap` for high-performance read-only operations.
12
+ - **Global Filters**: Ergonomic `enableFilter` and `addFilter` methods for multi-tenant and soft-delete workflows.
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ pnpm add @tsnx/data @tsnx/mikro-orm @mikro-orm/core @mikro-orm/sqlite
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ ```typescript
23
+ import { WebApplication } from '@tsnx/web';
24
+ import { addMikroOrm, MikroDbContext } from '@tsnx/mikro-orm';
25
+ import { defineConfig } from '@mikro-orm/sqlite';
26
+ import { User } from './entities/User.js';
27
+
28
+ const builder = WebApplication.createBuilder();
29
+
30
+ // 1. Configure MikroORM and DbContext
31
+ addMikroOrm(builder.services, defineConfig({
32
+ dbName: 'app.db',
33
+ entities: [User],
34
+ }), {
35
+ autoMigrate: true,
36
+ ensureCreated: true,
37
+ enableSensitiveDataLogging: true,
38
+ });
39
+
40
+ const app = builder.build();
41
+
42
+ // 2. Inject DbContext in route handlers (Scoped per request)
43
+ app.mapGet('/users', async (db: MikroDbContext) => {
44
+ return await db.set(User).findMany({}, { asNoTracking: true });
45
+ });
46
+
47
+ await app.run();
48
+ ```
@@ -0,0 +1,11 @@
1
+ import type { IDatabaseFacade } from '@tsnx/data';
2
+ import type { MikroORM } from '@mikro-orm/core';
3
+ export declare class MikroDatabaseFacade implements IDatabaseFacade {
4
+ private readonly orm;
5
+ readonly providerName = "mikro-orm";
6
+ constructor(orm: MikroORM);
7
+ canConnect(_signal?: AbortSignal): Promise<boolean>;
8
+ ensureCreated(_signal?: AbortSignal): Promise<boolean>;
9
+ migrate(_signal?: AbortSignal): Promise<void>;
10
+ }
11
+ //# sourceMappingURL=mikro-database-facade.d.ts.map
@@ -0,0 +1,35 @@
1
+ export class MikroDatabaseFacade {
2
+ orm;
3
+ providerName = 'mikro-orm';
4
+ constructor(orm) {
5
+ this.orm = orm;
6
+ }
7
+ async canConnect(_signal) {
8
+ try {
9
+ return await this.orm.isConnected();
10
+ }
11
+ catch {
12
+ return false;
13
+ }
14
+ }
15
+ async ensureCreated(_signal) {
16
+ const generator = this.orm.schema;
17
+ if (!generator)
18
+ return false;
19
+ try {
20
+ await generator.ensureDatabase();
21
+ await generator.updateSchema({ safe: true });
22
+ return true;
23
+ }
24
+ catch {
25
+ return false;
26
+ }
27
+ }
28
+ async migrate(_signal) {
29
+ const migrator = this.orm.migrator;
30
+ if (migrator) {
31
+ await migrator.up();
32
+ }
33
+ }
34
+ }
35
+ //# sourceMappingURL=mikro-database-facade.js.map
@@ -0,0 +1,15 @@
1
+ import type { IDbContextFactory } from '@tsnx/data';
2
+ import type { Constructor } from '@tsnx/hosting-abstractions';
3
+ import type { MikroORM } from '@mikro-orm/core';
4
+ import type { MikroDbContext } from './mikro-db-context.js';
5
+ /**
6
+ * Creates isolated MikroDbContext instances outside standard HTTP request scopes
7
+ * (e.g. background workers, queue consumers, CLI commands).
8
+ */
9
+ export declare class MikroDbContextFactory<TContext extends MikroDbContext = MikroDbContext> implements IDbContextFactory<TContext> {
10
+ private readonly orm;
11
+ private readonly contextClass;
12
+ constructor(orm: MikroORM, contextClass: Constructor<TContext>);
13
+ createDbContext(): TContext;
14
+ }
15
+ //# sourceMappingURL=mikro-db-context-factory.d.ts.map
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Creates isolated MikroDbContext instances outside standard HTTP request scopes
3
+ * (e.g. background workers, queue consumers, CLI commands).
4
+ */
5
+ export class MikroDbContextFactory {
6
+ orm;
7
+ contextClass;
8
+ constructor(orm, contextClass) {
9
+ this.orm = orm;
10
+ this.contextClass = contextClass;
11
+ }
12
+ createDbContext() {
13
+ const em = this.orm.em.fork();
14
+ return new this.contextClass(em, this.orm);
15
+ }
16
+ }
17
+ //# sourceMappingURL=mikro-db-context-factory.js.map
@@ -0,0 +1,35 @@
1
+ import { DbContext, type IDbTransaction, type IDbSet, type EntityConstructor, type IDatabaseFacade } from '@tsnx/data';
2
+ import { type EntityManager, type MikroORM } from '@mikro-orm/core';
3
+ /**
4
+ * MikroORM-backed DbContext conforming to EF Core Unit of Work architecture.
5
+ */
6
+ export declare class MikroDbContext extends DbContext {
7
+ readonly em: EntityManager;
8
+ readonly orm: MikroORM;
9
+ readonly database: IDatabaseFacade;
10
+ private _currentTransaction;
11
+ private _isDisposed;
12
+ constructor(em: EntityManager, orm: MikroORM);
13
+ get currentTransaction(): IDbTransaction | null;
14
+ get isDisposed(): boolean;
15
+ /**
16
+ * Persists all pending entity changes to the database.
17
+ * Automatically intercepts MikroORM's OptimisticLockError and rethrows as
18
+ * DbConcurrencyException (HTTP 409 Conflict).
19
+ */
20
+ saveChanges(_cancellationToken?: AbortSignal): Promise<number>;
21
+ /**
22
+ * Sets filter parameter values on this DbContext session (e.g. tenantId).
23
+ */
24
+ enableFilter(name: string, args?: Record<string, any>): this;
25
+ /**
26
+ * Registers a global filter onto this DbContext session.
27
+ */
28
+ addFilter(name: string, cond: Record<string, any> | ((args: Record<string, any>) => any), enabled?: boolean): this;
29
+ beginTransaction(isolationLevel?: string): Promise<IDbTransaction>;
30
+ executeInTransaction<T>(operation: (transaction: IDbTransaction) => Promise<T>, isolationLevel?: string): Promise<T>;
31
+ set<TEntity extends object, TId = unknown>(entityType: EntityConstructor<TEntity>): IDbSet<TEntity, TId>;
32
+ dispose(): void;
33
+ [Symbol.asyncDispose](): Promise<void>;
34
+ }
35
+ //# sourceMappingURL=mikro-db-context.d.ts.map
@@ -0,0 +1,105 @@
1
+ import { DbContext, DbConcurrencyException, } from '@tsnx/data';
2
+ import { OptimisticLockError, } from '@mikro-orm/core';
3
+ import { MikroDatabaseFacade } from './mikro-database-facade.js';
4
+ import { MikroDbTransaction } from './mikro-transaction.js';
5
+ import { MikroDbSet } from '../repository/mikro-repository.js';
6
+ /**
7
+ * MikroORM-backed DbContext conforming to EF Core Unit of Work architecture.
8
+ */
9
+ export class MikroDbContext extends DbContext {
10
+ em;
11
+ orm;
12
+ database;
13
+ _currentTransaction = null;
14
+ _isDisposed = false;
15
+ constructor(em, orm) {
16
+ super();
17
+ this.em = em;
18
+ this.orm = orm;
19
+ this.database = new MikroDatabaseFacade(orm);
20
+ }
21
+ get currentTransaction() {
22
+ return this._currentTransaction;
23
+ }
24
+ get isDisposed() {
25
+ return this._isDisposed;
26
+ }
27
+ /**
28
+ * Persists all pending entity changes to the database.
29
+ * Automatically intercepts MikroORM's OptimisticLockError and rethrows as
30
+ * DbConcurrencyException (HTTP 409 Conflict).
31
+ */
32
+ async saveChanges(_cancellationToken) {
33
+ if (this._isDisposed) {
34
+ throw new Error('Cannot save changes on a disposed DbContext.');
35
+ }
36
+ const uow = this.em.getUnitOfWork();
37
+ const count = uow.getPersistStack().size + uow.getRemoveStack().size;
38
+ try {
39
+ await this.em.flush();
40
+ }
41
+ catch (error) {
42
+ if (error instanceof OptimisticLockError ||
43
+ error?.name === 'OptimisticLockError' ||
44
+ error?.constructor?.name === 'OptimisticLockError') {
45
+ throw new DbConcurrencyException(error.message ?? 'A database concurrency conflict occurred. The entity was modified by another operation.', {
46
+ entityName: error?.entity?.constructor?.name,
47
+ entityId: error?.entity?.id,
48
+ cause: error,
49
+ });
50
+ }
51
+ throw error;
52
+ }
53
+ return count;
54
+ }
55
+ /**
56
+ * Sets filter parameter values on this DbContext session (e.g. tenantId).
57
+ */
58
+ enableFilter(name, args) {
59
+ this.em.setFilterParams(name, args ?? {});
60
+ return this;
61
+ }
62
+ /**
63
+ * Registers a global filter onto this DbContext session.
64
+ */
65
+ addFilter(name, cond, enabled = true) {
66
+ this.em.addFilter(name, cond, undefined, enabled);
67
+ return this;
68
+ }
69
+ async beginTransaction(isolationLevel) {
70
+ await this.em.begin({ isolationLevel: isolationLevel });
71
+ const tx = new MikroDbTransaction(this.em);
72
+ this._currentTransaction = tx;
73
+ return tx;
74
+ }
75
+ async executeInTransaction(operation, isolationLevel) {
76
+ const tx = await this.beginTransaction(isolationLevel);
77
+ try {
78
+ const result = await operation(tx);
79
+ await tx.commit();
80
+ return result;
81
+ }
82
+ catch (err) {
83
+ await tx.rollback();
84
+ throw err;
85
+ }
86
+ finally {
87
+ if (this._currentTransaction === tx) {
88
+ this._currentTransaction = null;
89
+ }
90
+ }
91
+ }
92
+ set(entityType) {
93
+ return new MikroDbSet(this.em, entityType);
94
+ }
95
+ dispose() {
96
+ if (this._isDisposed)
97
+ return;
98
+ this.em.clear();
99
+ this._isDisposed = true;
100
+ }
101
+ async [Symbol.asyncDispose]() {
102
+ this.dispose();
103
+ }
104
+ }
105
+ //# sourceMappingURL=mikro-db-context.js.map
@@ -0,0 +1,12 @@
1
+ import type { IDbTransaction } from '@tsnx/data';
2
+ import type { EntityManager } from '@mikro-orm/core';
3
+ export declare class MikroDbTransaction implements IDbTransaction {
4
+ private readonly em;
5
+ readonly transactionId: string;
6
+ private _isCompleted;
7
+ constructor(em: EntityManager, transactionId?: string);
8
+ get isCompleted(): boolean;
9
+ commit(): Promise<void>;
10
+ rollback(): Promise<void>;
11
+ }
12
+ //# sourceMappingURL=mikro-transaction.d.ts.map
@@ -0,0 +1,26 @@
1
+ export class MikroDbTransaction {
2
+ em;
3
+ transactionId;
4
+ _isCompleted = false;
5
+ constructor(em, transactionId) {
6
+ this.em = em;
7
+ this.transactionId =
8
+ transactionId ?? `tx_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
9
+ }
10
+ get isCompleted() {
11
+ return this._isCompleted;
12
+ }
13
+ async commit() {
14
+ if (this._isCompleted)
15
+ return;
16
+ await this.em.commit();
17
+ this._isCompleted = true;
18
+ }
19
+ async rollback() {
20
+ if (this._isCompleted)
21
+ return;
22
+ await this.em.rollback();
23
+ this._isCompleted = true;
24
+ }
25
+ }
26
+ //# sourceMappingURL=mikro-transaction.js.map
@@ -0,0 +1,6 @@
1
+ import { type ServiceToken } from '@tsnx/hosting-abstractions';
2
+ import type { MikroORM, EntityManager, Options } from '@mikro-orm/core';
3
+ export declare const MIKRO_ORM: ServiceToken<MikroORM>;
4
+ export declare const MIKRO_ORM_EM: ServiceToken<EntityManager>;
5
+ export declare const MIKRO_ORM_OPTIONS: ServiceToken<Options>;
6
+ //# sourceMappingURL=tokens.d.ts.map
@@ -0,0 +1,5 @@
1
+ import { createToken } from '@tsnx/hosting-abstractions';
2
+ export const MIKRO_ORM = createToken('MIKRO_ORM');
3
+ export const MIKRO_ORM_EM = createToken('MIKRO_ORM_EM');
4
+ export const MIKRO_ORM_OPTIONS = createToken('MIKRO_ORM_OPTIONS');
5
+ //# sourceMappingURL=tokens.js.map
@@ -0,0 +1,9 @@
1
+ import { Type } from '@mikro-orm/core';
2
+ import type { IValueConverter } from '@tsnx/data';
3
+ export type ValueConverterTypeConstructor<TModel, TProvider = string> = new () => Type<TModel, TProvider>;
4
+ /**
5
+ * Creates a MikroORM custom Type class from a TSNX IValueConverter,
6
+ * enabling transparent mapping between domain Value Objects and database primitives.
7
+ */
8
+ export declare function createValueConverterType<TModel, TProvider = string>(typeName: string, converter: IValueConverter<TModel, TProvider>, columnType?: string): ValueConverterTypeConstructor<TModel, TProvider>;
9
+ //# sourceMappingURL=value-converter-type.d.ts.map
@@ -0,0 +1,25 @@
1
+ import { Type } from '@mikro-orm/core';
2
+ /**
3
+ * Creates a MikroORM custom Type class from a TSNX IValueConverter,
4
+ * enabling transparent mapping between domain Value Objects and database primitives.
5
+ */
6
+ export function createValueConverterType(typeName, converter, columnType = 'text') {
7
+ class ConvertedType extends Type {
8
+ convertToDatabaseValue(value) {
9
+ if (value === null || value === undefined)
10
+ return value;
11
+ return converter.convertToDatabase(value);
12
+ }
13
+ convertToJSValue(value) {
14
+ if (value === null || value === undefined)
15
+ return value;
16
+ return converter.convertToModel(value);
17
+ }
18
+ getColumnType() {
19
+ return columnType;
20
+ }
21
+ }
22
+ Object.defineProperty(ConvertedType, 'name', { value: typeName });
23
+ return ConvertedType;
24
+ }
25
+ //# sourceMappingURL=value-converter-type.js.map
@@ -0,0 +1,29 @@
1
+ import type { IServiceCollection, Constructor, InjectionToken } from '@tsnx/hosting-abstractions';
2
+ import { type EntityConstructor, type IDbCommandInterceptor } from '@tsnx/data';
3
+ import { MikroDbContext } from '../context/mikro-db-context.js';
4
+ import { type MikroOrmSource } from '../lifetime/mikro-lifetime-service.js';
5
+ import { MikroDbSet } from '../repository/mikro-repository.js';
6
+ export interface AddMikroOrmOptions<TContext extends MikroDbContext = MikroDbContext> {
7
+ contextClass?: Constructor<TContext>;
8
+ autoMigrate?: boolean;
9
+ ensureCreated?: boolean;
10
+ enableLogging?: boolean;
11
+ enableSensitiveDataLogging?: boolean;
12
+ interceptors?: IDbCommandInterceptor[];
13
+ connectionString?: string;
14
+ }
15
+ /**
16
+ * Registers MikroORM integration into TSNX IServiceCollection modeled after EF Core's AddDbContext.
17
+ *
18
+ * - MIKRO_ORM: Singleton, manages connection pools and metadata
19
+ * - MIKRO_ORM_EM: Scoped, forked per scope/request for isolated Unit of Work
20
+ * - MikroDbContext / ContextClass: Scoped, EF Core-like unit of work session
21
+ * - IDbContextFactory: Singleton, creates isolated DbContext instances outside HTTP requests
22
+ * - Hosted Service: Transparently manages connection pooling and graceful shutdown
23
+ */
24
+ export declare function addMikroOrm<TContext extends MikroDbContext = MikroDbContext>(services: IServiceCollection, source: MikroOrmSource, optionsOrContext?: Constructor<TContext> | AddMikroOrmOptions<TContext>): IServiceCollection;
25
+ /**
26
+ * Registers an entity-specific repository or DbSet as Scoped in DI.
27
+ */
28
+ export declare function addEntityRepository<TEntity extends object>(services: IServiceCollection, tokenOrEntity: InjectionToken<MikroDbSet<TEntity>> | EntityConstructor<TEntity>, entityType?: EntityConstructor<TEntity>): IServiceCollection;
29
+ //# sourceMappingURL=service-collection-extensions.d.ts.map
@@ -0,0 +1,75 @@
1
+ import { DB_CONTEXT, DB_UNIT_OF_WORK, DB_CONTEXT_OPTIONS, DB_CONTEXT_FACTORY, } from '@tsnx/data';
2
+ import { MikroDbContext } from '../context/mikro-db-context.js';
3
+ import { MikroDbContextFactory } from '../context/mikro-db-context-factory.js';
4
+ import { MIKRO_ORM, MIKRO_ORM_EM } from '../context/tokens.js';
5
+ import { MikroHostService, } from '../lifetime/mikro-lifetime-service.js';
6
+ import { MikroDbSet } from '../repository/mikro-repository.js';
7
+ /**
8
+ * Registers MikroORM integration into TSNX IServiceCollection modeled after EF Core's AddDbContext.
9
+ *
10
+ * - MIKRO_ORM: Singleton, manages connection pools and metadata
11
+ * - MIKRO_ORM_EM: Scoped, forked per scope/request for isolated Unit of Work
12
+ * - MikroDbContext / ContextClass: Scoped, EF Core-like unit of work session
13
+ * - IDbContextFactory: Singleton, creates isolated DbContext instances outside HTTP requests
14
+ * - Hosted Service: Transparently manages connection pooling and graceful shutdown
15
+ */
16
+ export function addMikroOrm(services, source, optionsOrContext) {
17
+ let contextClass = MikroDbContext;
18
+ let dbOptions = { providerName: 'mikro-orm' };
19
+ if (typeof optionsOrContext === 'function') {
20
+ contextClass = optionsOrContext;
21
+ }
22
+ else if (optionsOrContext) {
23
+ if (optionsOrContext.contextClass) {
24
+ contextClass = optionsOrContext.contextClass;
25
+ }
26
+ dbOptions = {
27
+ providerName: 'mikro-orm',
28
+ autoMigrate: optionsOrContext.autoMigrate,
29
+ ensureCreated: optionsOrContext.ensureCreated,
30
+ enableLogging: optionsOrContext.enableLogging,
31
+ enableSensitiveDataLogging: optionsOrContext.enableSensitiveDataLogging,
32
+ interceptors: optionsOrContext.interceptors,
33
+ connectionString: optionsOrContext.connectionString,
34
+ };
35
+ }
36
+ // 1. Register DbContextOptions
37
+ services.addSingleton(DB_CONTEXT_OPTIONS, dbOptions);
38
+ // 2. Register Internal Host Service (Singleton)
39
+ const hostService = new MikroHostService(source, dbOptions);
40
+ services.addSingleton(MikroHostService, hostService);
41
+ // 3. Register Hosted Service for lifecycle start/stop
42
+ services.addHostedService().toFactory((sp) => sp.get(MikroHostService));
43
+ // 4. Register MIKRO_ORM singleton
44
+ services.addSingleton(MIKRO_ORM, (sp) => sp.get(MikroHostService).instance);
45
+ // 5. Register MIKRO_ORM_EM as Scoped (forked per scope / request)
46
+ services.addScoped(MIKRO_ORM_EM, (sp) => sp.get(MIKRO_ORM).em.fork());
47
+ // 6. Register DbContext class as Scoped
48
+ services.addScoped(contextClass, (sp) => {
49
+ const em = sp.get(MIKRO_ORM_EM);
50
+ const orm = sp.get(MIKRO_ORM);
51
+ return new contextClass(em, orm);
52
+ });
53
+ // 7. Bind IDbContext and IUnitOfWork tokens to the scoped DbContext
54
+ services.addScoped(DB_CONTEXT, (sp) => sp.get(contextClass));
55
+ services.addScoped(DB_UNIT_OF_WORK, (sp) => sp.get(contextClass));
56
+ // 8. Register IDbContextFactory singleton for background jobs / outside HTTP scopes
57
+ services.addSingleton(DB_CONTEXT_FACTORY, (sp) => {
58
+ const orm = sp.get(MIKRO_ORM);
59
+ return new MikroDbContextFactory(orm, contextClass);
60
+ });
61
+ return services;
62
+ }
63
+ /**
64
+ * Registers an entity-specific repository or DbSet as Scoped in DI.
65
+ */
66
+ export function addEntityRepository(services, tokenOrEntity, entityType) {
67
+ const entity = entityType ?? tokenOrEntity;
68
+ const token = tokenOrEntity;
69
+ services.addScoped(token, (sp) => {
70
+ const em = sp.get(MIKRO_ORM_EM);
71
+ return new MikroDbSet(em, entity);
72
+ });
73
+ return services;
74
+ }
75
+ //# sourceMappingURL=service-collection-extensions.js.map
@@ -0,0 +1,10 @@
1
+ export * from './context/tokens.js';
2
+ export * from './context/mikro-db-context.js';
3
+ export * from './context/mikro-db-context-factory.js';
4
+ export * from './context/mikro-database-facade.js';
5
+ export * from './context/mikro-transaction.js';
6
+ export * from './repository/mikro-repository.js';
7
+ export * from './converters/value-converter-type.js';
8
+ export * from './lifetime/mikro-lifetime-service.js';
9
+ export * from './extensions/service-collection-extensions.js';
10
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ // Tokens
2
+ export * from './context/tokens.js';
3
+ // Context, Factory & Transactions
4
+ export * from './context/mikro-db-context.js';
5
+ export * from './context/mikro-db-context-factory.js';
6
+ export * from './context/mikro-database-facade.js';
7
+ export * from './context/mikro-transaction.js';
8
+ // Repositories
9
+ export * from './repository/mikro-repository.js';
10
+ // Converters (ValueConverter)
11
+ export * from './converters/value-converter-type.js';
12
+ // Host Service & Types
13
+ export * from './lifetime/mikro-lifetime-service.js';
14
+ // DI Extensions
15
+ export * from './extensions/service-collection-extensions.js';
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,19 @@
1
+ import type { DbContextOptions } from '@tsnx/data';
2
+ import type { HostedService } from '@tsnx/hosting-abstractions';
3
+ import { MikroORM, type Options } from '@mikro-orm/core';
4
+ export type MikroOrmSource = MikroORM | Options | (() => Promise<MikroORM> | MikroORM);
5
+ /**
6
+ * Internal hosted service managing MikroORM startup initialization and clean connection pool shutdown.
7
+ */
8
+ export declare class MikroHostService implements HostedService {
9
+ private readonly source;
10
+ private readonly options?;
11
+ private _isReady;
12
+ private _orm;
13
+ constructor(source: MikroOrmSource, options?: DbContextOptions | undefined);
14
+ get isReady(): boolean;
15
+ get instance(): MikroORM;
16
+ start(signal: AbortSignal): Promise<void>;
17
+ stop(_signal: AbortSignal): Promise<void>;
18
+ }
19
+ //# sourceMappingURL=mikro-lifetime-service.d.ts.map
@@ -0,0 +1,111 @@
1
+ import { MikroORM } from '@mikro-orm/core';
2
+ /**
3
+ * Internal hosted service managing MikroORM startup initialization and clean connection pool shutdown.
4
+ */
5
+ export class MikroHostService {
6
+ source;
7
+ options;
8
+ _isReady = false;
9
+ _orm = null;
10
+ constructor(source, options) {
11
+ this.source = source;
12
+ this.options = options;
13
+ if (source instanceof MikroORM || (source && typeof source.isConnected === 'function')) {
14
+ this._orm = source;
15
+ }
16
+ }
17
+ get isReady() {
18
+ return this._isReady;
19
+ }
20
+ get instance() {
21
+ if (!this._orm) {
22
+ throw new Error('MikroORM instance is not yet initialized. Start the application host or supply an initialized MikroORM instance.');
23
+ }
24
+ return this._orm;
25
+ }
26
+ async start(signal) {
27
+ if (signal.aborted) {
28
+ throw new Error('Host startup aborted before database initialization.');
29
+ }
30
+ try {
31
+ // 1. Resolve or initialize MikroORM instance
32
+ if (!this._orm) {
33
+ if (typeof this.source === 'function') {
34
+ this._orm = await this.source();
35
+ }
36
+ else {
37
+ const config = { ...this.source };
38
+ this._orm = await MikroORM.init(config);
39
+ }
40
+ }
41
+ // 2. Attach query interceptors & debug logging via ORM logger
42
+ if (this.options?.interceptors?.length) {
43
+ const interceptors = this.options.interceptors;
44
+ const logger = this._orm.config.getLogger();
45
+ if (typeof logger.setDebugMode === 'function') {
46
+ logger.setDebugMode(true);
47
+ }
48
+ const originalWriter = typeof logger.writer === 'function' ? logger.writer : null;
49
+ logger.writer = (message) => {
50
+ const context = { command: message, startTime: Date.now() };
51
+ for (const interceptor of interceptors) {
52
+ interceptor.afterExecute?.(context);
53
+ }
54
+ if (this.options?.enableLogging && originalWriter) {
55
+ originalWriter(message);
56
+ }
57
+ };
58
+ }
59
+ else if (this.options?.enableSensitiveDataLogging) {
60
+ const logger = this._orm.config.getLogger();
61
+ if (typeof logger.setDebugMode === 'function') {
62
+ logger.setDebugMode(true);
63
+ }
64
+ }
65
+ else if (this.options?.enableLogging !== undefined) {
66
+ const logger = this._orm.config.getLogger();
67
+ if (typeof logger.setDebugMode === 'function') {
68
+ logger.setDebugMode(Boolean(this.options.enableLogging));
69
+ }
70
+ }
71
+ // 3. Verify / ensure connection
72
+ const isConnected = await this._orm.isConnected();
73
+ if (!isConnected) {
74
+ await this._orm.connect();
75
+ }
76
+ // 4. Auto-migration or schema creation if configured
77
+ if (this.options?.autoMigrate && this._orm.migrator) {
78
+ await this._orm.migrator.up();
79
+ }
80
+ else if (this.options?.ensureCreated && this._orm.schema) {
81
+ await this._orm.schema.ensureDatabase();
82
+ await this._orm.schema.updateSchema({ safe: true });
83
+ }
84
+ this._isReady = true;
85
+ }
86
+ catch (error) {
87
+ // Rollback and cleanup partial resources within catch to prevent dangling pools
88
+ try {
89
+ if (this._orm && (await this._orm.isConnected())) {
90
+ await this._orm.close(true);
91
+ }
92
+ }
93
+ catch {
94
+ // Suppress secondary cleanup error and rethrow the primary failure
95
+ }
96
+ this._isReady = false;
97
+ throw error;
98
+ }
99
+ }
100
+ async stop(_signal) {
101
+ try {
102
+ if (this._orm && (await this._orm.isConnected())) {
103
+ await this._orm.close(true);
104
+ }
105
+ }
106
+ finally {
107
+ this._isReady = false;
108
+ }
109
+ }
110
+ }
111
+ //# sourceMappingURL=mikro-lifetime-service.js.map
@@ -0,0 +1,26 @@
1
+ import type { IDbSet, EntityConstructor, FilterQuery, FindOptions } from '@tsnx/data';
2
+ import type { EntityManager } from '@mikro-orm/core';
3
+ export declare class MikroDbSet<TEntity extends object, TId = unknown> implements IDbSet<TEntity, TId> {
4
+ private readonly em;
5
+ readonly entityType: EntityConstructor<TEntity>;
6
+ constructor(em: EntityManager, entityType: EntityConstructor<TEntity>);
7
+ findById(id: TId, _signal?: AbortSignal): Promise<TEntity | null>;
8
+ findOne(filter: FilterQuery<TEntity>, options?: FindOptions<TEntity>): Promise<TEntity | null>;
9
+ findMany(filter?: FilterQuery<TEntity>, options?: FindOptions<TEntity>): Promise<TEntity[]>;
10
+ count(filter?: FilterQuery<TEntity>, _signal?: AbortSignal): Promise<number>;
11
+ exists(filter: FilterQuery<TEntity>, _signal?: AbortSignal): Promise<boolean>;
12
+ add(entity: TEntity): void;
13
+ addMany(entities: TEntity[]): void;
14
+ remove(entity: TEntity): void;
15
+ removeMany(entities: TEntity[]): void;
16
+ /**
17
+ * Executes a bulk update directly in the database without loading entities into memory.
18
+ */
19
+ executeUpdate(filter: FilterQuery<TEntity>, data: Partial<TEntity> | Record<string, any>): Promise<number>;
20
+ /**
21
+ * Executes a bulk delete directly in the database without loading entities into memory.
22
+ */
23
+ executeDelete(filter: FilterQuery<TEntity>): Promise<number>;
24
+ private mapOptions;
25
+ }
26
+ //# sourceMappingURL=mikro-repository.d.ts.map
@@ -0,0 +1,71 @@
1
+ export class MikroDbSet {
2
+ em;
3
+ entityType;
4
+ constructor(em, entityType) {
5
+ this.em = em;
6
+ this.entityType = entityType;
7
+ }
8
+ async findById(id, _signal) {
9
+ return (await this.em.findOne(this.entityType, id));
10
+ }
11
+ async findOne(filter, options) {
12
+ return (await this.em.findOne(this.entityType, filter, this.mapOptions(options)));
13
+ }
14
+ async findMany(filter, options) {
15
+ return (await this.em.find(this.entityType, (filter ?? {}), this.mapOptions(options)));
16
+ }
17
+ async count(filter, _signal) {
18
+ return await this.em.count(this.entityType, (filter ?? {}));
19
+ }
20
+ async exists(filter, _signal) {
21
+ const c = await this.count(filter);
22
+ return c > 0;
23
+ }
24
+ add(entity) {
25
+ this.em.persist(entity);
26
+ }
27
+ addMany(entities) {
28
+ this.em.persist(entities);
29
+ }
30
+ remove(entity) {
31
+ this.em.remove(entity);
32
+ }
33
+ removeMany(entities) {
34
+ for (const entity of entities) {
35
+ this.em.remove(entity);
36
+ }
37
+ }
38
+ /**
39
+ * Executes a bulk update directly in the database without loading entities into memory.
40
+ */
41
+ async executeUpdate(filter, data) {
42
+ return await this.em.nativeUpdate(this.entityType, (filter ?? {}), data);
43
+ }
44
+ /**
45
+ * Executes a bulk delete directly in the database without loading entities into memory.
46
+ */
47
+ async executeDelete(filter) {
48
+ return await this.em.nativeDelete(this.entityType, (filter ?? {}));
49
+ }
50
+ mapOptions(options) {
51
+ if (!options)
52
+ return undefined;
53
+ const mapped = {};
54
+ if (options.skip !== undefined)
55
+ mapped.offset = options.skip;
56
+ if (options.take !== undefined)
57
+ mapped.limit = options.take;
58
+ if (options.orderBy)
59
+ mapped.orderBy = options.orderBy;
60
+ if (options.populate)
61
+ mapped.populate = options.populate;
62
+ if (options.fields)
63
+ mapped.fields = options.fields;
64
+ if (options.asNoTracking)
65
+ mapped.disableIdentityMap = true;
66
+ if (options.cache !== undefined)
67
+ mapped.cache = options.cache;
68
+ return mapped;
69
+ }
70
+ }
71
+ //# sourceMappingURL=mikro-repository.js.map
package/package.json CHANGED
@@ -1,6 +1,55 @@
1
1
  {
2
2
  "name": "@tsnx/mikro-orm",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "1.2.0",
4
+ "description": "MikroORM provider adapter for TSNX Data - Scoped EntityManager, MikroDbContext, and Hosted Service integration",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "!dist/__tests__",
18
+ "!dist/**/*.tsbuildinfo",
19
+ "!dist/**/*.map",
20
+ "README.md",
21
+ "LICENSE"
22
+ ],
23
+ "engines": {
24
+ "node": ">=18.0.0"
25
+ },
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "dependencies": {
30
+ "@tsnx/data": "1.2.0",
31
+ "@tsnx/hosting-abstractions": "^1.1.0"
32
+ },
33
+ "peerDependencies": {
34
+ "@mikro-orm/core": ">=6.0.0"
35
+ },
36
+ "devDependencies": {
37
+ "@mikro-orm/core": "^6.4.16",
38
+ "@mikro-orm/sqlite": "^6.4.16",
39
+ "@tsnx/hosting": "^1.1.0",
40
+ "@types/node": "^22.13.9"
41
+ },
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "https://github.com/tsnxjs/data.git",
45
+ "directory": "packages/mikro-orm"
46
+ },
47
+ "bugs": "https://github.com/tsnxjs/data/issues",
48
+ "homepage": "https://github.com/tsnxjs/data#readme",
49
+ "scripts": {
50
+ "build": "tsc -b",
51
+ "clean": "tsc -b --clean",
52
+ "typecheck": "tsc -b --noEmit",
53
+ "test": "node --test dist/__tests__/**/*.test.js"
54
+ }
6
55
  }