@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 +21 -0
- package/README.md +47 -2
- package/dist/context/mikro-database-facade.d.ts +11 -0
- package/dist/context/mikro-database-facade.js +35 -0
- package/dist/context/mikro-db-context-factory.d.ts +15 -0
- package/dist/context/mikro-db-context-factory.js +17 -0
- package/dist/context/mikro-db-context.d.ts +35 -0
- package/dist/context/mikro-db-context.js +105 -0
- package/dist/context/mikro-transaction.d.ts +12 -0
- package/dist/context/mikro-transaction.js +26 -0
- package/dist/context/tokens.d.ts +6 -0
- package/dist/context/tokens.js +5 -0
- package/dist/converters/value-converter-type.d.ts +9 -0
- package/dist/converters/value-converter-type.js +25 -0
- package/dist/extensions/service-collection-extensions.d.ts +29 -0
- package/dist/extensions/service-collection-extensions.js +75 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +16 -0
- package/dist/lifetime/mikro-lifetime-service.d.ts +19 -0
- package/dist/lifetime/mikro-lifetime-service.js +111 -0
- package/dist/repository/mikro-repository.d.ts +26 -0
- package/dist/repository/mikro-repository.js +71 -0
- package/package.json +52 -3
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
|
-
#
|
|
1
|
+
# @tsnx/mikro-orm
|
|
2
2
|
|
|
3
|
-
|
|
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
|
package/dist/index.d.ts
ADDED
|
@@ -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": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
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
|
}
|