stabilize-orm 1.1.3 → 1.1.5
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/README.md +209 -155
- package/bun.lock +61 -0
- package/cache.ts +88 -17
- package/client.ts +153 -170
- package/decorators.ts +70 -4
- package/index.ts +92 -51
- package/logger.ts +76 -75
- package/migrations.ts +157 -65
- package/package.json +5 -2
- package/query-builder.ts +103 -13
- package/repository.ts +447 -287
- package/types.ts +58 -29
- package/cli/stabilize-cli.ts +0 -563
- package/dist/cli/stabilize-cli.js +0 -251
- package/dist/index.js +0 -181
package/client.ts
CHANGED
|
@@ -1,233 +1,216 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @file client.ts
|
|
3
|
+
* @description Provides a unified database client for interacting with PostgreSQL, MySQL, and SQLite.
|
|
4
|
+
* @author ElectronSz
|
|
5
|
+
*/
|
|
6
|
+
|
|
2
7
|
import { Database, Statement } from "bun:sqlite";
|
|
8
|
+
import { Pool, type PoolClient } from "pg";
|
|
9
|
+
import mysql from "mysql2/promise";
|
|
3
10
|
import {
|
|
4
11
|
type DBConfig,
|
|
5
12
|
StabilizeError,
|
|
6
|
-
type PoolMetrics,
|
|
7
13
|
DBType,
|
|
8
14
|
} from "./types";
|
|
9
15
|
import { type Logger, ConsoleLogger } from "./logger";
|
|
10
16
|
|
|
17
|
+
/** @internal Checks if the config is for SQLite. */
|
|
11
18
|
function isSQLiteConfig(config: DBConfig): boolean {
|
|
12
|
-
return
|
|
13
|
-
|
|
14
|
-
|
|
19
|
+
return config.type === DBType.SQLite;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** @internal Checks if the config is for MySQL. */
|
|
23
|
+
function isMySQLConfig(config: DBConfig): boolean {
|
|
24
|
+
return config.type === DBType.MySQL;
|
|
15
25
|
}
|
|
16
26
|
|
|
27
|
+
/** @internal A type guard to reliably identify a mysql2 Pool object. */
|
|
28
|
+
function isMySQLPool(client: any): client is mysql.Pool {
|
|
29
|
+
return typeof client.getConnection === 'function';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A unified database client that provides a consistent interface for
|
|
34
|
+
* PostgreSQL, MySQL, and SQLite databases. It handles connection pooling,
|
|
35
|
+
* query execution with retries, and transactions.
|
|
36
|
+
*/
|
|
17
37
|
export class DBClient {
|
|
18
|
-
private client
|
|
38
|
+
private client!: Database | Pool | mysql.Pool | PoolClient | mysql.PoolConnection;
|
|
19
39
|
private logger: Logger;
|
|
40
|
+
public readonly config: DBConfig;
|
|
20
41
|
private retryAttempts: number;
|
|
21
42
|
private retryDelay: number;
|
|
22
43
|
private maxJitter: number;
|
|
23
44
|
|
|
24
45
|
private preparedStatements: Map<string, Statement> = new Map();
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
46
|
+
public readonly isTransactionClient: boolean = false;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Creates an instance of DBClient.
|
|
50
|
+
* @param config The database configuration object.
|
|
51
|
+
* @param logger A logger instance for logging messages.
|
|
52
|
+
* @param existingClient An optional existing connection, used internally for transactions.
|
|
53
|
+
*/
|
|
54
|
+
constructor(
|
|
55
|
+
config: DBConfig,
|
|
56
|
+
logger: Logger = new ConsoleLogger(),
|
|
57
|
+
existingClient: PoolClient | mysql.PoolConnection | null = null,
|
|
58
|
+
) {
|
|
28
59
|
this.config = config;
|
|
29
60
|
this.logger = logger;
|
|
30
61
|
this.retryAttempts = config.retryAttempts || 3;
|
|
31
62
|
this.retryDelay = config.retryDelay || 1000;
|
|
32
63
|
this.maxJitter = config.maxJitter || 100;
|
|
33
64
|
|
|
34
|
-
|
|
65
|
+
if (existingClient) {
|
|
66
|
+
this.client = existingClient;
|
|
67
|
+
this.isTransactionClient = true;
|
|
68
|
+
} else {
|
|
69
|
+
this.initializeClient(config);
|
|
70
|
+
}
|
|
35
71
|
}
|
|
36
72
|
|
|
73
|
+
/**
|
|
74
|
+
* @internal
|
|
75
|
+
* Initializes the database client based on the provided configuration.
|
|
76
|
+
* @param config The database configuration.
|
|
77
|
+
*/
|
|
37
78
|
private initializeClient(config: DBConfig) {
|
|
38
79
|
if (isSQLiteConfig(config)) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
throw new StabilizeError(
|
|
48
|
-
`Failed to initialize SQLite database: ${(e as Error).message}`,
|
|
49
|
-
"INIT_ERROR",
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
} else {
|
|
53
|
-
this.client = new SQL(config.connectionString);
|
|
54
|
-
this.logger.logDebug(
|
|
55
|
-
`Initialized Bun SQL client for: ${config.connectionString}`,
|
|
56
|
-
);
|
|
80
|
+
this.client = new Database(config.connectionString, { create: true });
|
|
81
|
+
this.logger.logDebug(`Initialized Bun SQLite client.`);
|
|
82
|
+
} else if (isMySQLConfig(config)) {
|
|
83
|
+
this.client = mysql.createPool(config.connectionString);
|
|
84
|
+
this.logger.logDebug(`Initialized MySQL Pool client.`);
|
|
85
|
+
} else {
|
|
86
|
+
this.client = new Pool({ connectionString: config.connectionString });
|
|
87
|
+
this.logger.logDebug(`Initialized Postgres Pool client.`);
|
|
57
88
|
}
|
|
58
89
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
this.preparedStatements.clear();
|
|
76
|
-
this.retryAttempts = config.retryAttempts || 3;
|
|
77
|
-
this.retryDelay = config.retryDelay || 1000;
|
|
78
|
-
this.maxJitter = config.maxJitter || 100;
|
|
79
|
-
this.initializeClient(config);
|
|
80
|
-
}
|
|
81
|
-
|
|
90
|
+
|
|
91
|
+
/** @internal Gets a random jitter value to add to retry delays. */
|
|
92
|
+
private getJitter = () => Math.random() * this.maxJitter;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Executes a SQL query with parameters and returns the result.
|
|
96
|
+
* Automatically handles placeholder conversion for different databases and includes retry logic.
|
|
97
|
+
* @template T The expected type of the result rows.
|
|
98
|
+
* @param query The SQL query string with `?` as placeholders.
|
|
99
|
+
* @param params An array of parameters to bind to the query.
|
|
100
|
+
* @returns A promise that resolves to an array of results.
|
|
101
|
+
* @example
|
|
102
|
+
* ```
|
|
103
|
+
* const users = await dbClient.query('SELECT * FROM users WHERE status = ?', ['active']);
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
82
106
|
async query<T>(query: string, params: any[] = []): Promise<T[]> {
|
|
83
107
|
const start = Date.now();
|
|
84
108
|
this.logger.logQuery(query, params);
|
|
85
|
-
this.logger.logMetrics(this.getPoolMetrics());
|
|
86
|
-
|
|
87
|
-
let stmt: Statement | undefined;
|
|
88
|
-
|
|
89
|
-
// Check for Bun SQLite client
|
|
90
|
-
if (this.client instanceof Database) {
|
|
91
|
-
const stmtKey = query;
|
|
92
|
-
// Use prepared statement caching for SQLite
|
|
93
|
-
if (!this.preparedStatements.has(stmtKey)) {
|
|
94
|
-
// Use this.client (the Database instance) to prepare the statement
|
|
95
|
-
this.preparedStatements.set(stmtKey, this.client.prepare(query));
|
|
96
|
-
}
|
|
97
|
-
stmt = this.preparedStatements.get(stmtKey);
|
|
98
|
-
}
|
|
99
109
|
|
|
100
110
|
for (let attempt = 1; attempt <= this.retryAttempts; attempt++) {
|
|
101
111
|
try {
|
|
102
|
-
let result:
|
|
103
|
-
|
|
104
|
-
if (
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
112
|
+
let result: any;
|
|
113
|
+
|
|
114
|
+
if (this.client instanceof Database) { // SQLite
|
|
115
|
+
let stmt = this.preparedStatements.get(query);
|
|
116
|
+
if (!stmt) {
|
|
117
|
+
stmt = this.client.prepare(query);
|
|
118
|
+
this.preparedStatements.set(query, stmt);
|
|
119
|
+
}
|
|
120
|
+
result = stmt.all(...params);
|
|
121
|
+
} else if (isMySQLPool(this.client) || ('query' in this.client && 'release' in this.client)) { // mysql2 Pool or Connection
|
|
122
|
+
const [rows] = await (this.client as mysql.Pool).query(query, params);
|
|
123
|
+
result = rows;
|
|
124
|
+
} else { // Postgres Pool or Client
|
|
125
|
+
const pgQuery = query.replace(/\?/g, (_, i) => `$${i + 1}`);
|
|
126
|
+
const pgResult = await (this.client as Pool).query(pgQuery, params);
|
|
127
|
+
result = pgResult.rows;
|
|
113
128
|
}
|
|
114
129
|
|
|
115
130
|
const executionTime = Date.now() - start;
|
|
116
131
|
this.logger.logQuery(query, params, executionTime);
|
|
117
|
-
return result;
|
|
132
|
+
return result as T[];
|
|
118
133
|
} catch (error) {
|
|
119
134
|
this.logger.logError(error as Error);
|
|
120
|
-
if (attempt === this.retryAttempts) {
|
|
121
|
-
|
|
122
|
-
`Query failed after ${this.retryAttempts} attempts: ${(error as Error).message}`,
|
|
123
|
-
"QUERY_ERROR",
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
await new Promise((resolve) =>
|
|
127
|
-
setTimeout(
|
|
128
|
-
resolve,
|
|
129
|
-
this.retryDelay * Math.pow(2, attempt - 1) + this.getJitter(),
|
|
130
|
-
),
|
|
131
|
-
);
|
|
135
|
+
if (attempt === this.retryAttempts) throw new StabilizeError(`Query failed: ${(error as Error).message}`, "QUERY_ERROR");
|
|
136
|
+
await new Promise(res => setTimeout(res, this.retryDelay * Math.pow(2, attempt - 1) + this.getJitter()));
|
|
132
137
|
}
|
|
133
138
|
}
|
|
134
139
|
throw new StabilizeError("Query failed: no attempts made", "QUERY_ERROR");
|
|
135
140
|
}
|
|
136
141
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
142
|
+
/**
|
|
143
|
+
* Executes a series of database operations within a single atomic transaction.
|
|
144
|
+
* If any operation in the callback fails, the entire transaction is rolled back.
|
|
145
|
+
* @template T The return type of the callback function.
|
|
146
|
+
* @param callback An async function that receives a transactional `DBClient` instance.
|
|
147
|
+
* @returns A promise that resolves with the result of the callback.
|
|
148
|
+
* @example
|
|
149
|
+
* ```
|
|
150
|
+
* await dbClient.transaction(async (txClient) => {
|
|
151
|
+
* await txClient.query('UPDATE accounts SET balance = balance - 100 WHERE id = 1');
|
|
152
|
+
* await txClient.query('UPDATE accounts SET balance = balance + 100 WHERE id = 2');
|
|
153
|
+
* });
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
async transaction<T>(callback: (txClient: DBClient) => Promise<T>): Promise<T> {
|
|
157
|
+
if (this.isTransactionClient) return callback(this);
|
|
145
158
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
this.logger
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
+
if (this.client instanceof Database) {
|
|
160
|
+
const tx = this.client.transaction(() => callback(this));
|
|
161
|
+
return tx();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (isMySQLPool(this.client)) {
|
|
165
|
+
const connection = await this.client.getConnection();
|
|
166
|
+
const txClient = new DBClient(this.config, this.logger, connection);
|
|
167
|
+
this.logger.logDebug("Starting MySQL transaction.");
|
|
168
|
+
try {
|
|
169
|
+
await txClient.query("START TRANSACTION");
|
|
170
|
+
const result = await callback(txClient);
|
|
171
|
+
await txClient.query("COMMIT");
|
|
172
|
+
return result;
|
|
173
|
+
} catch (error) {
|
|
174
|
+
await txClient.query("ROLLBACK");
|
|
175
|
+
throw error;
|
|
176
|
+
} finally {
|
|
177
|
+
connection.release();
|
|
178
|
+
this.logger.logDebug("MySQL transaction connection released.");
|
|
179
|
+
}
|
|
159
180
|
}
|
|
160
181
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
182
|
+
if (this.client instanceof Pool) {
|
|
183
|
+
const connection = await this.client.connect();
|
|
184
|
+
const txClient = new DBClient(this.config, this.logger, connection);
|
|
185
|
+
this.logger.logDebug("Starting Postgres transaction.");
|
|
164
186
|
try {
|
|
165
|
-
|
|
166
|
-
const result = await callback();
|
|
167
|
-
await
|
|
168
|
-
this.logger.logDebug(
|
|
169
|
-
`Manual transaction committed in ${Date.now() - start}ms`,
|
|
170
|
-
);
|
|
187
|
+
await txClient.query("BEGIN");
|
|
188
|
+
const result = await callback(txClient);
|
|
189
|
+
await txClient.query("COMMIT");
|
|
171
190
|
return result;
|
|
172
191
|
} catch (error) {
|
|
173
|
-
await
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
throw new StabilizeError(
|
|
179
|
-
`Manual transaction failed after ${this.retryAttempts} attempts: ${(error as Error).message}`,
|
|
180
|
-
"TX_ERROR",
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
await new Promise((resolve) =>
|
|
184
|
-
setTimeout(
|
|
185
|
-
resolve,
|
|
186
|
-
this.retryDelay * Math.pow(2, attempt - 1) + this.getJitter(),
|
|
187
|
-
),
|
|
188
|
-
);
|
|
192
|
+
await txClient.query("ROLLBACK");
|
|
193
|
+
throw error;
|
|
194
|
+
} finally {
|
|
195
|
+
connection.release();
|
|
196
|
+
this.logger.logDebug("Postgres transaction connection released.");
|
|
189
197
|
}
|
|
190
198
|
}
|
|
191
|
-
throw new StabilizeError(
|
|
192
|
-
"Transaction failed: no attempts made",
|
|
193
|
-
"TX_ERROR",
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
199
|
|
|
197
|
-
|
|
198
|
-
const start = Date.now();
|
|
199
|
-
this.logger.logDebug(`Starting savepoint ${name}`);
|
|
200
|
-
await this.query(`SAVEPOINT ${name}`, []);
|
|
201
|
-
try {
|
|
202
|
-
const result = await callback();
|
|
203
|
-
await this.query(`RELEASE SAVEPOINT ${name}`, []);
|
|
204
|
-
this.logger.logDebug(
|
|
205
|
-
`Savepoint ${name} released in ${Date.now() - start}ms`,
|
|
206
|
-
);
|
|
207
|
-
return result;
|
|
208
|
-
} catch (error) {
|
|
209
|
-
await this.query(`ROLLBACK TO SAVEPOINT ${name}`, []).catch(() => {
|
|
210
|
-
this.logger.logDebug(
|
|
211
|
-
`Rollback to savepoint ${name} failed. Connection may be invalid.`,
|
|
212
|
-
);
|
|
213
|
-
});
|
|
214
|
-
this.logger.logError(error as Error);
|
|
215
|
-
throw error;
|
|
216
|
-
}
|
|
200
|
+
throw new StabilizeError("Transaction not supported by this client configuration.", "TX_ERROR");
|
|
217
201
|
}
|
|
218
202
|
|
|
203
|
+
/**
|
|
204
|
+
* Closes the database connection pool gracefully.
|
|
205
|
+
* Should be called when the application is shutting down.
|
|
206
|
+
*/
|
|
219
207
|
async close() {
|
|
220
|
-
this.
|
|
221
|
-
if (this.client instanceof Database) {
|
|
208
|
+
if (this.client instanceof Database) {
|
|
222
209
|
this.client.close();
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
this.client &&
|
|
226
|
-
typeof (this.client as any).close === "function"
|
|
227
|
-
) {
|
|
228
|
-
await (this.client as any).close();
|
|
229
|
-
this.client = null;
|
|
210
|
+
} else if (this.client && 'end' in this.client) {
|
|
211
|
+
await (this.client as any).end();
|
|
230
212
|
}
|
|
213
|
+
this.client = null!;
|
|
231
214
|
this.logger.logInfo("Database connection closed");
|
|
232
215
|
}
|
|
233
|
-
}
|
|
216
|
+
}
|
package/decorators.ts
CHANGED
|
@@ -1,26 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file decorators.ts
|
|
3
|
+
* @description Contains all the decorators used by the Stabilize ORM.
|
|
4
|
+
* @author ElectronSz
|
|
5
|
+
*/
|
|
6
|
+
|
|
1
7
|
import "reflect-metadata";
|
|
2
|
-
import { RelationType } from "./types";
|
|
8
|
+
import { RelationType, DataTypes } from "./types";
|
|
9
|
+
|
|
3
10
|
|
|
4
11
|
export const ModelKey = Symbol("model");
|
|
5
12
|
export const ColumnKey = Symbol("column");
|
|
6
13
|
export const ValidatorKey = Symbol("validator");
|
|
7
14
|
export const RelationKey = Symbol("relation");
|
|
8
15
|
export const SoftDeleteKey = Symbol("softDelete");
|
|
16
|
+
export const DefaultKey = Symbol("default");
|
|
17
|
+
export const IndexKey = Symbol("index");
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
export interface ColumnOptions {
|
|
21
|
+
name?: string;
|
|
22
|
+
type: DataTypes;
|
|
23
|
+
length?: number;
|
|
24
|
+
precision?: number;
|
|
25
|
+
scale?: number;
|
|
26
|
+
}
|
|
9
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Decorator to mark a class as a database model.
|
|
30
|
+
* @param tableName The name of the table in the database.
|
|
31
|
+
*/
|
|
10
32
|
export function Model(tableName: string) {
|
|
11
33
|
return function (constructor: Function) {
|
|
12
34
|
Reflect.defineMetadata(ModelKey, tableName, constructor);
|
|
13
35
|
};
|
|
14
36
|
}
|
|
15
37
|
|
|
16
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Decorator to mark a property as a database column.
|
|
40
|
+
* @param options The configuration for the column, including name, type, and length.
|
|
41
|
+
*/
|
|
42
|
+
export function Column(options: ColumnOptions | DataTypes) {
|
|
17
43
|
return function (target: any, propertyKey: string) {
|
|
18
44
|
const columns = Reflect.getMetadata(ColumnKey, target) || {};
|
|
19
|
-
|
|
45
|
+
|
|
46
|
+
const columnOptions: ColumnOptions = typeof options === 'object' ? options : { type: options };
|
|
47
|
+
|
|
48
|
+
columns[propertyKey] = {
|
|
49
|
+
name: columnOptions.name || propertyKey,
|
|
50
|
+
...columnOptions,
|
|
51
|
+
};
|
|
20
52
|
Reflect.defineMetadata(ColumnKey, columns, target);
|
|
21
53
|
};
|
|
22
54
|
}
|
|
23
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Decorator to enforce a NOT NULL constraint on a column.
|
|
58
|
+
*/
|
|
24
59
|
export function Required() {
|
|
25
60
|
return function (target: any, propertyKey: string) {
|
|
26
61
|
const validators = Reflect.getMetadata(ValidatorKey, target) || {};
|
|
@@ -29,6 +64,9 @@ export function Required() {
|
|
|
29
64
|
};
|
|
30
65
|
}
|
|
31
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Decorator to enforce a UNIQUE constraint on a column.
|
|
69
|
+
*/
|
|
32
70
|
export function Unique() {
|
|
33
71
|
return function (target: any, propertyKey: string) {
|
|
34
72
|
const validators = Reflect.getMetadata(ValidatorKey, target) || {};
|
|
@@ -37,12 +75,40 @@ export function Unique() {
|
|
|
37
75
|
};
|
|
38
76
|
}
|
|
39
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Decorator to set a default value for a column.
|
|
80
|
+
* @param value The default value.
|
|
81
|
+
*/
|
|
82
|
+
export function Default(value: any) {
|
|
83
|
+
return function (target: any, propertyKey: string) {
|
|
84
|
+
Reflect.defineMetadata(DefaultKey, value, target, propertyKey);
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Decorator to create a non-unique index on a column for performance.
|
|
90
|
+
* @param indexName Optional: A custom name for the index.
|
|
91
|
+
*/
|
|
92
|
+
export function Index(indexName?: string) {
|
|
93
|
+
return function (target: any, propertyKey: string) {
|
|
94
|
+
const indexes = Reflect.getMetadata(IndexKey, target) || {};
|
|
95
|
+
indexes[propertyKey] = indexName || `idx_${propertyKey}`;
|
|
96
|
+
Reflect.defineMetadata(IndexKey, indexes, target);
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Decorator to enable soft-delete functionality on a model.
|
|
103
|
+
* The decorated property will store the deletion timestamp.
|
|
104
|
+
*/
|
|
40
105
|
export function SoftDelete() {
|
|
41
106
|
return function (target: any, propertyKey: string) {
|
|
42
107
|
Reflect.defineMetadata(SoftDeleteKey, propertyKey, target);
|
|
43
108
|
};
|
|
44
109
|
}
|
|
45
110
|
|
|
111
|
+
|
|
46
112
|
export function OneToOne(model: () => any, foreignKey: string) {
|
|
47
113
|
return function (target: any, propertyKey: string) {
|
|
48
114
|
const relations = Reflect.getMetadata(RelationKey, target) || {};
|
|
@@ -96,4 +162,4 @@ export function ManyToMany(
|
|
|
96
162
|
};
|
|
97
163
|
Reflect.defineMetadata(RelationKey, relations, target);
|
|
98
164
|
};
|
|
99
|
-
}
|
|
165
|
+
}
|