stabilize-orm 1.3.3 → 1.3.9
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 +16 -1
- package/bun.lock +5 -5
- package/cache.ts +2 -2
- package/client.ts +1 -5
- package/docker-compose.yml +22 -0
- package/hooks.ts +1 -1
- package/index.ts +8 -5
- package/migrations.ts +39 -2
- package/package.json +11 -11
- package/public/logo_both-transparent.png +0 -0
- package/public/logo_iamge-transparent.png +0 -0
- package/public/logo_text-transparent.png +0 -0
- package/repository.ts +28 -7
package/README.md
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
# Stabilize ORM
|
|
2
2
|
|
|
3
3
|
_A Modern, Type-Safe, and Expressive ORM for Bun_
|
|
4
|
+
<p align="left">
|
|
5
|
+
<img src="./public/logo_both-transparent.png" alt="Stabilize ORM Logo" width="280" />
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
<p align="left">
|
|
9
|
+
<a href="https://www.npmjs.com/package/stabilize-orm"><img src="https://img.shields.io/npm/v/stabilize-orm.svg?label=version&color=blue" alt="NPM Version"></a>
|
|
10
|
+
<a href="https://github.com/ElectronSz/stabilize-cli/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/stabilize-orm.svg?color=green" alt="License"></a>
|
|
11
|
+
<a href="https://github.com/ElectronSz/stabilize-cli"><img src="https://img.shields.io/badge/Cli-Stabilize%201.2.0-blue.svg" alt="Stabilize CLI"></a>
|
|
12
|
+
<a href="#"><img src="https://img.shields.io/badge/PostgreSQL-supported-blue" alt="PostgreSQL"></a>
|
|
13
|
+
<a href="#"><img src="https://img.shields.io/badge/MySQL-supported-blue" alt="MySQL"></a>
|
|
14
|
+
<a href="#"><img src="https://img.shields.io/badge/SQLite-supported-blue" alt="SQLite"></a>
|
|
15
|
+
<a href="https://github.com/ElectronSz/stabilize-orm/actions/workflows/ci-cd.yml">
|
|
16
|
+
<img src="https://github.com/ElectronSz/stabilize-orm/actions/workflows/ci-cd.yml/badge.svg" alt="Build Status">
|
|
17
|
+
</a>
|
|
18
|
+
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-green.svg" alt="MIT License"></a>
|
|
19
|
+
</p>
|
|
4
20
|
|
|
5
|
-
---
|
|
6
21
|
|
|
7
22
|
**Stabilize** is a lightweight, feature-rich ORM designed for performance and developer experience. It provides a unified, database-agnostic API for **PostgreSQL**, **MySQL**, and **SQLite**. Powered by a robust query builder, programmatic model definitions, automatic versioning, and a full-featured command-line interface, Stabilize is built to scale with your app.
|
|
8
23
|
|
package/bun.lock
CHANGED
|
@@ -92,17 +92,17 @@
|
|
|
92
92
|
|
|
93
93
|
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="],
|
|
94
94
|
|
|
95
|
-
"@eslint/config-array": ["@eslint/config-array@0.21.
|
|
95
|
+
"@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="],
|
|
96
96
|
|
|
97
|
-
"@eslint/config-helpers": ["@eslint/config-helpers@0.4.
|
|
97
|
+
"@eslint/config-helpers": ["@eslint/config-helpers@0.4.1", "", { "dependencies": { "@eslint/core": "^0.16.0" } }, "sha512-csZAzkNhsgwb0I/UAV6/RGFTbiakPCf0ZrGmrIxQpYvGZ00PhTkSnyKNolphgIvmnJeGw6rcGVEXfTzUnFuEvw=="],
|
|
98
98
|
|
|
99
99
|
"@eslint/core": ["@eslint/core@0.16.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q=="],
|
|
100
100
|
|
|
101
101
|
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="],
|
|
102
102
|
|
|
103
|
-
"@eslint/js": ["@eslint/js@9.
|
|
103
|
+
"@eslint/js": ["@eslint/js@9.38.0", "", {}, "sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A=="],
|
|
104
104
|
|
|
105
|
-
"@eslint/object-schema": ["@eslint/object-schema@2.1.
|
|
105
|
+
"@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
|
|
106
106
|
|
|
107
107
|
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.0", "", { "dependencies": { "@eslint/core": "^0.16.0", "levn": "^0.4.1" } }, "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A=="],
|
|
108
108
|
|
|
@@ -316,7 +316,7 @@
|
|
|
316
316
|
|
|
317
317
|
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
|
318
318
|
|
|
319
|
-
"eslint": ["eslint@9.
|
|
319
|
+
"eslint": ["eslint@9.38.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.1", "@eslint/core": "^0.16.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.38.0", "@eslint/plugin-kit": "^0.4.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw=="],
|
|
320
320
|
|
|
321
321
|
"eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
|
|
322
322
|
|
package/cache.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import Redis from "ioredis";
|
|
8
8
|
import { type CacheConfig, type CacheStats } from "./types";
|
|
9
|
-
import {
|
|
9
|
+
import { StabilizeLogger, type Logger } from "./logger";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* A caching client that uses Redis to store and retrieve query results.
|
|
@@ -26,7 +26,7 @@ export class Cache {
|
|
|
26
26
|
* @param config The configuration for the cache, including Redis URL and TTL.
|
|
27
27
|
* @param logger A logger instance for logging messages.
|
|
28
28
|
*/
|
|
29
|
-
constructor(config: CacheConfig, logger: Logger = new
|
|
29
|
+
constructor(config: CacheConfig, logger: Logger = new StabilizeLogger()) {
|
|
30
30
|
this.config = config;
|
|
31
31
|
this.logger = logger;
|
|
32
32
|
|
package/client.ts
CHANGED
|
@@ -117,8 +117,6 @@ export class DBClient {
|
|
|
117
117
|
try {
|
|
118
118
|
let result: any;
|
|
119
119
|
|
|
120
|
-
this.logger.logQuery(query, params);
|
|
121
|
-
|
|
122
120
|
if (this.client instanceof Database) {
|
|
123
121
|
let stmt = this.preparedStatements.get(query);
|
|
124
122
|
if (!stmt) {
|
|
@@ -126,7 +124,7 @@ export class DBClient {
|
|
|
126
124
|
this.preparedStatements.set(query, stmt);
|
|
127
125
|
}
|
|
128
126
|
result = stmt.all(...params);
|
|
129
|
-
} else if (this.config.type === DBType.MySQL
|
|
127
|
+
} else if (this.config.type === DBType.MySQL) {
|
|
130
128
|
const [rows] = await (this.client as mysql.Pool).query(query, params);
|
|
131
129
|
result = rows;
|
|
132
130
|
} else if (this.config.type === DBType.Postgres ) {
|
|
@@ -230,8 +228,6 @@ export class DBClient {
|
|
|
230
228
|
*/
|
|
231
229
|
async migrationQuery(query: string, params: any[] = []): Promise<void> {
|
|
232
230
|
const start = Date.now();
|
|
233
|
-
this.logger.logQuery(query, params);
|
|
234
|
-
|
|
235
231
|
if (this.client instanceof Database) {
|
|
236
232
|
let stmt = this.preparedStatements.get(query);
|
|
237
233
|
if (!stmt) {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
services:
|
|
3
|
+
db:
|
|
4
|
+
image: mariadb:latest
|
|
5
|
+
container_name: mariadb-container
|
|
6
|
+
restart: always
|
|
7
|
+
environment:
|
|
8
|
+
# Required: set a secure root password
|
|
9
|
+
MARIADB_ROOT_PASSWORD: P@ssw0rd
|
|
10
|
+
# Optional: set up a default database and user
|
|
11
|
+
MARIADB_DATABASE: db
|
|
12
|
+
MARIADB_USER: admin
|
|
13
|
+
MARIADB_PASSWORD: P@ssw0rd
|
|
14
|
+
ports:
|
|
15
|
+
# Map host port 3306 to container port 3306
|
|
16
|
+
- '3306:3306'
|
|
17
|
+
volumes:
|
|
18
|
+
# Create a named volume for persistent data storage
|
|
19
|
+
- mariadb_data:/var/lib/mysql
|
|
20
|
+
|
|
21
|
+
volumes:
|
|
22
|
+
mariadb_data:
|
package/hooks.ts
CHANGED
|
@@ -26,7 +26,7 @@ export interface Hook {
|
|
|
26
26
|
// Extend ModelConfig to include hooks
|
|
27
27
|
declare module "./model" {
|
|
28
28
|
interface ModelConfig {
|
|
29
|
-
hooks?:
|
|
29
|
+
hooks?: Partial<Record<HookType, HookCallback | HookCallback[]>>;
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
package/index.ts
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { Cache } from "./cache";
|
|
7
7
|
import { DBClient } from "./client";
|
|
8
|
-
import { type Logger,
|
|
8
|
+
import { type Logger, StabilizeLogger } from "./logger";
|
|
9
9
|
import { QueryBuilder } from "./query-builder";
|
|
10
10
|
import { Repository } from "./repository";
|
|
11
|
-
import { runMigrations, generateMigration, type Migration } from "./migrations";
|
|
11
|
+
import { runMigrations, generateMigration, type Migration, mapDataTypeToSql} from "./migrations";
|
|
12
12
|
import {
|
|
13
13
|
type DBConfig,
|
|
14
14
|
type CacheConfig,
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
type CacheStats,
|
|
23
23
|
LogLevel,
|
|
24
24
|
} from "./types";
|
|
25
|
-
import { defineModel } from "./model";
|
|
25
|
+
import { defineModel, MetadataStorage } from "./model";
|
|
26
26
|
import type { Hook } from "./hooks";
|
|
27
27
|
|
|
28
28
|
export class Stabilize {
|
|
@@ -42,7 +42,7 @@ export class Stabilize {
|
|
|
42
42
|
loggerConfig: LoggerConfig = {},
|
|
43
43
|
existingClient?: DBClient,
|
|
44
44
|
) {
|
|
45
|
-
this.logger = new
|
|
45
|
+
this.logger = new StabilizeLogger(loggerConfig);
|
|
46
46
|
this.client = existingClient || new DBClient(config, this.logger);
|
|
47
47
|
this.cache = existingClient ? null : (cacheConfig.enabled
|
|
48
48
|
? new Cache(cacheConfig, this.logger)
|
|
@@ -131,15 +131,18 @@ export {
|
|
|
131
131
|
DBClient,
|
|
132
132
|
QueryBuilder,
|
|
133
133
|
Cache,
|
|
134
|
-
|
|
134
|
+
StabilizeLogger,
|
|
135
135
|
DBType,
|
|
136
136
|
DataTypes,
|
|
137
137
|
LogLevel,
|
|
138
138
|
RelationType,
|
|
139
|
+
MetadataStorage,
|
|
140
|
+
mapDataTypeToSql,
|
|
139
141
|
StabilizeError,
|
|
140
142
|
runMigrations,
|
|
141
143
|
generateMigration,
|
|
142
144
|
defineModel,
|
|
145
|
+
|
|
143
146
|
};
|
|
144
147
|
|
|
145
148
|
export type {
|
package/migrations.ts
CHANGED
|
@@ -133,6 +133,7 @@ export async function generateMigration(
|
|
|
133
133
|
const columns = MetadataStorage.getColumns(model);
|
|
134
134
|
const validators = MetadataStorage.getValidators(model);
|
|
135
135
|
const versioned = MetadataStorage.isVersioned(model);
|
|
136
|
+
const timestamps = MetadataStorage.getTimestamps(model);
|
|
136
137
|
|
|
137
138
|
const columnDefs: string[] = [];
|
|
138
139
|
|
|
@@ -163,6 +164,28 @@ export async function generateMigration(
|
|
|
163
164
|
columnDefs.push(defParts.join(" "));
|
|
164
165
|
}
|
|
165
166
|
|
|
167
|
+
// Add timestamp columns if enabled
|
|
168
|
+
if (timestamps) {
|
|
169
|
+
for (const [field, colName] of Object.entries(timestamps)) {
|
|
170
|
+
// Use the field name defined in the timestamps config
|
|
171
|
+
let sqlType = dbType === DBType.Postgres ? "TIMESTAMP" : "DATETIME";
|
|
172
|
+
let def = `${colName} ${sqlType} NOT NULL`;
|
|
173
|
+
|
|
174
|
+
// Set default value for createdAt, and optionally for updatedAt
|
|
175
|
+
if (field === "createdAt") {
|
|
176
|
+
def += " DEFAULT CURRENT_TIMESTAMP";
|
|
177
|
+
} else if (field === "updatedAt") {
|
|
178
|
+
def += " DEFAULT CURRENT_TIMESTAMP";
|
|
179
|
+
// For MySQL, add ON UPDATE CURRENT_TIMESTAMP
|
|
180
|
+
if (dbType === DBType.MySQL) {
|
|
181
|
+
def += " ON UPDATE CURRENT_TIMESTAMP";
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
columnDefs.push(def);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
166
189
|
const up: string[] = [`CREATE TABLE IF NOT EXISTS ${tableName} (${columnDefs.join(", ")})`];
|
|
167
190
|
const down: string[] = [`DROP TABLE IF EXISTS ${tableName}`];
|
|
168
191
|
|
|
@@ -194,8 +217,15 @@ function generateHistoryMigration(
|
|
|
194
217
|
let modByType = dbType === DBType.MySQL ? "VARCHAR(255)" : "TEXT";
|
|
195
218
|
let modAtType = tsType + (dbType === DBType.Postgres ? " DEFAULT CURRENT_TIMESTAMP" : "");
|
|
196
219
|
|
|
220
|
+
// Strip constraints for history columns
|
|
221
|
+
function cleanColumnDef(def: string): string {
|
|
222
|
+
return def
|
|
223
|
+
.replace(/\s+PRIMARY\s+KEY\b/gi, "")
|
|
224
|
+
.replace(/\s+UNIQUE\b/gi, "");
|
|
225
|
+
}
|
|
226
|
+
|
|
197
227
|
const historyColumns = [
|
|
198
|
-
...columnDefs,
|
|
228
|
+
...columnDefs.map(cleanColumnDef),
|
|
199
229
|
`operation ${opType}`,
|
|
200
230
|
`version ${versionType}`,
|
|
201
231
|
`valid_from ${tsType} NOT NULL`,
|
|
@@ -264,7 +294,13 @@ export async function runMigrations(config: DBConfig, migrations: Migration[]) {
|
|
|
264
294
|
}
|
|
265
295
|
|
|
266
296
|
const insertQuery = formatQuery(`INSERT INTO stabilize_migrations (name, applied_at) VALUES (?, ?)`, dbType);
|
|
267
|
-
|
|
297
|
+
let appliedAt: string;
|
|
298
|
+
if (dbType === DBType.MySQL) {
|
|
299
|
+
appliedAt = new Date().toISOString().slice(0, 19).replace("T", " ");
|
|
300
|
+
} else {
|
|
301
|
+
appliedAt = new Date().toISOString();
|
|
302
|
+
}
|
|
303
|
+
await txClient.query(insertQuery, [name, appliedAt]);
|
|
268
304
|
|
|
269
305
|
console.log(`Migration ${name} applied successfully.`);
|
|
270
306
|
});
|
|
@@ -279,3 +315,4 @@ export async function runMigrations(config: DBConfig, migrations: Migration[]) {
|
|
|
279
315
|
}
|
|
280
316
|
|
|
281
317
|
export type { Migration };
|
|
318
|
+
export { mapDataTypeToSql };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stabilize-orm",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.9",
|
|
4
4
|
"description": "A lightweight, type-safe ORM for Bun.js with support for SQLite, MySQL, PostgreSQL, and Redis caching",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -26,21 +26,21 @@
|
|
|
26
26
|
"@types/uuid": "^11.0.0",
|
|
27
27
|
"commander": "^12.1.0",
|
|
28
28
|
"figlet": "^1.9.3",
|
|
29
|
-
"glob": "^11.0.
|
|
30
|
-
"ioredis": "^5.
|
|
29
|
+
"glob": "^11.0.3",
|
|
30
|
+
"ioredis": "^5.8.1",
|
|
31
31
|
"mysql2": "^3.15.2",
|
|
32
32
|
"pg": "^8.16.3",
|
|
33
33
|
"reflect-metadata": "^0.2.2",
|
|
34
34
|
"uuid": "^13.0.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
38
|
-
"@typescript-eslint/parser": "^8.
|
|
39
|
-
"@vitest/coverage-v8": "^2.1.
|
|
40
|
-
"eslint": "^9.
|
|
41
|
-
"prettier": "^3.
|
|
42
|
-
"typescript": "^5.
|
|
43
|
-
"vitest": "^2.1.
|
|
37
|
+
"@typescript-eslint/eslint-plugin": "^8.46.1",
|
|
38
|
+
"@typescript-eslint/parser": "^8.46.1",
|
|
39
|
+
"@vitest/coverage-v8": "^2.1.9",
|
|
40
|
+
"eslint": "^9.38.0",
|
|
41
|
+
"prettier": "^3.6.2",
|
|
42
|
+
"typescript": "^5.9.3",
|
|
43
|
+
"vitest": "^2.1.9"
|
|
44
44
|
},
|
|
45
45
|
"repository": {
|
|
46
46
|
"type": "git",
|
|
@@ -54,6 +54,6 @@
|
|
|
54
54
|
"bun": ">=1.0.0"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
|
-
"bun": "
|
|
57
|
+
"bun": "^1.3.0"
|
|
58
58
|
}
|
|
59
59
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/repository.ts
CHANGED
|
@@ -46,6 +46,7 @@ export class Repository<T> {
|
|
|
46
46
|
private logger: Logger;
|
|
47
47
|
private versioned: boolean;
|
|
48
48
|
private historyTable: string;
|
|
49
|
+
private model: new (...args: any[]) => T;
|
|
49
50
|
|
|
50
51
|
/**
|
|
51
52
|
* Creates an instance of Repository.
|
|
@@ -87,6 +88,7 @@ export class Repository<T> {
|
|
|
87
88
|
this.logger = logger;
|
|
88
89
|
this.versioned = MetadataStorage.isVersioned(model);
|
|
89
90
|
this.historyTable = `${this.table}_history`;
|
|
91
|
+
this.model = model;
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
/**
|
|
@@ -287,15 +289,34 @@ export class Repository<T> {
|
|
|
287
289
|
"modified_at"
|
|
288
290
|
];
|
|
289
291
|
|
|
290
|
-
|
|
292
|
+
// Helper to sanitize each value before inserting into SQLite/Postgres/MySQL
|
|
293
|
+
function sanitizeSqlValue(val: any, dbType: DBType): string | number | boolean | bigint | null {
|
|
294
|
+
if (val === undefined) return null;
|
|
295
|
+
if (val instanceof Date) {
|
|
296
|
+
if (dbType === DBType.MySQL) {
|
|
297
|
+
// MySQL DATETIME: 'YYYY-MM-DD HH:MM:SS'
|
|
298
|
+
return val.toISOString().slice(0, 19).replace('T', ' ');
|
|
299
|
+
}
|
|
300
|
+
return val.toISOString();
|
|
301
|
+
}
|
|
302
|
+
if (typeof val === "boolean") return val ? 1 : 0;
|
|
303
|
+
if (
|
|
304
|
+
typeof val === "string" ||
|
|
305
|
+
typeof val === "number" ||
|
|
306
|
+
typeof val === "bigint"
|
|
307
|
+
) return val;
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
const dbType = client.config.type;
|
|
311
|
+
const values = propertyKeys.map((k) => sanitizeSqlValue(entity[k], dbType));
|
|
291
312
|
const params = [
|
|
292
313
|
...values,
|
|
293
|
-
operation,
|
|
294
|
-
entity.version || 1,
|
|
295
|
-
new Date(),
|
|
296
|
-
null,
|
|
297
|
-
user || "system",
|
|
298
|
-
new Date()
|
|
314
|
+
sanitizeSqlValue(operation, dbType),
|
|
315
|
+
sanitizeSqlValue(entity.version || 1, dbType),
|
|
316
|
+
sanitizeSqlValue(new Date(), dbType),
|
|
317
|
+
sanitizeSqlValue(null, dbType),
|
|
318
|
+
sanitizeSqlValue(user || "system", dbType),
|
|
319
|
+
sanitizeSqlValue(new Date(), dbType)
|
|
299
320
|
];
|
|
300
321
|
|
|
301
322
|
let placeholders: string;
|