stabilize-orm 1.3.3 → 1.3.8

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/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 { ConsoleLogger, type Logger } from "./logger";
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 ConsoleLogger()) {
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 && isMySQLPool(this.client)) {
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?: Record<HookType, HookCallback | HookCallback[]>;
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, ConsoleLogger } from "./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 ConsoleLogger(loggerConfig);
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
- ConsoleLogger,
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
- await txClient.query(insertQuery, [name, new Date().toISOString()]);
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",
3
+ "version": "1.3.8",
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",
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
- const values = propertyKeys.map((k) => entity[k]);
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;