forge-sql-orm 2.1.23 → 2.1.24

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.
Files changed (44) hide show
  1. package/README.md +1 -0
  2. package/dist/core/ForgeSQLAnalyseOperations.d.ts +4 -0
  3. package/dist/core/ForgeSQLAnalyseOperations.d.ts.map +1 -1
  4. package/dist/core/ForgeSQLAnalyseOperations.js +17 -21
  5. package/dist/core/ForgeSQLAnalyseOperations.js.map +1 -1
  6. package/dist/core/ForgeSQLCrudOperations.d.ts +16 -0
  7. package/dist/core/ForgeSQLCrudOperations.d.ts.map +1 -1
  8. package/dist/core/ForgeSQLCrudOperations.js +60 -28
  9. package/dist/core/ForgeSQLCrudOperations.js.map +1 -1
  10. package/dist/core/ForgeSQLQueryBuilder.d.ts +15 -28
  11. package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
  12. package/dist/core/ForgeSQLQueryBuilder.js +20 -47
  13. package/dist/core/ForgeSQLQueryBuilder.js.map +1 -1
  14. package/dist/core/Rovo.d.ts +32 -0
  15. package/dist/core/Rovo.d.ts.map +1 -1
  16. package/dist/core/Rovo.js +116 -67
  17. package/dist/core/Rovo.js.map +1 -1
  18. package/dist/lib/drizzle/extensions/additionalActions.d.ts.map +1 -1
  19. package/dist/lib/drizzle/extensions/additionalActions.js +168 -118
  20. package/dist/lib/drizzle/extensions/additionalActions.js.map +1 -1
  21. package/dist/utils/cacheTableUtils.d.ts +0 -8
  22. package/dist/utils/cacheTableUtils.d.ts.map +1 -1
  23. package/dist/utils/cacheTableUtils.js +183 -126
  24. package/dist/utils/cacheTableUtils.js.map +1 -1
  25. package/dist/utils/forgeDriverProxy.d.ts.map +1 -1
  26. package/dist/utils/forgeDriverProxy.js +31 -20
  27. package/dist/utils/forgeDriverProxy.js.map +1 -1
  28. package/dist/utils/sqlHints.d.ts.map +1 -1
  29. package/dist/utils/sqlHints.js +19 -29
  30. package/dist/utils/sqlHints.js.map +1 -1
  31. package/dist/utils/sqlUtils.d.ts +0 -29
  32. package/dist/utils/sqlUtils.d.ts.map +1 -1
  33. package/dist/utils/sqlUtils.js +107 -78
  34. package/dist/utils/sqlUtils.js.map +1 -1
  35. package/package.json +8 -8
  36. package/src/core/ForgeSQLAnalyseOperations.ts +18 -21
  37. package/src/core/ForgeSQLCrudOperations.ts +83 -33
  38. package/src/core/ForgeSQLQueryBuilder.ts +59 -154
  39. package/src/core/Rovo.ts +158 -98
  40. package/src/lib/drizzle/extensions/additionalActions.ts +287 -382
  41. package/src/utils/cacheTableUtils.ts +202 -144
  42. package/src/utils/forgeDriverProxy.ts +39 -21
  43. package/src/utils/sqlHints.ts +21 -26
  44. package/src/utils/sqlUtils.ts +151 -101
@@ -253,23 +253,15 @@ export class ForgeSQLCrudOperations implements VerioningModificationForgeSQL {
253
253
  }
254
254
 
255
255
  /**
256
- * Validates and retrieves version field metadata.
257
- * @param {string} tableName - The name of the table
258
- * @param {Record<string, AnyColumn>} columns - The table columns
259
- * @returns {Object | undefined} Version field metadata if valid, undefined otherwise
256
+ * Finds version field in columns by name.
260
257
  */
261
- private validateVersionField(
262
- tableName: string,
258
+ private findVersionField(
259
+ versionMetadata: { fieldName: string },
263
260
  columns: Record<string, AnyColumn>,
264
- ): { fieldName: string; type: string } | undefined {
265
- if (this.options.disableOptimisticLocking) {
266
- return undefined;
267
- }
268
- const versionMetadata = this.options.additionalMetadata?.[tableName]?.versionField;
269
- if (!versionMetadata) return undefined;
261
+ ): { fieldName: string; field: AnyColumn } | null {
270
262
  let fieldName = versionMetadata.fieldName;
271
-
272
263
  let versionField = columns[versionMetadata.fieldName];
264
+
273
265
  if (!versionField) {
274
266
  const find = Object.entries(columns).find(([, c]) => c.name === versionMetadata.fieldName);
275
267
  if (find) {
@@ -277,29 +269,38 @@ export class ForgeSQLCrudOperations implements VerioningModificationForgeSQL {
277
269
  versionField = find[1];
278
270
  }
279
271
  }
280
- if (!versionField) {
281
- // eslint-disable-next-line no-console
282
- console.warn(
283
- `Version field "${versionMetadata.fieldName}" not found in table ${tableName}. Versioning will be skipped.`,
284
- );
285
- return undefined;
286
- }
287
272
 
273
+ return versionField ? { fieldName, field: versionField } : null;
274
+ }
275
+
276
+ /**
277
+ * Validates that version field is not nullable.
278
+ */
279
+ private validateVersionFieldNotNull(
280
+ versionMetadata: { fieldName: string },
281
+ versionField: AnyColumn,
282
+ tableName: string,
283
+ ): boolean {
288
284
  if (!versionField.notNull) {
289
285
  // eslint-disable-next-line no-console
290
286
  console.warn(
291
287
  `Version field "${versionMetadata.fieldName}" in table ${tableName} is nullable. Versioning may not work correctly.`,
292
288
  );
293
- return undefined;
289
+ return false;
294
290
  }
291
+ return true;
292
+ }
295
293
 
296
- const fieldType = versionField.getSQLType();
297
- const isSupportedType =
298
- fieldType === "datetime" ||
299
- fieldType === "timestamp" ||
300
- fieldType === "int" ||
301
- fieldType === "number" ||
302
- fieldType === "decimal";
294
+ /**
295
+ * Validates that version field type is supported.
296
+ */
297
+ private validateVersionFieldType(
298
+ versionMetadata: { fieldName: string },
299
+ fieldType: string,
300
+ tableName: string,
301
+ ): boolean {
302
+ const supportedTypes = ["datetime", "timestamp", "int", "number", "decimal"];
303
+ const isSupportedType = supportedTypes.includes(fieldType);
303
304
 
304
305
  if (!isSupportedType) {
305
306
  // eslint-disable-next-line no-console
@@ -307,10 +308,50 @@ export class ForgeSQLCrudOperations implements VerioningModificationForgeSQL {
307
308
  `Version field "${versionMetadata.fieldName}" in table ${tableName} has unsupported type "${fieldType}". ` +
308
309
  `Only datetime, timestamp, int, and decimal types are supported for versioning. Versioning will be skipped.`,
309
310
  );
311
+ return false;
312
+ }
313
+ return true;
314
+ }
315
+
316
+ /**
317
+ * Validates and retrieves version field metadata.
318
+ * @param {string} tableName - The name of the table
319
+ * @param {Record<string, AnyColumn>} columns - The table columns
320
+ * @returns {Object | undefined} Version field metadata if valid, undefined otherwise
321
+ */
322
+ private validateVersionField(
323
+ tableName: string,
324
+ columns: Record<string, AnyColumn>,
325
+ ): { fieldName: string; type: string } | undefined {
326
+ // Early exit conditions
327
+ if (this.options.disableOptimisticLocking) {
328
+ return undefined;
329
+ }
330
+ const versionMetadata = this.options.additionalMetadata?.[tableName]?.versionField;
331
+ if (!versionMetadata) {
310
332
  return undefined;
311
333
  }
312
334
 
313
- return { fieldName, type: fieldType };
335
+ // Find and validate version field
336
+ const found = this.findVersionField(versionMetadata, columns);
337
+ if (!found) {
338
+ // eslint-disable-next-line no-console
339
+ console.warn(
340
+ `Version field "${versionMetadata.fieldName}" not found in table ${tableName}. Versioning will be skipped.`,
341
+ );
342
+ return undefined;
343
+ }
344
+
345
+ // Validate field properties
346
+ const isValid =
347
+ this.validateVersionFieldNotNull(versionMetadata, found.field, tableName) &&
348
+ this.validateVersionFieldType(versionMetadata, found.field.getSQLType(), tableName);
349
+
350
+ if (!isValid) {
351
+ return undefined;
352
+ }
353
+
354
+ return { fieldName: found.fieldName, type: found.field.getSQLType() };
314
355
  }
315
356
 
316
357
  /**
@@ -376,12 +417,23 @@ export class ForgeSQLCrudOperations implements VerioningModificationForgeSQL {
376
417
 
377
418
  const modelWithVersion = { ...model };
378
419
  const fieldType = versionField.getSQLType();
379
- const versionValue = fieldType === "datetime" || fieldType === "timestamp" ? new Date() : 1;
420
+ const dateTimeTypes = ["datetime", "timestamp"];
421
+ const versionValue = dateTimeTypes.includes(fieldType) ? new Date() : 1;
380
422
  modelWithVersion[fieldName as keyof typeof modelWithVersion] = versionValue as any;
381
423
 
382
424
  return modelWithVersion as InferInsertModel<T>;
383
425
  }
384
426
 
427
+ /**
428
+ * Calculates new version value based on field type.
429
+ */
430
+ private calculateNewVersionValue(fieldType: string, currentVersion: unknown): any {
431
+ const dateTimeTypes = ["datetime", "timestamp"];
432
+ return dateTimeTypes.includes(fieldType)
433
+ ? new Date()
434
+ : (((currentVersion as number) + 1) as any);
435
+ }
436
+
385
437
  /**
386
438
  * Prepares update data with version field.
387
439
  * @template T - The type of the table schema
@@ -404,9 +456,7 @@ export class ForgeSQLCrudOperations implements VerioningModificationForgeSQL {
404
456
  if (versionField) {
405
457
  const fieldType = versionField.getSQLType();
406
458
  updateData[versionMetadata.fieldName as keyof typeof updateData] =
407
- fieldType === "datetime" || fieldType === "timestamp"
408
- ? new Date()
409
- : (((currentVersion as number) + 1) as any);
459
+ this.calculateNewVersionValue(fieldType, currentVersion);
410
460
  }
411
461
  }
412
462
 
@@ -54,6 +54,28 @@ import {
54
54
  SelectResultField,
55
55
  } from "drizzle-orm/query-builders/select.types";
56
56
  import { SQLWrapper } from "drizzle-orm/sql/sql";
57
+
58
+ /**
59
+ * Type alias for MySqlSelectBase return type used in selectFrom methods.
60
+ * Reduces code duplication in type definitions.
61
+ */
62
+ export type SelectFromReturnType<T extends MySqlTable> = MySqlSelectBase<
63
+ GetSelectTableName<T>,
64
+ GetSelectTableSelection<T>,
65
+ "single",
66
+ MySqlRemotePreparedQueryHKT,
67
+ GetSelectTableName<T> extends string ? Record<string & GetSelectTableName<T>, "not-null"> : {},
68
+ false,
69
+ never,
70
+ {
71
+ [K in keyof {
72
+ [Key in keyof GetSelectTableSelection<T>]: SelectResultField<GetSelectTableSelection<T>[Key]>;
73
+ }]: {
74
+ [Key in keyof GetSelectTableSelection<T>]: SelectResultField<GetSelectTableSelection<T>[Key]>;
75
+ }[K];
76
+ }[],
77
+ any
78
+ >;
57
79
  import type { MySqlQueryResultKind } from "drizzle-orm/mysql-core/session";
58
80
  import type { WithBuilder } from "drizzle-orm/mysql-core/subquery";
59
81
  import { WithSubquery } from "drizzle-orm/subquery";
@@ -216,29 +238,7 @@ export interface QueryBuilderForgeSql {
216
238
  * const users = await forgeSQL.selectFrom(userTable).where(eq(userTable.id, 1));
217
239
  * ```
218
240
  */
219
- selectFrom<T extends MySqlTable>(
220
- table: T,
221
- ): MySqlSelectBase<
222
- GetSelectTableName<T>,
223
- GetSelectTableSelection<T>,
224
- "single",
225
- MySqlRemotePreparedQueryHKT,
226
- GetSelectTableName<T> extends string ? Record<string & GetSelectTableName<T>, "not-null"> : {},
227
- false,
228
- never,
229
- {
230
- [K in keyof {
231
- [Key in keyof GetSelectTableSelection<T>]: SelectResultField<
232
- GetSelectTableSelection<T>[Key]
233
- >;
234
- }]: {
235
- [Key in keyof GetSelectTableSelection<T>]: SelectResultField<
236
- GetSelectTableSelection<T>[Key]
237
- >;
238
- }[K];
239
- }[],
240
- any
241
- >;
241
+ selectFrom<T extends MySqlTable>(table: T): SelectFromReturnType<T>;
242
242
 
243
243
  /**
244
244
  * Creates a distinct select query with unique field aliases to prevent field name collisions in joins.
@@ -271,29 +271,7 @@ export interface QueryBuilderForgeSql {
271
271
  * const uniqueUsers = await forgeSQL.selectDistinctFrom(userTable).where(eq(userTable.status, 'active'));
272
272
  * ```
273
273
  */
274
- selectDistinctFrom<T extends MySqlTable>(
275
- table: T,
276
- ): MySqlSelectBase<
277
- GetSelectTableName<T>,
278
- GetSelectTableSelection<T>,
279
- "single",
280
- MySqlRemotePreparedQueryHKT,
281
- GetSelectTableName<T> extends string ? Record<string & GetSelectTableName<T>, "not-null"> : {},
282
- false,
283
- never,
284
- {
285
- [K in keyof {
286
- [Key in keyof GetSelectTableSelection<T>]: SelectResultField<
287
- GetSelectTableSelection<T>[Key]
288
- >;
289
- }]: {
290
- [Key in keyof GetSelectTableSelection<T>]: SelectResultField<
291
- GetSelectTableSelection<T>[Key]
292
- >;
293
- }[K];
294
- }[],
295
- any
296
- >;
274
+ selectDistinctFrom<T extends MySqlTable>(table: T): SelectFromReturnType<T>;
297
275
 
298
276
  /**
299
277
  * Creates a cacheable select query with unique field aliases to prevent field name collisions in joins.
@@ -330,30 +308,7 @@ export interface QueryBuilderForgeSql {
330
308
  * const users = await forgeSQL.selectCacheableFrom(userTable, 300).where(eq(userTable.id, 1));
331
309
  * ```
332
310
  */
333
- selectCacheableFrom<T extends MySqlTable>(
334
- table: T,
335
- cacheTTL?: number,
336
- ): MySqlSelectBase<
337
- GetSelectTableName<T>,
338
- GetSelectTableSelection<T>,
339
- "single",
340
- MySqlRemotePreparedQueryHKT,
341
- GetSelectTableName<T> extends string ? Record<string & GetSelectTableName<T>, "not-null"> : {},
342
- false,
343
- never,
344
- {
345
- [K in keyof {
346
- [Key in keyof GetSelectTableSelection<T>]: SelectResultField<
347
- GetSelectTableSelection<T>[Key]
348
- >;
349
- }]: {
350
- [Key in keyof GetSelectTableSelection<T>]: SelectResultField<
351
- GetSelectTableSelection<T>[Key]
352
- >;
353
- }[K];
354
- }[],
355
- any
356
- >;
311
+ selectCacheableFrom<T extends MySqlTable>(table: T, cacheTTL?: number): SelectFromReturnType<T>;
357
312
 
358
313
  /**
359
314
  * Creates a cacheable distinct select query with unique field aliases to prevent field name collisions in joins.
@@ -393,27 +348,7 @@ export interface QueryBuilderForgeSql {
393
348
  selectDistinctCacheableFrom<T extends MySqlTable>(
394
349
  table: T,
395
350
  cacheTTL?: number,
396
- ): MySqlSelectBase<
397
- GetSelectTableName<T>,
398
- GetSelectTableSelection<T>,
399
- "single",
400
- MySqlRemotePreparedQueryHKT,
401
- GetSelectTableName<T> extends string ? Record<string & GetSelectTableName<T>, "not-null"> : {},
402
- false,
403
- never,
404
- {
405
- [K in keyof {
406
- [Key in keyof GetSelectTableSelection<T>]: SelectResultField<
407
- GetSelectTableSelection<T>[Key]
408
- >;
409
- }]: {
410
- [Key in keyof GetSelectTableSelection<T>]: SelectResultField<
411
- GetSelectTableSelection<T>[Key]
412
- >;
413
- }[K];
414
- }[],
415
- any
416
- >;
351
+ ): SelectFromReturnType<T>;
417
352
 
418
353
  /**
419
354
  * Creates an insert query builder.
@@ -1344,28 +1279,38 @@ export interface ForgeSqlOrmOptions {
1344
1279
  additionalMetadata?: AdditionalMetadata;
1345
1280
  }
1346
1281
 
1282
+ /**
1283
+ * Creates a custom type for date/time fields with specified format and timestamp validation.
1284
+ */
1285
+ function createDateCustomType(dataType: string, format: string, isTimeStamp: boolean) {
1286
+ return customType<{
1287
+ data: Date;
1288
+ driver: string;
1289
+ config: { format?: string };
1290
+ }>({
1291
+ dataType() {
1292
+ return dataType;
1293
+ },
1294
+ toDriver(value: Date) {
1295
+ return formatDateTime(value, format, isTimeStamp);
1296
+ },
1297
+ fromDriver(value: unknown) {
1298
+ return parseDateTime(value as string, format);
1299
+ },
1300
+ });
1301
+ }
1302
+
1347
1303
  /**
1348
1304
  * Custom type for MySQL datetime fields.
1349
1305
  * Handles conversion between JavaScript Date objects and MySQL datetime strings.
1350
1306
  *
1351
1307
  * @type {CustomType}
1352
1308
  */
1353
- export const forgeDateTimeString = customType<{
1354
- data: Date;
1355
- driver: string;
1356
- config: { format?: string };
1357
- }>({
1358
- dataType() {
1359
- return "datetime";
1360
- },
1361
- toDriver(value: Date) {
1362
- return formatDateTime(value, "yyyy-MM-dd' 'HH:mm:ss.SSS", false);
1363
- },
1364
- fromDriver(value: unknown) {
1365
- const format = "yyyy-MM-dd' 'HH:mm:ss.SSS";
1366
- return parseDateTime(value as string, format);
1367
- },
1368
- });
1309
+ export const forgeDateTimeString = createDateCustomType(
1310
+ "datetime",
1311
+ "yyyy-MM-dd' 'HH:mm:ss.SSS",
1312
+ false,
1313
+ );
1369
1314
 
1370
1315
  /**
1371
1316
  * Custom type for MySQL timestamp fields.
@@ -1373,22 +1318,11 @@ export const forgeDateTimeString = customType<{
1373
1318
  *
1374
1319
  * @type {CustomType}
1375
1320
  */
1376
- export const forgeTimestampString = customType<{
1377
- data: Date;
1378
- driver: string;
1379
- config: { format?: string };
1380
- }>({
1381
- dataType() {
1382
- return "timestamp";
1383
- },
1384
- toDriver(value: Date) {
1385
- return formatDateTime(value, "yyyy-MM-dd' 'HH:mm:ss.SSS", true);
1386
- },
1387
- fromDriver(value: unknown) {
1388
- const format = "yyyy-MM-dd' 'HH:mm:ss.SSS";
1389
- return parseDateTime(value as string, format);
1390
- },
1391
- });
1321
+ export const forgeTimestampString = createDateCustomType(
1322
+ "timestamp",
1323
+ "yyyy-MM-dd' 'HH:mm:ss.SSS",
1324
+ true,
1325
+ );
1392
1326
 
1393
1327
  /**
1394
1328
  * Custom type for MySQL date fields.
@@ -1396,22 +1330,7 @@ export const forgeTimestampString = customType<{
1396
1330
  *
1397
1331
  * @type {CustomType}
1398
1332
  */
1399
- export const forgeDateString = customType<{
1400
- data: Date;
1401
- driver: string;
1402
- config: { format?: string };
1403
- }>({
1404
- dataType() {
1405
- return "date";
1406
- },
1407
- toDriver(value: Date) {
1408
- return formatDateTime(value, "yyyy-MM-dd", false);
1409
- },
1410
- fromDriver(value: unknown) {
1411
- const format = "yyyy-MM-dd";
1412
- return parseDateTime(value as string, format);
1413
- },
1414
- });
1333
+ export const forgeDateString = createDateCustomType("date", "yyyy-MM-dd", false);
1415
1334
 
1416
1335
  /**
1417
1336
  * Custom type for MySQL time fields.
@@ -1419,18 +1338,4 @@ export const forgeDateString = customType<{
1419
1338
  *
1420
1339
  * @type {CustomType}
1421
1340
  */
1422
- export const forgeTimeString = customType<{
1423
- data: Date;
1424
- driver: string;
1425
- config: { format?: string };
1426
- }>({
1427
- dataType() {
1428
- return "time";
1429
- },
1430
- toDriver(value: Date) {
1431
- return formatDateTime(value, "HH:mm:ss.SSS", false);
1432
- },
1433
- fromDriver(value: unknown) {
1434
- return parseDateTime(value as string, "HH:mm:ss.SSS");
1435
- },
1436
- });
1341
+ export const forgeTimeString = createDateCustomType("time", "HH:mm:ss.SSS", false);